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
gatsby
source code and figure out howgatsby
read its ownconfig.json
file.- Look into local
~/.config/gatsby/config.json
and pick up some unique string that we can search in gatsby sources.machineId
satisfies us. - Search for
machineId
substring ingatsby
sources. Find it intracking.ts
. In that file we see thatget-config-store
module is used to read config store (lineimport { getConfigStore } from "./get-config-store"
) - In
get-config-store
module we see that it usesconfigstore
package (lineimport Configstore from "configstore"
)
- Look into local
- Looking into
configstore
sources: 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-basedir
sources: 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.