When calling gatsby from PHP script which itself interpreted by apache2 web server I'm getting an error: Error: EACCES: permission denied, open '/root/.config/gatsby/config.json' You don't have access to this file..
Shell script that was called from PHP with output ('+' sign followed by shell command):
+ whoami
myuser
+ node_modules/.bin/gatsby clean
Error: EACCES: permission denied, open '/root/.config/gatsby/config.json'
You don't have access to this file.
This issue happens only on the web (invocation chain: Apache -> PHP -> gatsby). If I call same PHP script from the console at the same web-server's terminal, this error doesn't happen.
The essence of the problem is that the gatsby process for some reason tries to open its settings file from the root user's home directory, but not from the home directory of the current user myuser.
- Let's look at
gatsbysource code and figure out howgatsbyread its ownconfig.jsonfile.- Look into local
~/.config/gatsby/config.jsonand pick up some unique string that we can search in gatsby sources.machineIdsatisfies us. - Search for
machineIdsubstring ingatsbysources. Find it intracking.ts. In that file we see thatget-config-storemodule is used to read config store (lineimport { getConfigStore } from "./get-config-store") - In
get-config-storemodule we see that it usesconfigstorepackage (lineimport Configstore from "configstore")
- Look into local
- Looking into
configstoresources: https://github.com/yeoman/configstore/blob/main/index.js. To compose the resulting path that module usesimport {xdgConfig} from 'xdg-basedir';function and package. - Look into
xdg-basedirsources: https://github.com/sindresorhus/xdg-basedir/blob/main/index.js. And there we find key snippet:
const homeDirectory = os.homedir();
export const xdgConfig = env.XDG_CONFIG_HOME ||
(homeDirectory ? path.join(homeDirectory, '.config') : undefined);Let's figure out what we get with os.homedir() nodejs call. Here I post scripts to reproduce direct issue.
test.php
<?php
exec("node test.mjs 2>&1", $output, $result_code);
echo '<pre>';
echo join("\n", $output);
echo "\n" . $result_code;
echo '</pre>';
?>test.mjs
import os from 'os';
import {xdgConfig} from 'xdg-basedir';
import Configstore from "configstore";
console.log('os.homedir(): ', os.homedir());
console.log('os.userInfo():', os.userInfo());
console.log('xdgConfig: ', xdgConfig);
const config = new Configstore(
`gatsby`,
{},
{
globalConfigPath: true,
}
)
console.log('Configstore: ', config);Output from console (correct):
$ php test.php
<pre>os.homedir(): /home/myhome
os.userInfo(): {
uid: 11599,
gid: 600,
username: 'redsquares',
homedir: '/home/myhome',
shell: '/bin/bash'
}
xdgConfig: /home/myhome/.config
Configstore: Configstore { _path: '/home/myhome/.config/gatsby/config.json' }
Output from apache (has problems):
os.homedir(): /root
os.userInfo(): {
uid: 11599,
gid: 600,
username: 'myuser',
homedir: '/home/myhome',
shell: '/bin/bash'
}
xdgConfig: /root/.config
fs.js:461
handleErrorFromBinding(ctx);
^
Error: EACCES: permission denied, open '/root/.config/gatsby/config.json'
You don't have access to this file.
os.homedir() call shows root value when testing with Apache->PHP->nodejs call chain. Despite the fact that os.userInfo() shows correct username and home dir.
In xdg-basedir sources we see that it respects XDG_CONFIG_HOME environment variable. Therefore to workaround this issue, we can assign the correct path to that env variable: XDG_CONFIG_HOME=/home/myhome/.config. It's worth mentioning that SetEnv directive (apache2) doesn't help in our case. Variable will be accessible from PHP script, but not propagate to its child processes. For more details see next chapter.
Here we test how environment variables passed from web-server to PHP script and to its child processes. Let's say we have apache2 web server with mod_php and mod_env modules installed. In the env-test.php script we use shell_exec to call the nodejs script. Our goal is to use some environment variable in js-script. mod_env provides a SetEnv directive to set up the environment variable.
So let's set env variable in .htaccess:
SetEnv XDG_CONFIG_HOME /home/some-dir/.config
env-test.php:
<?php
echo '<pre>';
echo "From PHP:\nXDG_CONFIG_HOME: ". getenv('XDG_CONFIG_HOME');
echo "\n\nFrom child process:" . "\n";
$code = <<<JS
console.log(`PATH: `, process.env.PATH);
console.log(`XDG_CONFIG_HOME: `, process.env.XDG_CONFIG_HOME);
JS;
echo shell_exec("node -e '{$code}'");
echo '</pre>';
?>If we call env-test.php from apache web server, then the result will be:
From PHP:
XDG_CONFIG_HOME: /home/some-dir/.config
From child process:
PATH: /bin:/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/opt/bin
XDG_CONFIG_HOME: undefined
Which shows that environment variables assigned with SetEnv doesn't passed to child processes and only available in PHP script.