利用 verdaccio 搭建私有npm平台及在内网中使用

verdaccio 的安装使用方法,其官网上有更详情的内容。

这里我给大家重点介绍一下使用 docker 来安装,还有一些个性化的配置,以及邮件通知的配置方法。

安装

首先拉取 verdaccio 的镜像:

1
docker pull verdaccio/verdaccio

利用 docker-compose.yml 来启动容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
version: "3"
services:
verdaccio:
image: verdaccio/verdaccio:latest
container_name: npm-verdaccio
restart: always
ports:
- 4873:4873
volumes:
- ./data/verdaccio/conf:/verdaccio/conf
- ./data/verdaccio/storage:/verdaccio/storage
- ./data/verdaccio/plugins:/verdaccio/plugins
tty: true

在上面的配置中,我们将环境配置、库文件、插件目录都挂载到了本地当前对应的目录中,方便后面修改和数据的持久化。

运行启动容器:

1
docker-compose up -d

之后,会看到挂载的目录会生成对应的文件,下面我们来修改配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 上游npm包获取地址
uplinks:
npmjs:
url: https://registry.npm.taobao.org

# 包存储地址,这里需要配置当前的conf的上一层,否则会在当前conf目录下新建storage目录
storage: ../storage

# 一些自定义的web配置信息
web:
enable: true
title: npm私有库
logo: /verdaccio/conf/logo.svg
primary_color: "#de0000"
favicon: /verdaccio/conf/favicon.ico
pkgManagers:
- pnpm
- npm
showInfo: false
showSettings: false
showThemeSwitch: false
showFooter: false
scriptsBodyAfter:
- "<style></style>"

内网

如果你的 verdaccio 是搭建在内网的,还需要将外网的第三方包下载安装转移到内网。

这个工作首先需要在可以连接互联网的设备上同样使用 docker 安装 verdaccio 环境。

安装成功后,默认会在本地启一个 4873 的 http 服务。

本地还要安装 node 环境,通过 nodenpm init 创建本地项目,通过当前项目的根目录文件 .npmrc,如果没有可自行创建,内容为:

1
registry=http://localhost:4873

这样,在本地安装安装的项目,将会从私有平台 4873 上下载获取第三方包,verdaccio 通过其设置的上游链接,去互联网上下载后,会缓存放置在 storage 目录中。

这样操作之后,在 verdacciostorage 目录中就会存在我们需要的第三方包,我们可以将其转移到内网环境中。

1
sudo tar --exclude=storage/@sg --exclude=storage/@wgl --exclude=storage/.verdaccio-db.json -zcvf storage.tar storage

上面的压缩命令,我们排除了本地测试的自定义包,和 verdaccio 的版本数据文件,是为了覆盖时不要把内网 verdaccio 里的私有数据覆盖了,只允许覆盖第三方包。

解压覆盖到内网 verdacciostorage 目录后,重启内网的 docker 容器即可。

通知

verdaccio 给我们提供了 publish 的通知功能,当有新包被推送时,可以及时通知相关引用使用者,下面是一个比较好的通知配置:

1
2
3
4
5
notify:
method: POST
headers: [{ "Content-Type": "application/json" }]
endpoint: http://172.17.0.1:5000/send_email
content: '{"subject":"{{ publishedPackage }}已推送","sendMail":"`{{#each versions}} {\"enable\":\"{{ sendMail.enable }}\",\"msg\":\"{{sendMail.msg}}\",\"to\":\"{{sendMail.to}}\"}{{/each}}`","message":"User:{{ publisher.name }}<br/>Package:{{ publishedPackage }}`{{#each versions}} <br/> Author:{{author.name}}<br/>Email:{{author.email}}<br/>Integrity:{{dist.integrity}}<br/>Tarball:{{dist.tarball}}{{/each}}`","notify":true,"message_format":"text"}'

这里的 endpoint 是接口地址,我们可以使用 express 框架来建立一个发送邮件的服务。

index.js 的服务文件内容如下:

点击查看完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
const express = require("express");
const nodemailer = require("nodemailer");
const bodyParser = require("body-parser");

