介绍物理隔离内网环境中,使用NVM管理多版本Node,并通过Verdaccio搭建私有npm仓库,实现依赖缓存与离线迁移。
引言
你有没有想过这样一种场景,你所有的开发电脑全部都是内网的完全的物理隔离,而且这台电脑上没有安装前端开的的环境,但是你的项目又是不同的技术栈,比如说 vue2、vue3、react 等,同时你使用包管理器可能有 npm、yarn、pnpm、bun 等,而且你还需要在内网环境中开发桌面端类似于 electron、tauri 等,同时你的构建还需要二进制的依赖等,并且你还使用 vue3 开发了一些业务组件库之类的,解决这类问题,那这种你可能又两种选择:
- 方案一:全部改成 npm,把 node_modules 跟着项目走;
- 方案二:使用 verdaccio 搭建完整的私服 npm 源,支持不同的技术栈和包管理器
那就引发出来一类场景:我们怎么在一台纯物理隔离的纯净电脑上搭建出支持不同技术栈的前端私有 npm 仓库呢!
下面我们将开启 verdaccio 使用,希望你读完这篇文章之后能对 verdaccio 有一定的了解和认识。
准备
由于我们是全新的环境,以 windows 环境为例,我们首选需要安装的是 node, 但是我们的项目又涉及不同的 vue 版本等,所有的开发环境需要多个 node 版本,鉴于遮掩这样的情况我们需要 node 版本的管理工具,常见的用nvm、fnm、volta等,这里以使用 nvm 为例进行安装(可以自己自行选择),下面以nvm进行操作。
换句话说下面的操作可以让我们在一个纯净的内网机器下把前端开发环境搭建起来!
NVM 安装部署
下面示例进行 windows 和 linux 的 nvm 的安装。
Windows 环境安装
下载 nvm-windows
在 github 上搜索
nvm-windows, 进入项目,点击右侧的 Releases 的,下载nvm-setup.exe或nvm-noinstall.zip。
Github 地址:https://github.com/coreybutler/nvm-windows/releases
下载 node js
访问 https://nodejs.org/dist/ 下载需要的版本
Windows 版本列表:
https://nodejs.org/dist/v12.22.12/node-v12.22.12-win-x64.zip
https://nodejs.org/dist/v14.21.3/node-v14.21.3-win-x64.zip
https://nodejs.org/dist/v16.20.2/node-v16.20.2-win-x64.zip
https://nodejs.org/dist/v18.19.0/node-v18.19.0-win-x64.zip
https://nodejs.org/dist/v20.11.0/node-v20.11.0-win-x64.zip安装 nvm-windows
- 点击 nvm-setup.exe 进行安装
- 解压 nvm-noinstall.zip 到指定目录
安装目录以:C:\nvm 为例, 后续操作都是再这个目录下进行,如需更换做相应的替换即可
安装时记住两个路径:
- nvm 安装路径:
C:\nvm - node js 符号链接路径:
C:\Program Files\nodejs
手动添加 node js 版本
将下载的 node js 手动压缩包解压到 nvm 安装目录下:
操作步骤:
# 1. 解压 node-v12.22.12-win-x64.zip
# 2. 将解压后的文件夹重命名为 v12.22.12
# 3. 将 v12.22.12 文件夹移动到 C:\nvm\ 目录下
# 4. 重复以上步骤处理其他版本# 目录结构应该是这样
C:\nvm\
├─ v12.22.12\
│ ├─ node.exe
│ ├─ npm
│ └─ ...
├─ v14.21.3\
├─ v16.20.2\
├─ v18.19.0\
└─ v20.11.0\修改 settings.txt
编辑 C:\nvm\settings.txt,进行下面的修改,修改对应的root和path:
root: C:\nvm
path: C:\Program Files\nodejs
arch: 64
proxy: none配置环境变量
确保以下环境变量已设置:
NVM_HOME = C:\nvm
NVM_SYMLINK = C:\Program Files\nodejs
Path 中添加:
%NVM_HOME%
%NVM_SYMLINK%Linux 环境安装
下面以银河麒麟桌面版 V10 SP1 为例。内外网机器必须先核对系统、CPU 架构和 glibc,外网打包机应尽量使用相同版本的银河麒麟:
cat /etc/os-release # 期望 ID=kylin / ID_LIKE=debian
uname -m # x86_64 | aarch64 | loongarch64 | sw_64
ldd --version | head -1 # glibc 版本
free -h; df -h ~ # Verdaccio storage 可能很大,建议预留 20G+架构与 Node 官方包的对应关系:
uname -m | Node 官方包 | 说明 |
|---|---|---|
x86_64 | node-vX.Y.Z-linux-x64.tar.xz | 兆芯 / 海光 / Intel / AMD |
aarch64 | node-vX.Y.Z-linux-arm64.tar.xz | 飞腾 / 鲲鹏 / 麒麟 9000C |
loongarch64 | 官方无 | 需龙芯自有仓库或社区构建 |
sw_64 | 官方无 | 需申威平台自编译 |
Node 18/20 官方二进制通常要求 glibc ≥ 2.28。Verdaccio 6 需要 Node ≥ 18,且 Verdaccio 6.9.0 起需要 Node ≥ 22;如果系统 glibc < 2.28,只能考虑 Verdaccio 5.x 配 Node 16,或升级操作系统环境。
麒麟还应先检查 KySec:
sudo getstatus
# 如果后续出现 Operation not permitted,且已确认不是普通文件权限问题:
# sudo setstatus softmode # 需先评估并遵守所在环境的安全合规要求下载 nvm
在 github 上搜索
nvm, 进入项目,点击右侧的 Releases 的,下载对应版本的,解压即可。
Github 地址: https://github.com/nvm-sh/nvm
本文示例使用 nvm v0.40.2演示。在联网机器下载 v0.40.2.tar.gz 后,再传输到内网机器,避免内网安装过程中访问 GitHub。
# nvm 本体
wget https://github.com/nvm-sh/nvm/archive/refs/tags/v0.40.2.tar.gz说明: 当你发现下载的是源码工程而不是编译产物时可能会产生疑问! 不用担心源码包即可,应为源码工程是执行的sh文件
下载 node js
访问 https://nodejs.org/dist/ 下载需要的版本
本文中 Verdaccio 服务固定使用 Node.js 22,旧版 Node.js 仅用于运行历史项目。以 Node.js v22.21.0 为例,根据银河麒麟机器的实际架构选择其中一个安装包:
# 以 aarch64 为例;x86_64 请改为 linux-x64
wget https://nodejs.org/dist/v22.21.0/node-v22.21.0-linux-arm64.tar.xz
wget https://nodejs.org/dist/v22.11.0/node-v22.11.0-linux-arm64.tar.xz
wget https://nodejs.org/dist/v20.11.0/node-v20.11.0-linux-arm64.tar.xz
wget https://nodejs.org/dist/v18.19.0/node-v18.19.0-linux-arm64.tar.xz
wget https://nodejs.org/dist/v16.20.2/node-v16.20.2-linux-arm64.tar.xzNode.js 12、14、16、18、20 可以按照相同方式准备,但这些旧版本只建议用于兼容历史项目,不应作为新部署的 Verdaccio 服务运行环境。
内网机安装 nvm
方式一
将下载好的 v0.40.2.tar.gz 复制到用户家目录,然后执行:
mkdir -p "$HOME/.nvm"
tar -xzf "$HOME/v0.40.2.tar.gz" \
--strip-components=1 \
-C "$HOME/.nvm"不要使用 mv nvm-x.y.z .nvm 这种依赖当前工作目录的写法,否则在非用户家目录执行时,NVM 会被移动到错误的位置。
方式二
tar -xzf v0.40.1.tar.gz
mv nvm-0.40.1 "$HOME/.nvm"
chown -R "$USER:$USER" "$HOME/.nvm"方式三
手动解压并复制到 "$HOME/" 下,修改名称为.nvm
配置环境变量
编辑 ~/.bashrc 或 ~/.zshrc,写入环境变量:
方式一
使用vim直接编辑~/.bashrc,在文件最下方添加下面内容,执行:wq保存退出。
# >>> nvm >>>
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# <<< nvm <<<方式二
在linux的终端直接复制执行下面的命令:
cat >> ~/.bashrc << 'EOF'
# >>> nvm >>>
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# <<< nvm <<<
EOF生效配置:
source ~/.bashrc # 或 source ~/.zshrc手动添加 node js 版本
以 Node.js v22.21.0 为例:
# 创建版本目录
mkdir -p ~/.nvm/versions/node
# 解压并移动 Node.js
tar -xzf node-v22.21.0-linux-arm64.tar.xz
mv node-v22.21.0-linux-arm64 ~/.nvm/versions/node/v22.21.0
# 重复以上步骤处理其他版本如果下载的是 .tar.gz 文件,将解压参数从 -xJf 改成 -xzf。其他 Node.js 版本按照相同方式解压到 $HOME/.nvm/versions/node/v版本号。
目录结构:
~/.nvm/versions/node/
├─ v12.22.12/
├─ v14.21.3/
├─ v16.20.2/
├─ v18.19.0/
├─ v20.11.0/
└─ v22.21.0/NVM 验证和使用
验证安装
# 重新加载环境
source ~/.bashrc
# 验证NVM
nvm --version
# 查看已安装的Node.js版本
nvm list
# 使用特定版本
nvm use 22.21.0
# 验证Node.js和npm
node --version
npm --version
# 测试npm基本功能
npm config list
# 设置默认版本!!!!
# 这一步在银河麒麟中一定要执行,你使用use选择的版本一致
# 离线场景下的手动安装node,一定要执行一下这个动作
nvm alias default 22.21.0使用操作
# 切换Node.js版本
nvm use 16.20.2 # 切换到16版本
nvm use 18.19.0 # 切换到18版本
nvm use 20.11.0 # 切换到20版本
nvm use 22.23.2 # 切换到22版本,运行 Verdaccio 服务
# 设置默认版本
nvm alias default 22.23.2
# 查看当前使用的版本
nvm current
# 查看所有可用版本
nvm list
# 在特定版本下运行命令
nvm exec 16.20.2 node --version
# 显示可用命令
nvm --help
# 卸载指定版本(删除文件夹)
nvm uninstall 12.22.12因为我们时离线安装,所有nvm install version其实不可用的,上述步骤就是手动的实现这个命令。
NVM 常见问题
新终端找不到node
银河麒麟系统可能会存在这样问题:
也是使用
nvm use选择版本了,当前终端也显示node版本,但是新开终端后,执行node -v就不显示
这是银河麒麟需要使用 nvm alias default切换版本了,不能使用nvm use切换版本
Linux 找不到 nvm 命令
# 手动加载NVM
source ~/.nvm/nvm.sh
# 或者重新加载bashrc
source ~/.bashrc切换版本后 node 命令无效
# Windows: 重新打开命令行窗口
# Linux/Mac: 检查 PATH 环境变量
echo $PATH
# 手动设置PATH
export PATH="$NVM_DIR/versions/node/v22.21.0/bin:$PATH"npm 全局包丢失
这个是个比较常见的问题:
- 每个 node 版本有独立的全局包目录
- 切换版本后需要重新安装全局包
node 版本无法切换
# 检查版本目录是否正确
ls -la ~/.nvm/versions/node/
# 手动设置PATH
export PATH="$NVM_DIR/versions/node/v22.23.2/bin:$PATH"权限问题
# 修复 NVM 目录的所有者和权限
chown -R "$USER":"$(id -gn)" "$HOME/.nvm"
chmod -R u+rwX,go+rX "$HOME/.nvm"NVM 卸载
windows 卸载
把安装目录和环境变量删除即可
linux 卸载
如果需要完全卸载:
# 删除NVM目录
rm -rf "$HOME/.nvm"
# 从.bashrc中移除NVM配置
sed -i '/# >>> nvm >>>/,/# <<< nvm <<</d' ~/.bashrc
# 重新加载shell配置
source ~/.bashrc上面的操作我们相当于再内网机器上安装了 node 和 node 的版本管理器,这是第一步也是前置准备工作,下面将进入到 verdaccio 搭建的正式环节。
Verdaccio 安装部署
首先想一个问题,我们的 verdaccio 是通过 npm 全局安装的,verdaccio 是发布到 npm js 的,但是我的内网环境是访问不了 npm js 的?我需要怎么做呢?
下面以 windows 系统为例,详细说明我们要做的事情:
- 第一步:可以上网的机器安装 verdaccio
- 第二部:利用 verdaccio 缓存需要的依赖,包括项目依赖和全局依赖(pnpm 等)
- 第三步:把 verdaccio 本身以及缓存的依赖全部平移到内部机器
- 第四步:根据需求动态的更新 verdaccio 的 storage 中的依赖和 config.yaml
- 第五步:把更新的 storage 依赖和 config.yaml 传输到内部机器下做合并
重复上述步骤便能实现大多数场景的依赖私有化部署(但是某些特殊情况下可能不适用,后面在缺失内容中说明)。
Windows 环境安装部署
Verdaccio 安装
我们上面已经安装了 node,那我们直接使用 npm 再上网机上面直接全局安装
# 如果担心有问题,可以使用管理员权限安装
npm install -g verdaccio
# 其他包管理器的安装方式
yarn global add verdaccio
pnpm install -g verdaccio等待安装完成即可。
安装完成之后执行:
verdaccio --version # 验证是否安装成功之后进行启动,启动之后的启动信息要注意查看,这是有用的:
C:\Users\LMX>verdaccio
info --- config file - C:\Users\LMX\AppData\Roaming\verdaccio\config.yaml
info --- plugin @verdaccio/local-storage successfully loaded (storage)
info --- using htpasswd file: C:\Users\LMX\AppData\Roaming\verdaccio\htpasswd
info --- plugin verdaccio-htpasswd successfully loaded (authentication)
info --- plugin verdaccio-audit successfully loaded (middleware)
info --- plugin @verdaccio/ui-theme successfully loaded (theme)
warn --- http address - http://localhost:4873/ - verdaccio/6.2.1如果不侧重私有包的管理的话,我们要关注两个地方:
- C:\Users\LMX\AppData\Roaming\verdaccio\ (配置文件和 storage 所在路径)
- http://localhost:4873/ (可视化地址,我们后续会让所有的依赖再这个地址简单显示)
注册安装源
verdaccio 安装完成之后,后续一个比较重要的动作就是注册各个包管理器的 registry,以便 verdaccio 可以正常的缓存依赖。
为所有的项目设置全局的注册中心:
npm set config registry http://localhost:4873 # 注册地址就是上面的verdaccio的访问地址
yarn config set registry http://localhost:4873也可以为某个依赖单独注册:
npm install lodash --registry http://localhost:4873其他常用的命令:
# 终端窗口打开项目的根目录
npm set registry http://localhost:4873/ --location project
# 单次依赖安装指定注册中心,以lodash为例
npm install lodash --registry http://localhost:4873上面的命令其实就相当于再你的项目.npmrc下增加了注册中心, 你也可以直接修改.npmrc:
registry=http://localhost:4873/但是现在好多 vue3 的项目都是
monorepo多包管理的,那我们肯定离不开pnpm,其实这里有扩展出来一点知识点,
纯内网机制下的 pnpm 全局安装,这是其实用两种实现思路。
- 思路一: 借助 verdaccio 缓存 pnpm 依赖
- 思路二: 借助 npm 的 pack 命令打包 pnpm
思路二后续章节展开说明,先按照思路一进行实践。
按照上述步骤启动 verdaccio,并指定 pnpm 的注册源:
verdaccio
npm install pnpm --registry http://localhost:4873这时 verdaccio 的窗口会显示 pnpm 的被缓存安装的信息:
C:\Users\LMX>verdaccio
warn --- This is a deprecated method, please use runServer instead
info --- config file - C:\Users\LMX\AppData\Roaming\verdaccio\config.yaml
info --- plugin @verdaccio/local-storage successfully loaded (storage)
info --- using htpasswd file: C:\Users\LMX\AppData\Roaming\verdaccio\htpasswd
info --- plugin verdaccio-htpasswd successfully loaded (authentication)
info --- plugin verdaccio-audit successfully loaded (middleware)
info --- plugin @verdaccio/ui-theme successfully loaded (theme)
warn --- http address - http://localhost:4873/ - verdaccio/6.2.1
info <-- 127.0.0.1 requested 'GET /pnpm'
http <-- 200, user: null(127.0.0.1), req: 'GET /pnpm', bytes: 0/0
info --- making request: 'GET https://registry.npmjs.org/pnpm'
http --- 200, req: 'GET https://registry.npmjs.org/pnpm' (streaming)
http --- 200, req: 'GET https://registry.npmjs.org/pnpm', bytes: 0/5275788
http <-- 200, user: null(127.0.0.1), req: 'GET /pnpm', bytes: 0/779825
info <-- 127.0.0.1 requested 'POST /-/npm/v1/security/advisories/bulk'
http <-- 200, user: null(127.0.0.1), req: 'POST /-/npm/v1/security/advisories/bulk', bytes: 40/0
info <-- 127.0.0.1 requested 'GET /pnpm/-/pnpm-10.32.1.tgz'
http <-- 200, user: null(127.0.0.1), req: 'GET /pnpm/-/pnpm-10.32.1.tgz', bytes: 0/0
info --- making request: 'GET https://registry.npmjs.org/pnpm/-/pnpm-10.32.1.tgz'
http <-- 200, user: null(127.0.0.1), req: 'POST /-/npm/v1/security/advisories/bulk', bytes: 40/2
http <-- 200, user: null(127.0.0.1), req: 'GET /pnpm/-/pnpm-10.32.1.tgz', bytes: 0/4524746之后去 verdaccio 的 storage 目录查看是否被正常的缓存了。
正常 pnpm 文件夹下有两个文件:
package.json
pnpm-10.32.1.tgz正常的话,tgz 的包是有大小的,当是 0k 的时候要注意了,当然也不可能只有 pnpm 这个一个依赖,它是依赖树,这里只是举例说明。
注意点:
我们使用 verdaccio 做依赖缓存时可能会遇到,依赖没有缓存到的情况,确实存在这种情况,一般的处理思路如下:
- 清空缓存再次进行安装
- 重启 verdaccio 再次进行安装
- 条件允许的话删除整个 verdaccio 缓存再次安装
相关命令如下:
npm cache clean --force
pnpm store prune配置文件
上面配置完注册源了,我们需要来了解一下俩个文件config.yaml和.verdaccio-db.json, 这两个文件对我们后续操作有帮助,需要了解它。
config.yaml
这个文件是和 storage 再同级目录的,文件的配置内容如下,我们需要了解其中的一些配置项:
# 缓存依赖的存储位置
storage: ./storage
# 监听所有网卡(让其他机器能访问)
listen: 0.0.0.0:4873
web:
title: Verdaccio
auth:
htpasswd:
file: ./htpasswd
# 内网机器的一定要关闭uplinks
uplinks:
npmjs:
url: https://registry.npmjs.org/
cache: true
packages:
"@*/*":
access: $all
publish: $authenticated
unpublish: $authenticated
proxy: npmjs
"**":
access: $all
publish: $authenticated
unpublish: $authenticated
proxy: npmjs # 内网机绝对不能有这行
server:
keepAliveTimeout: 60
middlewares:
audit:
enabled: true
log:
type: stdout
format: pretty
level: http
i18n:
web: en-UScache属性官方的表述: 设置 cache 为 false 可以帮助节省你的硬盘空间。 这将避免存储 tarballs,但是它将保留元数据在文件夹里。
其实官方的解释有点晦涩,其实可以这样理解:
- cache: true(默认值); 从上游拉取的 tarball 会缓存到
storage目录下,下次请求直接从本地返回 - cache: false; tarball 不会写入磁盘,但是包的元数据(package.json、版本列表等)依然会被缓存,每次请求 tarball 都会请求上游。
在解释一下tarball是啥? 答:是依赖包的 tgz 文件。
这个配置设置不当也是导致缓存不能进行的一个重要原因。
更近一步说明:
⚠️ 关键点:内网机:物理隔离环境下,一定要去掉 proxy: npmjs,否则每次装包都会卡在尝试连接外网。
⚠️ 关键点:外网机:可以上网的机器,不要去掉 proxy 但是 cache 不设置或者设置为 true
.verdaccio-db.json
我们怎么理解.verdaccio-db.json?
官方给的说法是:
微型数据库用于存储用户发布的私有包。 该数据库基于 JSON 文件,其中包含已发布的私有包列表以及用于令牌签名的秘密令牌。 首次启动应用程序时会自动创建。
文件的内容如下:
{ "list": [], "secret": "KzZPQifqvCrV7HBMyVPb1C9+FdWteKqe" }我这里放出了secret(令牌密钥), 正常来说这个是不能暴漏的,只做演示用。
启动 verdaccio,访问: http://localhost:4873, 我们会发现什么也没有,那是因为我们的list: []是空的。
只用我们发部私服 npm 包时(发布私服包 verdaccio 才主动写入数据库文件),list才会更新。
但是我们想要 verdaccio 承担私用 npm 的方式,所有我们想要我们已经缓存的包放追加到list中,因此我们需要编写一个 js 文件sync-verdaccio-db.js来更新它:
主要实现的思路是访问:http://localhost:4873/-/all,获取所有的依赖数据。
const fs = require("fs");
const path = require("path");
const http = require("http");
const VERDACCIO_URL = "http://localhost:4873";
// 根据实际路径调整
const DB_PATH = path.join(__dirname, "storage", ".verdaccio-db.json");
/**
* 获取所有包名(从/-/all)
* @returns
*/
function fetchPackages() {
return new Promise((resolve, reject) => {
http
.get(`${VERDACCIO_URL}/-/all`, (res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
try {
const json = JSON.parse(data);
resolve(Object.keys(json)); // 返回包名数组
} catch (e) {
reject(e);
}
});
})
.on("error", reject);
});
}
/**
* 更新数据库文件
* @param {*} packages
*/
function updateDb(packages) {
let db;
try {
db = JSON.parse(fs.readFileSync(DB_PATH, "utf8"));
} catch (e) {
// 文件不存在或损坏,创建一个新的
db = {
list: [],
secret: require("crypto").randomBytes(16).toString("hex"),
};
}
// 去重并排序(可选)
db.list = [...new Set(packages)].sort();
fs.writeFileSync(DB_PATH, JSON.stringify(db, null, 2));
console.log(
`[${new Date().toISOString()}] Database updated with ${
db.list.length
} packages.`,
);
}
/**
* 主函数
*/
async function main() {
try {
const packages = await fetchPackages();
updateDb(packages);
} catch (err) {
console.error("Sync failed:", err.message);
}
}
main();注意: 我们上面的做法只是想让安装了哪些依赖都显示出来,依赖是有依赖树的,这个默认都理解,不做过多解释!
先启动 verdaccio, 再storage的同级目录使用 node 执行sync-verdaccio-db.js文件即可,如下:
node .sync-verdaccio-db.js⚠️ 完成之后重启 verdaccio,再浏览器中打开http://localhost:4873 , 可以查看缓存的依赖了。
创建用户
verdaccio 的用户注册和登录之类的也加单说明一下,可以后续会用的上,但不是当前文章的侧重点。
使用 npm 8 或更低版本时, adduser 或 login 都可以同时创建用户并登录。
npm adduser --registry http://localhost:4873在 npm@9 之后的版本,这两个命令分开工作:
npm login --registry http://localhost:4873npm adduser --registry http://localhost:4873默认情况下,两个命令都依赖于 web 登录,添加 --auth-type=legacy 可以使用之前的登录方式。
内网部署
上述操作完成之后,下一步需要做的就是怎么把 verdaccio 整体部署到内网机器了,只要把 verdaccio 移入到内网机器,启动起来,后续只需要 更新 storage 和.verdaccio-db.json 就可以实现依赖的更新了。
其实也有两种思路:
- 思路一: 使用 npm 的 pack 打包 verdaccio, 之后再内网机器使用 npm 全局安装
- 思路二: 把上网机器上的安装的 verdaccio 直接复制到内网机(个人认为最可靠的方式)
思路一感觉上规范,但是可能遇到意想不到的问题,思路二在 windows 环境成功率更高,先介绍思路二,在说明思路一。
上面已经全局安装了,我们需要查看全局安装路径
npm config get prefix输出结果如下:
C:\Users\LMX>npm config get prefix
C:\nvm4w\nodejs # 这个路径是你自己的,每个人的安装路径都不一样进入到安装目录复制相关文件到内网机器下 npm 的相同目录
需要复制的文件包括(verdaccio、verdaccio.cmd、verdaccio.ps1 和 node_modules/verdaccio 文件夹):


最后内网机器验证 verdaccio 是否成功
verdaccio --versionverdaccio 安装成功之后,启动 verdaccio 即可,在将上述保存的 storage 和 config.yaml 复制到内网机器下 verdaccio 缓存目录
思路二的方式虽然有点野路子,但是是在 windows 下尝试成功概率比较高的方式。
稍微解释一下为啥思路二简单粗暴:
我们安装依赖的时候不是单个的依赖,却是复杂的依赖树,我们直接拷贝 node_modules/verdaccio 相当于跳过了复杂依赖树的查找和安装过程,这种操作相当于是一个环境依赖平移的过程。
下面在来说明一下看似正规的思路一:
思路一要想成功需要很重要的一点,不光要 npm pack verdaccio,而且要 npm pack 每一个 verdaccio 需要的子依赖,极其繁琐,容易出错。
那我们是否可以直接打包 node_modules/verdaccio,类似于方案二的思想呢?那其实又 回到思路二,不做过多的说明。
但是我们可以通过思路一和思路二想到一个更优的工程实践:
用 verdaccio 来引导 verdaccio
先用思路二在内网机上部署完 verdaccio,在外网启动 verdaccio,让它缓存 verdaccio 自身及其所有依赖,然后把存储数据一起复制到内网机 verdaccio 的缓存下。 其他内网机直接通过已安装的 verdaccio 的内网机安装 verdaccio 即可。
上面所有的安装都是以 windows 进行举例说明的,那当我们的开发环境是 linux 的时候,我们需要怎么做呢?
通常来说前端大部分的开发环境是都是 windows 的,所有 windows 部分才是我们最关心的!
正好借此说明一下不通过 verdaccio 缓存 pnpm, 通过打包的方式平移 pnpm
Linux 环境安装部署
本节以银河麒麟桌面版 V10 SP1 为例。外网打包机与内网机必须使用相同 CPU 架构,最好使用同一发行版和版本;Windows 上的 node_modules/verdaccio 不能直接平移到 Linux。传输时始终保留为 .tar.gz,不要在 Windows 或 FAT32/exFAT/NTFS 介质上解压再压缩,否则可能丢失软链接和可执行位。
配套文件:pack-offline.sh、restore-offline.sh、sync-verdaccio-db.js。
外网机安装并灌满缓存
使用前文安装的 nvm,不要另外修改 npm prefix。锁定 Verdaccio 版本,避免自动安装到要求 Node 22 的 6.9.0 及以上版本:
先看一下npm的配置是否指定npm源为官方统一的源:https://registry.npmjs.org/.
npm config get prefix不是的执行下面的命令进行切换
npm config set registry https://registry.npmjs.org/执行安装
npm i -g verdaccio@6.2.1
verdaccio --version
# 首次启动,verdaccio会自动生成 ~/.config/verdaccio/config.yaml
# config.yaml就是verdaccio的配置文件
verdaccioverdaccio启动之后的信息如下:
(base) kylin@kylin-pc:~$ verdaccio
warn --- This is a deprecated method, please use runServer instead
info --- config file - /home/kylin/.config/verdaccio/config.yaml # 配置文件所在位置
info --- plugin @verdaccio/local-storage successfully loaded (storage)
info --- using htpasswd file: /home/kylin/.config/verdaccio/htpasswd
info --- plugin verdaccio-htpasswd successfully loaded (authentication)
info --- plugin verdaccio-audit successfully loaded (middleware)
info --- plugin @verdaccio/ui-theme successfully loaded (theme)
warn --- http address - http://localhost:4873/ - verdaccio/6.2.1外网机 ~/.config/verdaccio/config.yaml 的关键配置:
# 注意:安装完成verdaccio后这个地方需要改成相对路径,便于后续的操作,或者不修改记住也行
storage: ./storage
# 内网环境配置当注册中心使用
listen: 0.0.0.0:4873
# 开不开启都可以
max_body_size: 200mb
# 下面的外网机器一定要开启,内网及其关闭
uplinks:
npmjs:
url: https://registry.npmjs.org/ # 统一使用 npm 官方源
cache: true
packages:
"@*/*":
access: $all
publish: $authenticated
proxy: npmjs # 外网机器一定要开启,内网及其关闭
"**":
access: $all
publish: $authenticated
proxy: npmjs # 外网机器一定要开启,内网及其关闭重启 Verdaccio 后,让各包管理器经由私服安装所有项目依赖:
上方把npm config set registry设置成了npm的官方源,这时需要改成verdaccio的地址,便于缓存依赖。
npm config set registry http://localhost:4873/
npm i -g pnpm@10.32.1
pnpm config set registry http://localhost:4873/
yarn config set registry http://localhost:4873/确认 tarball 已真实落盘;0 字节文件表示缓存不完整:
find ~/.config/verdaccio/storage -name '*.tgz' -size -1k | head
# 需要重灌时:
npm cache clean --force && pnpm store prune外网机打包
先停止 Verdaccio,避免打包时 storage 处于不一致状态,然后在三个配套文件所在目录执行:
pkill -f 'bin/verdaccio'
chmod +x pack-offline.sh
./pack-offline.sh
# 产物:~/verdaccio-offline-<arch>-<日期>.tar.gz脚本会打包 Verdaccio、可选的 pnpm 以及 config.yaml、可选的 htpasswd,但不包含 storage。同时会生成 manifest.txt,记录架构、glibc、Node、npm、Verdaccio 和 pnpm 版本;storage 需要单独传输和放置。
脚本内网机还原
内网机应先按前文安装同版本 Node,并执行 nvm use。然后解压完整离线包并运行还原脚本。
还原脚本会校验 CPU 架构和 glibc、解包模块、使用相对路径重建命令软链接,并还原 Verdaccio 配置。如果已存在 ~/.config/verdaccio/config.yaml,脚本会先备份该文件。脚本不还原 storage,storage 需要随后手动放置。不要执行 npm config set prefix ~/.npm-global,它会与 nvm 冲突。
tar -xzf verdaccio-offline-*.tar.gz -C "$HOME"
cd "$HOME/verdaccio-offline"
chmod +x restore-offline.sh
./restore-offline.sh使用脚本方式(纯内网机器好多命令多没有更新或者确实)还原大概率会失败,可以尝试一下,推荐使用下方的手动方式还原
手动内网机还原(推荐)
最简核心还原命令
verdaccio-offline-aarch64.tar.gz解压之后,计入目录,目录中会存在
verdaccio-full.tar.gz 与 verdaccio-config.tar.gz,这是新建可以执行的shell脚本文件,
脚本内容如下:
verdaccio-offline文件夹中的pnpm-full.tar.gz是可选文件,我们后续会通过verdaccio安装pnpm。
#!/usr/bin/env bash
# 注意:!!!下面的路径使用npm config get prefix获取PREFIX的内容!!!
PREFIX="/home/kylin/.nvm/versions/node/v22.21.0"
MODULES="$PREFIX/lib/node_modules"
CONF_DIR="$HOME/.config/verdaccio"
mkdir -p "$MODULES" "$PREFIX/bin" "$CONF_DIR"
# 还原 Verdaccio 程序
tar -xzf verdaccio-full.tar.gz -C "$MODULES"
# 重建 verdaccio 命令软链接
chmod +x "$MODULES/verdaccio/bin/verdaccio"
ln -sfn ../lib/node_modules/verdaccio/bin/verdaccio "$PREFIX/bin/verdaccio"
# 还原 config.yaml 和可选的 htpasswd
tar -xzf verdaccio-config.tar.gz -C "$CONF_DIR"
chown -R "$USER:$USER" "$CONF_DIR"
# 当前终端立即生效并验证
export PATH="$PREFIX/bin:$PATH"执行上述脚本文件即可,之后执行verdaccio验证命令查看是否成功,大部分情况下是没有问题的!
verdaccio --version这两份压缩包只还原 Verdaccio 程序和配置,不包含 storage 缓存。storage 需要单独传输并放到
config.yaml指向的目录。
手动还原无法执行时
无法执行时需要具体问题具体分析了,上述是以银河麒麟v10(SP1)版本举例,大部分都可以执行成功。
复制依赖启动
验证成功后,还需要把单独传输的 storage 放到 config.yaml 中 storage 指向的目录。在修改config.yaml.
注意现有的storage中依赖是linux和windows下共用的,不同系统特有的依赖已经补齐!
内网机的 config.yaml 必须删除两组 packages 下的 proxy: npmjs,并关闭 audit:
storage: ./storage
listen: 0.0.0.0:4873
max_body_size: 200mb
# uplinks 可以整段删除;即使保留,也不能被 packages 引用
packages:
"@*/*":
access: $all
publish: $authenticated
"**":
access: $all
publish: $authenticated
middlewares:
audit:
enabled: false内网客户端需要访问 4873 端口。根据系统实际关闭防火墙,防止访问端口被防火墙关闭了。
下面操作《同步 Web 包列表》和《配置 systemd 开机自启》自选,可以不做!
同步 Web 包列表
先停服,再用配套脚本扫描 storage;不要调用 Verdaccio 6 中可能不可用的 /-/all:
sudo systemctl stop verdaccio 2>/dev/null || pkill -f 'bin/verdaccio'
node ./sync-verdaccio-db.js ~/.config/verdaccio/storage配置 systemd 开机自启
systemd 默认 PATH 不包含 nvm 的 Node,因此必须写入 Node 和 Verdaccio 的绝对路径,同时显式指定 HOME 与配置文件:
NODE_BIN="$(command -v node)"
VERD="$(npm config get prefix)/lib/node_modules/verdaccio/bin/verdaccio"
sudo tee /etc/systemd/system/verdaccio.service > /dev/null << EOF
[Unit]
Description=Verdaccio Private NPM Registry
After=network.target
[Service]
Type=simple
User=$USER
Environment=HOME=$HOME
WorkingDirectory=$HOME/.config/verdaccio
ExecStart=$NODE_BIN $VERD --config $HOME/.config/verdaccio/config.yaml --listen 0.0.0.0:4873
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now verdaccio
sudo systemctl status verdaccio
sudo journalctl -u verdaccio -f客户端接入与验证
npm config set registry http://<私服IP>:4873/
pnpm config set registry http://<私服IP>:4873/
yarn config set registry http://<私服IP>:4873/
npm ping --registry http://<私服IP>:4873
curl -s http://<私服IP>:4873/-/ping银河麒麟常见故障速查
| 现象 | 原因 | 处理 |
|---|---|---|
verdaccio: command not found | 软链接目标路径错误 | 检查 $(npm config get prefix)/bin/verdaccio,重新执行还原脚本 |
GLIBC_2.28 not found | Node 版本高于系统 glibc | 换 Node 16 与 Verdaccio 5,或升级系统 |
cannot execute binary file | CPU 架构不匹配 | 用 file "$(which node)" 检查并换同架构包 |
Permission denied 但普通权限正常 | 可执行位丢失或被 KySec 拦截 | 恢复 chmod +x,并用 sudo getstatus 检查 |
nvm is not compatible with the "prefix" config | nvm 与自定义 npm prefix 冲突 | 执行 npm config delete prefix |
| install 卡在 audit 或 fetch | 内网仍有 proxy: npmjs 或 audit 未关闭 | 删除 proxy,设置 middlewares.audit.enabled: false |
| Web 页面没有包 | .verdaccio-db.json 的 list 为空 | 停服后运行 sync-verdaccio-db.js |
| 其他机器连不上 4873 | 只监听 localhost 或防火墙未放行 | 设置 listen: 0.0.0.0:4873 并放行端口 |
| 上传大包返回 413 | 请求体限制过小 | 设置 max_body_size: 200mb |
缺失内容
上述我们只提到了最常见的场景,但是还有几类场景是不能忽视的,由于篇幅原因,这里先列出来,不做展开了,后续在补充。
- electron/tauri 等桌面端构建(涉及 native binary 依赖的处理)
- 二进制依赖, 如何处理 node-gyp、prebuild 等
- 私有业务组件库发布流程为涉及说明
扩展和思考
其实理论上有更加成熟的方案,Nexus Repository Manager或者淘宝 cnpm方案,但是cnpmcore对纯物理隔离部署有一定的要求,上述方案也是仅针对前端开发者去部署的,有更好的方案可以一起讨论。
缺失部分部分说明
Electron 举例说明
以 windows 下平移 electron 构建环境为列,进行说明
在完全物理隔离的内网环境中构建 Electron 等桌面端应用,核心痛点在于:Verdaccio 只能缓存 npm 包的源码和元数据,但 Electron 及其构建工具(如 electron-builder)在安装或打包时,会通过 postinstall 脚本去 GitHub Releases 动态下载大量底层的二进制文件(如 Electron 核心、NSIS、winCodeSign 等)。
要将这套环境平移到内网,你需要手动接管这些二进制文件的缓存。
Electron 构建离线迁移的核心挑战:
| 资源 | 来源 | 处理方式 |
|---|---|---|
| Electron 二进制包 | GitHub Releases | 手动下载/本地缓存 |
| npm 依赖(JS) | npmjs.org | Verdaccio 缓存 |
| electron-builder 构建工具链 | GitHub/各镜像站 | 手动下载/本地缓存 |
| 原生模块编译(node-gyp) | Node.js / Electron headers | 手动下载 headers |
npm 依赖我们不需要管了,剩下的是:Electron 二进制包、electron-builder 构建工具链和原生模块编译(node-gyp)。
解决 Electron 二进制包
当你执行 npm install electron 时,@electron/get 模块会去下载对应的 electron-vX.Y.Z-[os]-[arch].zip。
外网机操作:
在外网机正常执行 npm install electron 后,Electron 会将下载好的二进制压缩包缓存在本地系统的特定目录中:
- Windows:
%LOCALAPPDATA%\electron\Cache(通常是C:\Users\<用户名>\AppData\Local\electron\Cache) - Linux:
~/.cache/electron - macOS:
~/Library/Caches/electron
内网机平移迁移:
- 找到外网机上述对应的 Cache 目录,将其完整打包。
- 拷贝到内网机,并解压到内网机完全相同的物理路径下。
- 在内网机设置环境变量,强制 Electron 跳过网络下载直接使用缓存:
或者直接正常执行# Windows (cmd) set ELECTRON_SKIP_BINARY_DOWNLOAD=1npm install,只要底层检测到该版本哈希匹配的 Cache 压缩包存在,就会跳过下载环节。
electron-builder工具链的二进制
当你运行打包命令(如 electron-builder --win)时,它需要环境里具备 app-builder、winCodeSign、nsis 等打包工具链二进制文件。这些默认也没有缓存在 node_modules 中。
外网机操作:
和 Electron 类似,electron-builder 也有自己独立的全局缓存目录:
- Windows:
%LOCALAPPDATA%\electron-builder\Cache - Linux:
~/.cache/electron-builder - macOS:
~/Library/Caches/electron-builder
内网机平移迁移:
- 在外网机完整执行一次
npm run build,确保所需目标平台(无论是 win 还是 linux)的工具链全部下载完毕。 - 打包外网机对应的
electron-builder\Cache目录。 - 复制到内网机器同样的路径下进行解压。内网机器打包时寻找不到网络时,就会自动 fallback 消费这个目录下的缓存。
(可选的进阶方案:可以在内网搭建一个类似 Nginx 的静态文件服务器,将上述 zip 包放进去,然后通过设置环境变量 ELECTRON_MIRROR 和 ELECTRON_BUILDER_BINARIES_MIRROR 指向内网服务器,但这对于纯拷贝平移来说步骤偏重。)
解决原生 C++模块的问题
对于用到 sqlite3、serialport 等依赖底层 C++ 的包,通常通过 node-pre-gyp 或 prebuild-install 下载预编译好的 .node 文件。
平移方法:
- 基于二进制缓存:部分 prebuild 会缓存至
%APPDATA%\npm-cache\_prebuilds或%LOCALAPPDATA%\npm-cache,可以一并拷贝。 - 源码编译方案 (兜底):如果需要用到
node-gyp现场编译,那内网机器还需要安装Python和Visual Studio 编译工具链 (C++ workload)。同时,外网需要将 Node.js 的 C++ 头文件(通常在%LOCALAPPDATA%\node-gyp)打包拷贝给内网机器。 - 最暴力的解法 (推荐场景):在外网机和内网机操作系统及 Node 版本完全一致(比如同为 Win10 64 位, Node 18.x)的前提下,直接在外网机进行
npm install后,将带有被编译好.node文件的 node_modules 文件夹一起打成压缩包丢进内网机。这是在内网处理 C++ 原生扩展最不易错的方式。
注意事项
- 不要把删除
package-lock.json、pnpm-lock.yaml或yarn.lock作为常规操作。离线部署更需要锁文件保证版本可复现;如果锁文件中固定了外网 tarball 地址,应在外网项目副本中重新生成或规范化,而不是直接在正式项目中删除。 - 遇到缺失依赖时,不要简单地在外网重复安装。应使用全新的 npm cache 或 pnpm store 重新安装,并检查 Verdaccio 日志和 storage 中是否生成了非 0 字节的 tgz 文件。
- 外网和内网分别保存
config.online.yaml、config.offline.yaml,避免因为忘记删除或恢复proxy: npmjs而导致缓存失败或内网长时间等待外网连接。 - Verdaccio、pnpm 和 Node.js 都应锁定精确版本,并保存安装包 SHA256;更新版本后要重新执行一次断网验收。
- pnpm 可以通过 Verdaccio 安装,也可以和固定 Node.js 运行时一起整体打包;Linux 下推荐后者作为首次引导方案。
- Electron 的 npm 依赖、Electron 主程序二进制、electron-builder 工具链和原生模块缓存是不同资源,不能只复制
dist目录,需要分别准备和验证。
评论交流
支持作者