Read the statement by Michael Teeuw here.
Is there a way to store secrets in the environment?
-
I’d like to put my config.js in a git repo, but I don’t want to store my API keys and such in there. I’ve tried to do something like:
var config = { modules: [ { module: "currentweather", position: "top_right", config: { location: "My Location", locationID: "1234567", appid: process.env.APIKEY } } ] };
…but that always yields an error where the config.js is seen as invalid and everything defaults.
I’ve tried modifying it after the fact in a little code block after the
var config
ends but while I’ve updated the data and canconsole.log
it, it seems to be too late for the change to have taken effect. The current weather still sees the placeholder.var config = { modules: [ { module: "currentweather", position: "top_right", config: { location: "My Location", locationID: "1234567", appid: "OPENWEATHER_API_KEY" } } ] }; if (!process.env.OPENWEATHER_API_KEY) { console.log("You must define the OPENWEATHER_API_KEY for weather support."); } else { var owApiKey = process.env.OPENWEATHER_API_KEY; console.log("Updating modules that require the OPENWEATHER_API_KEY."); config.modules.forEach(function (mmModule) { if (mmModule.config && mmModule.config.appid == "OPENWEATHER_API_KEY") { console.log("- " + mmModule.module); mmModule.config.appid = owApiKey; } }); }
I feel like this is a n00b sort of Node.js question and I’m just missing something obvious.
-
I figured it out.
I am seeing this issue where, as it turns out, process environment variables are not actually carried over to the Electron app. Instead you have to request the variables from the remote process.
In the issue it recommends doing something like this:
const electron = window.require('electron'); const remote = electron.remote; var x = remote.process.env["MY_VAR"];
In
config.js
it appearsrequire
is not defined, nor iswindow.require
, at least when running in the Electron context.Some of the challenge I’m seeing I think is that the same config.js file is getting used in a Node.js/server context and in an Electron app context. I sort of feel like the Electron app should be requesting the configuration from the /config endpoint of the server rather than doing its own JS parsing, but I’m gathering that’s not actually what’s happening here.
To get this to work, there’s some careful hackery where:
- You need to enable the
nodeIntegration
feature on Electron. This is off for security reasons by default, however I’m not super concerned that someone on my local network will be hacking my Electron-based magic mirror app. Doing this allowswindow.require
to bring in theelectron
module, which you need to read the remote process. - You need to have conditional logic that either uses the current process environment (for the server side) or uses
electron.remote.process
(for the client side).
If you do that, you can get environment variables to work. It will look something like this:
var remote = null; if (typeof window !== "undefined") { remote = window.require("electron").remote; } var config = { address: "localhost", port: 8080, ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], language: "en", timeFormat: 24, units: "imperial", electronOptions: { webPreferences: { nodeIntegration: true } }, modules: [ { module: "currentweather", position: "top_right", config: { location: "Hillsboro", locationID: "5731371", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city appid: "OPENWEATHER_API_KEY" } }, { module: "weatherforecast", position: "top_right", header: "Weather Forecast", config: { location: "Hillsboro", locationID: "5731371", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city appid: "OPENWEATHER_API_KEY" } } ] }; var owApiKey = null; if (typeof process !== "undefined" && process.env.OPENWEATHER_API_KEY) { // process is undefined in the Electron app. owApiKey = process.env.OPENWEATHER_API_KEY; } if (remote && remote.process.env.OPENWEATHER_API_KEY) { // remote is null if the Electron nodeIntegration value isn't set to true. owApiKey = remote.process.env.OPENWEATHER_API_KEY; } if (!owApiKey) { console.log("You must define the OPENWEATHER_API_KEY environment variable for weather support."); } else { console.log("Updating modules that require the OPENWEATHER_API_KEY."); config.modules.forEach(function (mmModule) { if (mmModule.config && mmModule.config.appid == "OPENWEATHER_API_KEY") { console.log("- " + mmModule.module); mmModule.config.appid = owApiKey; } }); } /*************** DO NOT EDIT THE LINE BELOW ***************/ if (typeof module !== "undefined") { module.exports = config; }
I will probably refactor that into a little method in here to make it easier to use and clean up some of the redundancy, but that’s what you have to do.
- You need to enable the
-
-
Well, maybe it’ll help to be more specific about what I have. I am able to read environment variables but it seems there’s an order of execution thing that I’m missing.
I have my config.js as such:
var config = { address: "localhost", port: 8080, ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], language: "en", timeFormat: 24, units: "imperial", modules: [ { module: "compliments", position: "lower_third" }, { module: "currentweather", position: "top_right", config: { location: "Hillsboro", locationID: "5731371", appid: "OPENWEATHER_API_KEY" } }, { module: "weatherforecast", position: "top_right", header: "Weather Forecast", config: { location: "Hillsboro", locationID: "5731371", appid: "OPENWEATHER_API_KEY" } } ] }; // Replace the environment secrets - this doesn't seem to work if // you just call it inline in the JSON. if (!process.env.OPENWEATHER_API_KEY) { console.log("You must define the OPENWEATHER_API_KEY for weather support."); } else { var owApiKey = process.env.OPENWEATHER_API_KEY; console.log("Updating modules that require the OPENWEATHER_API_KEY."); config.modules.forEach(function (mmModule) { if (mmModule.config && mmModule.config.appid == "OPENWEATHER_API_KEY") { console.log("- " + mmModule.module); mmModule.config.appid = owApiKey; } }); } config.modules.forEach(function(mmModule) { console.log(mmModule.config); }); /*************** DO NOT EDIT THE LINE BELOW ***************/ if (typeof module !== "undefined") { module.exports = config; }
The goal is that I can read the environment variable
OPENWEATHER_API_KEY
and swap it in accordingly.When I run the Magic Mirror, I do this:
OPENWEATHER_API_KEY=abcd1234 export OPENWEATHER_API_KEY npm start
When I watch the logs, I can see that the appropriate
weatherforecast
andcurrentweather
modules are picked up. Further, it appears the configuration is updated:Updating modules that require the OPENWEATHER_API_KEY. - currentweather - weatherforecast undefined { location: 'Hillsboro', locationID: '5731371', appid: 'abcd1234' } { location: 'Hillsboro', locationID: '5731371', appid: 'abcd1234' }
(The ‘undefined’ in there is because ‘compliments’ doesn’t have configuration.)
So it seems I’m configuring things right.
However, if I run this with
npm start dev
, I see the requests going out to OpenWeather aren’t using my environment key:GET https://api.openweathermap.org/data/2.5/weather?id=5731371&units=imperial&lang=en&APPID=OPENWEATHER_API_KEY 401 (Unauthorized)
It’s still using the hardcoded placeholder that wasn’t updated. If I manually put my API key right into the JSON object, this works… but that’s what I’m trying to avoid by using the environment.
I did try doing something like this:
var config = { address: "localhost", port: 8080, ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], language: "en", timeFormat: 24, units: "imperial", modules: [ { module: "weatherforecast", position: "top_right", header: "Weather Forecast", config: { location: "Hillsboro", locationID: "5731371", appid: process.env.OPENWEATHER_API_KEY } } ] };
However, using
process.env
right in the JSON object causes some sort of error that results in the configuration being seen as invalid.I even temporarily modified the main
app.js
just after it parsed config to log all the module configurations and it was showing the correct value - my replaced value rather than the placeholder.I’m just super stumped as to what’s up. Since I see the request in Chrome dev tools, does that mean it’s the client part that’s making the request? Is there a problem with how the configuration is passed to the client?
-
Viewing
http://localhost:8080/config/
while it’s running, the configuration looks right to me with all theappid
values replaced, no placeholders in sight. -
I figured it out.
I am seeing this issue where, as it turns out, process environment variables are not actually carried over to the Electron app. Instead you have to request the variables from the remote process.
In the issue it recommends doing something like this:
const electron = window.require('electron'); const remote = electron.remote; var x = remote.process.env["MY_VAR"];
In
config.js
it appearsrequire
is not defined, nor iswindow.require
, at least when running in the Electron context.Some of the challenge I’m seeing I think is that the same config.js file is getting used in a Node.js/server context and in an Electron app context. I sort of feel like the Electron app should be requesting the configuration from the /config endpoint of the server rather than doing its own JS parsing, but I’m gathering that’s not actually what’s happening here.
To get this to work, there’s some careful hackery where:
- You need to enable the
nodeIntegration
feature on Electron. This is off for security reasons by default, however I’m not super concerned that someone on my local network will be hacking my Electron-based magic mirror app. Doing this allowswindow.require
to bring in theelectron
module, which you need to read the remote process. - You need to have conditional logic that either uses the current process environment (for the server side) or uses
electron.remote.process
(for the client side).
If you do that, you can get environment variables to work. It will look something like this:
var remote = null; if (typeof window !== "undefined") { remote = window.require("electron").remote; } var config = { address: "localhost", port: 8080, ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], language: "en", timeFormat: 24, units: "imperial", electronOptions: { webPreferences: { nodeIntegration: true } }, modules: [ { module: "currentweather", position: "top_right", config: { location: "Hillsboro", locationID: "5731371", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city appid: "OPENWEATHER_API_KEY" } }, { module: "weatherforecast", position: "top_right", header: "Weather Forecast", config: { location: "Hillsboro", locationID: "5731371", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city appid: "OPENWEATHER_API_KEY" } } ] }; var owApiKey = null; if (typeof process !== "undefined" && process.env.OPENWEATHER_API_KEY) { // process is undefined in the Electron app. owApiKey = process.env.OPENWEATHER_API_KEY; } if (remote && remote.process.env.OPENWEATHER_API_KEY) { // remote is null if the Electron nodeIntegration value isn't set to true. owApiKey = remote.process.env.OPENWEATHER_API_KEY; } if (!owApiKey) { console.log("You must define the OPENWEATHER_API_KEY environment variable for weather support."); } else { console.log("Updating modules that require the OPENWEATHER_API_KEY."); config.modules.forEach(function (mmModule) { if (mmModule.config && mmModule.config.appid == "OPENWEATHER_API_KEY") { console.log("- " + mmModule.module); mmModule.config.appid = owApiKey; } }); } /*************** DO NOT EDIT THE LINE BELOW ***************/ if (typeof module !== "undefined") { module.exports = config; }
I will probably refactor that into a little method in here to make it easier to use and clean up some of the redundancy, but that’s what you have to do.
- You need to enable the
-
I’ve added a feature request to GitHub here.