// 如果smtp服务器是ip,会报证书的一些问题,这里可以设置为0
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

const app = express();
const PORT = 5000;

// 创建 application/json 解析
app.use(bodyParser.json());

// 创建 application/x-www-form-urlencoded 解析
app.use(bodyParser.urlencoded({ extended: true }));

// 格式化接收内容
const baseFormat = (obj) => {
for (let key in obj) {
if (typeof obj[key] === "string") {
obj[key] = obj[key].replaceAll("`", "").trim();
}
}
return obj;
};

// 邮件配置
const emailConfig = {
defaultTo: ["webape@qq.com"],
from: "webape@qq.com",
host: "smtp.qq.com",
port: 465,
user: "webape@qq.com",
pass: "******",
};

// 设置路由
app.post("/send_email", (req, res) => {
const body = baseFormat(req.body);
const { sendMail, subject, message } = body;
console.log("body", body);

const sendMailObj = baseFormat(JSON.parse(sendMail));

if (["true", ""].indexOf(sendMailObj.enable) > -1) {
const defaultTo = emailConfig.defaultTo;
// 设置邮件内容
const mailOptions = {
from: emailConfig.from,
to: sendMailObj.to
? sendMailObj.to.split(",").map((item) => item.trim())
: defaultTo,
subject: subject,
html:
message.replaceAll("`", "") +
(sendMailObj.msg
? '<div style="background-color:red;color:#fff">' +
sendMailObj.msg +
"</div>"
: ""),
};

// 设置邮件发送配置
const transporter = nodemailer.createTransport({
host: emailConfig.host,
port: emailConfig.port,
auth: {
user: emailConfig.user,
pass: emailConfig.pass,
},
});

// 发送邮件
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.log(error);
res.status(500).send("邮件发送失败");
} else {
console.log("Email sent: " + info.response);
res.send("邮件发送成功");
}
});
}
});

// 启动服务
app.listen(PORT, "172.17.0.1", () => {
console.log(`Server is running on http://172.17.0.1:${PORT}`);
});

可以看到上面的服务是绑定在 ip:172.17.0.1 上的,这个是 docker 的网关地址,使 docker 内部可以访问外部宿主机的 http 服务。

我们可以在和 verdaccio 同一服务器中后台启动该服务:

1
2
3
4
# 在后台执行程序
nohup node index.js &
# 将该进程与当前 shell 分离,避免关闭当前 shell 连接后进程关闭
disown

由于 verdaccio 的通知是读取的项目的 package.json 的数据在我们的项目的 package.json 文件中,我们可以添加以下内容来配合邮件通知的发送行为和内容:

1
2
3
4
5
"sendMail":{
"enable":true,
"msg":"提示信息",
"to":["webape@qq.com"]
},

配置说明:

  • package.json 中没有定义 sendMail 对象时,默认会向内置的默认用户发送通知邮件。
  • sendMail.enabletrue 时,表示发送邮件,false 时表示不发送邮件,默认为 true
  • 用户可以通过 sendMail.msg 来在邮件中指定提示信息,例如,“此版本为测试版本,请勿使用”。
  • 用户可以通过指定 to 来限定邮件的发送范围,为空数组时,默认会向内置的默认用户发送。

注意

  1. 私有库绑定域名后,用什么域名第一次访问,则默认就是哪个域名。
    我们一般还会配合绑定域名使用成功一个可以对外提供服务的站点,需要注意的时,绑定域名后,第一次访问后,再次访问需要使用该域名才能访问。

  2. 私有库需要 npm adduser 添加用户后输入邮箱后半天没有反应,应该是 htpasswd 文件写权限问题。
    可以给其 775 的权限。

  3. 使用 pnpm 时的缓存问题。
    因为 pnpm 本身也会创建本地的存储库,当想在外网缓存外部库转到内网中时,.nprc 配置 registry=[地址],执行 pnpm store prunepnpm store path 查看缓存库位置删除,删除 node_modulespnpm-lock.yaml,后执行 pnpm install