以root用户身份运行NPM 8时出现EACCESS错误

6
当使用npm调用时,使用fs.readFileSync的脚本会抛出EACCESS异常,但使用node则不会。

在一台古老的(2016年)Docker镜像上,我需要运行一个涉及Bower的postinstall NPM脚本 (bower install --allow-root),但每次运行时都会出现EACCES: permission denied, open '/root/.config/configstore/bower-github.json'的错误。我发现使用npx bower 也会导致相同的问题。将npx bower在Docker之外运行则正常。

通常情况下,我可以轻松地处理这些问题,因为它们通常会在某人使用sudo时出现误操作。解决这些问题的方法通常是将所有者更改回当前用户或只需使用sudo--allow-root来运行bower命令 (示例1示例2)。

然而,这不是这些问题之一。 我已经是root用户了!

完整的错误信息与类似问题的任何错误信息一样:

root@eaa32456c249:/var/www/myproj# npx bower --allow-root
/var/www/myproj/node_modules/bower/lib/node_modules/configstore/index.js:54
                throw err;
                ^

Error: EACCES: permission denied, open '/root/.config/configstore/bower-github.json'
You don't have access to this file.

    at Object.openSync (node:fs:585:3)
    at Object.readFileSync (node:fs:453:35)
    at Configstore.get (/var/www/myproj/node_modules/bower/lib/node_modules/configstore/index.js:35:38)
    at new Configstore (/var/www/myproj/node_modules/bower/lib/node_modules/configstore/index.js:28:48)
    at readCachedConfig (/var/www/myproj/node_modules/bower/lib/config.js:19:23)
    at defaultConfig (/var/www/myproj/node_modules/bower/lib/config.js:11:12)
    at Object.<anonymous> (/var/www/myproj/node_modules/bower/lib/index.js:16:32)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32) {
  errno: -13,
  syscall: 'open',
  code: 'EACCES',
  path: '/root/.config/configstore/bower-github.json'

我无法进一步提升我的权限,添加--allow-root也没有任何作用。我甚至检查了相关模块,发现始终失败的调用只是这样的:

readFileSync(this.path, 'utf8');

当然,this.path'/root/.config/configstore/bower-github.json'

我接着编写了这个小测试模块,它也能够正常运行:

root@eaa32456c249:/var/www/myproj# cat test.js
const execSync = require('child_process').execSync;
const fs = require('fs');
const path = '/root/.config/configstore/bower-github.json';

console.log('exec whoami: ', execSync('whoami').toString());
try {
    const result = execSync('ls -l ' + path, { encoding: 'utf8' });
    console.log('exec ls -l: ', result);
} catch (err) {}

try {
    const parsed = JSON.parse(fs.readFileSync(path, 'utf8', { encoding: 'utf8' }));
    console.log('parsed: ', parsed);
} catch (err) {
    console.error(err.message);
}
root@eaa32456c249:/var/www/myproj# node test.js
exec whoami:  root

exec ls -l:  -rw-r--r-- 1 root root 3 Dec  8 22:55 /root/.config/configstore/bower-github.json

parsed:  {}

一个谜!
2个回答

16
NPM版本7和8将以根包目录的所有者身份运行。换句话说,如果您想以root身份运行,请在项目的根目录上执行“chown root.root -R .”。较早和较新的版本缺乏此行为,因此升级NPM也可以避免此问题。
更新 NPM 9 恢复了更改,这意味着上述内容仅适用于 NPM 7 和 8。请参阅 PR
在让这个问题酝酿了一段时间后,我突然想到可能需要检查一下代码运行的身份,所以我打开了相关的模块(node_modules/configstore/index.js),并在失败的调用之前的几行代码中添加了以下内容:
const execSync = require('child_process').execSync;
console.log('exec `id`: ', execSync('id', { encoding: 'utf8' }));
console.log('exec `ls`: ', execSync('ls -l /root', { encoding: 'utf8' }));

确实有些可疑的事情正在发生,正如那些印刷的字行所显示的:
exec `id`:  uid=1000 gid=1000 groups=1000

ls: cannot open directory '/root': Permission denied

所以不知怎么的,以root身份运行npx bower会使bower以uid=1000的用户身份运行?以同样的问题运行npm run postinstall也会出现相同的问题。
好的...让我们仔细看看这个。如果我手动使用node运行bower CLI模块呢?
$ node node_modules/.bin/bower  --allow-root
root@eaa32456c249:/var/www/myproj# node node_modules/.bin/bower  --allow-root
exec `id`:  uid=0(root) gid=0(root) groups=0(root)

exec `ls`:  total 0

它起作用了 - 我仍然是root用户!显然,无论是npx还是npm,在底层都以某种方式与命令运行的用户有关,这真是有点奇怪!
NPM、root用户和当前工作目录的所有者
深入挖掘以找到解决方案
在发现上述事实后,我进行了一些搜索,并找到了这个NPM问题,虽然没有直接的解释,但让我开始追踪Node尝试以文件所有者的身份执行的轨迹。而且这是我第一次真正检查文件的所有者是谁:
root@eaa32456c249:/var/www/myproj# ls -lh node_modules/bower/
total 72K
-rw-r--r-- 1 1000 1000  40K Oct 20 08:50 CHANGELOG.md
-rw-r--r-- 1 1000 1000 1.1K Oct 20 08:50 LICENSE
-rw-r--r-- 1 1000 1000  14K Oct 20 08:50 README.md
drwxr-xr-x 2 1000 1000 4.0K Oct 20 08:50 bin
drwxr-xr-x 9 1000 1000 4.0K Oct 20 08:50 lib
-rw-r--r-- 1 1000 1000  460 Oct 20 08:50 package.json

只是简单地执行chown root.root -R node_modules没有任何作用,所以我继续搜索。然后我阅读了this article,其中有这段摘录:

如果使用root权限调用npm,它将更改uid为用户配置中指定的用户帐户或uid,默认为nobody。设置unsafe-perm标志以使用root权限运行脚本。

好的,让我们尝试将unsafe-perm设置为true。不行,我仍然以uid=1000的身份运行。然后我进入了实际的NPM库,搜索与uid相关的内容,并在其中找到了答案。
 /usr/local/lib/node_modules/npm/node_modules/@npmcli/promise-spawn/index.js

对于NPM 8,它使用以下代码来确定要以谁的身份运行:
const { uid, gid } = isRoot ? inferOwner.sync(cwd) : {}

根据infer-owner模块的文档所述:
推断路径的所有者,基于其最近现有父级的所有者
请注意cwd部分。它不会查看node_modules,而是当前工作目录!所以我在项目的根目录上执行了chown root.root -R .,然后神奇地,它起作用了!
附加说明
此行为仅适用于NPM版本7和8。当以root身份运行时,NPM版本<7(随Node 12-14一起提供)完全正常工作。因此,最快的解决方法可能只是使用Node 14。从NPM 9开始,旧的行为恢复了(可能随Node 20一起提供)。

1
非常感谢!救了我的一天!顺便说一下,“在根目录上运行 do chown root.root -R .”这件事,我没有在我的笔记本电脑的根目录上运行它,而是在我的项目根目录上运行“chown root.root -R .”。 - Peng-Watcher37-Link37
1
很高兴听到它对你有所帮助。是的,当然应该放在项目的根目录下。我本可以详细说明的。现在已经修正了 :) - oligofren
1
此行为在npm v9中已关闭。请参见https://github.com/npm/rfcs/issues/546#issuecomment-1280967238。 @oligofren 您可能需要更新您的答案以说明这一点。=) - Zanna_37

0

这是一个非常具体的解决方案/场景 - 您正在使用 Docusaurus。如果您在 docusaurus.config.js 中设置了 path: '/',则可能会发生这种情况。将其设置为其他内容,也许您正在尝试设置 routeBasePath 而不是 path


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接