Skip to content

Instantly share code, notes, and snippets.

@maestrow
Last active December 15, 2021 15:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maestrow/0f89fe85b87cf8788a4a0a4daa5fea55 to your computer and use it in GitHub Desktop.
Save maestrow/0f89fe85b87cf8788a4a0a4daa5fea55 to your computer and use it in GitHub Desktop.
Why gatsby look into root for its config.json?

EACCES: permission denied, open '/root/.config/gatsby/config.json'

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.

Investigation

  • Let's look at gatsby source code and figure out how gatsby read its own config.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 in gatsby sources. Find it in tracking.ts. In that file we see that get-config-store module is used to read config store (line import { getConfigStore } from "./get-config-store")
    • In get-config-store module we see that it uses configstore package (line import Configstore from "configstore")
  • Looking into configstore sources: https://github.com/yeoman/configstore/blob/main/index.js. To compose the resulting path that module uses import {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);

Narrowing and Reproducing issue

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.

Workaround

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.

How Env passed to child process: Apache -> PHP -> child

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment