Accessing environment variables

I'm trying to use an MQTT broker to broadcast some data.
For flexibility, I want to set the MQTT variables in the system environment and then read them
in my Node-Red flow. It used to work (some years ago) but no longer does. Here's a simple example
showing this.
Working context:
Node-RED version: v3.1.6
Node.js version: v18.19.1
Linux 6.1.0-rpi7-rpi-v8 arm64 LE

(1) Check that the environment variables are set:
user@RP01:~$ set | grep MQTT
which returns

MQTT_BROKER=broker.hivemq.com
MQTT_CLIENT_ID=Client007
MQTT_NAME=MyName
MQTT_PORT=1883
MQTT_TOPIC=ViewMe/Meter01

(2) Based on what I found searching the Internet (and possibly not the right thing to do),
here's the (only) change I made to settings.js in the ~/.node-red directory:

	functionGlobalContext: {
			// os:require('os'), 
			env: process.env,
	},

(3) Now start Node-Red
user@RP01:~$ node-red-start
which returns

Start Node-RED
    ...[snipped]...
Starting as a systemd service.

(4) Run the following flow in a browser.
Flow to display the MQTT variables:

[
    {
        "id": "98320a06c471a025",
        "type": "tab",
        "label": "Flow 3",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "179e115a0c5b7eae",
        "type": "function",
        "z": "98320a06c471a025",
        "name": "View MQTT variables",
        "func": "msg.payload =  \"MQTT_BROKER: '\" + env.get('MQTT_BROKER') +\"'    \\n\";\nmsg.payload = msg.payload + \"MQTT_PORT: '\" + env.get('MQTT_PORT')  +\"'    \\n\";\nmsg.payload = msg.payload + \"MQTT_TOPIC: '\" + env.get('MQTT_TOPIC')  +\"'    \\n\";\nmsg.payload = msg.payload + \"--- following are not used ---\" + \"    \\n\";\nmsg.payload = msg.payload + \"MQTT_CLIENT_ID: '\"+ env.get('MQTT_CLIENT_ID') + \"'    \\n\";\nmsg.payload = msg.payload + \"MQTT_NAME: '\" + env.get('MQTT_NAME')  +\"'    \\n\";\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 360,
        "y": 80,
        "wires": [
            [
                "3e6758c2883dfd69"
            ]
        ]
    },
    {
        "id": "3e6758c2883dfd69",
        "type": "debug",
        "z": "98320a06c471a025",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "x": 570,
        "y": 80,
        "wires": []
    },
    {
        "id": "f37e16fcb00b5f1a",
        "type": "inject",
        "z": "98320a06c471a025",
        "name": "",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "true",
        "payloadType": "bool",
        "x": 130,
        "y": 80,
        "wires": [
            [
                "179e115a0c5b7eae"
            ]
        ]
    }
]

The result is

msg.payload : string[178]
"MQTT_BROKER: 'undefined'    ↵MQTT_PORT: 'undefined'    ↵MQTT_TOPIC: 'undefined'    ↵--- following are not used ---    ↵MQTT_CLIENT_ID: 'undefined'    ↵MQTT_NAME: 'undefined'    ↵"

I.e., everything is undefined.

Many thanks for help in figuring this out.
~ThisEndUp.

None of that is necessary. Just enter the parameters in the MQTT config node setup using this syntax ${ENVVAR}.

it's all in the docs: Using environment variables : Node-RED

This might be helpfull, some stuff I only just discovered:

There are several ways to define environment variables for Node-red.
One approach (on Linux) is to declare them in a file .node-red/EnvironmentFile (the filename is specified in /etc/systemd/system/multi-user.target.wants/nodered.service") and restarting Node-red. eg:

WIBBLE="This variable set in .node-red/EnvironmentFile"

Accessing the environment variable - in an Inject node there are two options

Feeding that to a debug node gives

My attempts are still failing. The variables are set in the shell immediately before starting node-red, as confirmed by the command set | grep MQTT. I then follow instructions in the Using environment variables.
For example, those instructions state:

Within a Function node, environment variables get be accessed using the env.get function:

let foo = env.get("FOO");

But this is essentially what I posted. To follow the instructions more exactly, I changed it to

let broker = env.get("MQTT_BROKER");
msg.payload = "MQTT_BROKER: '" + broker + "'    \n";

and the result is still 'MQTT_BROKER: 'undefined' .

As for the suggestion that I define variables in a .node-red/EnvironmentFile, where this file is specified in a nodered.service file, there are no node* files at all in /etc/systemd/system/multi-user.target.wants/.

root@RP01:/etc/systemd/system/multi-user.target.wants# ls -l node*
ls: cannot access 'node*': No such file or directory

If I wanted to add a nodered.service file, what would it contain, and what would its format be? And would its name be nodered.service or node-red.service (with a hyphen)?

So, I'm still lost.
~ThisEndUp

OK my apologies, the Raspberry install script for Node-red does setup systemd but unless you run sudo systemctl enable nodered they are not accessible in that directory. Enabling makes a link so that Node-red is automatically started at boot. I always do this.

pi@zero2green:/etc/systemd/system/multi-user.target.wants $ ls -l nodered.service
lrwxrwxrwx 1 root root 35 Mar 15 00:14 nodered.service -> /lib/systemd/system/nodered.service

So the actual file is at /lib/systemd/system/nodered

ps I suspect that even with node-red auto start disabled, if you were to edit /lib/systemd/system/nodered.service and start Node-red with the node-red-start script, it would still use the systemd script. But I have never tried it.

pps It looks like you might be running Node-red as root? This is universally acknowledged to be a Bad Thing.

ppps You can also set environment variables in settings.js, eg this creates an environment variable HOSTNAME (I know, $HOSTNAME should already exist)

// just above module.exports = {
 hostname = require('os').hostname();
 process.env.HOSTNAME = hostname;

Are you exporting the variables?

EVERYTHING=42
sets the variable, while
export EVERYTHING=42
makes it available in subshells

If you set an environment variable in a shell window it only affects processes run from that terminal. If you were to start node red using
node-red
Then it should be accessible in node red. However, by using node-red-start you are telling systemd to start node red, it is not running within your terminal window so the variable is not available.

You will have to define a system wide env var, or set it within the node red systemd script.

Many thanks. I've found something that does work and some things that don't.

What does work:
I returned ~/.node-red/settings.js to its original contents.
I followed jbudd's advice and ran sudo systemctl enable nodered and I created a file to set variables in ~/.node-red/EnvironmentFile.

user@RP01:~$ cat ~/.node-red/EnvironmentFile 
export MQTT_BROKER=broker.hivemq.com
export MQTT_PORT=1883
export MQTT_CLIENT_ID=Client007     
export MQTT_TOPIC=ViewMe/Meter01    
export MQTT_NAME=MyName

I rebooted the Raspberry Pi to make sure that node-red was started when the system comes up.
But see (1) below for the results of that (look for on boot-up).

I have a file named ~/startNR.sh with contents:

#!/bin/sh
export MQTT_BROKER=broker.hivemq.com
export MQTT_PORT=1883
export MQTT_CLIENT_ID=Client007     
export MQTT_TOPIC=ViewMe/Meter01    
export MQTT_NAME=MyName
node-red-stop
node-red

When I run this, even asynchronously (i.e., ~/startNR.sh &) , it stops node-red and then restarts it. As Colin suggested, this uses the command node-red and not node-red-start. Then the variables are defined and display properly. Hooray!

What does not work:
(1) setting variables in ~/.node-red/EnvironmentFile.
And yes, there is a line

EnvironmentFile=-/home/user/.node-red/EnvironmentFile

in /lib/systemd/system/nodered.service which is linked as expected.

user@RP01:~$ ls -l /etc/systemd/system/multi-user.target.wants/nodered.service
lrwxrwxrwx 1 root root 35 Mar 16 12:06 /etc/systemd/system/multi-user.target.wants/nodered.service -> /lib/systemd/system/nodered.service

This has no effect, no matter when node-red is started, on boot-up, from a shell file, or directly from a command line. I don't understand this.

(2) Confirming Colin's comments, if I were to change node-red in the startNR.sh file to node-red-start, it does not work. Note that whenever the command node-red-stop runs, it advises Use node-red-start to start Node-RED again. This is not good advice if you want to set environment variables.

(3) Setting the ownership and permissions of ~/startNR.sh -- e.g., to root with setuid -- doesn't help.

Again, thanks for the help.
ThisEndUp

That's a surprise because as I showed in my first post above, it works for me.
I don't have "export" in that file though, that's a bashism and I presume this file is interpreted by JavaScript.
Maybe it only works if node red autostart is enabled

What have you got in EnvironmentFile?

It's listed in my response.

But I did try it once without the export at the beginning of the lines.

~ThisEndUp

If you will indulge me a little longer, please:

Edit your /home/user/node-red/EnvironmentFile to contain

MQTT_BROKER='broker.hivemq.com'
MQTT_PORT=1883
MQTT_CLIENT_ID=Client007     
MQTT_TOPIC='ViewMe/Meter01'    
MQTT_NAME='MyName'

Import this little flow
image

[{"id":"c0c11435666359ac","type":"inject","z":"2b90ab1f09978dc3","name":"","props":[{"p":"broker","v":"${MQTT_BROKER}","vt":"str"},{"p":"topic","v":"MQTT_TOPIC","vt":"env"},{"p":"port","v":"${MQTT_PORT}","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":170,"y":120,"wires":[["b15b20cf035d36f3"]]},{"id":"b15b20cf035d36f3","type":"debug","z":"2b90ab1f09978dc3","name":"From Environment","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":350,"y":120,"wires":[]}]

Stop Node-red
Start Node-red by either

sudo systemctl start nodered

or

sudo systemctl enable nodered
sudo reboot

Show us what the debug output is.

What I get, on a Raspberry running Node-red 4.0 is
image

OK -- success. Here are the details:
(1) I've now modified the EnvironmentFile contents to remove export from the beginning of each line.

user@RP01:~$ cat ~/.node-red/EnvironmentFile
MQTT_BROKER=broker.hivemq.com
MQTT_PORT=1883
MQTT_CLIENT_ID=Client007
MQTT_TOPIC=ViewMe/Meter01
MQTT_NAME=MyName

(2) I've now got the following flow (the code files are in entries above, so I won't repeat them here).

(3) I make sure node red is not running:

user@RP01:~$ ps -ef | grep [Nn]ode
root        1264    1208  0 16:16 ?        00:00:00 fusermount3 -o rw,nosuid,nodev,fsname=portal,auto_unmount,subtype=portal -- /run/user/1000/doc
user@RP01:~$ 

(4) I then start node red with jbudd's recommended

sudo systemctl start nodered

I've also tried the alternative of rebooting the Raspberry Pi, and it works as well.

(5) The result is

3/18/2024, 4:23:29 PM
node: 3e6758c2883dfd69
msg.payload : string[183]
string[183]
MQTT_BROKER: 'broker.hivemq.com'    
MQTT_PORT: '1883'    
MQTT_TOPIC: 'ViewMe/Meter01'    
--- following are not used ---    
MQTT_CLIENT_ID: 'Client007'    
MQTT_NAME: 'MyName'    
3/18/2024, 4:23:31 PM
node: From Environment
ViewMe/Meter01 : msg : Object
object
_msgid: "15809db93a2e651b"
broker: "broker.hivemq.com"
topic: "ViewMe/Meter01"
port: "1883"

Hooray! The results are the desired ones, i.e.both the function named View MQTT variables which uses the env.get() function and the inject node, which uses TypedInput widgets, successfully access the environment variables in ~/.node-red/EnvironmentFile.

Takeaway: The two essential changes were steps 1 and 4 above. Thank you jbudd for the actions that solved this and Colin for the explanation of the (IMHO subtle) reason that step 4 is necessary.

~ThisEndUp

Just to note that that is what
node-red-start
does.

Thanks for the info.

I did try to test node-red-start. When that didn't work, I tried a subsequent sudo systemctl start nodered, and that did work. However, there are a number of moving parts -- most particularly ~/.node-red/EnvironmentFile and open browser pages -- that were involved in all my tests. I tried to be careful, but it's quite possible that the tests that failed booted the Raspberry Pi when the environment file was not available, or that a browser page was stale.

In any case, I've tried node-red-start again in a three scenarios:
(1) with a reboot that started node-red automatically,
(2) after a reboot with an automatic start followed by a manual stop and then a start, and
(3) after a reboot after disabling node-red, so it didn't start automatically, followed by a manual start.
It worked each time. I've even tried it using
bash -x node-red-start
to see what it actually does, step-by-step. Indeed it does run sudo systemctl start nodered, so I don't really understand why it failed in my earlier tests.

I'm glad that the correct information is now out. Thanks again,
~ThisEndUp

1 Like

Unfortunately the Node-red documentation on environment variables is wrong.

https://discourse.nodered.org/t/error-in-user-guide/86486

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.