Environment variables outside settings.js file

Hi, after read several messages I still have some troubles, could you please help me?
I have never used environment variables before.
Now, in settings.js I've added "env: process.env" just below " functionGlobalContext: {" like this:

functionGlobalContext: {
    env: process.env

Moreover, following other post and documents I've modifided the module.exports = { section like this:

module.exports = {
process.env.HOSTNAME = require('os').hostname();
process.env.FOO = 'just another bar';

but in this way it doesn't work. In order to make it works I had to write it as following:

module.exports = {
test1: process.env.HOSTNAME = require('os').hostname();
test2: process.env.FOO = 'just another bar';

I don't know if it's correct but is the only way I was able to make it working.
So, in this way I can use the environment variables set inside the settings.js file.
But I have 2 more needs:

  • I would have the environment variables writes in an external file rather than inside settings.js. How can I do this?
  • I would even pass as variables users and passwords, for nodes that requires connection like mysql, mqtt, etc.

Briefly, since I have the some project for several users, I would like to have jast one file where to make these changing and let it read and passed to the flow at load, when node-red starts.
Thanks

I like very much this documentation on Environmental Variables. It explains how to modify the settings.js file to add them. You should add the environmental variables in the beginning of the file, not inside module.exports

https://flows.nodered.org/flow/8a13d11dfe80e279df83341e3e17bcc1

Thanks a lot Andrei. I already saw it, but I misunderstood where to declare the variables. Thanks for pointing it out again. Now it works perfectly.
One more thing. It would be really useful for me having this variables within an external file, and not inside setting.js. How can I make this external file read from setting.js at node-red load?

Hi @Lupin_III, reading an external file from settings.js is easy but I couldn't possibly explain better than @TotallyInformation did in a previous post (so useful that I saved that post as a reference):

Simply create another file with a module.exports statement and then require that file in the settings file

He explains in details here.

2 Likes

The link doesn't work

The link is to a private conversation... perhaps @TotallyInformation would be kind enough to re-post his comment here.

Oh sorry, I overlooked that that was a private conversation. So, giving the answer without explaining.

Here is like I did (first time ever, perhaps there is a better way).

  1. Create external file named env-external.js (testing in a windows environment), with the following content:
module.exports =  {

hostname : process.env.HOSTNAME = require("os").hostname(),
mqttLocal: process.env.MQTTLOCAL = "192.168.1.50",
mqttPublic: process.env.MQTTPUBLIC = "broker.hivemq.com",
Lat: process.env.LAT = "-25.442414",
Lon: process.env.LON = "-49.236903" ,
friend: process.env.FRIEND = "vercingetorix"
}

2- Added this line to the top of settings.js (outside modules.export session):

var	env_external = require("./env-external");

3- Restarted the runtime

4- Tested using a change node to read an environmental variable to msg.payload

r-01

5- Sounds strange for me but it worked well right first time (apparently) :laughing:

r-02

Your wish is my command!


If you look at settings.js you will see that all of the settings are contained within a module.exports. This is what turns a .js file into a node.js module that you can use require on.

So lets say that you wanted to split out the contextStorage settings so that they were easier to change. You might currently have something like this in settings.js

    contextStorage: {
        default: {
            module:'memory'
        },
        file: {
            module: 'localfilesystem'
        }
    },

So now create a new file, lets say contextStorage.js:

module.exports =  {
        default: {
            module:'memory'
        },
        file: {
            module: 'localfilesystem'
        }
    },

Then you can change settings.js like so:

    contextStorage: require('./contextStorage.js'),

You can take this further and create more complex objects for export including functions. Lets take a random example. Let's say that you wanted to run lots of instances of Node-RED, you need multiple ports. Perhaps you want to manage those ports in a database. So you could create a module that exports a function that returns a port number perhaps looked up from a db or maybe simply from running a script to check for ports in use.

In this more complex scenario, you are likely to put your require at the top of the settings.js file like you may do with things like fs. So your config file might now look like:

module.exports = {
    freePort: function() {
        ...
    },
    contextStorage: {
        default: {
            module:'memory'
        },
        file: {
            module: 'localfilesystem'
        }
    },
}

In settings.js you'd have this at the top of the file:

const mySettings = require('./mySettings.js')

And setting the port and context storage:

    uiPort: mySettings.freePort(),
    contextStorage: mySettings.contextStorage,

As for missing comma's. In JS files, as long as you are using a reasonably up to date version of JavaScript, you can always have trailing commas and I always use them as it saves both time and frustration. Configure you linting tool to require them and you will always spot the missing ones.

Thanks a lot to all. It works great.
I add one more step if someone can help me. Actually I ned to read these my own environment variable even within a python script.
What would be for you the best way? At the moment as suggested I create a new test.js file like this

module.exports = {
mqttLocal: process.env.MQTTLOCAL = "192.168.1.201",
mqttPublic: process.env.MQTTPUBLIC = "test.com",
testPetch: process.env.testPetch = " C:\test\"
};

and at the top of setting-js in node-red

var env_external = require("./test.js");

I need to read these variable in python, too.

Well, you could read the file in and manually parse it in Python however the format isnt great for python.

There are a number of options - here are some off the top of my head...

save the file as pure JSON and read the JSON file into python (easy to parse)

OR

you might be better off storing the env vars in OS environment variables (python and node-red can read these)

Python...

os.getenv('MQTTPUBLIC', default_value)

OR

you could transmit them from node-red to an MQTT broker then have your python app subscribe to the env topics e.g. environment/MQTTPUBLIC, environment/MQTTPRIVATE , etc.

Thanks Steve.
I think in my case the best way is your first suggestion, using a json pure file. But How can now pass the variables inside the json file to setting js?
Now I have the json file like

{
"mqttLocal" : "192.168.1.201",
"mqttPublic": "test.com",
"testPetch" : "C:\test\"
}

I read it in python. Now I would even use these as environment voriables inside node-red. How can I pass them to setting.js?

same as youre already doing (except using a file name xxxxx.json)

Proof...
image

IMPORTANT NOTE...

that is not valid JSON (back slashes should be escaped)

e.g...

{
  "mqttLocal" : "192.168.1.201",
  "mqttPublic": "test.com",
  "testPetch" : "C:\\test\\"
}

A simple dsm use case that outputs RED.settings.
Just in case you'd like to check your settings.

[{"id":"942c6e6b.a3a76","type":"dsm","z":"ac4aa9f6.c24288","name":"RED settings","sm_config":"{\n    \"methods\": {\n        \"onTransition\": [\n            \"msg.payload = RED.settings;\",\n            \"output = true;\"\n        ]\n    }\n}","x":580,"y":80,"wires":[["d3e784c0.4ec658"]]},{"id":"aab96586.c40018","type":"inject","z":"ac4aa9f6.c24288","name":"","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":430,"y":80,"wires":[["942c6e6b.a3a76"]]},{"id":"d3e784c0.4ec658","type":"debug","z":"ac4aa9f6.c24288","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":80,"wires":[]}]

Thanks guys.
At the end I'm able to use these environment variables inside flows, but I don't know if I do it in the correct way. Let me recap.
this is my test.json file

{
"mqttLocal" : "192.168.1.201",
"mqttPublic": "test.com",
"testPetch" : "C:/test/"
}

and this is the beginning of my settings.js

my_var_env = require("C:/test/test.json");
module.exports = {
MQTTLocal: process.env.MQTTLocal = my_var_env.mqttLocal,
MQTTPublic: process.env.MQTTPublic = my_var_env.mqttPublic,
TestPetch: process.env.TestPetch = my_var_env.testPetch,
......
uiPort: process.env.PORT || 1880,
......

within a function node I call my variables in this way:

var env = global.get('env');
msg.payload={
"MQTTLocal": env.MQTTLocal
};
return [msg];

Using the nodes posted by Cflurin I see that in this way these variables are present directly as object properties, plus as environment variables, and looking at my code I think this is correct even if I'm not able to use them as object properties. But there is a better way in order to avoid to duplicate them inside the node-red instance?
@ Steve. Since I need to pass the path inside the json file in python, where I need backslashes C:\\folder\\ I tryed using it inside the json file end it is correctly read by settings.js and works as well.

You should never need backslashes, certainly in any modern app. If you need cross-platform support, best to use a library. node.js has the path library which is built in, not sure if Python has an equivalent. Use path.join() in Node.js always rather than using fixed paths, that will also sort out intermediate relative paths as well.

Thanks TotallyInformation. I set it in setting.js about the path of my test.json file. How can I do the same inside my test.json file? I have path's directories here, too

You can't do it inside a JSON file. However, you might consider using an array instead and then path.join the array when you need it. That way, never any issues :wink:

Many thanks

As an option you could read the file test.json and store the settings into a global variable
without the need to modify settings.js and to restart node-red.

Assuming the test.json is in your userdir the JSON object is stored into the golbal ext_settings.

[{"id":"b5583107.705f7","type":"dsm","z":"ac4aa9f6.c24288","name":"","sm_config":"{\n    \"currentState\": \"idle\",\n    \"states\": {\n        \"idle\": {\n            \"start\": \"reading\"\n        },\n        \"reading\": {\n            \"done\": \"idle\"\n        }\n    },\n    \"methods\": {\n        \"init\": [\n            \"sm.fs = require('fs');\",\n            \"sm.udir = RED.settings.userDir + '/';\",\n            \"sm.filename = sm.udir + 'test.json';\"        \n        ],\n        \"start\": [\n            \"sm.fs.readFile(sm.filename, 'utf8', function(err, data) {\",\n                \"if (err) {\",\n                    \"node.error(err);\",\n                \"} else {\",\n                    \"var ext_settings = JSON.parse(data);\",\n                    \"global.set('ext_settings', ext_settings);\",\n                    \"msg.payload = ext_settings;\",\n                    \"resume('done', msg);\",\n                \"};\",\n            \"});\",\n            \"output = false;\"\n        ],\n        \"done\": [\n            \"sm.fill = 'green';\",\n            \"sm.text = 'done';\"\n        ],\n        \"status\": {\n            \"fill\": {\n                \"get\": \"sm.fill\"\n            },\n            \"shape\": \"dot\",\n            \"text\": {\n                \"get\": \"sm.text\"\n            }\n        }\n    }\n}","x":810,"y":300,"wires":[["311049db.f64e26"]]},{"id":"4fb989b1.fe0ae8","type":"inject","z":"ac4aa9f6.c24288","name":"","topic":"start","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":640,"y":300,"wires":[["b5583107.705f7"]]},{"id":"311049db.f64e26","type":"debug","z":"ac4aa9f6.c24288","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":970,"y":300,"wires":[]}]