Compare commits

..

261 Commits

Author SHA1 Message Date
xiaojunnuo
9b420ad33f v1.33.8 2025-04-27 01:56:54 +08:00
xiaojunnuo
5891290672 build: prepare to build 2025-04-27 01:54:19 +08:00
xiaojunnuo
72a7b51d47 fix: 修复http上传方式无法清除记录文件的bug 2025-04-27 01:52:42 +08:00
xiaojunnuo
2943e0e58d chore: oss 库 完善 2025-04-27 01:31:46 +08:00
xiaojunnuo
5abce916a8 chore: 2025-04-25 18:45:42 +08:00
xiaojunnuo
89d4be8a0a chore: 2025-04-25 18:37:29 +08:00
xiaojunnuo
b7113bda23 perf: 七牛oss支持删除过期备份 2025-04-25 18:36:49 +08:00
xiaojunnuo
0088929622 perf: 支持反向代理增加contextPath路径 2025-04-25 18:04:59 +08:00
xiaojunnuo
b3468cf7f2 perf: 支持阿里云中文域名申请 2025-04-25 18:04:24 +08:00
xiaojunnuo
f88c5c8528 chore: 2025-04-25 02:11:23 +08:00
xiaojunnuo
687fdda7f7 chore: 2025-04-25 02:11:08 +08:00
xiaojunnuo
aec51e514c chore: 2025-04-25 01:44:15 +08:00
xiaojunnuo
308d4600ef perf: 数据库备份支持oss 2025-04-25 01:26:04 +08:00
xiaojunnuo
50a5fa15bb fix: 修复token过期后,疯狂打印token过期信息的bug 2025-04-24 23:54:09 +08:00
xiaojunnuo
7d96a57d73 chore: 2025-04-24 17:27:13 +08:00
xiaojunnuo
162ebfd4e0 perf: 支持中文域名 2025-04-24 11:55:14 +08:00
xiaojunnuo
a586a92d5e perf: 从域名的soa获取主域名,子域名托管无需额外配置 2025-04-24 11:54:54 +08:00
xiaojunnuo
3df20a924f fix: 修复复制流水线无效的bug 2025-04-24 09:09:38 +08:00
xiaojunnuo
ddcf466e4e docs: 升级前切记备份数据 2025-04-23 17:59:48 +08:00
xiaojunnuo
5d10cbf18d fix: 服务器时间获取不准确的bug 2025-04-23 14:55:51 +08:00
xiaojunnuo
8d9afa7592 build: publish 2025-04-22 22:32:56 +08:00
xiaojunnuo
95e05336c2 build: trigger build image 2025-04-22 22:32:18 +08:00
xiaojunnuo
a188385817 v1.33.7 2025-04-22 22:27:50 +08:00
xiaojunnuo
0a6baf331b build: prepare to build 2025-04-22 22:12:51 +08:00
xiaojunnuo
0e29e052d5 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-04-22 22:11:32 +08:00
xiaojunnuo
d8d255980e chore: 2025-04-22 15:53:19 +08:00
xiaojunnuo
dc5a5fa543 chore: 2025-04-22 11:41:45 +08:00
xiaojunnuo
8638fc91ff perf: 证书申请支持51dns 2025-04-22 11:39:09 +08:00
xiaojunnuo
96a0900edc perf: 支持51dns 2025-04-22 11:39:07 +08:00
xiaojunnuo
abea80e3ab perf: 添加部署证书至火山 Live
- 新增 VolcengineDeployToLive 插件,用于将证书部署到火山引擎视频直播
- 新增 VolcengineDeployToVOD 插件,用于将证书部署到火山引擎视频点播
- 更新 ve-client.ts,增加对 Live 和 VOD 服务的支持
2025-04-21 23:39:33 +08:00
xiaojunnuo
42dfe936b7 perf: ssh伪终端模式优化,windows下不开启 2025-04-21 17:34:26 +08:00
xiaojunnuo
8385bcc2d7 perf: ssh PTY模式登录设置 2025-04-21 17:26:42 +08:00
xiaojunnuo
9b8f60b64b perf: 优化首页插件列表展示 2025-04-21 12:13:01 +08:00
xiaojunnuo
474114236e build: publish 2025-04-21 00:08:56 +08:00
xiaojunnuo
238b0b421a build: trigger build image 2025-04-21 00:08:38 +08:00
xiaojunnuo
8abe62886a v1.33.6 2025-04-21 00:06:48 +08:00
xiaojunnuo
78cc9cffe4 build: prepare to build 2025-04-21 00:04:13 +08:00
xiaojunnuo
59a5dd713f chore: 2025-04-20 23:58:02 +08:00
xiaojunnuo
a39024ff03 build: prepare to build 2025-04-20 23:55:30 +08:00
xiaojunnuo
72bfbd93a8 chore: 2025-04-20 23:55:05 +08:00
xiaojunnuo
c9a3e3d9d2 perf: 新增部署到火山引擎ALB/CLB、上传到证书中心 2025-04-20 23:53:27 +08:00
xiaojunnuo
8387708901 docs: 2025-04-20 21:23:59 +08:00
xiaojunnuo
b565b4b3b9 perf: 优化华为cdn插件引用ccm证书 2025-04-20 20:16:36 +08:00
xiaojunnuo
893dcd4f24 perf: 切换到不同的分组后再打开创建对话框,会自动选择分组 2025-04-19 17:08:44 +08:00
xiaojunnuo
d613aa8f3e perf: 优化证书流水线创建,支持选择分组 2025-04-19 16:05:24 +08:00
xiaojunnuo
5750bb7067 fix: 上传商用证书,直接粘贴文本报错的问题;修复无法上传ec加密证书的bug 2025-04-19 15:00:34 +08:00
xiaojunnuo
0e07ae6ce8 fix: 修复下载证书时提示token已过期的问题 2025-04-19 14:25:56 +08:00
xiaojunnuo
02b6351e13 docs: 宝塔network 2025-04-19 12:14:19 +08:00
xiaojunnuo
78367af830 perf: 更新license时同时绑定url 2025-04-19 11:48:40 +08:00
xiaojunnuo
dc05cd481f perf: 优化/api缓存为0 2025-04-18 15:23:51 +08:00
xiaojunnuo
7daad5477a Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-04-18 10:17:48 +08:00
xiaojunnuo
45cdfbfae8 chore: 2025-04-18 10:17:24 +08:00
xiaojunnuo
3fb5c38571 build: publish 2025-04-17 23:37:26 +08:00
xiaojunnuo
59f80ebc47 build: trigger build image 2025-04-17 23:37:09 +08:00
xiaojunnuo
198a97b00c v1.33.5 2025-04-17 23:35:14 +08:00
xiaojunnuo
3ea4e917e8 build: prepare to build 2025-04-17 23:32:08 +08:00
xiaojunnuo
60ad077172 pref: 任务日志查看页面,增加强制重新运行按钮 2025-04-17 23:31:43 +08:00
xiaojunnuo
356ad28e41 chore: 2025-04-17 23:11:04 +08:00
xiaojunnuo
e241141220 build: prepare to build 2025-04-17 23:07:14 +08:00
xiaojunnuo
14bb1b467a chore: 2025-04-17 23:06:17 +08:00
xiaojunnuo
2bbea6fd3f chore: 2025-04-17 23:05:52 +08:00
xiaojunnuo
48aef25b3f perf: 登录支持双重认证 2025-04-17 22:34:21 +08:00
xiaojunnuo
8e50e5dee3 chore: plus 2025-04-17 13:41:08 +08:00
xiaojunnuo
d5d54d4d3b chore: 2FA 2025-04-17 01:15:55 +08:00
xiaojunnuo
412e8a32dd Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-04-17 00:06:58 +08:00
xiaojunnuo
0f82cf409b perf: 多重认证登录 2025-04-17 00:06:49 +08:00
xiaojunnuo
79df39acab perf: 优化部署到华为云CDN,支持先上传到ccm,再使用证书id部署,修复offline状态下导致部署报错的bug 2025-04-16 09:34:04 +08:00
xiaojunnuo
8786bae7dc chore: 2025-04-16 00:03:13 +08:00
xiaojunnuo
4b3f8ca361 chore: 2025-04-16 00:02:58 +08:00
xiaojunnuo
03183218f7 chore: 2025-04-16 00:01:52 +08:00
xiaojunnuo
95b6db57e1 chore: 2025-04-15 23:57:50 +08:00
xiaojunnuo
bbe0c2457b build: publish 2025-04-15 23:50:06 +08:00
xiaojunnuo
c894c53e69 build: trigger build image 2025-04-15 23:49:48 +08:00
xiaojunnuo
5b3fb7387d v1.33.4 2025-04-15 23:48:00 +08:00
xiaojunnuo
feac310caf build: prepare to build 2025-04-15 23:45:18 +08:00
xiaojunnuo
d67ec3feb3 chore: 2025-04-15 23:45:11 +08:00
xiaojunnuo
cf8abb4528 perf: 插件支持导入导出 2025-04-15 23:43:01 +08:00
xiaojunnuo
d66de26de4 Merge branch 'v2' into v2-dev 2025-04-15 21:34:13 +08:00
greper
7edf3f6147 fix: 修复腾讯云部署到任意资源插件,无法使用之前已上传的腾讯云证书问题 from xinghejd/dev @xinghejd
fix: 修复腾讯云部署到任意资源插件,无法使用之前已上传的腾讯云证书问题
2025-04-15 21:16:49 +08:00
xinghejd
2143dff2ae fix: 补充类型断言 2025-04-15 08:54:28 +00:00
xinghejd
32c714d1b6 fix: 修复腾讯云部署到任意资源插件,无法使用之前已上传的腾讯云证书问题 2025-04-15 08:47:57 +00:00
xiaojunnuo
84e699ee24 chore: 思维导图 2025-04-15 11:22:42 +08:00
xiaojunnuo
7fdb572b8b chore: 思维导图 2025-04-15 11:07:27 +08:00
xiaojunnuo
cfd3b66be9 perf: 支持上传证书到华为云CCM 2025-04-14 23:31:59 +08:00
xiaojunnuo
75c4f9dea8 build: publish 2025-04-14 22:27:24 +08:00
xiaojunnuo
a76a32230d build: trigger build image 2025-04-14 22:26:59 +08:00
xiaojunnuo
0730f5ff4f v1.33.3 2025-04-14 22:25:16 +08:00
xiaojunnuo
c43d0a684c build: prepare to build 2025-04-14 22:22:17 +08:00
xiaojunnuo
66f1eda6cf chore: 2025-04-14 22:22:01 +08:00
xiaojunnuo
bf4d191c8b fix: 修复登录错误次数过多阻止再次登录逻辑 2025-04-14 18:09:54 +08:00
xiaojunnuo
d76d56fcce pref: 安全特性支持,站点隐藏功能 2025-04-14 17:40:23 +08:00
xiaojunnuo
251b0c58de chore: 2025-04-14 10:06:58 +08:00
xiaojunnuo
073cca4e8e chore: 2025-04-14 09:54:24 +08:00
xiaojunnuo
a4ad99f189 build: publish 2025-04-13 01:33:57 +08:00
xiaojunnuo
d37b910889 build: trigger build image 2025-04-13 01:33:28 +08:00
xiaojunnuo
be69244e8d v1.33.2 2025-04-13 01:31:52 +08:00
xiaojunnuo
617f74a225 build: prepare to build 2025-04-13 01:29:05 +08:00
xiaojunnuo
a2710ddc25 perf: 修复内置插件分页查询逻辑
- 在前端添加 lastType 变量,用于判断类型变化并重置分页偏移量
- 在后端修改内置插件查询逻辑,支持分页请求
- 优化后端返回数据结构,使其与前端请求一致
2025-04-13 01:28:10 +08:00
xiaojunnuo
70101bfa7a fix: 修复某些情况下无法输出日志的bug 2025-04-13 01:17:52 +08:00
xiaojunnuo
203f2984d7 chore: 1.33.1 2025-04-13 01:05:51 +08:00
xiaojunnuo
1d510e76b8 build: trigger build image 2025-04-13 01:02:07 +08:00
xiaojunnuo
64244af2cc v1.33.1 2025-04-13 00:49:14 +08:00
xiaojunnuo
35e109882e build: prepare to build 2025-04-13 00:45:32 +08:00
xiaojunnuo
18a32ffb0b chore: 修复一些小问题 2025-04-13 00:45:01 +08:00
xiaojunnuo
a5af3ba0cb build: prepare to build 2025-04-13 00:19:38 +08:00
xiaojunnuo
83bd39a9a8 chore: 1 2025-04-13 00:10:23 +08:00
xiaojunnuo
cc0657aaa8 pref: 优化插件store 2025-04-12 23:59:03 +08:00
xiaojunnuo
965dc2cb47 fix: 修复阿里云cdn证书部署失败问题,增加certname参数传入
- 添加证书所在地域选择功能,默认为 cn-hangzhou(杭州)
- 国际站用户可选择 ap-southeast-1(新加坡)地域
- 优化证书上传和设置流程,增加证书名称和地域参数
2025-04-12 23:58:38 +08:00
xiaojunnuo
9c4cbe17a2 fix: 修复ssh插件报length空指针的bug
- 在数据加密判断中增加非空检查,避免对未定义或空值进行加密判断- 提高了代码的健壮性和安全性
2025-04-12 22:04:14 +08:00
xiaojunnuo
835fcfa4ea pref: 优化mysql版的插件脚本字段为longtext 2025-04-12 22:02:54 +08:00
xiaojunnuo
932780c578 pref: 优化部署到腾讯云任意资源插件,支持region,和资源类型选择
- 添加资源类型选择输入,支持多种腾讯云产品
-增加证书上传功能,支持直接上传证书到腾讯云
- 优化证书 ID 输入,支持选择上传任务或申请任务的输出- 添加 Region 输入,用于指定云资源所在地域
- 更新文档链接,提供更详细的参考信息
2025-04-12 22:02:16 +08:00
xiaojunnuo
37f160a452 pref: 增加又拍云 CDN 部署时的 HTTPS 配置选项
- 在插件中添加了两个新的配置项:强制 HTTPS 和开启 HTTPS- 用户可以在部署证书时选择是否强制 HTTPS 或开启 HTTPS
-根据用户选择,插件会相应地设置 CDN 域名的 HTTPS 配置
2025-04-12 22:01:29 +08:00
xiaojunnuo
f80b706fc3 chore: 2025-04-12 03:29:02 +08:00
xiaojunnuo
f78cbed4d8 perf: 镜像支持armv7 2025-04-12 03:25:43 +08:00
xiaojunnuo
e0b12c78ff chore: build 1.33.0 2025-04-12 03:13:31 +08:00
xiaojunnuo
e7cf814a59 build: prepare to build 2025-04-12 03:13:06 +08:00
xiaojunnuo
865c45593b chore: build 1.33.0 2025-04-12 03:12:53 +08:00
xiaojunnuo
62e6f109c7 chore: build 1.33.0 2025-04-12 03:03:26 +08:00
xiaojunnuo
60be8ed022 chore: build 1.33.0 2025-04-12 02:58:19 +08:00
xiaojunnuo
c157882900 chore: build 1.33.0 2025-04-12 02:55:28 +08:00
xiaojunnuo
a23c211a65 build: publish 2025-04-12 02:40:33 +08:00
xiaojunnuo
293ed6bd7e build: trigger build image 2025-04-12 02:40:15 +08:00
xiaojunnuo
13ddd7c5f9 v1.33.0 2025-04-12 02:38:31 +08:00
xiaojunnuo
0de015fc8b build: prepare to build 2025-04-12 02:35:34 +08:00
xiaojunnuo
d34fedae01 build: prepare to build 2025-04-12 02:34:03 +08:00
xiaojunnuo
7c623fc467 chore: 新增插件编辑页面跳转
- 在插件创建成功后跳转到编辑页面
- 优化了插件管理功能,提高了用户操作的便捷性
2025-04-12 02:33:44 +08:00
xiaojunnuo
359079c3e6 chore: v21适配多数据库 2025-04-12 02:24:38 +08:00
xiaojunnuo
ba72fa3f05 chore: 2025-04-12 02:10:17 +08:00
xiaojunnuo
23caab5b06 chore: 添加子域名托管解析设置并更新相关提示
- 在证书申请页面添加子域名托管解析设置入口
- 更新域名输入提示,增加子域名托管解析相关说明
- 更改子域名托管解析页面图标
2025-04-12 02:00:40 +08:00
xiaojunnuo
b506bd15a5 chore: 2025-04-12 01:48:08 +08:00
xiaojunnuo
d0d9d68fe6 feat: 支持在线自定义插件,无需源码开发 2025-04-12 01:38:48 +08:00
xiaojunnuo
88134ac130 refactor(plugin): 优化插件配置界面和功能
-调整插件配置界面布局和样式
- 增加插件类型和图标字段
- 修改字段显示逻辑,根据不同插件类型显示相应字段
- 优化插件服务端处理逻辑,支持不同类型的插件配置
2025-04-12 01:34:48 +08:00
xiaojunnuo
3d8a5196a0 refactor(core): 重构访问控制和插件实例化逻辑
- 修改访问控制和插件注册方式,使用异步函数统一实例化逻辑
- 更新相关组件和控制器以适应新的异步实例化方式
- 优化 DNS 提供商选择器,增加访问类型支持
2025-04-12 01:21:50 +08:00
xiaojunnuo
c4fb138ae8 chore: 2025-04-12 00:21:19 +08:00
xiaojunnuo
759cfdaabd pref: 日志中加密授权信息输出替换成星号 2025-04-12 00:14:55 +08:00
xiaojunnuo
3d9620abb0 refactor(plugin): 重构插件定义和安装流程
- 更新插件配置格式,增加依赖库和插件类型字段
- 修改插件安装流程,支持安装依赖插件和第三方库
- 优化插件列表过滤逻辑,按类型筛选插件
- 调整 Dockerfile,使用 Node.js22 镜像并更新 pnpm 安装方式
2025-04-11 23:39:40 +08:00
xiaojunnuo
420b0394a7 Merge remote-tracking branch 'origin/v2-plugin' into v2-plugin 2025-04-11 22:38:16 +08:00
xiaojunnuo
84bb4c8b07 Merge branch 'v2-dev' into v2-plugin 2025-04-11 22:35:35 +08:00
greper
310dbb61ee 发布镜像到 GitHub Packages @5aaee9
发布镜像到 GitHub Packages
2025-04-11 16:53:00 +08:00
Indexyz
9b536af9e6 feat: release image to ghcr 2025-04-11 16:37:31 +08:00
xiaojunnuo
c2ca1ea1e5 chore: 新增插件额外配置功能
- 在插件管理中添加 extra 字段,用于存储额外配置信息
- 实现插件编辑页面的额外配置编辑功能
- 更新数据库结构,增加 extra 列
- 优化代码编辑器的导入方式
- 更新 fast-crud 相关包版本
2025-04-11 14:00:28 +08:00
greper
ada4b226de Lego 支持设定加密算法 @5aaee9
Lego 支持设定加密算法
2025-04-11 12:17:37 +08:00
xiaojunnuo
67f956d4a0 pref: 支持子域名托管的域名证书申请 2025-04-11 12:14:09 +08:00
xiaojunnuo
f68af7dcf2 chore: 2025-04-10 23:44:11 +08:00
xiaojunnuo
be1b6f8edc chore: 2025-04-10 13:30:56 +08:00
xiaojunnuo
1150f62927 Merge branch 'v2-dev' into v2-plugin 2025-04-10 11:48:45 +08:00
xiaojunnuo
b4c7a521b4 chore: 2025-04-10 11:38:51 +08:00
xiaojunnuo
5d083a1536 perf: 增加手动上传证书功能说明 2025-04-10 10:34:10 +08:00
xiaojunnuo
2f5ed3aead fix: 升级mysql驱动,支持mysql8最新版本的认证 2025-04-10 10:24:34 +08:00
xiaojunnuo
2951df0cd9 perf: 隐藏运行策略选项 2025-04-10 09:35:50 +08:00
xiaojunnuo
ec22070957 Merge branch 'v2-dev' into v2-plugin 2025-04-10 00:22:51 +08:00
xiaojunnuo
0e36f03954 chore: plugin default 2025-04-10 00:22:05 +08:00
xiaojunnuo
57309ae3d5 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-04-09 10:07:14 +08:00
xiaojunnuo
7545194f97 chore: 2025-04-09 00:00:53 +08:00
xiaojunnuo
4bb0918e27 chore: 2025-04-08 23:36:50 +08:00
xiaojunnuo
64e5449ab3 perf: 修复tab页缓存问题 2025-04-08 23:31:25 +08:00
xiaojunnuo
a0eeb17d73 chore: 插件编辑与运行测试beta 2025-04-08 22:56:38 +08:00
xiaojunnuo
c021dd03d3 Merge branch 'v2-dev' into v2-plugin 2025-04-08 21:14:54 +08:00
xiaojunnuo
2f1683b26a fix: 修复eab授权,没有email绑定的bug 2025-04-08 19:54:25 +08:00
xiaojunnuo
c99939f435 chore: 2025-04-08 18:06:12 +08:00
xiaojunnuo
efad8bac3c chore: 2025-04-08 13:53:54 +08:00
xiaojunnuo
eaf68fa463 chore: 2025-04-08 13:40:58 +08:00
xiaojunnuo
9475f2e56c chore: code-editor 2025-04-07 23:52:21 +08:00
xiaojunnuo
2e0c067cd2 chore: 2025-04-07 18:22:39 +08:00
xiaojunnuo
59a6043549 chore: 2025-04-06 23:16:54 +08:00
xiaojunnuo
840a7b7c73 chore: 插件编辑器 2025-04-06 18:06:21 +08:00
xiaojunnuo
61e322678b chore: 2025-04-06 00:20:05 +08:00
xiaojunnuo
04acd08ad2 Merge branch 'v2-dev' into v2-plugin 2025-04-05 19:01:23 +08:00
5aaee9
f3bf4faee0 feat(lego): support set key type 2025-04-05 17:01:41 +08:00
xiaojunnuo
c3603ba220 build: publish 2025-04-05 01:37:41 +08:00
xiaojunnuo
a3a52fd12c build: trigger build image 2025-04-05 01:37:22 +08:00
xiaojunnuo
7c4756da81 v1.32.0 2025-04-05 01:35:54 +08:00
xiaojunnuo
f4fe03c790 build: prepare to build 2025-04-05 01:33:15 +08:00
xiaojunnuo
a748bb9352 build: prepare to build 2025-04-05 01:14:24 +08:00
xiaojunnuo
d24fb6ed48 chore: 2025-04-05 01:13:46 +08:00
xiaojunnuo
021dc5b82c Merge branch 'v2-dev' into v2-plugin 2025-04-05 00:48:23 +08:00
xiaojunnuo
9339b78f80 perf: 又拍云支持云存储 2025-04-05 00:47:34 +08:00
xiaojunnuo
8449f8580d perf: 又拍云支持云存储 2025-04-05 00:46:56 +08:00
xiaojunnuo
0948c5bc69 perf: 优化华为dns解析记录创建和删除问题 2025-04-05 00:24:57 +08:00
xiaojunnuo
857589b365 feat: 优化证书申请速度,修复某些情况下letsencrypt 校验失败的问题 2025-04-04 23:17:05 +08:00
xiaojunnuo
c39b1bf823 fix: 修复从本地dns获取记录报错的bug 2025-04-04 20:46:48 +08:00
xiaojunnuo
545aa50898 Merge branch 'v2-dev' into v2-plugin 2025-04-04 20:14:24 +08:00
xiaojunnuo
298006a4b9 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-04-03 11:40:56 +08:00
xiaojunnuo
903a4131ab fix: 创建cname记录移除域名两端的空格 2025-04-03 11:39:36 +08:00
xiaojunnuo
a0ec0ddb14 build: publish 2025-04-03 00:33:32 +08:00
xiaojunnuo
46eb876f9b build: trigger build image 2025-04-03 00:33:07 +08:00
xiaojunnuo
8374c3941a v1.31.11 2025-04-03 00:31:04 +08:00
xiaojunnuo
8cec14c2ff build: prepare to build 2025-04-03 00:28:39 +08:00
xiaojunnuo
c716f105bd build: prepare to build 2025-04-03 00:26:38 +08:00
xiaojunnuo
737be1f571 chore: 2025-04-03 00:25:17 +08:00
xiaojunnuo
6f17c700b8 perf: 支持部署到京东云cdn 2025-04-03 00:19:54 +08:00
xiaojunnuo
04d79f9117 perf: 支持京东云dns申请证书 2025-04-02 23:13:55 +08:00
xiaojunnuo
8145808c43 fix: 修复ssh支持键盘事件登录 2025-04-02 00:20:09 +08:00
xiaojunnuo
86d5626d92 chore: 2025-04-01 23:03:44 +08:00
xiaojunnuo
071ef281c1 chore: 2025-04-01 22:34:15 +08:00
xiaojunnuo
70760e4ede build: publish 2025-03-30 01:50:01 +08:00
xiaojunnuo
8a32fd36ad build: trigger build image 2025-03-30 01:49:43 +08:00
xiaojunnuo
2e30fff221 v1.31.10 2025-03-30 01:47:57 +08:00
xiaojunnuo
667ffa5dff build: prepare to build 2025-03-30 01:43:56 +08:00
xiaojunnuo
d271120c24 chore: 2025-03-30 01:43:03 +08:00
xiaojunnuo
a03ae5a216 perf: tab增加图标显示 2025-03-30 01:41:27 +08:00
xiaojunnuo
0e73fa40bc chore: 2025-03-30 00:44:01 +08:00
xiaojunnuo
5d6f0d8546 pref(plugin-volcengine): 新增火山引擎 CDN部署功能 2025-03-30 00:30:42 +08:00
xiaojunnuo
41e23fb6a8 pref: 优化查找TXT记录逻辑,提升CNAME解析效率 2025-03-29 23:10:59 +08:00
xiaojunnuo
4e15556e5e perf: 升级lego版本到4.22.2 2025-03-29 10:47:12 +08:00
xiaojunnuo
bf30b7afae perf: 优化华为dns接口报错信息输出 2025-03-29 09:01:33 +08:00
xiaojunnuo
8801afa586 chore: 2025-03-28 23:51:32 +08:00
xiaojunnuo
47804353f9 build: trigger build image 2025-03-28 23:51:12 +08:00
xiaojunnuo
be58e3084b v1.31.9 2025-03-28 23:49:37 +08:00
xiaojunnuo
71d5ab21ed build: prepare to build 2025-03-28 23:47:13 +08:00
xiaojunnuo
993bc7432f perf: 站点监控保存时异步检查 2025-03-28 23:46:44 +08:00
xiaojunnuo
99ff879d93 perf: dns支持火山引擎 2025-03-28 23:27:24 +08:00
xiaojunnuo
caa15b4735 fix: 修复华为云dns接口请求出错的bug 2025-03-28 21:23:29 +08:00
xiaojunnuo
cc8da0cf13 fix: 修复网站证书监控https port设置无效的bug 2025-03-28 09:10:49 +08:00
xiaojunnuo
0b6618ff70 fix: 修复某些情况下站点证书监控报undefined.includes的错误 2025-03-27 17:10:46 +08:00
xiaojunnuo
6f180f534c build: publish 2025-03-26 22:03:01 +08:00
xiaojunnuo
4220ef2a5b build: trigger build image 2025-03-26 22:02:41 +08:00
xiaojunnuo
2acaa66635 v1.31.8 2025-03-26 22:01:03 +08:00
xiaojunnuo
ee5eecbec1 build: prepare to build 2025-03-26 21:57:43 +08:00
xiaojunnuo
687bb8a237 fix: 修复lego模式无法创建流水线的bug 2025-03-26 21:54:34 +08:00
xiaojunnuo
e51123a951 perf: 优化scp上传 2025-03-26 21:48:51 +08:00
xiaojunnuo
92bb12e2e3 chore: 2025-03-26 20:54:33 +08:00
xiaojunnuo
fd0536bd4b perf: 支持又拍云cdn 2025-03-26 20:48:26 +08:00
xiaojunnuo
57389a79a1 perf: 支持又拍云cdn 2025-03-26 20:47:50 +08:00
xiaojunnuo
6cd7bddc37 fix: 修复编辑通知勾选默认,导致出现多个默认通知的bug 2025-03-26 12:06:20 +08:00
xiaojunnuo
c3c5006daa perf: 优化通知格式 2025-03-26 12:05:28 +08:00
xiaojunnuo
27a8a57cf5 fix: 修复网站监控无法设置端口的bug 2025-03-26 09:21:13 +08:00
xiaojunnuo
3bded6fdcc chore: 2025-03-25 11:14:28 +08:00
xiaojunnuo
c7f31337ce chore: 2025-03-25 11:12:24 +08:00
xiaojunnuo
fd507f2692 perf: 优化txt本地校验效率 2025-03-25 11:08:25 +08:00
xiaojunnuo
96a39ef9c3 chore: 2025-03-25 09:19:32 +08:00
xiaojunnuo
81a8d6c3bf chore: 2025-03-25 00:18:52 +08:00
xiaojunnuo
d4719208f0 chore: 2025-03-25 00:18:36 +08:00
xiaojunnuo
c36dce6464 chore: 2025-03-25 00:17:38 +08:00
xiaojunnuo
2da4474bb1 chore: 2025-03-25 00:17:08 +08:00
xiaojunnuo
cab11202c7 build: publish 2025-03-25 00:05:26 +08:00
xiaojunnuo
21d7d35bb1 build: trigger build image 2025-03-25 00:05:04 +08:00
xiaojunnuo
0725c663c4 v1.31.7 2025-03-25 00:03:50 +08:00
xiaojunnuo
ce82239c8b build: prepare to build 2025-03-25 00:01:49 +08:00
xiaojunnuo
2523008cb6 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-03-24 23:59:24 +08:00
xiaojunnuo
69212b9ad1 build: publish 2025-03-24 23:52:43 +08:00
xiaojunnuo
636cb3399b build: trigger build image 2025-03-24 23:52:22 +08:00
xiaojunnuo
86ca35ce33 v1.31.6 2025-03-24 23:51:00 +08:00
xiaojunnuo
675ab31305 build: prepare to build 2025-03-24 23:48:42 +08:00
xiaojunnuo
83c2d743bc chore: 2025-03-24 23:48:34 +08:00
xiaojunnuo
2c87b3d906 build: prepare to build 2025-03-24 23:47:10 +08:00
xiaojunnuo
05b6159802 perf: 上传到主机支持scp方式 2025-03-24 23:45:45 +08:00
xiaojunnuo
c56f48c1e3 perf: 优化图标 2025-03-24 21:27:31 +08:00
xiaojunnuo
e18e399ce6 perf: 支持部署到lucky 2025-03-24 18:39:22 +08:00
xiaojunnuo
d66ade4e47 perf: 增加服务器时间警告 2025-03-24 18:39:08 +08:00
xiaojunnuo
90b045af6d fix: 修复dns.la无法申请证书的bug 2025-03-24 09:38:18 +08:00
xiaojunnuo
5cccb21175 chore: 2025-03-24 00:10:01 +08:00
xiaojunnuo
1a71969403 chore: 2025-03-24 00:05:19 +08:00
xiaojunnuo
389a1fbd04 build: publish 2025-03-22 15:05:31 +08:00
xiaojunnuo
ca02ae6183 build: trigger build image 2025-03-22 15:05:11 +08:00
xiaojunnuo
92446cb048 v1.31.5 2025-03-22 15:03:34 +08:00
xiaojunnuo
954ce4533f build: prepare to build 2025-03-22 15:01:00 +08:00
xiaojunnuo
f7b88f9e3b fix: 修复通知选择器无法选择的bug
https://github.com/certd/certd/issues/351
2025-03-22 15:00:17 +08:00
xiaojunnuo
736fe038eb fix: 修复证书流水线创建失败的bug 2025-03-22 14:59:54 +08:00
xiaojunnuo
abcd257db0 build: publish 2025-03-22 02:10:40 +08:00
xiaojunnuo
c38b5f3cdc build: trigger build image 2025-03-22 02:10:22 +08:00
702 changed files with 437200 additions and 3826 deletions

View File

@@ -10,6 +10,7 @@ on:
# - cron: '17 19 * * *'
permissions:
contents: read
packages: write
jobs:
build-certd-image:
@@ -43,7 +44,7 @@ jobs:
# cache: 'npm'
# working-directory: ./packages/ui/certd-client
- run: |
npm install -g pnpm@8.15.7
npm install -g pnpm
pnpm install
npm run build
working-directory: ./packages/ui/certd-client
@@ -61,19 +62,38 @@ jobs:
username: ${{ secrets.aliyun_cs_username }}
password: ${{ secrets.aliyun_cs_password }}
- name: Login to GitHub Packages
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.dockerhub_username }}
password: ${{ secrets.dockerhub_password }}
- name: Build default platforms
# - name: Build default platforms
# uses: docker/build-push-action@v6
# with:
# platforms: linux/amd64,linux/arm64
# push: true
# context: ./packages/ui/
# tags: |
# registry.cn-shenzhen.aliyuncs.com/handsfree/certd-dev:latest
# greper/certd-dev:latest
# ghcr.io/${{ github.repository }}:dev-latest
- name: Build armv7
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
platforms: linux/arm/v7
push: true
context: ./packages/ui/
tags: |
registry.cn-shenzhen.aliyuncs.com/handsfree/certd-dev:latest
greper/certd-dev:latest
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:armv7
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}-armv7
greper/certd:armv7
greper/certd:${{steps.get_certd_version.outputs.result}}-armv7

View File

@@ -10,6 +10,7 @@ on:
# - cron: '17 19 * * *'
permissions:
contents: read
packages: write
jobs:
build-certd-image:
@@ -43,7 +44,7 @@ jobs:
# cache: 'npm'
# working-directory: ./packages/ui/certd-client
- run: |
npm install -g pnpm@8.15.7
npm install -g pnpm
pnpm install
npm run build
working-directory: ./packages/ui/certd-client
@@ -61,6 +62,13 @@ jobs:
username: ${{ secrets.aliyun_cs_username }}
password: ${{ secrets.aliyun_cs_password }}
- name: Login to GitHub Packages
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
@@ -78,18 +86,21 @@ jobs:
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}
greper/certd:latest
greper/certd:${{steps.get_certd_version.outputs.result}}
# - name: Build armv7
# uses: docker/build-push-action@v6
# with:
# platforms: linux/arm/v7
# push: true
# context: ./packages/ui/
# tags: |
# registry.cn-shenzhen.aliyuncs.com/handsfree/certd:armv7
# registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}-armv7
# greper/certd:armv7
# greper/certd:${{steps.get_certd_version.outputs.result}}-armv7
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{steps.get_certd_version.outputs.result}}
- name: Build armv7
uses: docker/build-push-action@v6
with:
platforms: linux/arm/v7
push: true
context: ./packages/ui/
tags: |
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:armv7
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}-armv7
greper/certd:armv7
greper/certd:${{steps.get_certd_version.outputs.result}}-armv7
ghcr.io/${{ github.repository }}:armv7
ghcr.io/${{ github.repository }}:${{steps.get_certd_version.outputs.result}}-armv7
# - name: Build agent
# uses: docker/build-push-action@v6

View File

@@ -3,6 +3,207 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.33.8](https://github.com/certd/certd/compare/v1.33.7...v1.33.8) (2025-04-26)
### Bug Fixes
* 服务器时间获取不准确的bug ([5d10cbf](https://github.com/certd/certd/commit/5d10cbf18daf94a90a7551641a3b13e3c5fec611))
* 修复复制流水线无效的bug ([3df20a9](https://github.com/certd/certd/commit/3df20a924f32970b052e2588ea20de095f0ea693))
* 修复http上传方式无法清除记录文件的bug ([72a7b51](https://github.com/certd/certd/commit/72a7b51d479602b2c54c6c3ac8d8a0dcb9664e73))
* 修复token过期后疯狂打印token过期信息的bug ([50a5fa1](https://github.com/certd/certd/commit/50a5fa15bb240a125bbc91d2ce1ff3c835888a77))
### Performance Improvements
* 从域名的soa获取主域名子域名托管无需额外配置 ([a586a92](https://github.com/certd/certd/commit/a586a92d5e32ea846ac37be52a7ad8c328d89966))
* 七牛oss支持删除过期备份 ([b7113bd](https://github.com/certd/certd/commit/b7113bda2378116d6c116dc583f563cce7cf9f00))
* 数据库备份支持oss ([308d460](https://github.com/certd/certd/commit/308d4600efe2002f199c33b4594d3071784e58ea))
* 支持阿里云中文域名申请 ([b3468cf](https://github.com/certd/certd/commit/b3468cf7f28228d7c9cf68de6b5a9bbeb67f2c6d))
* 支持反向代理增加contextPath路径 ([0088929](https://github.com/certd/certd/commit/0088929622160cc922995de9a563e8061686ff34))
* 支持中文域名 ([162ebfd](https://github.com/certd/certd/commit/162ebfd4e0c25727efb33952d3bbf7420a02e2c3))
## [1.33.7](https://github.com/certd/certd/compare/v1.33.6...v1.33.7) (2025-04-22)
### Performance Improvements
* 添加部署证书至火山 Live ([abea80e](https://github.com/certd/certd/commit/abea80e3ab9b1672aebe1c5d5e856693b29931a8))
* 优化首页插件列表展示 ([9b8f60b](https://github.com/certd/certd/commit/9b8f60b64b5f9a3db7dfa9b3dcbd9201984358d0))
* 证书申请支持51dns ([8638fc9](https://github.com/certd/certd/commit/8638fc91ff34fccaf12ff9874fd3fa9d2a8c18b7))
* 支持51dns ([96a0900](https://github.com/certd/certd/commit/96a0900edc95dcfd9acccf9d13592f12f5a09b3d))
* ssh PTY模式登录设置 ([8385bcc](https://github.com/certd/certd/commit/8385bcc2d7f2411a07748bb5c53f9eaf4d38d7cc))
* ssh伪终端模式优化windows下不开启 ([42dfe93](https://github.com/certd/certd/commit/42dfe936b773b7bdd82ca3378363252ffffd7b71))
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
### Bug Fixes
* 上传商用证书直接粘贴文本报错的问题修复无法上传ec加密证书的bug ([5750bb7](https://github.com/certd/certd/commit/5750bb706779da274d8e7a87e71416cb64d2df79))
* 修复下载证书时提示token已过期的问题 ([0e07ae6](https://github.com/certd/certd/commit/0e07ae6ce84dcb9279d3c44060d621566afa593c))
### Performance Improvements
* 更新license时同时绑定url ([78367af](https://github.com/certd/certd/commit/78367af8307f801e778c76d49f0918c21ffe032f))
* 切换到不同的分组后再打开创建对话框,会自动选择分组 ([893dcd4](https://github.com/certd/certd/commit/893dcd4f2487891199ed3e5a3d47a79a75efc942))
* 新增部署到火山引擎ALB/CLB、上传到证书中心 ([c9a3e3d](https://github.com/certd/certd/commit/c9a3e3d9d26f964c7af7b56667936f1414fbf42a))
* 优化/api缓存为0 ([dc05cd4](https://github.com/certd/certd/commit/dc05cd481f186b13375192be965000e6b4b429a5))
* 优化华为cdn插件引用ccm证书 ([b565b4b](https://github.com/certd/certd/commit/b565b4b3b919b71b98ea2517670bc1ef00e00dc9))
* 优化证书流水线创建,支持选择分组 ([d613aa8](https://github.com/certd/certd/commit/d613aa8f3e85d8dc475ef1b62d49394ce7fd7d24))
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
### Performance Improvements
* 登录支持双重认证 ([48aef25](https://github.com/certd/certd/commit/48aef25b3f6499d674ca4e4ef16f4c62399fb735))
* 多重认证登录 ([0f82cf4](https://github.com/certd/certd/commit/0f82cf409bc60706ab07e4ca4f272b9a1ca7eecb))
* 优化部署到华为云CDN支持先上传到ccm再使用证书id部署修复offline状态下导致部署报错的bug ([79df39a](https://github.com/certd/certd/commit/79df39acabab10ae7e1864dadcdc186bb007a3c5))
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
### Bug Fixes
* 补充类型断言 ([2143dff](https://github.com/certd/certd/commit/2143dff2ae96e6a78bef9f0498e36f8cd9e6941f))
* 修复腾讯云部署到任意资源插件,无法使用之前已上传的腾讯云证书问题 ([32c714d](https://github.com/certd/certd/commit/32c714d1b6e68c71a74a7452115040c87ac4bfdc))
### Performance Improvements
* 插件支持导入导出 ([cf8abb4](https://github.com/certd/certd/commit/cf8abb45282070c8ba91469f93fd379fabf1f74a))
* 支持上传证书到华为云CCM ([cfd3b66](https://github.com/certd/certd/commit/cfd3b66be9ebf53a26693057e70ed60c3f116be9))
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
### Bug Fixes
* 修复登录错误次数过多阻止再次登录逻辑 ([bf4d191](https://github.com/certd/certd/commit/bf4d191c8bd2f9209eb6768f662b9c77de99e998))
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
### Bug Fixes
* 修复某些情况下无法输出日志的bug ([70101bf](https://github.com/certd/certd/commit/70101bfa7ade65678d9202c804bbae2cb808b594))
### Performance Improvements
* 修复内置插件分页查询逻辑 ([a2710dd](https://github.com/certd/certd/commit/a2710ddc2525e4e637fd157f0180e6d3b801c8be))
## [1.33.1](https://github.com/certd/certd/compare/v1.33.0...v1.33.1) (2025-04-12)
### Bug Fixes
* 修复阿里云cdn证书部署失败问题增加certname参数传入 ([965dc2c](https://github.com/certd/certd/commit/965dc2cb476f690af716f291c6b20ba98be0c8f0))
* 修复ssh插件报length空指针的bug ([9c4cbe1](https://github.com/certd/certd/commit/9c4cbe17a22b548611cf1fbefecc83a421788e42))
### Performance Improvements
* 镜像支持armv7 ([f78cbed](https://github.com/certd/certd/commit/f78cbed4d817859721fdafe7d348864848d0dfbf))
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
### Bug Fixes
* 升级mysql驱动支持mysql8最新版本的认证 ([2f5ed3a](https://github.com/certd/certd/commit/2f5ed3aead97641f2c80d692a50226839016df0b))
* 修复eab授权没有email绑定的bug ([2f1683b](https://github.com/certd/certd/commit/2f1683b26acebbfb7d6e2d751435be04a4e7cab4))
### Features
* 支持在线自定义插件,无需源码开发 ([d0d9d68](https://github.com/certd/certd/commit/d0d9d68fe6740f6ff49fe40b7c9917c5a2e4b442))
* **lego:** support set key type ([f3bf4fa](https://github.com/certd/certd/commit/f3bf4faee0be5bdbfdbcf70a502849ed4c8ed4c4))
* release image to ghcr ([9b536af](https://github.com/certd/certd/commit/9b536af9e656dc89e2a87078c129cad6f591e467))
### Performance Improvements
* 修复tab页缓存问题 ([64e5449](https://github.com/certd/certd/commit/64e5449ab3c6b219b0e89eddad14bfb6b71a0650))
* 隐藏运行策略选项 ([2951df0](https://github.com/certd/certd/commit/2951df0cd94c23e2efee84ff1b843055aac56cae))
* 增加手动上传证书功能说明 ([5d083a1](https://github.com/certd/certd/commit/5d083a153637caddbc6f44e915d9fb2d1ae87b33))
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
### Bug Fixes
* 创建cname记录移除域名两端的空格 ([903a413](https://github.com/certd/certd/commit/903a4131ab5f42c8286cd2150ed1032d486fda2f))
* 修复从本地dns获取记录报错的bug ([c39b1bf](https://github.com/certd/certd/commit/c39b1bf823ddc6216bed2049e4c87e6107def08a))
### Features
* 优化证书申请速度修复某些情况下letsencrypt 校验失败的问题 ([857589b](https://github.com/certd/certd/commit/857589b365c6f709e0ae67914d2f50ce182e6dd6))
### Performance Improvements
* 优化华为dns解析记录创建和删除问题 ([0948c5b](https://github.com/certd/certd/commit/0948c5bc691d2ee6eb47c72a85da1b7453361878))
* 又拍云支持云存储 ([9339b78](https://github.com/certd/certd/commit/9339b78f801d193472c0af25749e8e7a27ffb7af))
* 又拍云支持云存储 ([8449f85](https://github.com/certd/certd/commit/8449f8580da90c1f6b5d02d07c3236ebaf6cf161))
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Bug Fixes
* 修复ssh支持键盘事件登录 ([8145808](https://github.com/certd/certd/commit/8145808c4370364377b4ffe3ae88ff465b49f20b))
### Performance Improvements
* 支持部署到京东云cdn ([6f17c70](https://github.com/certd/certd/commit/6f17c700b84965baa01b40fe2abaa0a91bcbaffd))
* 支持京东云dns申请证书 ([04d79f9](https://github.com/certd/certd/commit/04d79f9117670be504960b018fd49ae3bf7c1c11))
## [1.31.10](https://github.com/certd/certd/compare/v1.31.9...v1.31.10) (2025-03-29)
### Performance Improvements
* tab增加图标显示 ([a03ae5a](https://github.com/certd/certd/commit/a03ae5a216a1df2c1d3da12ae18dcd0f089a92d3))
* 升级lego版本到4.22.2 ([4e15556](https://github.com/certd/certd/commit/4e15556e5e8100719497edb1729570d5a29668e1))
* 优化华为dns接口报错信息输出 ([bf30b7a](https://github.com/certd/certd/commit/bf30b7afaef623dd8126570344f1fcc2c06f1215))
## [1.31.9](https://github.com/certd/certd/compare/v1.31.8...v1.31.9) (2025-03-28)
### Bug Fixes
* 修复华为云dns接口请求出错的bug ([caa15b4](https://github.com/certd/certd/commit/caa15b47355363cbb8847f415ff12363cd53eeda))
* 修复某些情况下站点证书监控报undefined.includes的错误 ([0b6618f](https://github.com/certd/certd/commit/0b6618ff709322a0eeba78953c8c6e9d073d083a))
* 修复网站证书监控https port设置无效的bug ([cc8da0c](https://github.com/certd/certd/commit/cc8da0cf130f0c469371b59ac5bd04567f4a4414))
### Performance Improvements
* 站点监控保存时异步检查 ([993bc74](https://github.com/certd/certd/commit/993bc7432fce2d954e9897ed85b54f22150bfc7e))
* dns支持火山引擎 ([99ff879](https://github.com/certd/certd/commit/99ff879d93658c29ea493a4bde7e9e3f85996d64))
## [1.31.8](https://github.com/certd/certd/compare/v1.31.7...v1.31.8) (2025-03-26)
### Bug Fixes
* 修复编辑通知勾选默认导致出现多个默认通知的bug ([6cd7bdd](https://github.com/certd/certd/commit/6cd7bddc37da8b0d7b9860fd9a26ddfe84c869a7))
* 修复网站监控无法设置端口的bug ([27a8a57](https://github.com/certd/certd/commit/27a8a57cf52b4bf83d628aa3049be1efaa74f29c))
* 修复lego模式无法创建流水线的bug ([687bb8a](https://github.com/certd/certd/commit/687bb8a2376d0de7b72739a174e4a9560581f866))
### Performance Improvements
* 优化通知格式 ([c3c5006](https://github.com/certd/certd/commit/c3c5006daa39c20624cb58864f2b92b230a38a7a))
* 优化scp上传 ([e51123a](https://github.com/certd/certd/commit/e51123a95131cc76d655937488caf08956a67020))
* 优化txt本地校验效率 ([fd507f2](https://github.com/certd/certd/commit/fd507f269253607e68c5c099c99e0de11636f229))
* 支持又拍云cdn ([fd0536b](https://github.com/certd/certd/commit/fd0536bd4b41f15b6b5d42e0b447f0dcbf73b8a8))
* 支持又拍云cdn ([57389a7](https://github.com/certd/certd/commit/57389a79a1a61c45d081712562f8b33c9633158e))
## [1.31.7](https://github.com/certd/certd/compare/v1.31.6...v1.31.7) (2025-03-24)
### Performance Improvements
* 增加服务器时间警告 ([d66ade4](https://github.com/certd/certd/commit/d66ade4e4783850b6c7625c6f164a5a0fc0aa509))
* 支持部署到lucky ([e18e399](https://github.com/certd/certd/commit/e18e399ce6529e8c7e36b56c5f674cfdbbd3d3d1))
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
### Bug Fixes
* 修复dns.la无法申请证书的bug ([90b045a](https://github.com/certd/certd/commit/90b045af6d1a4f46986e4b118885c1f050df067c))
### Performance Improvements
* 上传到主机支持scp方式 ([05b6159](https://github.com/certd/certd/commit/05b6159802b9e85b6a410361b60b5c28875b48e7))
* 优化图标 ([c56f48c](https://github.com/certd/certd/commit/c56f48c1e3c54c4e203fafb380d9091d75681b7e))
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
### Bug Fixes
* 修复通知选择器无法选择的bug ([f7b88f9](https://github.com/certd/certd/commit/f7b88f9e3b7d9d9122e4fd2003a20c555bd50c7d))
* 修复证书流水线创建失败的bug ([736fe03](https://github.com/certd/certd/commit/736fe038ebda56648bcc4c12884a700341d2c049))
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
### Bug Fixes

148
README.md
View File

@@ -9,13 +9,16 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
* 全自动申请证书(支持所有注册商注册的域名)
* 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等目前已支持60+部署插件)
* 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等70+部署插件)
* 支持DNS-01、HTTP-01、CNAME代理等多种域名验证方式
* 支持通配符域名/泛域名支持多个域名打到一个证书上支持pem、pfx、der、jks等多种证书格式
* 邮件通知、webhook通知
* 私有化部署数据保存本地授权信息加密存储镜像由Github Actions构建过程公开透明
* 支持SQLitePostgreSQL、MySQL数据库
![](./docs/images/intro/intro.svg)
>
> 流水线数量现已调整为无限制,欢迎大家使用
>
@@ -23,7 +26,7 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
> 关于证书续期:
>* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
>* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
>* 免费证书过期时间90天以后可能还会缩短所以自动化部署必不可少
## 二、在线体验
@@ -62,7 +65,7 @@ https://certd.handfree.work/
-------> [点我查看详细使用步骤演示](./step.md) <--------
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
更多教程请访问文档网站 [certd.docmirror.cn](https://certd.docmirror.cn/)
更多教程请访问官方文档 [certd.docmirror.cn](https://certd.docmirror.cn/guide/)
@@ -72,10 +75,10 @@ https://certd.handfree.work/
您可以根据实际情况从如下方式中选择一种方式进行私有化部署:
1. [宝塔面板方式部署](https://certd.docmirror.cn/guide/install/docker/)
2. [1Panel面板方式部署](https://certd.docmirror.cn/guide/install/1panel/)
3. [Docker方式部署](https://certd.docmirror.cn/guide/install/docker/)
4. [源码方式部署](https://certd.docmirror.cn/guide/install/source/)
1. [宝塔面板方式部署 推荐](https://certd.docmirror.cn/guide/install/docker/)
2. [1Panel面板方式部署 推荐](https://certd.docmirror.cn/guide/install/1panel/)
3. [Docker方式部署 推荐](https://certd.docmirror.cn/guide/install/docker/)
4. [源码方式部署 不建议](https://certd.docmirror.cn/guide/install/source/)
#### Docker镜像说明
* 国内镜像地址:
@@ -85,93 +88,36 @@ https://certd.handfree.work/
* `https://hub.docker.com/r/greper/certd`
* `greper/certd:latest`
* `greper/certd:armv7``greper/certd:[version]-armv7`
* GitHub Packages地址:
* `ghcr.io/certd/certd:latest`
* `ghcr.io/certd/certd:armv7``ghcr.io/certd/certd:[version]-armv7`
* 镜像构建通过`Actions`自动执行,过程公开透明,请放心使用
* [点我查看镜像构建日志](https://github.com/certd/certd/actions/workflows/build-image.yml)
![](./docs/images/action/action-build.jpg)
## 五、 升级
### docker-compose方式部署
#### 1. 如果使用固定版本号
1. 修改`docker-compose.yaml`中的镜像版本号
2. 运行`docker compose up -d` 即可
#### 2. 如果需要使用最新版本
```shell
#重新拉取镜像
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
# 重新启动容器
docker compose down
docker compose up -d
```
> 数据默认存在`/data/certd`目录下,不用担心数据丢失
### 自动升级(仅限尝鲜建议非生产使用)
```yaml
version: '3.3'
services:
certd:
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
container_name: certd
restart: unless-stopped
volumes:
- /data/certd:/app/data
ports:
- "7001:7001"
- "7002:7002"
environment:
- certd_system_resetAdminPasswd=false
labels:
com.centurylinklabs.watchtower.enable: "true"
certd-updater: # 添加 Watchtower 服务
image: containrrr/watchtower:latest
container_name: certd-updater
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# 配置 自动更新
environment:
- WATCHTOWER_CLEANUP=true # 自动清理旧版本容器
- WATCHTOWER_INCLUDE_STOPPED=false # 不更新已停止的容器
- WATCHTOWER_LABEL_ENABLE=true # 根据容器标签进行更新
- WATCHTOWER_POLL_INTERVAL=300 # 每 5 分钟检查一次更新
```
### 其他部署方式升级方法
请参考 https://certd.docmirror.cn/guide/install/upgrade.html
> 注意:
> * 本应用存储的证书、授权信息等属于高度敏感数据,请做好安全防护
> * 请务必使用HTTPS协议访问本应用避免被中间人攻击
> * 请务必使用web应用防火墙防护本应用防止XSS、SQL注入等攻击
> * 请务必做好服务器本身的安全防护,防止数据库泄露
> * 请务必做好数据备份,避免数据丢失
### 更新日志:
[CHANGELOG](./CHANGELOG.md)
## 五、更多帮助
请访问官方文档:[https://certd.docmirror.cn/](https://certd.docmirror.cn/guide/)
* 升级方法:[升级方法](https://certd.docmirror.cn/guide/install/upgrade/)
* 常见问题:[忘记密码](https://certd.docmirror.cn/guide/use/forgotpasswd/)
* 多数据库:[多数据库配置](https://certd.docmirror.cn/guide/install/database/)
* 站点安全:[站点安全特性](https://certd.docmirror.cn/guide/feature/safe/)
* 更新日志:[CHANGELOG](./CHANGELOG.md)
## 六、一些说明
* 本项目ssl证书提供商为letencrypt/Google/ZeroSSL
* 申请过程遵循acme协议
* 证书续期:
* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
* 免费证书过期时间90天以后可能还会缩短所以自动化部署必不可少
* 设置每天自动运行当证书过期前35天会自动重新申请证书并部署
## 七、不同平台的设置说明
* 已迁移到新的文档网站,请到常见问题章节查看
* [最新文档站链接 https://certd.docmirror.cn](https://certd.docmirror.cn/)
## 八、问题处理
### 7.1 忘记管理员密码
[重置管理员密码方法](https://certd.docmirror.cn/guide/use/forgotpasswd/)
## 九、联系作者
## 六、联系作者
如有疑问欢迎加入群聊请备注certd
| 加群 | 微信群 | QQ群 |
@@ -185,7 +131,7 @@ services:
| 二维码 | <img height="230" src="./docs/guide/contact/images/me.png"> |
## 、捐赠
## 、捐赠
************************
支持开源,为爱发电,我已入驻爱发电
https://afdian.com/a/greper
@@ -194,49 +140,41 @@ https://afdian.com/a/greper
1. 可加入发电专属群,可以获得作者一对一技术支持
2. 您的需求我们将优先实现,并且将作为专业版功能提供
3. 一年期专业版激活码
4. 赠送国外免费服务器部署方案0成本使用Certd可能需要翻墙不过现在性能越来越差了
专业版特权对比
| 功能 | 基础版 | 专业版 |
|------|-----------------|-------------------|
| 免费证书申请 | 免费无限制 | 无限制 |
| 域名数量 | 免费无限制 | 无限制 |
| 证书流水线条数 | 免费无限制 | 无限制 |
| 站点证书监控 | 1条 | 无限制 |
| 自动部署插件 | 阿里云、腾讯云、七牛云、SSH | 支持群晖、宝塔、1Panel等持续开发中 |
| 通知 | 邮件webhook | server酱、企微、anpush等 |
| 功能 | 免费版 | 专业版 |
|---------|--------------------|-----------------------------|
| 免费证书申请 | 免费无限制 | 免费无限制 |
| 域名数量 | 无限制 | 无限制 |
| 证书流水线条数 | 无限制 | 无限制 |
| 站点证书监控 | 限制1条 | 无限制 |
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署等 | 支持群晖、宝塔、1Panel等持续开发中 |
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、飞书、anpush、server酱等 |
************************
## 十一、贡献代码
## 、贡献代码
1. 本地开发 [贡献插件](https://certd.docmirror.cn/guide/development/)
1. 本地开发请参考 [贡献插件向导](https://certd.docmirror.cn/guide/development/)
2. 作为贡献者,代表您同意您贡献的代码如下许可:
1. 可以调整开源协议以使其更严格或更宽松。
2. 可以用于商业用途。
## 十二、 开源许可
## 、 开源许可
* 本项目遵循 GNU Affero General Public LicenseAGPL开源协议。
* 允许个人和公司内部自由使用、复制、修改和分发本项目,未获得商业授权情况下禁止任何形式的商业用途
* 未获得商业授权情况下禁止任何对logo、版权信息及授权许可相关代码的修改。
* 如需商业授权,请联系作者。
## 十三、我的其他项目求Star
## 十、我的其他项目求Star
| 项目名称 | stars | 项目描述 |
|---------------------------------------------------------|-------------------------------------------------------------------------------------------------------|-----------------------------------|
| [袖手AI](https://ai.handsfree.work/) | | 袖手GPT国内可用无需FQ每日免费额度 |
| [fast-crud](https://gitee.com/fast-crud/fast-crud/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/fast-crud/fast-crud?logo=github"/> | 基于vue3的crud快速开发框架 |
| [dev-sidecar](https://github.com/docmirror/dev-sidecar/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/docmirror/dev-sidecar?logo=github"/> | 直连访问github工具无需FQ解决github无法访问的问题 |
## 十四、更新日志
更新日志:[CHANGELOG](./CHANGELOG.md)

View File

@@ -1 +1 @@
1
2

View File

@@ -1 +1 @@
01:19
22:32

View File

@@ -1,147 +1,166 @@
import { defineConfig } from "vitepress";
import {defineConfig} from "vitepress";
// Import lightbox plugin
import lightbox from "vitepress-plugin-lightbox";
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "Certd",
description: "Certd帮助文档,Certd是一款开源免费的全自动SSL证书管理工具证书自动化申请部署流水线;自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔。",
markdown: {
config: (md) => {
// Use lightbox plugin
md.use(lightbox, {});
}
},
sitemap: {
hostname: 'https://certd.docmirror.cn'
},
head: [
// [
// 'meta',
// {
// name: 'viewport',
// content:
// 'width=device-width,initial-scale=1,minimfast-cum-scale=1.0,maximum-scale=1.0,user-scalable=no',
// },
// ],
["meta", {
name: "keywords",
content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化https证书pfx证书der证书TLS证书nginx证书自动续签自动部署,SSL平台证书管理平台证书流水线"
}],
// ["meta", { name: "google-site-verification",content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"}],
//<meta name="baidu-site-verification" content="codeva-MiWN8Y07Ua" />
// ["meta", {name: "baidu-site-verification",content: "codeva-MiWN8Y07Ua"}],
["link", { rel: "icon", href: "/static/logo/logo.svg" }]
],
themeConfig: {
logo: "/static/logo/logo.svg",
search: {
provider: "local",
options: {
detailedView: true,
translations: {
button: {
buttonText: "搜索文档",
buttonAriaLabel: "搜索文档"
},
modal: {
noResultsText: "无法找到相关结果",
resetButtonTitle: "清除查询条件",
footer: {
selectText: "选择",
closeText: "关闭",
navigateText: "切换"
title: "Certd",
titleTemplate: "开源SSL证书管理工具证书自动化申请部署,让你的网站证书永不过期",
description: "Certd帮助文档,Certd是一款开源免费的全自动SSL证书管理工具证书自动化申请部署流水线自动证书申请、更新、续期通配符证书泛域名证书申请证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔。",
markdown: {
config: (md) => {
// Use lightbox plugin
md.use(lightbox, {});
}
},
sitemap: {
hostname: 'https://certd.docmirror.cn'
},
head: [
// [
// 'meta',
// {
// name: 'viewport',
// content:
// 'width=device-width,initial-scale=1,minimfast-cum-scale=1.0,maximum-scale=1.0,user-scalable=no',
// },
// ],
["meta", {
name: "keywords",
content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化https证书pfx证书der证书TLS证书nginx证书自动续签自动部署,SSL平台证书管理平台证书流水线"
}],
// ["meta", { name: "google-site-verification",content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"}],
//<meta name="baidu-site-verification" content="codeva-MiWN8Y07Ua" />
// ["meta", {name: "baidu-site-verification",content: "codeva-MiWN8Y07Ua"}],
["link", {rel: "icon", href: "/static/logo/logo.svg"}]
],
themeConfig: {
logo: "/static/logo/logo.svg",
search: {
provider: "local",
options: {
detailedView: true,
translations: {
button: {
buttonText: "搜索文档",
buttonAriaLabel: "搜索文档"
},
modal: {
noResultsText: "无法找到相关结果",
resetButtonTitle: "清除查询条件",
footer: {
selectText: "选择",
closeText: "关闭",
navigateText: "切换"
}
}
}
}
}
}
}
},
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: "首页", link: "/" },
{ text: "指南", link: "/guide/" },
{ text: "商业版", link: "/comm/" },
{ text: "Demo体验", link: "https://certd.handfree.work" }
],
sidebar: {
"/guide/": [
{
text: "入门",
items: [
{ text: "简介", link: "/guide/" },
{ text: "快速开始", link: "/guide/start.md" },
{
text: "私有化部署",
items: [
{ text: "docker部署", link: "/guide/install/docker/" },
{ text: "宝塔面板部署", link: "/guide/install/baota/" },
{ text: "1Panel部署", link: "/guide/install/1panel/" },
{ text: "群晖部署", link: "/guide/use/synology/" },
{ text: "源码部署", link: "/guide/install/source/" }
]
},
{ text: "演示教程", link: "/guide/tutorial.md" },
{ text: "版本升级", link: "/guide/install/upgrade.md" }
]
},
{
text: "特性",
items: [
{ text: "CNAME代理校验", link: "/guide/feature/cname/index.md" },
{ text: "插件列表", link: "/guide/plugins.md" },
{ text: "多数据库支持", link: "/guide/install/database.md" },
{ text: "开放接口", link: "/guide/open/index.md" }
]
},
{
text: "常见问题",
items: [
{ text: "群晖证书部署", link: "/guide/use/synology/" },
{ text: "腾讯云密钥获取", link: "/guide/use/tencent/" },
{ text: "连接windows主机", link: "/guide/use/host/windows.md" },
{ text: "Google EAB获取", link: "/guide/use/google/" },
{ text: "阿里云相关", link: "/guide/use/aliyun/" },
{ text: "忘记密码", link: "/guide/use/forgotpasswd/" },
{ text: "数据备份", link: "/guide/use/backup/" },
{ text: "Certd本身的证书更新", link: "/guide/use/https/index.md" },
{ text: "js脚本插件使用", link: "/guide/use/custom-script/index.md" },
{ text: "邮箱配置", link: "/guide/use/email/index.md" },
{ text: "IPv6支持", link: "/guide/use/setting/ipv6.md" },
]
},
{
text: "其他",
items: [
{ text: "贡献代码", link: "/guide/development/index.md" },
{ text: "更新日志", link: "/guide/changelogs/CHANGELOG.md" },
{ text: "镜像说明", link: "/guide/image.md" },
{ text: "联系我们", link: "/guide/contact/" },
{ text: "捐赠", link: "/guide/donate/" },
{ text: "开源协议", link: "/guide/license/" },
{ text: "我的其他开源项目", link: "/guide/link/" },
// https://vitepress.dev/reference/default-theme-config
nav: [
{text: "首页", link: "/"},
{text: "指南", link: "/guide/"},
{text: "Demo体验", link: "https://certd.handfree.work"}
],
sidebar: {
"/guide/": [
{
text: "入门",
items: [
{text: "简介", link: "/guide/"},
{text: "快速开始", link: "/guide/start.md"},
{
text: "私有化部署",
items: [
{text: "docker部署", link: "/guide/install/docker/"},
{text: "宝塔面板部署", link: "/guide/install/baota/"},
{text: "1Panel部署", link: "/guide/install/1panel/"},
{text: "群晖部署", link: "/guide/use/synology/"},
{text: "源码部署", link: "/guide/install/source/"}
]
},
{text: "演示教程", link: "/guide/tutorial.md"},
{text: "版本升级", link: "/guide/install/upgrade.md"}
]
},
{
text: "特性",
items: [
{text: "CNAME代理校验", link: "/guide/feature/cname/index.md"},
{text: "插件列表", link: "/guide/plugins.md"},
{text: "多数据库支持", link: "/guide/install/database.md"},
{text: "开放接口", link: "/guide/open/index.md"},
{
text: "站点安全", items: [
{text: "安全特性", link: "/guide/feature/safe"},
{text: "站点隐藏", link: "/guide/feature/safe/hidden"},
{text: "安全生产建议", link: "/guide/feature/safe/suggest"},
]
},
]
}
],
"/comm/": [
{
text: "商业版",
items: [
{ text: "支付宝配置", link: "/comm/payments/alipay.md" },
{ text: "微信支付配置", link: "/comm/payments/wxpay.md" },
{ text: "彩虹易支付配置", link: "/comm/payments/yizhifu.md" },
]
}
]
,
},
]
},
{
text: "常见问题",
items: [
{text: "群晖证书部署", link: "/guide/use/synology/"},
{text: "腾讯云密钥获取", link: "/guide/use/tencent/"},
{text: "连接windows主机", link: "/guide/use/host/windows.md"},
{text: "Google EAB获取", link: "/guide/use/google/"},
{text: "阿里云相关", link: "/guide/use/aliyun/"},
{text: "忘记密码", link: "/guide/use/forgotpasswd/"},
{text: "数据备份", link: "/guide/use/backup/"},
{text: "Certd本身的证书更新", link: "/guide/use/https/index.md"},
{text: "js脚本插件使用", link: "/guide/use/custom-script/index.md"},
{text: "邮箱配置", link: "/guide/use/email/index.md"},
{text: "IPv6支持", link: "/guide/use/setting/ipv6.md"},
{text: "其他插件使用", link: "/deploy/"},
{text: "商业版说明", link: "/comm/"},
]
},
{
text: "其他",
items: [
{text: "贡献代码", link: "/guide/development/index.md"},
{text: "更新日志", link: "/guide/changelogs/CHANGELOG.md"},
{text: "镜像说明", link: "/guide/image.md"},
{text: "联系我们", link: "/guide/contact/"},
{text: "捐赠", link: "/guide/donate/"},
{text: "开源协议", link: "/guide/license/"},
{text: "我的其他开源项目", link: "/guide/link/"},
socialLinks: [
{ icon: "github", link: "https://github.com/certd/certd" }
],
footer: {
message: "Certd帮助文档 | <a href='https://beian.miit.gov.cn/' target='_blank'>粤ICP备14088435号</a> ",
copyright: "Copyright © 2021-present <a href='https://handfree.work/' target='_blank'>handfree.work</a> "
]
}
],
"/deploy/": [
{
text: "部署证书插件",
items: [
{text: "插件说明", link: "/deploy/index.md"},
{text: "部署到ESXi", link: "/deploy/ESXi/index.md"},
]
}
],
"/comm/": [
{
text: "商业版",
items: [
{text: "支付宝配置", link: "/comm/payments/alipay.md"},
{text: "微信支付配置", link: "/comm/payments/wxpay.md"},
{text: "彩虹易支付配置", link: "/comm/payments/yizhifu.md"},
]
}
]
,
},
socialLinks: [
{icon: "github", link: "https://github.com/certd/certd"}
],
footer: {
message: "Certd帮助文档 | <a href='https://beian.miit.gov.cn/' target='_blank'>粤ICP备14088435号</a> ",
copyright: "Copyright © 2021-present <a href='https://handfree.work/' target='_blank'>handfree.work</a> "
}
}
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

17
docs/deploy/ESXi/index.md Normal file
View File

@@ -0,0 +1,17 @@
# 部署证书到ESXi
使用`部署证书到主机插件`即可
## 开启ssh
登陆ESXi Web后台点击 主机 -> 操作 -> 服务 -> 启用 Secure ShellSSH打开SSH
## 添加部署到主机任务
![img.png](./images/ssh.png)
## 配置重启脚本
```bash
/etc/init.d/hostd restart
/etc/init.d/vpxa restart
```

4
docs/deploy/index.md Normal file
View File

@@ -0,0 +1,4 @@
# 部署插件说明
## 待完善

View File

@@ -3,6 +3,210 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.33.7](https://github.com/certd/certd/compare/v1.33.6...v1.33.7) (2025-04-22)
### Performance Improvements
* 添加部署证书至火山 Live ([abea80e](https://github.com/certd/certd/commit/abea80e3ab9b1672aebe1c5d5e856693b29931a8))
* 优化首页插件列表展示 ([9b8f60b](https://github.com/certd/certd/commit/9b8f60b64b5f9a3db7dfa9b3dcbd9201984358d0))
* 证书申请支持51dns ([8638fc9](https://github.com/certd/certd/commit/8638fc91ff34fccaf12ff9874fd3fa9d2a8c18b7))
* 支持51dns ([96a0900](https://github.com/certd/certd/commit/96a0900edc95dcfd9acccf9d13592f12f5a09b3d))
* ssh PTY模式登录设置 ([8385bcc](https://github.com/certd/certd/commit/8385bcc2d7f2411a07748bb5c53f9eaf4d38d7cc))
* ssh伪终端模式优化windows下不开启 ([42dfe93](https://github.com/certd/certd/commit/42dfe936b773b7bdd82ca3378363252ffffd7b71))
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
### Bug Fixes
* 上传商用证书直接粘贴文本报错的问题修复无法上传ec加密证书的bug ([5750bb7](https://github.com/certd/certd/commit/5750bb706779da274d8e7a87e71416cb64d2df79))
* 修复下载证书时提示token已过期的问题 ([0e07ae6](https://github.com/certd/certd/commit/0e07ae6ce84dcb9279d3c44060d621566afa593c))
### Performance Improvements
* 更新license时同时绑定url ([78367af](https://github.com/certd/certd/commit/78367af8307f801e778c76d49f0918c21ffe032f))
* 切换到不同的分组后再打开创建对话框,会自动选择分组 ([893dcd4](https://github.com/certd/certd/commit/893dcd4f2487891199ed3e5a3d47a79a75efc942))
* 新增部署到火山引擎ALB/CLB、上传到证书中心 ([c9a3e3d](https://github.com/certd/certd/commit/c9a3e3d9d26f964c7af7b56667936f1414fbf42a))
* 优化/api缓存为0 ([dc05cd4](https://github.com/certd/certd/commit/dc05cd481f186b13375192be965000e6b4b429a5))
* 优化华为cdn插件引用ccm证书 ([b565b4b](https://github.com/certd/certd/commit/b565b4b3b919b71b98ea2517670bc1ef00e00dc9))
* 优化证书流水线创建,支持选择分组 ([d613aa8](https://github.com/certd/certd/commit/d613aa8f3e85d8dc475ef1b62d49394ce7fd7d24))
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
### Performance Improvements
* 登录支持双重认证 ([48aef25](https://github.com/certd/certd/commit/48aef25b3f6499d674ca4e4ef16f4c62399fb735))
* 多重认证登录 ([0f82cf4](https://github.com/certd/certd/commit/0f82cf409bc60706ab07e4ca4f272b9a1ca7eecb))
* 优化部署到华为云CDN支持先上传到ccm再使用证书id部署修复offline状态下导致部署报错的bug ([79df39a](https://github.com/certd/certd/commit/79df39acabab10ae7e1864dadcdc186bb007a3c5))
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
### Bug Fixes
* 补充类型断言 ([2143dff](https://github.com/certd/certd/commit/2143dff2ae96e6a78bef9f0498e36f8cd9e6941f))
* 修复腾讯云部署到任意资源插件,无法使用之前已上传的腾讯云证书问题 ([32c714d](https://github.com/certd/certd/commit/32c714d1b6e68c71a74a7452115040c87ac4bfdc))
### Performance Improvements
* 插件支持导入导出 ([cf8abb4](https://github.com/certd/certd/commit/cf8abb45282070c8ba91469f93fd379fabf1f74a))
* 支持上传证书到华为云CCM ([cfd3b66](https://github.com/certd/certd/commit/cfd3b66be9ebf53a26693057e70ed60c3f116be9))
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
### Bug Fixes
* 修复登录错误次数过多阻止再次登录逻辑 ([bf4d191](https://github.com/certd/certd/commit/bf4d191c8bd2f9209eb6768f662b9c77de99e998))
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
### Bug Fixes
* 修复某些情况下无法输出日志的bug ([70101bf](https://github.com/certd/certd/commit/70101bfa7ade65678d9202c804bbae2cb808b594))
### Performance Improvements
* 修复内置插件分页查询逻辑 ([a2710dd](https://github.com/certd/certd/commit/a2710ddc2525e4e637fd157f0180e6d3b801c8be))
## [1.33.1](https://github.com/certd/certd/compare/v1.33.0...v1.33.1) (2025-04-12)
### Bug Fixes
* 修复阿里云cdn证书部署失败问题增加certname参数传入 ([965dc2c](https://github.com/certd/certd/commit/965dc2cb476f690af716f291c6b20ba98be0c8f0))
* 修复ssh插件报length空指针的bug ([9c4cbe1](https://github.com/certd/certd/commit/9c4cbe17a22b548611cf1fbefecc83a421788e42))
### Performance Improvements
* 镜像支持armv7 ([f78cbed](https://github.com/certd/certd/commit/f78cbed4d817859721fdafe7d348864848d0dfbf))
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
### Bug Fixes
* 升级mysql驱动支持mysql8最新版本的认证 ([2f5ed3a](https://github.com/certd/certd/commit/2f5ed3aead97641f2c80d692a50226839016df0b))
* 修复eab授权没有email绑定的bug ([2f1683b](https://github.com/certd/certd/commit/2f1683b26acebbfb7d6e2d751435be04a4e7cab4))
### Features
* 支持在线自定义插件,无需源码开发 ([d0d9d68](https://github.com/certd/certd/commit/d0d9d68fe6740f6ff49fe40b7c9917c5a2e4b442))
* **lego:** support set key type ([f3bf4fa](https://github.com/certd/certd/commit/f3bf4faee0be5bdbfdbcf70a502849ed4c8ed4c4))
* release image to ghcr ([9b536af](https://github.com/certd/certd/commit/9b536af9e656dc89e2a87078c129cad6f591e467))
### Performance Improvements
* 修复tab页缓存问题 ([64e5449](https://github.com/certd/certd/commit/64e5449ab3c6b219b0e89eddad14bfb6b71a0650))
* 隐藏运行策略选项 ([2951df0](https://github.com/certd/certd/commit/2951df0cd94c23e2efee84ff1b843055aac56cae))
* 增加手动上传证书功能说明 ([5d083a1](https://github.com/certd/certd/commit/5d083a153637caddbc6f44e915d9fb2d1ae87b33))
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
### Bug Fixes
* 创建cname记录移除域名两端的空格 ([903a413](https://github.com/certd/certd/commit/903a4131ab5f42c8286cd2150ed1032d486fda2f))
* 修复从本地dns获取记录报错的bug ([c39b1bf](https://github.com/certd/certd/commit/c39b1bf823ddc6216bed2049e4c87e6107def08a))
### Features
* 优化证书申请速度修复某些情况下letsencrypt 校验失败的问题 ([857589b](https://github.com/certd/certd/commit/857589b365c6f709e0ae67914d2f50ce182e6dd6))
### Performance Improvements
* 优化华为dns解析记录创建和删除问题 ([0948c5b](https://github.com/certd/certd/commit/0948c5bc691d2ee6eb47c72a85da1b7453361878))
* 又拍云支持云存储 ([9339b78](https://github.com/certd/certd/commit/9339b78f801d193472c0af25749e8e7a27ffb7af))
* 又拍云支持云存储 ([8449f85](https://github.com/certd/certd/commit/8449f8580da90c1f6b5d02d07c3236ebaf6cf161))
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Bug Fixes
* 修复ssh支持键盘事件登录 ([8145808](https://github.com/certd/certd/commit/8145808c4370364377b4ffe3ae88ff465b49f20b))
### Performance Improvements
* 支持部署到京东云cdn ([6f17c70](https://github.com/certd/certd/commit/6f17c700b84965baa01b40fe2abaa0a91bcbaffd))
* 支持京东云dns申请证书 ([04d79f9](https://github.com/certd/certd/commit/04d79f9117670be504960b018fd49ae3bf7c1c11))
## [1.31.10](https://github.com/certd/certd/compare/v1.31.9...v1.31.10) (2025-03-29)
### Performance Improvements
* tab增加图标显示 ([a03ae5a](https://github.com/certd/certd/commit/a03ae5a216a1df2c1d3da12ae18dcd0f089a92d3))
* 升级lego版本到4.22.2 ([4e15556](https://github.com/certd/certd/commit/4e15556e5e8100719497edb1729570d5a29668e1))
* 优化华为dns接口报错信息输出 ([bf30b7a](https://github.com/certd/certd/commit/bf30b7afaef623dd8126570344f1fcc2c06f1215))
## [1.31.9](https://github.com/certd/certd/compare/v1.31.8...v1.31.9) (2025-03-28)
### Bug Fixes
* 修复华为云dns接口请求出错的bug ([caa15b4](https://github.com/certd/certd/commit/caa15b47355363cbb8847f415ff12363cd53eeda))
* 修复某些情况下站点证书监控报undefined.includes的错误 ([0b6618f](https://github.com/certd/certd/commit/0b6618ff709322a0eeba78953c8c6e9d073d083a))
* 修复网站证书监控https port设置无效的bug ([cc8da0c](https://github.com/certd/certd/commit/cc8da0cf130f0c469371b59ac5bd04567f4a4414))
### Performance Improvements
* 站点监控保存时异步检查 ([993bc74](https://github.com/certd/certd/commit/993bc7432fce2d954e9897ed85b54f22150bfc7e))
* dns支持火山引擎 ([99ff879](https://github.com/certd/certd/commit/99ff879d93658c29ea493a4bde7e9e3f85996d64))
## [1.31.8](https://github.com/certd/certd/compare/v1.31.7...v1.31.8) (2025-03-26)
### Bug Fixes
* 修复编辑通知勾选默认导致出现多个默认通知的bug ([6cd7bdd](https://github.com/certd/certd/commit/6cd7bddc37da8b0d7b9860fd9a26ddfe84c869a7))
* 修复网站监控无法设置端口的bug ([27a8a57](https://github.com/certd/certd/commit/27a8a57cf52b4bf83d628aa3049be1efaa74f29c))
* 修复lego模式无法创建流水线的bug ([687bb8a](https://github.com/certd/certd/commit/687bb8a2376d0de7b72739a174e4a9560581f866))
### Performance Improvements
* 优化通知格式 ([c3c5006](https://github.com/certd/certd/commit/c3c5006daa39c20624cb58864f2b92b230a38a7a))
* 优化scp上传 ([e51123a](https://github.com/certd/certd/commit/e51123a95131cc76d655937488caf08956a67020))
* 优化txt本地校验效率 ([fd507f2](https://github.com/certd/certd/commit/fd507f269253607e68c5c099c99e0de11636f229))
* 支持又拍云cdn ([fd0536b](https://github.com/certd/certd/commit/fd0536bd4b41f15b6b5d42e0b447f0dcbf73b8a8))
* 支持又拍云cdn ([57389a7](https://github.com/certd/certd/commit/57389a79a1a61c45d081712562f8b33c9633158e))
## [1.31.7](https://github.com/certd/certd/compare/v1.31.6...v1.31.7) (2025-03-24)
### Performance Improvements
* 增加服务器时间警告 ([d66ade4](https://github.com/certd/certd/commit/d66ade4e4783850b6c7625c6f164a5a0fc0aa509))
* 支持部署到lucky ([e18e399](https://github.com/certd/certd/commit/e18e399ce6529e8c7e36b56c5f674cfdbbd3d3d1))
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
### Bug Fixes
* 修复dns.la无法申请证书的bug ([90b045a](https://github.com/certd/certd/commit/90b045af6d1a4f46986e4b118885c1f050df067c))
### Performance Improvements
* 上传到主机支持scp方式 ([05b6159](https://github.com/certd/certd/commit/05b6159802b9e85b6a410361b60b5c28875b48e7))
* 优化图标 ([c56f48c](https://github.com/certd/certd/commit/c56f48c1e3c54c4e203fafb380d9091d75681b7e))
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
### Bug Fixes
* 修复通知选择器无法选择的bug ([f7b88f9](https://github.com/certd/certd/commit/f7b88f9e3b7d9d9122e4fd2003a20c555bd50c7d))
* 修复证书流水线创建失败的bug ([736fe03](https://github.com/certd/certd/commit/736fe038ebda56648bcc4c12884a700341d2c049))
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
### Bug Fixes
* 修复站点监控通知通过webhook发送失败的bug ([9be1ecc](https://github.com/certd/certd/commit/9be1ecc8aab3ea23dd0dc2dab3688f4edb90ef2c))
* 修复dns.la域名申请失败的bug ([1de8eee](https://github.com/certd/certd/commit/1de8eee6ea8307f3c11626af75303d3cc104bb95))
### Performance Improvements
* 宝塔支持doker站点证书部署 ([589a373](https://github.com/certd/certd/commit/589a373142ef7f50d64d3aa767a90b1f4b64da93))
* 保存调整后的列宽 ([873f2b6](https://github.com/certd/certd/commit/873f2b618b9d7320045baf69d6da83afe48a780f))
* 创建证书流水线时,支持更多参数展开 ([36aa7f8](https://github.com/certd/certd/commit/36aa7f82b078a053a102331b3c6f132fb9d492f9))
* 流水线页面可以鼠标按住左右拖动 ([d85a02f](https://github.com/certd/certd/commit/d85a02feeb3183c5abd6c1ea790d5923a32d7271))
* 流水线增加上传证书快捷方式 ([425bba6](https://github.com/certd/certd/commit/425bba67c539b734e2a85a83a4f9ecc9b2434fb4))
* 手动上传证书部署流水线 ([fbb66f3](https://github.com/certd/certd/commit/fbb66f3c4389489aa8a43b194d82bc8cf391607b))
* 优化选择任务时手机版展示效果 ([d01004d](https://github.com/certd/certd/commit/d01004d53071a75ac91ee21cc96bde9369f77ff3))
* 站点监控,手动测试也发通知 ([729b19c](https://github.com/certd/certd/commit/729b19c8da60d5efb5baef7cf8df0518e7f6b471))
* 站点证书监控支持模糊查询 ([0069c0e](https://github.com/certd/certd/commit/0069c0e3992946a8dd6410f299d4fc974ef0e76b))
* 支持飞书通知 ([b82e1dc](https://github.com/certd/certd/commit/b82e1dcd6217b09a7d7e21cd648bb31de320cadf))
* 支持手动上传证书并部署 ([a9fffa5](https://github.com/certd/certd/commit/a9fffa5180c83da27b35886aa2e858a92a2c5f94))
## [1.31.3](https://github.com/certd/certd/compare/v1.31.2...v1.31.3) (2025-03-13)
### Bug Fixes

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -7,16 +7,22 @@ https://afdian.com/a/greper
1. 可加入发电专属群,可以获得作者一对一技术支持
2. 您的需求我们将优先实现,并且将作为专业版功能提供
3. 一年期专业版激活码
4. 赠送国外免费服务器部署方案0成本使用Certd可能需要翻墙不过现在性能越来越差了
## 专业版特权对比
| 功能 | 免费版 | 专业版 |
|---------|------------------------|-----------------------|
| 免费证书申请 | 免费无限制 | 免费无限制 |
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署等 | 支持群晖、宝塔、1Panel等持续开发中 |
| 发邮件功能 | 需要配置 | 免配置 |
| 证书流水线条数 | 10条 | 无限制 |
| 功能 | 免费版 | 专业版 |
|---------|------------------------|-----------------------------|
| 免费证书申请 | 免费无限制 | 免费无限制 |
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署等 | 支持群晖、宝塔、1Panel等持续开发中 |
| 证书流水线条数 | 无限制 | 无限制 |
| 站点证书监控 | 限制1条 | 无限制 |
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、飞书、anpush、server酱等 |
## 专业版激活方式
![](./images/plus.png)
发电后,在私信中获取激活码
************************

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,25 @@
# 站点隐藏
* 一般来说Certd设置好之后很少需要访问。
* 所以我们`平时`可以把`站点访问关闭`,需要的时候再打开,减少站点被攻击的风险
## 1、开启站点隐藏
`系统管理->系统设置->安全设置->站点隐藏 `
![](./images/hidden1.png)
:::warning
注意保存好`解除地址``解除密码`
:::
## 2、临时关闭站点隐藏
访问上面的`解除地址`,输入`解除密码``临时解除`站点隐藏
![](./images/hidden2.png)
## 3、忘记解除地址和解除密码怎么办
登录服务器,在数据库平级的目录下创建`.unhidden`文件即可`临时解除`站点隐藏

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,36 @@
# 站点安全特性
Certd 存储了证书以及授权等敏感数据,所以需要严格保障安全。
我们非常重视您的数据安全,提供了以下安全特性
## 1、 授权数据加密存储【默认开启】
* 所有的授权敏感字段会加密后存储
* 每个用户独立维护授权数据,连管理员都无权查看
![星号部分为加密数据](./images/access.png)
星号部分为加密数据
## 2、 密码防爆破【默认开启】
* 登录失败次数过多账号将被锁定最高24小时(重启服务可解除锁定)
* 用户登录密码加密hash后存储无法计算出密码明文
![](./images/login.png)
## 3、站点隐藏【建议开启】
* 一般来说Certd设置好之后后续很少需要访问修改。
* 所以我们平时可以把站点访问关闭,需要的时候再打开,减少站点被攻击的风险
* 请前往 `系统管理->系统设置->安全设置->开启站点隐藏`
* [站点隐藏设置说明](./hidden/)
![](./images/hidden.png)
## 4、登录双重验证
支持2FA双重认证
![](./images/2fa.png)
## 5、数据库自动备份【建议开启】
* [自动备份设置说明](../../use/backup/)
## 更多安全生产建议
[安全生产建议](./suggest.md)

View File

@@ -0,0 +1,10 @@
# 安全生产建议
尽管`Cert`本身实现了很多安全特性,但`外部环境的安全`仍需要您来确保。
`务必`遵循如下建议做好安全防护
*`务必`使用`HTTPS协议`访问本应用,避免被中间人攻击
*`务必`使用`web应用防火墙`防护本应用防止XSS、SQL注入等攻击
*`务必`做好`服务器本身`的安全防护,防止数据库泄露
*`务必`做好[`数据备份`](../../use/backup/),避免数据丢失
* 建议开启[`站点隐藏`](./hidden/)功能

View File

@@ -9,6 +9,10 @@
* `greper/certd:latest`
* `greper/certd:armv7``greper/certd:[version]-armv7`
## GitHub Packages地址:
* `ghcr.io/certd/certd:latest`
* `ghcr.io/certd/certd:armv7``ghcr.io/certd/certd:[version]-armv7`
*
## 镜像构建公开
镜像构建通过`Actions`自动执行,过程公开透明,请放心使用
* [点我查看镜像构建日志](https://github.com/certd/certd/actions/workflows/build-image.yml)

View File

@@ -17,6 +17,8 @@ Certd 是一款开源、免费、全自动申请和部署更新SSL证书的工
* 支持SQLite、Postgresql、MySQL数据库
![](../images/intro/intro.svg)
## 二、一些说明
* 本项目申请证书过程遵循acme协议
* 需要验证域名所有权,一般有两种方式

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -13,7 +13,7 @@
#### 2.1 应用商店一键部署【推荐】
* 在应用商店中找到`certd`(要先点右上角更新应用)
*宝塔Docker应用商店中找到`certd`(要先点右上角更新应用)
* 点击安装,配置域名等基本信息即可完成安装
> 需要宝塔9.2.0及以上版本才支持
@@ -70,3 +70,12 @@ admin/123456
## 五、备份恢复
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置重启certd即可
## 六、宝塔部署相关问题排查
### 1. 无法访问Certd
1. 确认服务器的安全规则,是否放开了对应端口
2. 确认宝塔防火墙是否放开对应端口
3. 尝试将Certd容器加入宝塔的`bridge`网络
![](./images/network.png)

View File

@@ -55,6 +55,11 @@ https://your_server_ip:7002
## 二、升级
::: warning
如果您是第一次升级certd版本切记切记先备份一下数据
:::
### 如果使用固定版本号
1. 修改`docker-compose.yaml`中的镜像版本号
2. 运行`docker compose up -d` 即可

View File

@@ -44,6 +44,11 @@ kill -9 $(lsof -t -i:7001)
./start.sh
```
::: warning
升级certd版本前切记切记先备份一下数据
:::
## 三、数据备份
> 数据默认保存在 `./packages/ui/certd-server/data` 目录下
> 建议配置一条[数据库备份流水线](../../use/backup/) 自动备份

View File

@@ -8,5 +8,9 @@
3. [1Panel面板方式部署升级](./1panel/#三、升级)
4. [源码方式部署](./source/#二、升级)
::: warning
如果您是第一次升级certd版本切记切记先备份一下数据
:::
## 升级日志
[CHANGELOG](../changelogs/CHANGELOG.md)

View File

@@ -1,5 +1,6 @@
# 数据库自动备份
# 数据库备份
* 两种备份方法: 1、手动备份 2、自动备份
* 本文仅限sqlite数据库。
## 一、手动备份
数据库文件根据不同的部署方式保存的位置不一样,您可以手动复制出来进行备份

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.31.4"
"version": "1.33.8"
}

View File

@@ -3,6 +3,95 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.33.8](https://github.com/publishlab/node-acme-client/compare/v1.33.7...v1.33.8) (2025-04-26)
### Bug Fixes
* 修复http上传方式无法清除记录文件的bug ([72a7b51](https://github.com/publishlab/node-acme-client/commit/72a7b51d479602b2c54c6c3ac8d8a0dcb9664e73))
### Performance Improvements
* 从域名的soa获取主域名子域名托管无需额外配置 ([a586a92](https://github.com/publishlab/node-acme-client/commit/a586a92d5e32ea846ac37be52a7ad8c328d89966))
* 七牛oss支持删除过期备份 ([b7113bd](https://github.com/publishlab/node-acme-client/commit/b7113bda2378116d6c116dc583f563cce7cf9f00))
## [1.33.7](https://github.com/publishlab/node-acme-client/compare/v1.33.6...v1.33.7) (2025-04-22)
**Note:** Version bump only for package @certd/acme-client
## [1.33.6](https://github.com/publishlab/node-acme-client/compare/v1.33.5...v1.33.6) (2025-04-20)
**Note:** Version bump only for package @certd/acme-client
## [1.33.5](https://github.com/publishlab/node-acme-client/compare/v1.33.4...v1.33.5) (2025-04-17)
**Note:** Version bump only for package @certd/acme-client
## [1.33.4](https://github.com/publishlab/node-acme-client/compare/v1.33.3...v1.33.4) (2025-04-15)
**Note:** Version bump only for package @certd/acme-client
## [1.33.3](https://github.com/publishlab/node-acme-client/compare/v1.33.2...v1.33.3) (2025-04-14)
**Note:** Version bump only for package @certd/acme-client
## [1.33.2](https://github.com/publishlab/node-acme-client/compare/v1.33.1...v1.33.2) (2025-04-12)
**Note:** Version bump only for package @certd/acme-client
## [1.33.1](https://github.com/publishlab/node-acme-client/compare/v1.33.0...v1.33.1) (2025-04-12)
**Note:** Version bump only for package @certd/acme-client
# [1.33.0](https://github.com/publishlab/node-acme-client/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/acme-client
# [1.32.0](https://github.com/publishlab/node-acme-client/compare/v1.31.11...v1.32.0) (2025-04-04)
### Bug Fixes
* 修复从本地dns获取记录报错的bug ([c39b1bf](https://github.com/publishlab/node-acme-client/commit/c39b1bf823ddc6216bed2049e4c87e6107def08a))
### Features
* 优化证书申请速度修复某些情况下letsencrypt 校验失败的问题 ([857589b](https://github.com/publishlab/node-acme-client/commit/857589b365c6f709e0ae67914d2f50ce182e6dd6))
### Performance Improvements
* 优化华为dns解析记录创建和删除问题 ([0948c5b](https://github.com/publishlab/node-acme-client/commit/0948c5bc691d2ee6eb47c72a85da1b7453361878))
## [1.31.11](https://github.com/publishlab/node-acme-client/compare/v1.31.10...v1.31.11) (2025-04-02)
**Note:** Version bump only for package @certd/acme-client
## [1.31.10](https://github.com/publishlab/node-acme-client/compare/v1.31.9...v1.31.10) (2025-03-29)
**Note:** Version bump only for package @certd/acme-client
## [1.31.9](https://github.com/publishlab/node-acme-client/compare/v1.31.8...v1.31.9) (2025-03-28)
### Performance Improvements
* dns支持火山引擎 ([99ff879](https://github.com/publishlab/node-acme-client/commit/99ff879d93658c29ea493a4bde7e9e3f85996d64))
## [1.31.8](https://github.com/publishlab/node-acme-client/compare/v1.31.7...v1.31.8) (2025-03-26)
### Performance Improvements
* 优化txt本地校验效率 ([fd507f2](https://github.com/publishlab/node-acme-client/commit/fd507f269253607e68c5c099c99e0de11636f229))
## [1.31.7](https://github.com/publishlab/node-acme-client/compare/v1.31.6...v1.31.7) (2025-03-24)
**Note:** Version bump only for package @certd/acme-client
## [1.31.6](https://github.com/publishlab/node-acme-client/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/acme-client
## [1.31.5](https://github.com/publishlab/node-acme-client/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/acme-client
## [1.31.4](https://github.com/publishlab/node-acme-client/compare/v1.31.3...v1.31.4) (2025-03-21)
**Note:** Version bump only for package @certd/acme-client

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.31.4",
"version": "1.33.8",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.31.4",
"@certd/basic": "^1.33.8",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
@@ -26,7 +26,8 @@
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.5",
"lodash-es": "^4.17.21",
"node-forge": "^1.3.1"
"node-forge": "^1.3.1",
"punycode": "^2.3.1"
},
"devDependencies": {
"@types/node": "^20.14.10",
@@ -67,5 +68,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
"gitHead": "a188385817e8dbd270dbdbf7f86a5b812884cf59"
}

View File

@@ -1,10 +1,10 @@
/**
* ACME auto helper
*/
import { readCsrDomains } from './crypto/index.js';
import { log } from './logger.js';
import { wait } from './wait.js';
import { CancelError } from './error.js';
import { readCsrDomains } from "./crypto/index.js";
import { log } from "./logger.js";
import { wait } from "./wait.js";
import { CancelError } from "./error.js";
const defaultOpts = {
@@ -13,13 +13,13 @@ const defaultOpts = {
preferredChain: null,
termsOfServiceAgreed: false,
skipChallengeVerification: false,
challengePriority: ['http-01', 'dns-01'],
challengePriority: ["http-01", "dns-01"],
challengeCreateFn: async () => {
throw new Error('Missing challengeCreateFn()');
throw new Error("Missing challengeCreateFn()");
},
challengeRemoveFn: async () => {
throw new Error('Missing challengeRemoveFn()');
},
throw new Error("Missing challengeRemoveFn()");
}
};
/**
@@ -30,7 +30,7 @@ const defaultOpts = {
* @returns {Promise<buffer>} Certificate
*/
export default async (client, userOpts) => {
export default async (client, userOpts) => {
const opts = { ...defaultOpts, ...userOpts };
const accountPayload = { termsOfServiceAgreed: opts.termsOfServiceAgreed };
@@ -49,14 +49,13 @@ export default async (client, userOpts) => {
* Register account
*/
log('[auto] Checking account');
log("[auto] Checking account");
try {
client.getAccountUrl();
log('[auto] Account URL already exists, skipping account registration');
}
catch (e) {
log('[auto] Registering account');
log("[auto] Account URL already exists, skipping account registration 证书申请账户已存在,跳过注册 ");
} catch (e) {
log("[auto] Registering account (注册证书申请账户)");
await client.createAccount(accountPayload);
}
@@ -64,7 +63,7 @@ export default async (client, userOpts) => {
* Parse domains from CSR
*/
log('[auto] Parsing domains from Certificate Signing Request');
log("[auto] Parsing domains from Certificate Signing Request ");
const { commonName, altNames } = readCsrDomains(opts.csr);
const uniqueDomains = Array.from(new Set([commonName].concat(altNames).filter((d) => d)));
@@ -74,8 +73,8 @@ export default async (client, userOpts) => {
* Place order
*/
log('[auto] Placing new certificate order with ACME provider');
const orderPayload = { identifiers: uniqueDomains.map((d) => ({ type: 'dns', value: d })) };
log("[auto] Placing new certificate order with ACME provider");
const orderPayload = { identifiers: uniqueDomains.map((d) => ({ type: "dns", value: d })) };
const order = await client.createOrder(orderPayload);
const authorizations = await client.getAuthorizations(order);
@@ -85,82 +84,81 @@ export default async (client, userOpts) => {
* Resolve and satisfy challenges
*/
log('[auto] Resolving and satisfying authorization challenges');
log("[auto] Resolving and satisfying authorization challenges");
const clearTasks = [];
const localVerifyTasks = [];
const completeChallengeTasks = [];
const challengeFunc = async (authz) => {
const d = authz.identifier.value;
let challengeCompleted = false;
/* Skip authz that already has valid status */
if (authz.status === 'valid') {
if (authz.status === "valid") {
log(`[auto] [${d}] Authorization already has valid status, no need to complete challenges`);
return;
}
const keyAuthorizationGetter = async (challenge) => {
return await client.getChallengeKeyAuthorization(challenge);
}
};
try {
log(`[auto] [${d}] Trigger challengeCreateFn()`);
async function deactivateAuth(e) {
log(`[auto] [${d}] Unable to complete challenge: ${e.message}`);
try {
const { recordReq, recordRes, dnsProvider,challenge ,keyAuthorization} = await opts.challengeCreateFn(authz, keyAuthorizationGetter);
clearTasks.push(async () => {
/* Trigger challengeRemoveFn(), suppress errors */
log(`[auto] [${d}] Trigger challengeRemoveFn()`);
try {
await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider);
}
catch (e) {
log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);
}
});
// throw new Error('测试异常');
/* Challenge verification */
if (opts.skipChallengeVerification === true) {
log(`[auto] [${d}] Skipping challenge verification since skipChallengeVerification=truewait 60s`);
await wait(60 * 1000);
}
else {
log(`[auto] [${d}] Running challenge verification, type = ${challenge.type}`);
try {
await client.verifyChallenge(authz, challenge);
}
catch (e) {
log(`[auto] [${d}] challenge verification threw error: ${e.message}`);
}
}
/* Complete challenge and wait for valid status */
log(`[auto] [${d}] Completing challenge with ACME provider and waiting for valid status`);
await client.completeChallenge(challenge);
challengeCompleted = true;
await client.waitForValidStatus(challenge);
}
catch (e) {
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message}`);
throw e;
log(`[auto] [${d}] Deactivating failed authorization`);
await client.deactivateAuthorization(authz);
} catch (f) {
/* Suppress deactivateAuthorization() errors */
log(`[auto] [${d}] Authorization deactivation threw error: ${f.message}`);
}
}
catch (e) {
/* Deactivate pending authz when unable to complete challenge */
if (!challengeCompleted) {
log(`[auto] [${d}] Unable to complete challenge: ${e.message}`);
log(`[auto] [${d}] Trigger challengeCreateFn()`);
try {
const { recordReq, recordRes, dnsProvider, challenge, keyAuthorization ,httpUploader} = await opts.challengeCreateFn(authz, keyAuthorizationGetter);
clearTasks.push(async () => {
/* Trigger challengeRemoveFn(), suppress errors */
log(`[auto] [${d}] Trigger challengeRemoveFn()`);
try {
log(`[auto] [${d}] Deactivating failed authorization`);
await client.deactivateAuthorization(authz);
await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider,httpUploader);
} catch (e) {
log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);
}
catch (f) {
/* Suppress deactivateAuthorization() errors */
log(`[auto] [${d}] Authorization deactivation threw error: ${f.message}`);
}
}
});
localVerifyTasks.push(async () => {
/* Challenge verification */
log(`[auto] [${d}] 开始本地验证, type = ${challenge.type}`);
try {
await client.verifyChallenge(authz, challenge);
} catch (e) {
log(`[auto] [${d}] 本地验证失败尝试请求ACME提供商获取状态: ${e.message}`);
}
});
completeChallengeTasks.push(async () => {
/* Complete challenge and wait for valid status */
log(`[auto] [${d}] 请求ACME提供商完成验证`);
try{
await client.completeChallenge(challenge);
}catch (e) {
await deactivateAuth(e);
throw e;
}
challengeCompleted = true;
log(`[auto] [${d}] 等待返回valid状态`);
await client.waitForValidStatus(challenge,d);
});
} catch (e) {
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message}`);
await deactivateAuth(e);
throw e;
}
};
const domainSets = [];
@@ -168,7 +166,7 @@ export default async (client, userOpts) => {
const d = authz.identifier.value;
log(`authorization:domain = ${d}, value = ${JSON.stringify(authz)}`);
if (authz.status === 'valid') {
if (authz.status === "valid") {
log(`[auto] [${d}] Authorization already has valid status, no need to complete challenges`);
return;
}
@@ -192,8 +190,9 @@ export default async (client, userOpts) => {
const allChallengePromises = [];
// eslint-disable-next-line no-restricted-syntax
const challengePromises = [];
allChallengePromises.push(challengePromises);
for (const domainSet of domainSets) {
const challengePromises = [];
// eslint-disable-next-line guard-for-in,no-restricted-syntax
for (const domain in domainSet) {
const authz = domainSet[domain];
@@ -202,12 +201,11 @@ export default async (client, userOpts) => {
await challengeFunc(authz);
});
}
allChallengePromises.push(challengePromises);
}
log(`[auto] challengeGroups:${allChallengePromises.length}`);
function runAllPromise(tasks) {
async function runAllPromise(tasks) {
let promise = Promise.resolve();
tasks.forEach((task) => {
promise = promise.then(task);
@@ -215,73 +213,60 @@ export default async (client, userOpts) => {
return promise;
}
async function runPromisePa(tasks) {
async function runPromisePa(tasks, waitTime = 5000) {
const results = [];
// eslint-disable-next-line no-await-in-loop,no-restricted-syntax
for (const task of tasks) {
results.push(task());
// eslint-disable-next-line no-await-in-loop
await wait(10000);
await wait(waitTime);
}
return Promise.all(results);
}
try {
log(`开始challenge${allChallengePromises.length}`);
let i = 0;
// eslint-disable-next-line no-restricted-syntax
for (const challengePromises of allChallengePromises) {
i += 1;
log(`开始第${i}`);
if (opts.signal && opts.signal.aborted) {
throw new CancelError('用户取消');
log(`开始challenge${allChallengePromises.length}`);
let i = 0;
// eslint-disable-next-line no-restricted-syntax
for (const challengePromises of allChallengePromises) {
i += 1;
log(`开始第${i}`);
if (opts.signal && opts.signal.aborted) {
throw new CancelError("用户取消");
}
try {
// eslint-disable-next-line no-await-in-loop
await runPromisePa(challengePromises);
if (opts.skipChallengeVerification === true) {
log(`跳过本地验证skipChallengeVerification=true等待 60s`);
await wait(60 * 1000);
} else {
await runPromisePa(localVerifyTasks, 1000);
log("本地校验完成等待30s")
await wait(30 * 1000)
}
try {
// eslint-disable-next-line no-await-in-loop
await runPromisePa(challengePromises);
}
catch (e) {
log(`证书申请失败${e.message}`);
throw e;
}
finally {
if (client.opts.sslProvider !== 'google') {
// letsencrypt 如果同时检出两个TXT记录会以第一个为准就会校验失败所以需要提前删除
// zerossl 此方式测试无问题
log(`清理challenge痕迹length:${clearTasks.length}`);
try {
// eslint-disable-next-line no-await-in-loop
await runAllPromise(clearTasks);
}
catch (e) {
log('清理challenge失败');
log(e);
}
}
}
}
}
finally {
if (client.opts.sslProvider === 'google') {
// google 相同的域名txt记录是一样的不能提前删除否则校验失败报错如下
// Error: The TXT record retrieved from _acme-challenge.bbc.handsfree.work.
// at the time the challenge was validated did not contain JshHVu7dt_DT6uYILWhokHefFVad2Q6Mw1L-fNZFcq8
// (the base64url-encoded SHA-256 digest of RlJZNBR0LWnxNK_xd2zqtYVvCiNJOKJ3J1NmCjU_9BjaUJgL3k-qSpIhQ-uF4FBS.NRyqT8fRiq6THzzrvkgzgR5Xai2LsA2SyGLAq_wT3qc).
// See https://tools.ietf.org/html/rfc8555#section-8.4 for more information.
log("开始向提供商请求挑战验证");
await runPromisePa(completeChallengeTasks, 1000);
} catch (e) {
log(`证书申请失败${e.message}`);
throw e;
} finally {
// letsencrypt 如果同时检出两个TXT记录会以第一个为准就会校验失败所以需要提前删除
// zerossl 此方式测试无问题
log(`清理challenge痕迹length:${clearTasks.length}`);
try {
// eslint-disable-next-line no-await-in-loop
// eslint-disable-next-line no-await-in-loop
await runAllPromise(clearTasks);
}
catch (e) {
log('清理challenge失败');
} catch (e) {
log("清理challenge失败");
log(e);
}
}
}
log('challenge结束');
log("challenge结束");
// log('[auto] Waiting for challenge valid status');
// await Promise.all(challengePromises);
@@ -289,7 +274,7 @@ export default async (client, userOpts) => {
* Finalize order and download certificate
*/
log('[auto] Finalizing order and downloading certificate');
log("[auto] Finalizing order and downloading certificate");
const finalized = await client.finalizeOrder(order, opts.csr);
const res = await client.getCertificate(finalized, opts.preferredChain);
return res;

View File

@@ -500,7 +500,7 @@ class AcmeClient {
await verify[challenge.type](authz, challenge, keyAuthorization);
};
log('Waiting for ACME challenge verification', this.backoffOpts);
log('Waiting for ACME challenge verification等待ACME挑战验证', this.backoffOpts);
return util.retry(verifyFn, this.backoffOpts);
}
@@ -554,9 +554,9 @@ class AcmeClient {
* ```
*/
async waitForValidStatus(item) {
async waitForValidStatus(item,d) {
if (!item.url) {
throw new Error('Unable to verify status of item, URL not found');
throw new Error(`[${d}] Unable to verify status of item, URL not found`);
}
const verifyFn = async (abort) => {
@@ -568,23 +568,23 @@ class AcmeClient {
const resp = await this.api.apiRequest(item.url, null, [200]);
/* Verify status */
log(`Item has status: ${resp.data.status}`);
log(`[${d}] Item has status(挑战状态): ${resp.data.status}`);
if (invalidStates.includes(resp.data.status)) {
abort();
throw new Error(util.formatResponseError(resp));
}
else if (pendingStates.includes(resp.data.status)) {
throw new Error('Operation is pending or processing');
throw new Error(`[${d}] Operation is pending or processing(当前仍然在等待状态)`);
}
else if (validStates.includes(resp.data.status)) {
return resp.data;
}
throw new Error(`Unexpected item status: ${resp.data.status}`);
throw new Error(`[${d}] Unexpected item status: ${resp.data.status}`);
};
log(`Waiting for valid status from: ${item.url}`, this.backoffOpts);
log(`[${d}] Waiting for valid status 等待valid状态: ${item.url}`, this.backoffOpts);
return util.retry(verifyFn, this.backoffOpts);
}

View File

@@ -46,3 +46,5 @@ export * from './axios.js'
export * from './logger.js'
export * from './verify.js'
export * from './error.js'
export * from './util.js'

View File

@@ -60,8 +60,9 @@ async function retryPromise(fn, attempts, backoff) {
throw e;
}
log(`Promise rejected: ${e.message}`);
const duration = backoff.duration();
log(`Promise rejected attempt #${backoff.attempts}, retrying in ${duration}ms: ${e.message}`);
log(`Promise rejected attempt #${backoff.attempts}, ${duration}ms 后重试: ${e.message}`);
await new Promise((resolve) => { setTimeout(resolve, duration); });
return retryPromise(fn, attempts, backoff);
@@ -218,15 +219,15 @@ function formatResponseError(resp) {
async function resolveDomainBySoaRecord(recordName) {
try {
await dns.resolveSoa(recordName);
log(`Found SOA record, considering domain to be: ${recordName}`);
log(`找到${recordName}的SOA记录`);
return recordName;
}
catch (e) {
log(`Unable to locate SOA record for name: ${recordName}`);
log(`找不到${recordName}的SOA记录,继续往主域名查找`);
const parentRecordName = recordName.split('.').slice(1).join('.');
if (!parentRecordName.includes('.')) {
throw new Error('Unable to resolve domain by SOA record');
throw new Error('SOA record查找失败');
}
return resolveDomainBySoaRecord(parentRecordName);
@@ -241,7 +242,7 @@ async function resolveDomainBySoaRecord(recordName) {
*/
async function getAuthoritativeDnsResolver(recordName) {
log(`Locating authoritative NS records for name: ${recordName}`);
log(`获取域名${recordName}的权威NS服务器: `);
const resolver = new dns.Resolver();
try {
@@ -249,13 +250,14 @@ async function getAuthoritativeDnsResolver(recordName) {
const domain = await resolveDomainBySoaRecord(recordName);
/* Resolve authoritative NS addresses */
log(`Looking up authoritative NS records for domain: ${domain}`);
log(`获取到权威NS服务器name: ${domain}`);
const nsRecords = await dns.resolveNs(domain);
log(`域名权威NS服务器${nsRecords}`);
const nsAddrArray = await Promise.all(nsRecords.map(async (r) => dns.resolve4(r)));
const nsAddresses = [].concat(...nsAddrArray).filter((a) => a);
if (!nsAddresses.length) {
throw new Error(`Unable to locate any valid authoritative NS addresses for domain: ${domain}`);
throw new Error(`Unable to locate any valid authoritative NS addresses for domain获取权威服务器IP失败: ${domain}`);
}
/* Authoritative NS success */
@@ -263,12 +265,12 @@ async function getAuthoritativeDnsResolver(recordName) {
resolver.setServers(nsAddresses);
}
catch (e) {
log(`Authoritative NS lookup error: ${e.message}`);
log(`Authoritative NS lookup error获取权威NS服务器地址失败: ${e.message}`);
}
/* Return resolver */
const addresses = resolver.getServers();
log(`DNS resolver addresses: ${addresses.join(', ')}`);
log(`DNS resolver addresses域名的权威NS服务器地址: ${addresses.join(', ')}`);
return resolver;
}
@@ -338,5 +340,6 @@ export {
formatResponseError,
getAuthoritativeDnsResolver,
retrieveTlsAlpnCertificate,
resolveDomainBySoaRecord
};

View File

@@ -48,55 +48,85 @@ async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix =
* Walk DNS until TXT records are found
*/
async function walkDnsChallengeRecord(recordName, resolver = dns) {
/* Resolve CNAME record first */
// try {
// log(`Checking name for CNAME records: ${recordName}`);
// const cnameRecords = await resolver.resolveCname(recordName);
//
// if (cnameRecords.length) {
// log(`CNAME record found at ${recordName}, new challenge record name: ${cnameRecords[0]}`);
// return walkDnsChallengeRecord(cnameRecords[0]);
// }
// }
// catch (e) {
// log(`No CNAME records found for name: ${recordName}`);
// }
async function walkDnsChallengeRecord(recordName, resolver = dns,deep = 0) {
let records = [];
/* Resolve TXT records */
try {
log(`Checking name for TXT records: ${recordName}`);
log(`检查域名 ${recordName} 的TXT记录`);
const txtRecords = await resolver.resolveTxt(recordName);
if (txtRecords && txtRecords.length) {
log(`Found ${txtRecords.length} TXT records at ${recordName}`);
log(`找到 ${txtRecords.length} TXT记录( ${recordName}`);
log(`TXT records: ${JSON.stringify(txtRecords)}`);
return [].concat(...txtRecords);
records = records.concat(...txtRecords);
}
return [];
} catch (e) {
log(`解析 TXT 记录出错, ${recordName} :${e.message}`);
}
catch (e) {
log(`Resolve TXT records error, ${recordName} :${e.message}`);
throw e;
/* Resolve CNAME record first */
try {
log(`检查是否存在CNAME映射: ${recordName}`);
const cnameRecords = await resolver.resolveCname(recordName);
if (cnameRecords.length) {
const cnameRecord = cnameRecords[0];
log(`已找到${recordName}的CNAME记录将检查: ${cnameRecord}`);
let res= await walkTxtRecord(cnameRecord,deep+1);
if (res && res.length) {
log(`从CNAME中找到TXT记录: ${JSON.stringify(res)}`);
records = records.concat(...res);
}
}else{
log(`没有CNAME映射${recordName}`);
}
} catch (e) {
log(`检查CNAME出错${recordName} :${e.message}`);
}
return records
}
export async function walkTxtRecord(recordName) {
export async function walkTxtRecord(recordName,deep = 0) {
if(deep >5){
log(`walkTxtRecord too deep (#${deep}) , skip walk`)
return []
}
const txtRecords = []
try {
/* Default DNS resolver first */
log('Attempting to resolve TXT with default DNS resolver first');
const res = await walkDnsChallengeRecord(recordName);
log('从本地DNS服务器获取TXT解析记录');
const res = await walkDnsChallengeRecord(recordName,dns,deep);
if (res && res.length > 0) {
return res;
for (const item of res) {
txtRecords.push(item)
}
}
throw new Error('No TXT records found');
} catch (e) {
log(`本地获取TXT解析记录失败:${e.message}`)
}
catch (e) {
try{
/* Authoritative DNS resolver */
log(`Error using default resolver, attempting to resolve TXT with authoritative NS: ${e.message}`);
log(`从域名权威服务器获取TXT解析记录`);
const authoritativeResolver = await util.getAuthoritativeDnsResolver(recordName);
return await walkDnsChallengeRecord(recordName, authoritativeResolver);
const res = await walkDnsChallengeRecord(recordName, authoritativeResolver,deep);
if (res && res.length > 0) {
for (const item of res) {
txtRecords.push(item)
}
}
}catch (e) {
log(`权威服务器获取TXT解析记录失败:${e.message}`)
}
if (txtRecords.length === 0) {
throw new Error(`没有找到TXT解析记录${recordName}`);
}
return txtRecords;
}
/**
@@ -113,14 +143,16 @@ export async function walkTxtRecord(recordName) {
async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') {
const recordName = `${prefix}${authz.identifier.value}`;
log(`Resolving DNS TXT from record: ${recordName}`);
const recordValues = await walkTxtRecord(recordName);
log(`DNS query finished successfully, found ${recordValues.length} TXT records`);
log(`本地校验TXT记录: ${recordName}`);
let recordValues = await walkTxtRecord(recordName);
//去重
recordValues = [...new Set(recordValues)];
log(`DNS查询成功, 找到 ${recordValues.length} 条TXT记录${recordValues}`);
if (!recordValues.length || !recordValues.includes(keyAuthorization)) {
throw new Error(`Authorization not found in DNS TXT record: ${recordName}need:${keyAuthorization},found:${recordValues}`);
throw new Error(`没有找到需要的DNS TXT记录: ${recordName}期望:${keyAuthorization},结果:${recordValues}`);
}
log(`Key authorization match for ${challenge.type}/${recordName}, ACME challenge verified`);
log(`关键授权匹配成功(${challenge.type}/${recordName}:${keyAuthorization},校验成功, ACME challenge verified`);
return true;
}

View File

@@ -59,7 +59,7 @@ export interface ClientExternalAccountBindingOptions {
export interface ClientAutoOptions {
csr: CsrBuffer | CsrString;
challengeCreateFn: (authz: Authorization, keyAuthorization: (challenge:rfc8555.Challenge)=>Promise<string>) => Promise<{recordReq?:any,recordRes?:any,dnsProvider?:any,challenge: rfc8555.Challenge,keyAuthorization:string}>;
challengeRemoveFn: (authz: Authorization, challenge: rfc8555.Challenge, keyAuthorization: string,recordReq:any, recordRes:any,dnsProvider:any) => Promise<any>;
challengeRemoveFn: (authz: Authorization, challenge: rfc8555.Challenge, keyAuthorization: string,recordReq:any, recordRes:any,dnsProvider:any,httpUploader:any) => Promise<any>;
email?: string;
termsOfServiceAgreed?: boolean;
skipChallengeVerification?: boolean;
@@ -204,4 +204,6 @@ export function setLogger(fn: (message: any, ...args: any[]) => void): void;
export function walkTxtRecord(record: any): Promise<string[]>;
export const CancelError: typeof CancelError;
export const CancelError: typeof CancelError;
export function resolveDomainBySoaRecord(domain: string): Promise<string>;

View File

@@ -1,137 +0,0 @@
"use strict";
/**
* acme-client type definition tests
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var acme = require("acme-client");
(function () { return __awaiter(void 0, void 0, void 0, function () {
var accountKey, client, order, authorizations, authorization, challenge, _a, certKey, certCsr;
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, acme.crypto.createPrivateKey()];
case 1:
accountKey = _b.sent();
client = new acme.Client({
accountKey: accountKey,
directoryUrl: acme.directory.letsencrypt.staging
});
/* Account */
return [4 /*yield*/, client.createAccount({
termsOfServiceAgreed: true,
contact: ['mailto:test@example.com']
})];
case 2:
/* Account */
_b.sent();
return [4 /*yield*/, client.createOrder({
identifiers: [
{ type: 'dns', value: 'example.com' },
{ type: 'dns', value: '*.example.com' },
]
})];
case 3:
order = _b.sent();
return [4 /*yield*/, client.getOrder(order)];
case 4:
_b.sent();
return [4 /*yield*/, client.getAuthorizations(order)];
case 5:
authorizations = _b.sent();
authorization = authorizations[0];
challenge = authorization.challenges[0];
return [4 /*yield*/, client.getChallengeKeyAuthorization(challenge)];
case 6:
_b.sent();
return [4 /*yield*/, client.verifyChallenge(authorization, challenge)];
case 7:
_b.sent();
return [4 /*yield*/, client.completeChallenge(challenge)];
case 8:
_b.sent();
return [4 /*yield*/, client.waitForValidStatus(challenge)];
case 9:
_b.sent();
return [4 /*yield*/, acme.crypto.createCsr({
commonName: 'example.com',
altNames: ['example.com', '*.example.com']
})];
case 10:
_a = _b.sent(), certKey = _a[0], certCsr = _a[1];
return [4 /*yield*/, client.finalizeOrder(order, certCsr)];
case 11:
_b.sent();
return [4 /*yield*/, client.getCertificate(order)];
case 12:
_b.sent();
return [4 /*yield*/, client.getCertificate(order, 'DST Root CA X3')];
case 13:
_b.sent();
/* Auto */
return [4 /*yield*/, client.auto({
csr: certCsr,
challengeCreateFn: function (authz, challenge, keyAuthorization) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
return [2 /*return*/];
}); }); },
challengeRemoveFn: function (authz, challenge, keyAuthorization) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
return [2 /*return*/];
}); }); }
})];
case 14:
/* Auto */
_b.sent();
return [4 /*yield*/, client.auto({
csr: certCsr,
email: 'test@example.com',
termsOfServiceAgreed: false,
skipChallengeVerification: false,
challengePriority: ['http-01', 'dns-01'],
preferredChain: 'DST Root CA X3',
challengeCreateFn: function (authz, challenge, keyAuthorization) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
return [2 /*return*/];
}); }); },
challengeRemoveFn: function (authz, challenge, keyAuthorization) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
return [2 /*return*/];
}); }); }
})];
case 15:
_b.sent();
return [2 /*return*/];
}
});
}); })();

View File

@@ -3,6 +3,82 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.33.8](https://github.com/certd/certd/compare/v1.33.7...v1.33.8) (2025-04-26)
**Note:** Version bump only for package @certd/basic
## [1.33.7](https://github.com/certd/certd/compare/v1.33.6...v1.33.7) (2025-04-22)
### Performance Improvements
* 支持51dns ([96a0900](https://github.com/certd/certd/commit/96a0900edc95dcfd9acccf9d13592f12f5a09b3d))
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
**Note:** Version bump only for package @certd/basic
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
### Performance Improvements
* 多重认证登录 ([0f82cf4](https://github.com/certd/certd/commit/0f82cf409bc60706ab07e4ca4f272b9a1ca7eecb))
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
**Note:** Version bump only for package @certd/basic
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
**Note:** Version bump only for package @certd/basic
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
### Bug Fixes
* 修复某些情况下无法输出日志的bug ([70101bf](https://github.com/certd/certd/commit/70101bfa7ade65678d9202c804bbae2cb808b594))
## [1.33.1](https://github.com/certd/certd/compare/v1.33.0...v1.33.1) (2025-04-12)
**Note:** Version bump only for package @certd/basic
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/basic
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/basic
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
**Note:** Version bump only for package @certd/basic
## [1.31.10](https://github.com/certd/certd/compare/v1.31.9...v1.31.10) (2025-03-29)
**Note:** Version bump only for package @certd/basic
## [1.31.9](https://github.com/certd/certd/compare/v1.31.8...v1.31.9) (2025-03-28)
**Note:** Version bump only for package @certd/basic
## [1.31.8](https://github.com/certd/certd/compare/v1.31.7...v1.31.8) (2025-03-26)
### Performance Improvements
* 支持又拍云cdn ([fd0536b](https://github.com/certd/certd/commit/fd0536bd4b41f15b6b5d42e0b447f0dcbf73b8a8))
## [1.31.7](https://github.com/certd/certd/compare/v1.31.6...v1.31.7) (2025-03-24)
**Note:** Version bump only for package @certd/basic
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/basic
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/basic
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
**Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
02:06
01:54

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.31.4",
"version": "1.33.8",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -44,5 +44,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
"gitHead": "a188385817e8dbd270dbdbf7f86a5b812884cf59"
}

View File

@@ -1,18 +1,18 @@
import crypto, { BinaryToTextEncoding } from 'crypto';
import crypto, { BinaryToTextEncoding } from "crypto";
function md5(data: string, digest: BinaryToTextEncoding = 'hex') {
return crypto.createHash('md5').update(data).digest(digest);
function md5(data: string, digest: BinaryToTextEncoding = "hex") {
return crypto.createHash("md5").update(data).digest(digest);
}
function sha256(data: string, digest: BinaryToTextEncoding = 'hex') {
return crypto.createHash('sha256').update(data).digest(digest);
function sha256(data: string, digest: BinaryToTextEncoding = "hex") {
return crypto.createHash("sha256").update(data).digest(digest);
}
function hmacSha256(data: string, digest: BinaryToTextEncoding = 'base64') {
return crypto.createHmac('sha256', data).update(Buffer.alloc(0)).digest(digest);
function hmacSha256(data: string, digest: BinaryToTextEncoding = "base64") {
return crypto.createHmac("sha256", data).update(Buffer.alloc(0)).digest(digest);
}
function base64(data: string) {
return Buffer.from(data).toString('base64');
return Buffer.from(data).toString("base64");
}
export const hashUtils = {
md5,

View File

@@ -1,4 +1,4 @@
import { customAlphabet } from 'nanoid';
import { customAlphabet } from "nanoid";
export const randomNumber = customAlphabet('1234567890', 4);
export const simpleNanoId = customAlphabet('1234567890abcdefghijklmopqrstuvwxyz', 12);
export const randomNumber = customAlphabet("1234567890", 4);
export const simpleNanoId = customAlphabet("1234567890abcdefghijklmopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ", 12);

View File

@@ -1,4 +1,4 @@
import log4js, { LoggingEvent, Logger } from 'log4js';
import log4js, { LoggingEvent, Logger } from "log4js";
const OutputAppender = {
configure: (config: any, layouts: any, findAppender: any, levels: any) => {
@@ -21,17 +21,31 @@ const OutputAppender = {
export function resetLogConfigure() {
// @ts-ignore
log4js.configure({
appenders: { std: { type: 'stdout' }, output: { type: OutputAppender } },
categories: { default: { appenders: ['std'], level: 'info' }, pipeline: { appenders: ['std', 'output'], level: 'info' } },
appenders: { std: { type: "stdout" }, output: { type: OutputAppender } },
categories: { default: { appenders: ["std"], level: "info" }, pipeline: { appenders: ["std", "output"], level: "info" } },
});
}
resetLogConfigure();
export const logger = log4js.getLogger('default');
export const logger = log4js.getLogger("default");
export function buildLogger(write: (text: string) => void) {
const logger = log4js.getLogger('pipeline');
logger.addContext('outputHandler', {
write,
const logger = log4js.getLogger("pipeline");
const _secrets: string[] = [];
//@ts-ignore
logger.addSecret = (secret: string) => {
_secrets.push(secret);
};
logger.addContext("outputHandler", {
write: (text: string) => {
for (const item of _secrets) {
if (item == null) {
continue;
}
//换成同长度的*号, item可能有多行
text = text.replaceAll(item, "*".repeat(item.length));
}
write(text);
},
});
return logger;
}

View File

@@ -1,13 +1,13 @@
import axios, { AxiosHeaders, AxiosRequestConfig } from 'axios';
import { ILogger, logger } from './util.log.js';
import { Logger } from 'log4js';
import { HttpProxyAgent } from 'http-proxy-agent';
import { HttpsProxyAgent } from 'https-proxy-agent';
import nodeHttp from 'http';
import * as https from 'node:https';
import { merge } from 'lodash-es';
import { safePromise } from './util.promise.js';
import fs from 'fs';
import axios, { AxiosHeaders, AxiosRequestConfig } from "axios";
import { ILogger, logger } from "./util.log.js";
import { Logger } from "log4js";
import { HttpProxyAgent } from "http-proxy-agent";
import { HttpsProxyAgent } from "https-proxy-agent";
import nodeHttp from "http";
import * as https from "node:https";
import { merge } from "lodash-es";
import { safePromise } from "./util.promise.js";
import fs from "fs";
export class HttpError extends Error {
status?: number;
statusText?: string;
@@ -22,10 +22,10 @@ export class HttpError extends Error {
super(error.message || error.response?.statusText);
const message = error?.message;
if (message && typeof message === 'string') {
if (message.indexOf && message.indexOf('ssl3_get_record:wrong version number') >= 0) {
if (message && typeof message === "string") {
if (message.indexOf && message.indexOf("ssl3_get_record:wrong version number") >= 0) {
this.message = `${message}(http协议错误服务端要求http协议请检查是否使用了https请求)`;
} else if (message.indexOf('getaddrinfo EAI_AGAIN') >= 0) {
} else if (message.indexOf("getaddrinfo EAI_AGAIN") >= 0) {
this.message = `${message}(无法解析域名请检查网络连接或dns配置更换docker-compose.yaml中dns配置)`;
}
}
@@ -47,7 +47,7 @@ export class HttpError extends Error {
};
let url = error.config?.url;
if (error.config?.baseURL) {
url = (error.config?.baseURL || '') + url;
url = (error.config?.baseURL || "") + url;
}
if (url) {
this.message = `${this.message}${url}`;
@@ -73,7 +73,7 @@ export const HttpCommonError = HttpError;
let defaultAgents = createAgent();
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
logger.info('setGlobalProxy:', opts);
logger.info("setGlobalProxy:", opts);
defaultAgents = createAgent(opts);
}
@@ -102,12 +102,12 @@ export function createAxiosService({ logger }: { logger: Logger }) {
if (config.skipSslVerify || config.httpProxy) {
let rejectUnauthorized = true;
if (config.skipSslVerify) {
logger.info('跳过SSL验证');
logger.info("跳过SSL验证");
rejectUnauthorized = false;
}
const proxy: any = {};
if (config.httpProxy) {
logger.info('使用自定义http代理:', config.httpProxy);
logger.info("使用自定义http代理:", config.httpProxy);
proxy.httpProxy = config.httpProxy;
proxy.httpsProxy = config.httpProxy;
}
@@ -128,7 +128,7 @@ export function createAxiosService({ logger }: { logger: Logger }) {
},
(error: Error) => {
// 发送失败
logger.error('接口请求失败:', error);
logger.error("接口请求失败:", error);
return Promise.reject(error);
}
);
@@ -143,7 +143,7 @@ export function createAxiosService({ logger }: { logger: Logger }) {
logger.info(`http response : status=${response?.status},data=${resData}`);
} else {
logger.info('http response status:', response?.status);
logger.info("http response status:", response?.status);
}
if (response?.config?.returnResponse) {
return response;
@@ -154,53 +154,51 @@ export function createAxiosService({ logger }: { logger: Logger }) {
const status = error.response?.status;
switch (status) {
case 400:
error.message = '请求错误';
error.message = "请求错误";
break;
case 401:
error.message = '认证/登录失败';
error.message = "认证/登录失败";
break;
case 403:
error.message = '拒绝访问';
error.message = "拒绝访问";
break;
case 404:
error.message = `请求地址出错`;
break;
case 408:
error.message = '请求超时';
error.message = "请求超时";
break;
case 500:
error.message = '服务器内部错误';
error.message = "服务器内部错误";
break;
case 501:
error.message = '服务未实现';
error.message = "服务未实现";
break;
case 502:
error.message = '网关错误';
error.message = "网关错误";
break;
case 503:
error.message = '服务不可用';
error.message = "服务不可用";
break;
case 504:
error.message = '网关超时';
error.message = "网关超时";
break;
case 505:
error.message = 'HTTP版本不受支持';
error.message = "HTTP版本不受支持";
break;
default:
break;
}
logger.error(
`请求出错status:${error.response?.status},statusText:${error.response?.statusText},url:${error.config?.url},method:${error.config?.method}`
);
logger.error('返回数据:', JSON.stringify(error.response?.data));
logger.error(`请求出错status:${error.response?.status},statusText:${error.response?.statusText},url:${error.config?.url},method:${error.config?.method}`);
logger.error("返回数据:", JSON.stringify(error.response?.data));
if (error.response?.data) {
const message = error.response.data.message || error.response.data.msg || error.response.data.error;
if (typeof message === 'string') {
if (typeof message === "string") {
error.message = message;
}
}
if (error instanceof AggregateError) {
logger.error('AggregateError', error);
logger.error("AggregateError", error);
}
const err = new HttpError(error);
return Promise.reject(err);
@@ -244,24 +242,24 @@ export function createAgent(opts: CreateAgentOptions = {}) {
if (httpProxy) {
process.env.HTTP_PROXY = httpProxy;
process.env.http_proxy = httpProxy;
logger.info('use httpProxy:', httpProxy);
logger.info("use httpProxy:", httpProxy);
httpAgent = new HttpProxyAgent(httpProxy, opts as any);
merge(httpAgent.options, opts);
} else {
process.env.HTTP_PROXY = '';
process.env.http_proxy = '';
process.env.HTTP_PROXY = "";
process.env.http_proxy = "";
httpAgent = new nodeHttp.Agent(opts);
}
const httpsProxy = opts.httpsProxy;
if (httpsProxy) {
process.env.HTTPS_PROXY = httpsProxy;
process.env.https_proxy = httpsProxy;
logger.info('use httpsProxy:', httpsProxy);
logger.info("use httpsProxy:", httpsProxy);
httpsAgent = new HttpsProxyAgent(httpsProxy, opts as any);
merge(httpsAgent.options, opts);
} else {
process.env.HTTPS_PROXY = '';
process.env.https_proxy = '';
process.env.HTTPS_PROXY = "";
process.env.https_proxy = "";
httpsAgent = new https.Agent(opts);
}
return {
@@ -276,27 +274,27 @@ export async function download(req: { http: HttpClient; config: HttpRequestConfi
http
.request({
logRes: false,
responseType: 'stream',
responseType: "stream",
...config,
})
.then(res => {
const writer = fs.createWriteStream(savePath);
res.pipe(writer);
writer.on('close', () => {
logger.info('文件下载成功');
writer.on("close", () => {
logger.info("文件下载成功");
resolve(true);
});
//error
writer.on('error', err => {
logger.error('下载失败', err);
writer.on("error", err => {
logger.error("下载失败", err);
reject(err);
});
//进度条打印
const totalLength = res.headers['content-length'];
const totalLength = res.headers["content-length"];
let currentLength = 0;
// 每5%打印一次
const step = (totalLength / 100) * 5;
res.on('data', (chunk: any) => {
res.on("data", (chunk: any) => {
currentLength += chunk.length;
if (currentLength % step < chunk.length) {
const percent = ((currentLength / totalLength) * 100).toFixed(2);
@@ -305,19 +303,19 @@ export async function download(req: { http: HttpClient; config: HttpRequestConfi
});
})
.catch(err => {
logger.info('下载失败', err);
logger.info("下载失败", err);
reject(err);
});
});
}
export function getCookie(response: any, name: string) {
const cookies = response.headers['set-cookie'];
const cookies = response.headers["set-cookie"];
//根据name 返回对应的cookie
const found = cookies.find((cookie: any) => cookie.includes(name));
if (!found) {
return null;
}
const cookie = found.split(';')[0];
return cookie.substring(cookie.indexOf('=') + 1);
const cookie = found.split(";")[0];
return cookie.substring(cookie.indexOf("=") + 1);
}

View File

@@ -26,3 +26,4 @@ dist-ssr
test/user.secret.*
test/**/*.js
src/**/*.spec.ts
.test.mjs

View File

@@ -3,6 +3,84 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.33.8](https://github.com/certd/certd/compare/v1.33.7...v1.33.8) (2025-04-26)
**Note:** Version bump only for package @certd/pipeline
## [1.33.7](https://github.com/certd/certd/compare/v1.33.6...v1.33.7) (2025-04-22)
**Note:** Version bump only for package @certd/pipeline
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
**Note:** Version bump only for package @certd/pipeline
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
**Note:** Version bump only for package @certd/pipeline
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
**Note:** Version bump only for package @certd/pipeline
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
**Note:** Version bump only for package @certd/pipeline
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
**Note:** Version bump only for package @certd/pipeline
## [1.33.1](https://github.com/certd/certd/compare/v1.33.0...v1.33.1) (2025-04-12)
### Bug Fixes
* 修复ssh插件报length空指针的bug ([9c4cbe1](https://github.com/certd/certd/commit/9c4cbe17a22b548611cf1fbefecc83a421788e42))
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
### Performance Improvements
* 隐藏运行策略选项 ([2951df0](https://github.com/certd/certd/commit/2951df0cd94c23e2efee84ff1b843055aac56cae))
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/pipeline
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Performance Improvements
* 支持部署到京东云cdn ([6f17c70](https://github.com/certd/certd/commit/6f17c700b84965baa01b40fe2abaa0a91bcbaffd))
## [1.31.10](https://github.com/certd/certd/compare/v1.31.9...v1.31.10) (2025-03-29)
### Performance Improvements
* tab增加图标显示 ([a03ae5a](https://github.com/certd/certd/commit/a03ae5a216a1df2c1d3da12ae18dcd0f089a92d3))
## [1.31.9](https://github.com/certd/certd/compare/v1.31.8...v1.31.9) (2025-03-28)
### Performance Improvements
* dns支持火山引擎 ([99ff879](https://github.com/certd/certd/commit/99ff879d93658c29ea493a4bde7e9e3f85996d64))
## [1.31.8](https://github.com/certd/certd/compare/v1.31.7...v1.31.8) (2025-03-26)
**Note:** Version bump only for package @certd/pipeline
## [1.31.7](https://github.com/certd/certd/compare/v1.31.6...v1.31.7) (2025-03-24)
**Note:** Version bump only for package @certd/pipeline
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/pipeline
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/pipeline
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
### Bug Fixes

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.31.4",
"version": "1.33.8",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -16,8 +16,8 @@
"test": "mocha --loader=ts-node/esm"
},
"dependencies": {
"@certd/basic": "^1.31.4",
"@certd/plus-core": "^1.31.4",
"@certd/basic": "^1.33.8",
"@certd/plus-core": "^1.33.8",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
@@ -43,5 +43,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
"gitHead": "a188385817e8dbd270dbdbf7f86a5b812884cf59"
}

View File

@@ -26,7 +26,9 @@ export function IsAccess(define: AccessDefine): ClassDecorator {
target.define = define;
accessRegistry.register(define.name, {
define,
target,
target: async () => {
return target;
},
});
};
}
@@ -39,13 +41,15 @@ export function AccessInput(input?: AccessInputDefine): PropertyDecorator {
};
}
export function newAccess(type: string, input: any, ctx?: AccessContext) {
export async function newAccess(type: string, input: any, ctx?: AccessContext) {
const register = accessRegistry.get(type);
if (register == null) {
throw new Error(`access ${type} not found`);
}
// @ts-ignore
const access = new register.target();
const accessCls = await register.target();
// @ts-ignore
const access = new accessCls();
for (const key in input) {
access[key] = input[key];
}
@@ -57,5 +61,6 @@ export function newAccess(type: string, input: any, ctx?: AccessContext) {
};
}
access.setCtx(ctx);
access._type = type;
return access;
}

View File

@@ -1,6 +1,6 @@
import { ConcurrencyStrategy, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task, ResultError } from "../dt/index.js";
import { RunHistory, RunnableCollection } from "./run-history.js";
import { AbstractTaskPlugin, PluginDefine, pluginRegistry, TaskInstanceContext, UserInfo } from "../plugin/index.js";
import { AbstractTaskPlugin, ITaskPlugin, PluginDefine, pluginRegistry, TaskInstanceContext, UserInfo } from "../plugin/index.js";
import { ContextFactory, IContext } from "./context.js";
import { IStorage } from "./storage.js";
import { createAxiosService, hashUtils, HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
@@ -261,6 +261,7 @@ export class Executor {
const resList: ResultType[] = [];
for (const step of task.steps) {
step.runnableType = "step";
// @ts-ignore
const res: ResultType = await this.runWithHistory(step, "step", async () => {
return await this.runStep(step);
});
@@ -276,8 +277,18 @@ export class Executor {
//执行任务
const plugin: RegistryItem<AbstractTaskPlugin> = pluginRegistry.get(step.type);
// @ts-ignore
const instance: ITaskPlugin = new plugin.target();
//@ts-ignore
let instance: ITaskPlugin = null;
try {
//@ts-ignore
const pluginCls = await plugin.target();
//@ts-ignore
instance = new pluginCls();
} catch (e: any) {
currentLogger.error(`实例化插件失败:${e.message}`);
throw new Error(`实例化插件失败`, e);
}
// @ts-ignore
const define: PluginDefine = plugin.define;
const pluginName = define.name;

View File

@@ -150,11 +150,11 @@ export class RunnableCollection {
pipeline.stages = [];
return;
}
pipeline.stages.forEach((stage) => {
pipeline.stages.forEach(stage => {
stage.runnableType = "stage";
stage.tasks.forEach((task) => {
stage.tasks.forEach(task => {
task.runnableType = "task";
task.steps.forEach((step) => {
task.steps.forEach(step => {
step.runnableType = "step";
});
});
@@ -162,7 +162,7 @@ export class RunnableCollection {
}
static each<T extends Runnable>(list: T[], exec: (item: Runnable) => void) {
list.forEach((item) => {
list.forEach(item => {
exec(item);
if (item.runnableType === "pipeline") {
// @ts-ignore
@@ -179,7 +179,7 @@ export class RunnableCollection {
public toMap(pipeline: Pipeline) {
const map: RunnableMap = {};
RunnableCollection.each(pipeline.stages, (item) => {
RunnableCollection.each(pipeline.stages, item => {
map[item.id] = item;
});
return map;
@@ -193,7 +193,7 @@ export class RunnableCollection {
if (!this.pipeline) {
return;
}
RunnableCollection.each(this.pipeline.stages, (item) => {
RunnableCollection.each(this.pipeline.stages, item => {
item.status = undefined;
});
}

View File

@@ -26,7 +26,9 @@ export function IsNotification(define: NotificationDefine): ClassDecorator {
target.define = define;
notificationRegistry.register(define.name, {
define,
target,
target: async () => {
return target;
},
});
};
}
@@ -44,9 +46,10 @@ export async function newNotification(type: string, input: any, ctx: Notificatio
if (register == null) {
throw new Error(`notification ${type} not found`);
}
// @ts-ignore
const plugin = new register.target();
const pluginCls = await register.target();
// @ts-ignore
const plugin = new pluginCls();
merge(plugin, input);
if (!ctx) {
throw new Error("ctx is required");

View File

@@ -1,7 +1,7 @@
import { Registrable } from "../registry/index.js";
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
import { FileStore } from "../core/file-store.js";
import { IAccessService } from "../access/index.js";
import { accessRegistry, IAccessService } from "../access/index.js";
import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService } from "../service/index.js";
import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/index.js";
import { HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
@@ -64,6 +64,9 @@ export type PluginDefine = Registrable & {
};
};
needPlus?: boolean;
showRunStrategy?: boolean;
pluginType?: string; //类型
type?: string; //来源
};
export type ITaskPlugin = {
@@ -155,14 +158,35 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
this.http = ctx.http;
}
async getAccess<T = any>(accessId: string) {
async getAccess<T = any>(accessId: string | number, isCommon = false) {
if (accessId == null) {
throw new Error("您还没有配置授权");
}
const res = await this.ctx.accessService.getById(accessId);
let res: any = null;
if (isCommon) {
res = await this.ctx.accessService.getCommonById(accessId);
} else {
res = await this.ctx.accessService.getById(accessId);
}
if (res == null) {
throw new Error("授权不存在,可能已被删除,请前往任务配置里面重新选择授权");
}
// @ts-ignore
if (this.logger?.addSecret) {
// 隐藏加密信息,不在日志中输出
const type = res._type;
const plugin = accessRegistry.get(type);
const define = plugin.define;
// @ts-ignore
const input = define.input;
for (const key in input) {
if (input[key].encrypt && res[key] != null) {
// @ts-ignore
this.logger.addSecret(res[key]);
}
}
}
return res as T;
}

View File

@@ -48,14 +48,26 @@ export function IsTaskPlugin(define: PluginDefine): ClassDecorator {
inputMap[item[0]] = item[1];
});
merge(define, { input: inputMap, autowire: autowires, output: outputs });
const defaultConfig = {
showRunStrategy: false,
default: {
strategy: {
runStrategy: 1, // 0:正常执行1:成功后跳过
},
},
};
define = merge(defaultConfig, define, { input: inputMap, autowire: autowires, output: outputs });
Reflect.defineMetadata(PLUGIN_CLASS_KEY, define, target);
target.define = define;
pluginRegistry.register(define.name, {
define,
target,
target: async () => {
return target;
},
});
};
}

View File

@@ -6,24 +6,28 @@ export class PluginGroup {
desc?: string;
order: number;
plugins: PluginDefine[];
constructor(key: string, title: string, order = 0, desc = "") {
icon?: string;
constructor(key: string, title: string, order = 0, icon = "") {
this.key = key;
this.title = title;
this.order = order;
this.desc = desc;
this.icon = icon;
this.plugins = [];
}
}
export const pluginGroups = {
cert: new PluginGroup("cert", "证书申请", 1),
aliyun: new PluginGroup("aliyun", "阿里云", 2),
huawei: new PluginGroup("huawei", "华为云", 3),
tencent: new PluginGroup("tencent", "腾讯云", 4),
qiniu: new PluginGroup("qiniu", "七牛云", 5),
aws: new PluginGroup("aws", "亚马逊云", 6),
host: new PluginGroup("host", "主机", 7),
cdn: new PluginGroup("cdn", "CDN", 8),
panel: new PluginGroup("panel", "面板", 9),
other: new PluginGroup("other", "其他", 10),
cert: new PluginGroup("cert", "证书申请", 1, "ph:certificate"),
host: new PluginGroup("host", "主机", 2, "clarity:host-line"),
cdn: new PluginGroup("cdn", "CDN", 2, "svg:icon-cdn"),
panel: new PluginGroup("panel", "面板", 2, "fluent:panel-left-header-32-filled"),
aliyun: new PluginGroup("aliyun", "阿里云", 2, "svg:icon-aliyun"),
huawei: new PluginGroup("huawei", "华为云", 3, "svg:icon-huawei"),
tencent: new PluginGroup("tencent", "腾讯云", 4, "svg:icon-tencentcloud"),
volcengine: new PluginGroup("volcengine", "火山引擎", 4, "svg:icon-volcengine"),
jdcloud: new PluginGroup("jdcloud", "京东云", 4, "svg:icon-jdcloud"),
qiniu: new PluginGroup("qiniu", "七牛云", 5, "svg:icon-qiniuyun"),
aws: new PluginGroup("aws", "亚马逊云", 6, "svg:icon-aws"),
other: new PluginGroup("other", "其他", 10, "clarity:plugin-line"),
};

View File

@@ -3,14 +3,23 @@ import { AbstractTaskPlugin } from "./api.js";
import { pluginGroups } from "./group.js";
const onRegister = ({ key, value }: OnRegisterContext<AbstractTaskPlugin>) => {
//如果有相同名字的先移除
for (const group of Object.values(pluginGroups)) {
const index = group.plugins.findIndex(plugin => plugin.name === key);
if (index > -1) {
group.plugins.splice(index, 1);
}
}
const group = value?.define?.group as string;
if (group) {
if (pluginGroups.hasOwnProperty(group)) {
// @ts-ignore
pluginGroups[group].plugins.push(value.define);
} else {
pluginGroups.other.plugins.push(value.define);
return;
}
}
pluginGroups.other.plugins.push(value.define);
};
export const pluginRegistry = createRegistry<AbstractTaskPlugin>("plugin", onRegister);

View File

@@ -6,11 +6,12 @@ export type Registrable = {
desc?: string;
group?: string;
deprecated?: string;
order?: number;
};
export type TargetGetter<T> = () => Promise<T>;
export type RegistryItem<T> = {
define: Registrable;
target: T;
target: TargetGetter<T>;
};
export type OnRegisterContext<T> = {

View File

@@ -4,5 +4,5 @@ export * from "./config.js";
export * from "./url.js";
export * from "./emit.js";
export type IServiceGetter = {
get: (name: string) => Promise<any>;
get: <T>(name: string) => Promise<T>;
};

View File

@@ -3,6 +3,80 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.33.8](https://github.com/certd/certd/compare/v1.33.7...v1.33.8) (2025-04-26)
**Note:** Version bump only for package @certd/lib-huawei
## [1.33.7](https://github.com/certd/certd/compare/v1.33.6...v1.33.7) (2025-04-22)
**Note:** Version bump only for package @certd/lib-huawei
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
**Note:** Version bump only for package @certd/lib-huawei
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
**Note:** Version bump only for package @certd/lib-huawei
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
**Note:** Version bump only for package @certd/lib-huawei
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
**Note:** Version bump only for package @certd/lib-huawei
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
**Note:** Version bump only for package @certd/lib-huawei
## [1.33.1](https://github.com/certd/certd/compare/v1.33.0...v1.33.1) (2025-04-12)
**Note:** Version bump only for package @certd/lib-huawei
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/lib-huawei
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/lib-huawei
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Performance Improvements
* 支持京东云dns申请证书 ([04d79f9](https://github.com/certd/certd/commit/04d79f9117670be504960b018fd49ae3bf7c1c11))
## [1.31.10](https://github.com/certd/certd/compare/v1.31.9...v1.31.10) (2025-03-29)
### Performance Improvements
* 优化华为dns接口报错信息输出 ([bf30b7a](https://github.com/certd/certd/commit/bf30b7afaef623dd8126570344f1fcc2c06f1215))
## [1.31.9](https://github.com/certd/certd/compare/v1.31.8...v1.31.9) (2025-03-28)
### Bug Fixes
* 修复华为云dns接口请求出错的bug ([caa15b4](https://github.com/certd/certd/commit/caa15b47355363cbb8847f415ff12363cd53eeda))
## [1.31.8](https://github.com/certd/certd/compare/v1.31.7...v1.31.8) (2025-03-26)
**Note:** Version bump only for package @certd/lib-huawei
## [1.31.7](https://github.com/certd/certd/compare/v1.31.6...v1.31.7) (2025-03-24)
**Note:** Version bump only for package @certd/lib-huawei
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/lib-huawei
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/lib-huawei
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.31.4",
"version": "1.33.8",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -23,5 +23,5 @@
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
"gitHead": "a188385817e8dbd270dbdbf7f86a5b812884cf59"
}

View File

@@ -30,7 +30,7 @@ module.exports = {
allowSyntheticDefaultImports: true,
}),
json(),
terser(),
// terser(),
],
external: ["vue", "lodash-es", "dayjs", "log4js", "@midwayjs/core", "@certd/pipeline", "axios"],
};

View File

@@ -2,8 +2,10 @@ import { Signer, SigHttpRequest } from "./signer.js";
import axios from "axios";
export class HuaweiYunClient {
access;
logger;
constructor(access, logger) {
this.access = access;
this.logger = logger
}
async request(options) {
const sig = new Signer(this.access.accessKeyId, this.access.accessKeySecret);
@@ -32,7 +34,7 @@ export class HuaweiYunClient {
return res.data;
} catch (e) {
this.logger.error("华为云接口请求出错:", e?.response?.data);
const error = new Error(e?.response?.data.message);
const error = new Error(JSON.stringify(e?.response?.data));
error.code = e?.response?.code;
throw error;
}

View File

@@ -3,6 +3,76 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.33.8](https://github.com/certd/certd/compare/v1.33.7...v1.33.8) (2025-04-26)
**Note:** Version bump only for package @certd/lib-iframe
## [1.33.7](https://github.com/certd/certd/compare/v1.33.6...v1.33.7) (2025-04-22)
**Note:** Version bump only for package @certd/lib-iframe
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
### Performance Improvements
* 更新license时同时绑定url ([78367af](https://github.com/certd/certd/commit/78367af8307f801e778c76d49f0918c21ffe032f))
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
**Note:** Version bump only for package @certd/lib-iframe
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
**Note:** Version bump only for package @certd/lib-iframe
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
**Note:** Version bump only for package @certd/lib-iframe
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
**Note:** Version bump only for package @certd/lib-iframe
## [1.33.1](https://github.com/certd/certd/compare/v1.33.0...v1.33.1) (2025-04-12)
**Note:** Version bump only for package @certd/lib-iframe
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/lib-iframe
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.10](https://github.com/certd/certd/compare/v1.31.9...v1.31.10) (2025-03-29)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.9](https://github.com/certd/certd/compare/v1.31.8...v1.31.9) (2025-03-28)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.8](https://github.com/certd/certd/compare/v1.31.7...v1.31.8) (2025-03-26)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.7](https://github.com/certd/certd/compare/v1.31.6...v1.31.7) (2025-03-24)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.6](https://github.com/certd/certd/compare/v1.31.5...v1.31.6) (2025-03-24)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.5](https://github.com/certd/certd/compare/v1.31.4...v1.31.5) (2025-03-22)
**Note:** Version bump only for package @certd/lib-iframe
## [1.31.4](https://github.com/certd/certd/compare/v1.31.3...v1.31.4) (2025-03-21)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.31.4",
"version": "1.33.8",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -30,5 +30,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "cfbbac9796477f830e1f57f77777f6554da9e31d"
"gitHead": "a188385817e8dbd270dbdbf7f86a5b812884cf59"
}

View File

@@ -65,7 +65,7 @@ export class IframeClient {
return window.self !== window.top;
}
register<T = any>(action: string, handler: (data: IframeMessageData<T>) => Promise<void>) {
register<T = any>(action: string, handler: (data: IframeMessageData<T>) => Promise<any>) {
this.handlers[action] = handler;
}

View File

@@ -0,0 +1,22 @@
{
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier"
],
"env": {
"mocha": true
},
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-unused-vars": "off"
}
}

28
packages/libs/lib-jdcloud/.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
test/user.secret.ts
.rollup.cache

View File

@@ -0,0 +1,3 @@
node_modules
src
.rollup.cache

View File

@@ -0,0 +1 @@
language: node_js

View File

@@ -0,0 +1,51 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.33.8](https://github.com/certd/certd/compare/v1.33.7...v1.33.8) (2025-04-26)
**Note:** Version bump only for package @certd/jdcloud
## [1.33.7](https://github.com/certd/certd/compare/v1.33.6...v1.33.7) (2025-04-22)
**Note:** Version bump only for package @certd/jdcloud
## [1.33.6](https://github.com/certd/certd/compare/v1.33.5...v1.33.6) (2025-04-20)
**Note:** Version bump only for package @certd/jdcloud
## [1.33.5](https://github.com/certd/certd/compare/v1.33.4...v1.33.5) (2025-04-17)
**Note:** Version bump only for package @certd/jdcloud
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
**Note:** Version bump only for package @certd/jdcloud
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
**Note:** Version bump only for package @certd/jdcloud
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
**Note:** Version bump only for package @certd/jdcloud
## [1.33.1](https://github.com/certd/certd/compare/v1.33.0...v1.33.1) (2025-04-12)
**Note:** Version bump only for package @certd/jdcloud
# [1.33.0](https://github.com/certd/certd/compare/v1.32.0...v1.33.0) (2025-04-11)
**Note:** Version bump only for package @certd/jdcloud
# [1.32.0](https://github.com/certd/certd/compare/v1.31.11...v1.32.0) (2025-04-04)
**Note:** Version bump only for package @certd/jdcloud
## [1.31.11](https://github.com/certd/certd/compare/v1.31.10...v1.31.11) (2025-04-02)
### Performance Improvements
* 支持部署到京东云cdn ([6f17c70](https://github.com/certd/certd/commit/6f17c700b84965baa01b40fe2abaa0a91bcbaffd))
* 支持京东云dns申请证书 ([04d79f9](https://github.com/certd/certd/commit/04d79f9117670be504960b018fd49ae3bf7c1c11))

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,177 @@
---
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
# 简介 #
欢迎使用京东云开发者Node.js工具套件Node.js SDK。使用京东云Node.js SDK您无需复杂编程就可以访问京东云提供的各种服务。
为了方便您理解SDK中的一些概念和参数的含义使用SDK前建议您先查看[京东云OpenAPI使用入门](http://www.jdcloud.com/help/detail/355/isCatalog/0)。要了解每个API的具体参数和含义请参考程序注释或参考OpenAPI&SDK下具体产品线的API文档。
# 环境准备 #
1.京东云Node.js SDK适用于Node.js 8.6.0及以上npm 5.6.0及以上。
2.在开始调用京东云open API之前需提前在京东云用户中心账户管理下的[AccessKey管理页面](https://uc.jdcloud.com/accesskey/index)申请accesskey和secretKey密钥对简称AK/SK。AK/SK信息请妥善保管如果遗失可能会造成非法用户使用此信息操作您在云上的资源给你造成数据和财产损失。
# SDK使用方法 #
建议使用npm安装京东云Node.js SDK如下所示
npm install jdcloud-sdk-js
您还可以下载sdk源代码自行使用[源代码地址](https://github.com/jdcloud-api/jdcloud-sdk-nodejs)。
SDK使用中的任何问题欢迎您在[SDK使用问题反馈页面](https://github.com/jdcloud-api/jdcloud-sdk-nodejs/issues)交流。
注意:京东云并没有提供其他下载方式,请务必使用上述官方下载方式!
# 调用SDK #
## 两种引用方式 ##
var JDCloud = require('jdcloud-sdk-js');
这种引用方式会加载所有的可用的services
var NC = require('jdcloud-sdk-js/services/nativecontainer');
这种引用方式只会加载用到的service此时仍然可以使用var JDCloud = require('jdcloud-sdk-js/global')来引用JDCloud对象
## 配置方法 ##
对JDCloud的配置为通用配置所有services共享配置:
JDCloud.config.update({//*配置项/*/});
对某个service的配置会覆盖通用配置
var NC = require('jdcloud-sdk-js/services/nativecontainer');
var nc = new NATIVECONTAINER({//*配置项/*/});
## 配置项 ##
let config = {
credentials: {
accessKeyId: global.accessKeyId, secretAccessKey: global.secretAccessKey
},
regionId: 'cn-north-1' //地域信息某个api调用可以单独传参regionId如果不传则会使用此配置中的regionId
}
## 调用示例 ##
以下是查询单个云主机实例详情的调用示例
### 引用和配置 ###
var VM = require('jdcloud-sdk-js/services/vm')
var vm = new VM({
credentials: {
accessKeyId: global.accessKeyId,
secretAccessKey: global.secretAccessKey
},
regionId: 'cn-north-1'
})
### Promise方式调用 ####
vm.createInstances({
instanceSpec: {
instanceType: 'g.s1.micro',
az: 'cn-north-1a',
imageId: '98d44a0f-88c1-451a-8971-f1f769073b6c',
name: 'node-sdk-test',
elasticIp: {
bandwidthMbps: 2, provider: 'BGP'
},
primaryNetworkInterface: {
networkInterface: {
subnetId: 'subnet-3dm13k30gh',
az: 'cn-north-1a'
}
},
systemDisk: {
diskCategory: 'local'
},
description: 'sdk'
},
maxCount: 1
},'cn-north-1').then(function(data){ // 返回数据处理 data
},
function(e){ // 调用API失败错误处理
})
### callback方式调用 ###
vm.createInstances({
instanceSpec: {
instanceType: 'g.s1.micro',
az: 'cn-north-1a',
imageId: '98d44a0f-88c1-451a-8971-f1f769073b6c',
name: 'node-sdk-test',
elasticIp: {
bandwidthMbps: 2,
provider: 'BGP'
},
primaryNetworkInterface: {
networkInterface: {
subnetId: 'subnet-3dm13k30gh',
az: 'cn-north-1a'
}
},
systemDisk: {
diskCategory: 'local'
},
description: 'sdk'
},
maxCount: 1
},'cn-north-1',
function(err, data){
if(err){ // 调用API失败错误处理
}
else { // 返回数据处理 data
}
})
如果需要设置访问点,配置超时,额外请求头等,请参考如下更复杂的例子:
```
var nc = new NATIVECONTAINER({
credentials: {
accessKeyId: global.accessKeyId,
secretAccessKey: global.secretAccessKey
},
endpoint: {
host: 'nativecontainer.internal.cn-north-1.jdcloud-api.com', //指定非默认Endpoint
protocol: 'http' //设置使用HTTP而不是HTTPSvpc专用域名不支持HTTPS
},
'x-extra-header': { //指定额外header
"x-jdcloud-security-token" : "xxx", //要调用开启了MFA操作保护的接口需要传递
"x-jdcloud-content-sha256" : "xxx", //body过大希望用此value替代对body进行哈希的过程
"MyOwn" : "xxx"
},
version: {
nativecontainer: 'v1'
}
})
```
更多调用示例参考 [SDK使用Demo](https://github.com/jdcloud-api/jdcloud-sdk-nodejs/tree/master/test)

View File

@@ -0,0 +1,64 @@
{
"name": "@certd/jdcloud",
"version": "1.33.8",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
"scripts": {
"test": "cross-env NODE_CONFIG_DIR=./test/config mocha --recursive --require babel-register",
"dev": "babel src --out-dir babel -w",
"build": "rollup -c ",
"dev-build": "npm run build"
},
"author": "",
"license": "Apache",
"dependencies": {
"babel-register": "^6.26.0",
"buffer": "^5.0.8",
"create-hash": "^1.1.3",
"create-hmac": "^1.1.6",
"debug": "^3.1.0",
"node-fetch": "^2.1.2",
"querystring": "^0.2.0",
"rollup": "^3.7.4",
"url": "^0.11.0",
"uuid": "^3.1.0"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.0.0",
"@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1",
"babel-cli": "^6.26.0",
"babel-preset-env": "^1.6.1",
"chai": "^4.1.2",
"config": "^1.30.0",
"cross-env": "^5.1.4",
"js-yaml": "^3.11.0",
"mocha": "^5.0.0",
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"engines": {
"node": ">= 8.6.0",
"npm": ">= 5.6.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 9"
],
"standard": {
"env": [
"node",
"mocha",
"browser"
],
"globals": [
"Request",
"Headers",
"fetch"
]
},
"gitHead": "a188385817e8dbd270dbdbf7f86a5b812884cf59"
}

View File

@@ -0,0 +1,36 @@
const resolve = require("@rollup/plugin-node-resolve");
const commonjs = require("@rollup/plugin-commonjs");
//const Typescript = require("rollup-plugin-typescript2");
const Typescript = require("@rollup/plugin-typescript");
const json = require("@rollup/plugin-json");
const terser = require("@rollup/plugin-terser");
module.exports = {
input: "src/index.ts",
output: {
file: "dist/bundle.js",
format: "cjs",
},
plugins: [
// 解析第三方依赖
resolve(),
// 识别 commonjs 模式第三方依赖
commonjs({
// dynamicRequireRoot: "../../../../",
// dynamicRequireTargets: [
// // include using a glob pattern (either a string or an array of strings)
// "../../../../**/shelljs/src/*",
// ],
}),
Typescript({
target: "esnext",
rootDir: "src",
declaration: true,
declarationDir: "dist/d",
exclude: ["./node_modules/**", "./src/**/*.vue", "./src/**/*.spec.ts"],
allowSyntheticDefaultImports: true,
}),
json(),
// terser(),
],
external: ["vue", "lodash-es", "dayjs", "log4js", "@midwayjs/core", "@certd/pipeline", "axios"],
};

View File

@@ -0,0 +1,2 @@
require('./lib/node_loader')
module.exports = require('./lib/core')

View File

@@ -0,0 +1,11 @@
import jdCloud from "./lib/core.js";
import jdService from './lib/service.js'
import domainService from './repo/domainservice/v2/domainservice.js'
import cdnService from './repo/cdn/v1/cdn.js'
import sslService from './repo/ssl/v1/ssl.js'
export const JDCloud = jdCloud;
export const JDService = jdService;
export const JDDomainService = domainService;
export const JDCdnService = cdnService;
export const JDSslService = sslService;

View File

@@ -0,0 +1 @@
module.exports = require('./lib/jc')

View File

@@ -0,0 +1,5 @@
require('babel-polyfill')
require('./browser_loader')
var JC = require('./core')
require('../services/all')
module.exports = JC

View File

@@ -0,0 +1,13 @@
var util = require('./util')
util.crypto.lib = {
createHash: require('create-hash'),
createHmac: require('create-hmac')
}
util.Buffer = require('buffer/').Buffer
util.url = require('url/')
util.querystring = require('querystring/')
util.environment = 'js'
var JC = require('./core')
module.exports = JC

View File

@@ -0,0 +1,6 @@
require('./core')
require('./config')
require('./request')
require('./service')
require('./credentials')

View File

@@ -0,0 +1,78 @@
var JDCloud = require('./core')
let defaultValues = {
credentials: null,
regionId: null,
apiVersions: null,
endpoint: {},
version: {},
logger: function (string, level = 'INFO') {
// level: INFO / DEBUG / ERROR / WARN
console.log(string)
}
}
JDCloud.Config = class Config {
constructor (options = {}) {
options = this.extractCredentials(options)
JDCloud.util.each.call(this, defaultValues, function (key, value) {
if (options[key] === undefined) {
this[key] = value
} else {
this[key] = options[key]
}
})
JDCloud.util.each.call(this, JDCloud.Service._services, function (
key,
value
) {
if (options[key] !== undefined) {
this[key] = options[key]
}
})
}
extractCredentials (options) {
if (options.accessKeyId && options.secretAccessKey) {
options = Object.assign({}, options)
options.credentials = new JDCloud.Credentials(options)
}
return options
}
getCredentials () {
var p = new Promise((resolve, reject) => {
if (this.credentials) {
if (typeof this.credentials.get === 'function') {
} else if (
this.credentials.accessKeyId &&
this.credentials.secretAccessKey
) {
resolve()
} else {
reject(new Error('missing credentials'))
}
} else if (this.credentialProvider) {
} else {
reject(new Error('get credentials failed'))
}
})
return p
}
update (options, allowUnknownKeys = false) {
options = this.extractCredentials(options)
JDCloud.util.each.call(this, options, function (key, value) {
if (
allowUnknownKeys ||
defaultValues.hasOwnProperty(key) ||
JDCloud.Service.hasService(key)
) {
this[key] = options[key]
}
})
}
}
JDCloud.config = new JDCloud.Config()

View File

@@ -0,0 +1,13 @@
require('node-fetch')
var JDCloud = {
util: require('./util'),
// todo swaggerVar
VERSION: ''
}
module.exports = JDCloud
require('./service')
require('./config')
require('./request')

View File

@@ -0,0 +1,21 @@
var JDCloud = require('./core')
JDCloud.Credentials = class Credentials {
constructor () {
this.expired = false
this.expireTime = null
if (arguments.length === 1 && typeof arguments[0] === 'object') {
var creds = arguments[0].credentials || arguments[0]
this.accessKeyId = creds.accessKeyId
this.secretAccessKey = creds.secretAccessKey
this.sessionToken = creds.sessionToken
} else {
this.accessKeyId = arguments[0]
this.secretAccessKey = arguments[1]
this.sessionToken = arguments[2]
}
}
}
module.exports = JDCloud.Credentials

View File

@@ -0,0 +1,4 @@
require('./node_loader')
var JDCloud = require('./core')
require('../services/all')
module.exports = JDCloud

View File

@@ -0,0 +1,12 @@
var util = require('./util')
util.crypto.lib = require('crypto')
util.Buffer = require('buffer').Buffer
util.url = require('url')
util.querystring = require('querystring')
util.environment = 'nodejs'
let JDCloud = require('./core')
JDCloud.fetch = require('node-fetch')
module.exports = JDCloud
require('./credentials')

View File

@@ -0,0 +1,159 @@
var JDCloud = require('./core')
let util = JDCloud.util
JDCloud.JCRequest = class JCRequest {
constructor (
service,
path,
httpMethod,
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType
) {
this.service = service
var endpoint = service.config.endpoint
pathParams.regionId = pathParams.regionId || service.config.regionId
this.regionId = pathParams.regionId
this.path = this.buildPath(path, pathParams)
this.path = util.uriEscapePath(this.path)
var queryString = this.buildQuery(queryParams)
var url = this.path
if (queryString) {
url = this.path + '?' + queryString
}
var contentType = this.jsonPreferredMime(contentTypes) || 'application/json'
headerParams['content-type'] = contentType
var requestHeaders = this.buildHeaders(headerParams)
var requestInit = {
method: httpMethod || 'GET',
headers: requestHeaders
}
if (contentType === 'application/x-www-form-urlencoded') {
} else if (contentType === 'multipart/form-data') {
} else if (postBody) {
requestInit.body = JSON.stringify(postBody)
}
var fetchUrl = endpoint.protocol + '://' + endpoint.host + url
JDCloud.config.logger(
`make request where url is :${fetchUrl} \nwith fetch config:${JSON.stringify(
requestInit
)}`
)
this.request = new JDCloud.fetch.Request(fetchUrl, requestInit)
}
buildPath (path, pathParams) {
var uri = (this.service.config.basePath || '') + path
uri = uri.replace(/\{([\w-]+)\}/g, (fullMatch, key) => {
var value
if (pathParams.hasOwnProperty(key)) {
value = pathParams[key]
} else {
value = fullMatch
}
return value
})
return uri
}
buildQuery (queryParams) {
var queryParamsWithoutEmptyItem = {}
var keys = Object.keys(queryParams)
for (let key of keys) {
if (queryParams[key] !== undefined) {
queryParamsWithoutEmptyItem[key] = queryParams[key]
}
}
return JDCloud.util.querystring.stringify(queryParamsWithoutEmptyItem)
}
search () {
var query = this.request.url.split('?', 2)[1]
if (query) {
query = JDCloud.util.querystring.parse(query)
return JDCloud.util.queryParamsToString(query)
}
return ''
}
digitizationArray (key, obj) {
var result = key
if (Array.isArray(obj)) {
JDCloud.util.arrayEach(obj, (arrayValue, index) => {
result += this.digitizationArray(`.${index + 1}`, arrayValue)
})
} else if (typeof obj === 'object' && obj != null) {
JDCloud.util.each(obj, (key, ObjValue) => {
result += `.name=${key}&${result}.values` + this.digitizationArray()
result +=
key +
'.name=' +
ObjValue +
'&' +
this.digitizationArray(key + '.values', ObjValue)
})
} else {
result += key + '=' + encodeURI(obj)
}
return result
}
buildHeaders (headerParams) {
var headers = new JDCloud.fetch.Headers({
accept: 'application/json'
})
util.each.call(this, headerParams, function (key) {
if (headerParams[key] !== undefined && headerParams[key] != null) {
headers.append(key, headerParams[key])
}
})
return headers
}
/**
* Checks whether the given content type represents JSON.<br>
* JSON content type examples:<br>
* <ul>
* <li>application/json</li>
* <li>application/json; charset=UTF8</li>
* <li>APPLICATION/JSON</li>
* </ul>
* @param {String} contentType The MIME content type to check.
* @returns {Boolean} <code>true</code> if <code>contentType</code> represents JSON, otherwise <code>false</code>.
*/
isJsonMime (contentType) {
return Boolean(
contentType != null && contentType.match(/^application\/json(;.*)?$/i)
)
}
/**
* Chooses a content type from the given array, with JSON preferred; i.e. return JSON if included, otherwise return the first.
* @param {Array.<String>} contentTypes
* @returns {String} The chosen content type, preferring JSON.
*/
jsonPreferredMime (contentTypes) {
for (var i = 0; i < contentTypes.length; i++) {
if (this.isJsonMime(contentTypes[i])) {
return contentTypes[i]
}
}
return contentTypes[0]
}
}
module.exports = JDCloud.JCRequest

View File

@@ -0,0 +1,231 @@
var JDCloud = require('./core')
var SignerV2 = require('./signers/v2')
JDCloud.Service = class Service {
constructor (serviceId, config = {}) {
this.serviceId = serviceId
this.init(config)
}
init (config) {
// 某个服务类型的全局配置
var serviceConfig = JDCloud.config[this.serviceId]
// 全局配置
this.config = new JDCloud.Config(JDCloud.config)
if (serviceConfig) {
this.config.update(serviceConfig, true)
}
if (config) {
if (!this.config.endpoint.host && !config.endpoint) {
config.endpoint = config._defaultEndpoint
}
delete config._defaultEndpoint
this.config.update(config, true)
}
}
makeRequest (
path,
httpMethod,
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType,
callback
) {
var request = new JDCloud.JCRequest(
this,
path,
httpMethod,
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType
)
var signer = new SignerV2(request, this.serviceId)
return this.config.getCredentials().then(() => {
signer.addAuthorization(this.config.credentials)
return JDCloud.fetch(signer.request.request).then(response => {
return response.json().then(
result => {
result.responseObj = response
if (response.ok) {
return result
}
return Promise.reject(result)
},
error => {
error.responseObj = response
if (error.type === 'invalid-json') {
// oss没有返回json
if (response.ok) {
return Promise.resolve({
requestId: response.headers.get('x-jdcloud-request-id') || ''
})
} else {
/* eslint-disable */
return Promise.reject({
requestId: response.headers.get('x-jdcloud-request-id') || ''
})
/* eslint-enable */
}
}
throw error
}
)
})
})
}
buildCollectionParam (param, collectionFormat) {
if (param === null || param === undefined) {
return param
}
switch (collectionFormat) {
case 'csv':
return param.map(this.paramToString).join(',')
case 'ssv':
return param.map(this.paramToString).join(' ')
case 'tsv':
return param.map(this.paramToString).join('\t')
case 'pipes':
return param.map(this.paramToString).join('|')
case 'multi':
// return the array directly as SuperAgent will handle it as expected
return param.map(this.paramToString)
default:
throw new Error('Unknown collection format: ' + collectionFormat)
}
}
/**
* filter is a special type of array
* only contains:
* [
* { name: someString , values:[ someString, someString ] ,operator: someString}
* ]
*
*/
buildFilterParam (param, key) {
var result = {}
if (Array.isArray(param)) {
let index = 0
for (var i = 0; i < param.length; i++) {
var obj = param[i]
// 兼容空字符串
if (obj.values !== '' && !Array.isArray(obj.values)) {
throw new Error('The type of filters.values should be Array!')
}
if (obj.name && obj.values) {
if (!obj.values.length) continue
result[`${key}.${index + 1}.name`] = obj.name
for (var j = 0; j < obj.values.length; j++) {
var someString = obj.values[j]
result[`${key}.${index + 1}.values.${j + 1}`] = someString
}
if (obj.operator) {
result[`${key}.${index + 1}.operator`] = obj.operator
}
index++
}
}
}
return result
}
buildTagFilterParam (param = [], key) {
var result = {}
if (!Array.isArray(param)) {
throw new Error(`The type of param 'param' should be Array!`)
}
for (var i = 0; i < param.length; i++) {
var obj = param[i]
if (obj.values && !Array.isArray(obj.values)) {
throw new Error(
`The type of param 'param[${i}].values' should be Array or NULL!`
)
}
if (obj.key) {
result[`${key}.${i + 1}.key`] = obj.key
if (obj.values) {
for (var j = 0; j < obj.values.length; j++) {
var someString = obj.values[j]
result[`${key}.${i + 1}.values.${j + 1}`] = someString
}
}
}
}
return result
}
buildSortParam (param = [], key) {
var result = {}
if (!Array.isArray(param)) {
throw new Error(`The type of param 'param' should be Array!`)
}
var index = 0
for (var i = 0; i < param.length; i++) {
var obj = param[i]
if (obj.name && obj.direction) {
index++
result[`${key}.${index}.name`] = obj.name
result[`${key}.${index}.direction`] = obj.direction
}
}
return result
}
// arr=[a,b,c] => arr={arr1:a, arr2:b, arr3:c}
buildArrayParam (param = [], key) {
var result = {}
if (!Array.isArray(param)) {
throw new Error(`The type of param 'param' should be Array!`)
}
for (var i = 0; i < param.length; i++) {
var value = param[i]
result[`${key}.${i + 1}`] = value
}
return result
}
/**
* Returns a string representation for an actual parameter.
* @param param The actual parameter.
* @returns {String} The string representation of <code>param</code>.
*/
paramToString (param) {
if (param === undefined || param === null) {
return ''
}
if (param instanceof Date) {
return param.toJSON()
}
return param.toString()
}
static hasService (id) {
return JDCloud.Service._services.hasOwnProperty(id)
}
}
JDCloud.Service._services = {}
module.exports = JDCloud.Service

View File

@@ -0,0 +1,13 @@
module.exports = class RequestSigner {
constructor (request) {
this.request = request
}
setServiceClientId (id) {
this.serviceClientId = id
}
getServiceClientId () {
return this.serviceClientId
}
}

View File

@@ -0,0 +1,190 @@
// Copyright 2018 JDCLOUD.COM
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License
// This signer is modified from AWS V4 signer algorithm.
var util = require('../util')
var RequestSigner = require('./request_signer')
var v2Credentials = require('./v2_credentials')
var uuid = require('uuid')
var JDCloud = require('../core')
module.exports = class SignerV2 extends RequestSigner {
constructor (request, serviceName, options = {}) {
super(request)
this.signatureCache = true
this.algorithm = 'JDCLOUD2-HMAC-SHA256'
this.unsignableHeaders = ['authorization', 'user-agent']
this.serviceName = serviceName
// this.signatureCache = typeof options.signatureCache === 'boolean' ? options.signatureCache : true;
}
// 签名流程见 https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
addAuthorization (credentials, date) {
// var datetime = '20180119T070300Z';
var datetime = util.date.iso8601(date).replace(/[:-]|\.\d{3}/g, '')
this.addHeaders(credentials, datetime)
this.request.request.headers.set(
'Authorization',
this.authorization(credentials, datetime)
)
}
addHeaders (credentials, datetime) {
this.request.request.headers.set('x-jdcloud-date', datetime)
this.request.request.headers.set('x-jdcloud-nonce', uuid.v4())
this.request.request.headers.set(
'host',
this.request.service.config.endpoint.host
)
}
signedHeaders () {
var keys = []
this.request.request.headers.forEach((value, key) => {
key = key.toLowerCase()
if (this.isSignableHeader(key)) {
keys.push(key)
}
})
/* util.each.call(this, this.request.headers, function (key) {
}); */
return keys.sort().join(';')
}
credentialString (datetime) {
return v2Credentials.createScope(
datetime.substr(0, 8),
this.request.regionId,
this.serviceName
)
}
signature (credentials, datetime) {
var signingKey = v2Credentials.getSigningKey(
credentials,
datetime.substr(0, 8),
this.request.regionId,
this.serviceName,
this.signatureCache
)
return util.crypto.hmac(signingKey, this.stringToSign(datetime), 'hex')
}
stringToSign (datetime) {
var parts = []
parts.push(this.algorithm)
parts.push(datetime)
parts.push(this.credentialString(datetime))
parts.push(this.hexEncodedHash(this.canonicalString()))
JDCloud.config.logger('StringToSign is \n' + JSON.stringify(parts), 'DEBUG')
return parts.join('\n')
}
// 构建标准签名字符串
canonicalString () {
var parts = []
var pathname = this.request.path
// if (this.serviceName !== 'jfs') {
// pathname = util.uriEscapePath(pathname)
// }
parts.push(this.request.request.method)
parts.push(pathname)
parts.push(this.request.search())
parts.push(this.canonicalHeaders() + '\n')
parts.push(this.signedHeaders())
parts.push(this.hexEncodedBodyHash())
JDCloud.config.logger(
'canonicalString is \n' + JSON.stringify(parts),
'DEBUG'
)
return parts.join('\n')
}
canonicalHeaders () {
var headers = []
this.request.request.headers.forEach((value, key) => {
headers.push([key, value])
})
headers.sort(function (a, b) {
return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1
})
var parts = []
util.arrayEach.call(this, headers, function (item) {
var key = item[0].toLowerCase()
if (this.isSignableHeader(key)) {
var value = item[1]
if (
typeof value === 'undefined' ||
value === null ||
typeof value.toString !== 'function'
) {
throw util.error(
new Error('Header ' + key + ' contains invalid value'),
{
code: 'InvalidHeader'
}
)
}
parts.push(key + ':' + this.canonicalHeaderValues(value.toString()))
}
})
return parts.join('\n')
}
canonicalHeaderValues (values) {
return values.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '')
}
authorization (credentials, datetime) {
var parts = []
var credString = this.credentialString(datetime)
parts.push(
this.algorithm +
' Credential=' +
credentials.accessKeyId +
'/' +
credString
)
parts.push('SignedHeaders=' + this.signedHeaders())
parts.push('Signature=' + this.signature(credentials, datetime))
JDCloud.config.logger('Signature is \n' + JSON.stringify(parts), 'DEBUG')
return parts.join(', ')
}
hexEncodedHash (string) {
return util.crypto.sha256(string, 'hex')
}
hexEncodedBodyHash () {
return this.hexEncodedHash(this.request.request.body || '')
/* var request = this.request;
if (this.isPresigned() && this.serviceName === 's3' && !request.body) {
return 'UNSIGNED-PAYLOAD';
} else if (request.headers['X-Amz-Content-Sha256']) {
return request.headers['X-Amz-Content-Sha256'];
} else {
return this.hexEncodedHash(this.request.body || '');
} */
}
isSignableHeader (key) {
if (key.toLowerCase().includes('x-jdcloud-')) {
return true
}
return !this.unsignableHeaders.includes(key.toLowerCase())
}
}

View File

@@ -0,0 +1,80 @@
var cachedSecret = {}
var cacheQueue = []
var maxCacheEntries = 50
var v2Identifier = 'jdcloud2_request'
var util = require('../util')
module.exports = {
/**
* @api private
*
* @param date [String]
* @param region [String]
* @param serviceName [String]
* @return [String]
*/
createScope: function createScope (date, region, serviceName) {
return [date.substr(0, 8), region, serviceName, v2Identifier].join('/')
},
/**
* @api private
*
* @param credentials [Credentials]
* @param date [String]
* @param region [String]
* @param service [String]
* @param shouldCache [Boolean]
* @return [String]
*/
getSigningKey: function getSigningKey (
credentials,
date,
region,
service,
shouldCache
) {
var credsIdentifier = util.crypto.hmac(
credentials.secretAccessKey,
credentials.accessKeyId,
'base64'
)
var cacheKey = [credsIdentifier, date, region, service].join('_')
shouldCache = shouldCache !== false
if (shouldCache && cacheKey in cachedSecret) {
return cachedSecret[cacheKey]
}
var kDate = util.crypto.hmac(
'JDCLOUD2' + credentials.secretAccessKey,
date,
'buffer'
)
var kRegion = util.crypto.hmac(kDate, region, 'buffer')
var kService = util.crypto.hmac(kRegion, service, 'buffer')
var signingKey = util.crypto.hmac(kService, v2Identifier, 'buffer')
if (shouldCache) {
cachedSecret[cacheKey] = signingKey
cacheQueue.push(cacheKey)
if (cacheQueue.length > maxCacheEntries) {
// remove the oldest entry (not the least recently used)
delete cachedSecret[cacheQueue.shift()]
}
}
return signingKey
},
/**
* @api private
*
* Empties the derived signing key cache. Made available for testing purposes
* only.
*/
emptyCache: function emptyCache () {
cachedSecret = {}
cacheQueue = []
}
}

View File

@@ -0,0 +1,477 @@
var util = {
isBrowser: function isBrowser () {
return process && process.browser
},
isNode: function isNode () {
return !util.isBrowser()
},
uriEscape: function uriEscape (string) {
var output = encodeURIComponent(string)
output = output.replace(/[^A-Za-z0-9_.~\-%]+/g, escape)
// AWS percent-encodes some extra non-standard characters in a URI
output = output.replace(/[*]/g, function (ch) {
return (
'%' +
ch
.charCodeAt(0)
.toString(16)
.toUpperCase()
)
})
return output
},
uriEscapePath: function uriEscapePath (string) {
var parts = []
util.arrayEach(string.split('/'), function (part) {
parts.push(util.uriEscape(part))
})
return parts.join('/')
},
abort: {},
each: function each (object, iterFunction) {
for (var key in object) {
if (Object.prototype.hasOwnProperty.call(object, key)) {
var ret = iterFunction.call(this, key, object[key])
if (ret === util.abort) break
}
}
},
arrayEach: function arrayEach (array, iterFunction) {
for (var idx in array) {
if (Object.prototype.hasOwnProperty.call(array, idx)) {
var ret = iterFunction.call(this, array[idx], parseInt(idx, 10))
if (ret === util.abort) break
}
}
},
arraySliceFn: function arraySliceFn (obj) {
var fn = obj.slice || obj.webkitSlice || obj.mozSlice
return typeof fn === 'function' ? fn : null
},
queryParamsToString: function queryParamsToString (params) {
var items = []
var escape = util.uriEscape
var sortedKeys = Object.keys(params).sort()
util.arrayEach(sortedKeys, function (name) {
var value = params[name]
var ename = escape(name)
var result = ename + '='
if (Array.isArray(value)) {
var vals = []
util.arrayEach(value, function (item) {
vals.push(escape(item))
})
result = ename + '=' + vals.sort().join('&' + ename + '=')
} else if (value !== undefined && value !== null) {
result = ename + '=' + escape(value)
}
items.push(result)
})
return items.join('&')
},
date: {
getDate () {
return new Date()
},
iso8601: function iso8601 (date) {
if (date === undefined) {
date = util.date.getDate()
}
return date.toISOString().replace(/\.\d{3}Z$/, 'Z')
}
},
crypto: {
/* eslint-disable no-use-before-define */
crc32Table: [
0x00000000,
0x77073096,
0xee0e612c,
0x990951ba,
0x076dc419,
0x706af48f,
0xe963a535,
0x9e6495a3,
0x0edb8832,
0x79dcb8a4,
0xe0d5e91e,
0x97d2d988,
0x09b64c2b,
0x7eb17cbd,
0xe7b82d07,
0x90bf1d91,
0x1db71064,
0x6ab020f2,
0xf3b97148,
0x84be41de,
0x1adad47d,
0x6ddde4eb,
0xf4d4b551,
0x83d385c7,
0x136c9856,
0x646ba8c0,
0xfd62f97a,
0x8a65c9ec,
0x14015c4f,
0x63066cd9,
0xfa0f3d63,
0x8d080df5,
0x3b6e20c8,
0x4c69105e,
0xd56041e4,
0xa2677172,
0x3c03e4d1,
0x4b04d447,
0xd20d85fd,
0xa50ab56b,
0x35b5a8fa,
0x42b2986c,
0xdbbbc9d6,
0xacbcf940,
0x32d86ce3,
0x45df5c75,
0xdcd60dcf,
0xabd13d59,
0x26d930ac,
0x51de003a,
0xc8d75180,
0xbfd06116,
0x21b4f4b5,
0x56b3c423,
0xcfba9599,
0xb8bda50f,
0x2802b89e,
0x5f058808,
0xc60cd9b2,
0xb10be924,
0x2f6f7c87,
0x58684c11,
0xc1611dab,
0xb6662d3d,
0x76dc4190,
0x01db7106,
0x98d220bc,
0xefd5102a,
0x71b18589,
0x06b6b51f,
0x9fbfe4a5,
0xe8b8d433,
0x7807c9a2,
0x0f00f934,
0x9609a88e,
0xe10e9818,
0x7f6a0dbb,
0x086d3d2d,
0x91646c97,
0xe6635c01,
0x6b6b51f4,
0x1c6c6162,
0x856530d8,
0xf262004e,
0x6c0695ed,
0x1b01a57b,
0x8208f4c1,
0xf50fc457,
0x65b0d9c6,
0x12b7e950,
0x8bbeb8ea,
0xfcb9887c,
0x62dd1ddf,
0x15da2d49,
0x8cd37cf3,
0xfbd44c65,
0x4db26158,
0x3ab551ce,
0xa3bc0074,
0xd4bb30e2,
0x4adfa541,
0x3dd895d7,
0xa4d1c46d,
0xd3d6f4fb,
0x4369e96a,
0x346ed9fc,
0xad678846,
0xda60b8d0,
0x44042d73,
0x33031de5,
0xaa0a4c5f,
0xdd0d7cc9,
0x5005713c,
0x270241aa,
0xbe0b1010,
0xc90c2086,
0x5768b525,
0x206f85b3,
0xb966d409,
0xce61e49f,
0x5edef90e,
0x29d9c998,
0xb0d09822,
0xc7d7a8b4,
0x59b33d17,
0x2eb40d81,
0xb7bd5c3b,
0xc0ba6cad,
0xedb88320,
0x9abfb3b6,
0x03b6e20c,
0x74b1d29a,
0xead54739,
0x9dd277af,
0x04db2615,
0x73dc1683,
0xe3630b12,
0x94643b84,
0x0d6d6a3e,
0x7a6a5aa8,
0xe40ecf0b,
0x9309ff9d,
0x0a00ae27,
0x7d079eb1,
0xf00f9344,
0x8708a3d2,
0x1e01f268,
0x6906c2fe,
0xf762575d,
0x806567cb,
0x196c3671,
0x6e6b06e7,
0xfed41b76,
0x89d32be0,
0x10da7a5a,
0x67dd4acc,
0xf9b9df6f,
0x8ebeeff9,
0x17b7be43,
0x60b08ed5,
0xd6d6a3e8,
0xa1d1937e,
0x38d8c2c4,
0x4fdff252,
0xd1bb67f1,
0xa6bc5767,
0x3fb506dd,
0x48b2364b,
0xd80d2bda,
0xaf0a1b4c,
0x36034af6,
0x41047a60,
0xdf60efc3,
0xa867df55,
0x316e8eef,
0x4669be79,
0xcb61b38c,
0xbc66831a,
0x256fd2a0,
0x5268e236,
0xcc0c7795,
0xbb0b4703,
0x220216b9,
0x5505262f,
0xc5ba3bbe,
0xb2bd0b28,
0x2bb45a92,
0x5cb36a04,
0xc2d7ffa7,
0xb5d0cf31,
0x2cd99e8b,
0x5bdeae1d,
0x9b64c2b0,
0xec63f226,
0x756aa39c,
0x026d930a,
0x9c0906a9,
0xeb0e363f,
0x72076785,
0x05005713,
0x95bf4a82,
0xe2b87a14,
0x7bb12bae,
0x0cb61b38,
0x92d28e9b,
0xe5d5be0d,
0x7cdcefb7,
0x0bdbdf21,
0x86d3d2d4,
0xf1d4e242,
0x68ddb3f8,
0x1fda836e,
0x81be16cd,
0xf6b9265b,
0x6fb077e1,
0x18b74777,
0x88085ae6,
0xff0f6a70,
0x66063bca,
0x11010b5c,
0x8f659eff,
0xf862ae69,
0x616bffd3,
0x166ccf45,
0xa00ae278,
0xd70dd2ee,
0x4e048354,
0x3903b3c2,
0xa7672661,
0xd06016f7,
0x4969474d,
0x3e6e77db,
0xaed16a4a,
0xd9d65adc,
0x40df0b66,
0x37d83bf0,
0xa9bcae53,
0xdebb9ec5,
0x47b2cf7f,
0x30b5ffe9,
0xbdbdf21c,
0xcabac28a,
0x53b39330,
0x24b4a3a6,
0xbad03605,
0xcdd70693,
0x54de5729,
0x23d967bf,
0xb3667a2e,
0xc4614ab8,
0x5d681b02,
0x2a6f2b94,
0xb40bbe37,
0xc30c8ea1,
0x5a05df1b,
0x2d02ef8d
],
/* eslint-disable no-use-before-define */
crc32: function crc32 (data) {
var tbl = util.crypto.crc32Table
var crc = 0 ^ -1
if (typeof data === 'string') {
data = new util.Buffer(data)
}
for (var i = 0; i < data.length; i++) {
var code = data.readUInt8(i)
crc = (crc >>> 8) ^ tbl[(crc ^ code) & 0xff]
}
return (crc ^ -1) >>> 0
},
hmac: function hmac (key, string, digest, fn) {
if (!digest) digest = 'binary'
if (digest === 'buffer') {
digest = undefined
}
if (!fn) fn = 'sha256'
if (typeof string === 'string') string = new util.Buffer(string)
return util.crypto.lib
.createHmac(fn, key)
.update(string)
.digest(digest)
},
md5: function md5 (data, digest, callback) {
return util.crypto.hash('md5', data, digest, callback)
},
sha256: function sha256 (data, digest, callback) {
return util.crypto.hash('sha256', data, digest, callback)
},
hash: function (algorithm, data, digest, callback) {
var hash = util.crypto.createHash(algorithm)
if (!digest) {
digest = 'binary'
}
if (digest === 'buffer') {
digest = undefined
}
if (typeof data === 'string') data = new util.Buffer(data)
var sliceFn = util.arraySliceFn(data)
var isBuffer = util.Buffer.isBuffer(data)
// Identifying objects with an ArrayBuffer as buffers
if (
util.isBrowser() &&
typeof ArrayBuffer !== 'undefined' &&
data &&
data.buffer instanceof ArrayBuffer
) {
isBuffer = true
}
if (
callback &&
typeof data === 'object' &&
typeof data.on === 'function' &&
!isBuffer
) {
data.on('data', function (chunk) {
hash.update(chunk)
})
data.on('error', function (err) {
callback(err)
})
data.on('end', function () {
callback(null, hash.digest(digest))
})
} else if (
callback &&
sliceFn &&
!isBuffer &&
typeof FileReader !== 'undefined'
) {
// this might be a File/Blob
var index = 0
var size = 1024 * 512
var reader = new FileReader()
reader.onerror = function () {
callback(new Error('Failed to read data.'))
}
reader.onload = function () {
var buf = new util.Buffer(new Uint8Array(reader.result))
hash.update(buf)
index += buf.length
reader._continueReading()
}
reader._continueReading = function () {
if (index >= data.size) {
callback(null, hash.digest(digest))
return
}
var back = index + size
if (back > data.size) back = data.size
reader.readAsArrayBuffer(sliceFn.call(data, index, back))
}
reader._continueReading()
} else {
if (util.isBrowser() && typeof data === 'object' && !isBuffer) {
data = new util.Buffer(new Uint8Array(data))
}
var out = hash.update(data).digest(digest)
if (callback) callback(null, out)
return out
}
},
toHex: function toHex (data) {
var out = []
for (var i = 0; i < data.length; i++) {
out.push(('0' + data.charCodeAt(i).toString(16)).substr(-2, 2))
}
return out.join('')
},
createHash: function createHash (algorithm) {
return util.crypto.lib.createHash(algorithm)
}
}
}
module.exports = util

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,261 @@
/*
* Copyright 2018 JDCLOUD.COM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:#www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* 应用管理平台API (仅对授权用户使用)
* 应用管理平台API
*
* OpenAPI spec version: v1
* Contact:
*
* NOTE: This class is auto generated by the jdcloud code generator program.
*/
require('../../../lib/node_loader')
var JDCloud = require('../../../lib/core')
var Service = JDCloud.Service
var serviceId = 'ams'
Service._services[serviceId] = true
/**
* ams service.
* @version 1.0.0
*/
JDCloud.AMS = class AMS extends Service {
constructor (options = {}) {
options._defaultEndpoint = {}
options._defaultEndpoint.protocol =
options._defaultEndpoint.protocol || 'https'
options._defaultEndpoint.host =
options._defaultEndpoint.host || 'ams.jdcloud-api.com'
options.basePath = '/v1' // 默认要设为空""
super(serviceId, options)
}
/**
* 获取收流基础数据查询
* @param {Object} opts - parameters
* @param {string} opts.streamId - 流ID
* @param {string} [opts.startTime] - 起始时间 optional
* @param {string} [opts.endTime] - 结束时间 optional
* @param {string} callback - callback
@return {Object} result
* @param streamInputData streamInputDatas
*/
describeStreamsInput (opts, callback) {
opts = opts || {}
if (opts.streamId === undefined || opts.streamId === null) {
throw new Error(
"Missing the required parameter 'opts.streamId' when calling describeStreamsInput"
)
}
let postBody = null
let queryParams = {}
if (opts.startTime !== undefined && opts.startTime !== null) {
queryParams['startTime'] = opts.startTime
}
if (opts.endTime !== undefined && opts.endTime !== null) {
queryParams['endTime'] = opts.endTime
}
let pathParams = {
regionId: 'jdcloud',
streamId: opts.streamId
}
let headerParams = {
'User-Agent': 'JdcloudSdkNode/1.0.0 ams/1.0.0'
}
let contentTypes = ['application/json']
let accepts = ['application/json']
// 扩展自定义头
if (opts['x-extra-header']) {
for (let extraHeader in opts['x-extra-header']) {
headerParams[extraHeader] = opts['x-extra-header'][extraHeader]
}
if (Array.isArray(opts['x-extra-header']['content-type'])) {
contentTypes = opts['x-extra-header']['content-type']
} else if (typeof opts['x-extra-header']['content-type'] === 'string') {
contentTypes = opts['x-extra-header']['content-type'].split(',')
}
if (Array.isArray(opts['x-extra-header']['accept'])) {
accepts = opts['x-extra-header']['accept']
} else if (typeof opts['x-extra-header']['accept'] === 'string') {
accepts = opts['x-extra-header']['accept'].split(',')
}
}
let formParams = {}
let returnType = null
this.config.logger(
`call describeStreamsInput with params:\npathParams:${JSON.stringify(
pathParams
)},\nqueryParams:${JSON.stringify(
queryParams
)}, \nheaderParams:${JSON.stringify(
headerParams
)}, \nformParams:${JSON.stringify(
formParams
)}, \npostBody:${JSON.stringify(postBody)}`,
'DEBUG'
)
let request = this.makeRequest(
'/streams/{streamId}/inputs',
'GET',
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType,
callback
)
return request.then(
function (result) {
if (callback && typeof callback === 'function') {
return callback(null, result)
}
return result
},
function (error) {
if (callback && typeof callback === 'function') {
return callback(error)
}
return Promise.reject(error)
}
)
}
/**
* 客户端鉴权查询
* @param {Object} opts - parameters
* @param {string} opts.pId - PinId
* @param {integer} [opts.ver] - 版本 optional
* @param {string} callback - callback
@return {Object} result
* @param string pId PinId
* @param integer ver 版本
* @param number blacklist 集合
* @param integer status 状态
* @param string license 授权号
*/
describeAuthenticate (opts, callback) {
opts = opts || {}
if (opts.pId === undefined || opts.pId === null) {
throw new Error(
"Missing the required parameter 'opts.pId' when calling describeAuthenticate"
)
}
let postBody = null
let queryParams = {}
if (opts.ver !== undefined && opts.ver !== null) {
queryParams['ver'] = opts.ver
}
let pathParams = {
regionId: 'jdcloud',
pId: opts.pId
}
let headerParams = {
'User-Agent': 'JdcloudSdkNode/1.0.0 ams/1.0.0'
}
let contentTypes = ['application/json']
let accepts = ['application/json']
// 扩展自定义头
if (opts['x-extra-header']) {
for (let extraHeader in opts['x-extra-header']) {
headerParams[extraHeader] = opts['x-extra-header'][extraHeader]
}
if (Array.isArray(opts['x-extra-header']['content-type'])) {
contentTypes = opts['x-extra-header']['content-type']
} else if (typeof opts['x-extra-header']['content-type'] === 'string') {
contentTypes = opts['x-extra-header']['content-type'].split(',')
}
if (Array.isArray(opts['x-extra-header']['accept'])) {
accepts = opts['x-extra-header']['accept']
} else if (typeof opts['x-extra-header']['accept'] === 'string') {
accepts = opts['x-extra-header']['accept'].split(',')
}
}
let formParams = {}
let returnType = null
this.config.logger(
`call describeAuthenticate with params:\npathParams:${JSON.stringify(
pathParams
)},\nqueryParams:${JSON.stringify(
queryParams
)}, \nheaderParams:${JSON.stringify(
headerParams
)}, \nformParams:${JSON.stringify(
formParams
)}, \npostBody:${JSON.stringify(postBody)}`,
'DEBUG'
)
let request = this.makeRequest(
'/appManager/{pId}/authenticates',
'GET',
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType,
callback
)
return request.then(
function (result) {
if (callback && typeof callback === 'function') {
return callback(null, result)
}
return result
},
function (error) {
if (callback && typeof callback === 'function') {
return callback(error)
}
return Promise.reject(error)
}
)
}
}
module.exports = JDCloud.AMS

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,274 @@
/*
* Copyright 2018 JDCLOUD.COM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:#www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* JDCLOUD asset API
* API JDCLOUD asset API
*
* OpenAPI spec version: v1
* Contact:
*
* NOTE: This class is auto generated by the jdcloud code generator program.
*/
require('../../../lib/node_loader')
var JDCloud = require('../../../lib/core')
var Service = JDCloud.Service
var serviceId = 'asset'
Service._services[serviceId] = true
/**
* asset service.
* @version 0.0.3
*/
class ASSET extends Service {
constructor (options = {}) {
options._defaultEndpoint = {}
options._defaultEndpoint.protocol =
options._defaultEndpoint.protocol || 'https'
options._defaultEndpoint.host =
options._defaultEndpoint.host || 'asset.jdcloud-api.com'
options.basePath = '/v1' // 默认要设为空""
super(serviceId, options)
}
/**
* 查询账户金额(总金额、可用金额、冻结金额、可提现金额、提现中金额)
* @param {Object} opts - parameters
* @param {string} regionId - ID of the region
* @param {string} callback - callback
@return {Object} result
* @param string totalAmount 总金额:可用金额+冻结金额
* @param string availableAmount 可用金额
* @param string frozenAmount 冻结金额:提现失败、处理中或预占中金额
* @param string enableWithdrawAmount 可提现金额:排除对公充值金额总额后的充值总额,如果余额大于非对公充值总额,则为非对公充值总额,否则为余额(对公充值&#x3D;企业线下汇款+企业充值+活动充值金额)
* @param string withdrawingAmount 提现中金额:提现状态为处理中和预占中的金额
*/
describeAccountAmount (opts, regionId = this.config.regionId, callback) {
if (typeof regionId === 'function') {
callback = regionId
regionId = this.config.regionId
}
if (regionId === undefined || regionId === null) {
throw new Error(
"Missing the required parameter 'regionId' when calling describeAccountAmount"
)
}
opts = opts || {}
let postBody = null
let queryParams = {}
let pathParams = {
regionId: regionId
}
let headerParams = {
'User-Agent': 'JdcloudSdkNode/1.0.0 asset/0.0.3'
}
let contentTypes = ['application/json']
let accepts = ['application/json']
// 扩展自定义头
if (opts['x-extra-header']) {
for (let extraHeader in opts['x-extra-header']) {
headerParams[extraHeader] = opts['x-extra-header'][extraHeader]
}
if (Array.isArray(opts['x-extra-header']['content-type'])) {
contentTypes = opts['x-extra-header']['content-type']
} else if (typeof opts['x-extra-header']['content-type'] === 'string') {
contentTypes = opts['x-extra-header']['content-type'].split(',')
}
if (Array.isArray(opts['x-extra-header']['accept'])) {
accepts = opts['x-extra-header']['accept']
} else if (typeof opts['x-extra-header']['accept'] === 'string') {
accepts = opts['x-extra-header']['accept'].split(',')
}
}
let formParams = {}
let returnType = null
this.config.logger(
`call describeAccountAmount with params:\npathParams:${JSON.stringify(
pathParams
)},\nqueryParams:${JSON.stringify(
queryParams
)}, \nheaderParams:${JSON.stringify(
headerParams
)}, \nformParams:${JSON.stringify(
formParams
)}, \npostBody:${JSON.stringify(postBody)}`,
'DEBUG'
)
let request = super.makeRequest(
'/regions/{regionId}/assets:describeAccountAmount',
'GET',
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType,
callback
)
return request.then(
function (result) {
if (callback && typeof callback === 'function') {
return callback(null, result)
}
return result
},
function (error) {
if (callback && typeof callback === 'function') {
return callback(error)
}
return Promise.reject(error)
}
)
}
/**
* 设置余额预警信息
* @param {Object} opts - parameters
* @param {balanceWarningInfoVo} opts.balanceWarningInfoVo
* @param {string} regionId - ID of the region
* @param {string} callback - callback
@return {Object} result
* @param boolean status
*/
modifyBalanceWarningInfo (opts, regionId = this.config.regionId, callback) {
if (typeof regionId === 'function') {
callback = regionId
regionId = this.config.regionId
}
if (regionId === undefined || regionId === null) {
throw new Error(
"Missing the required parameter 'regionId' when calling modifyBalanceWarningInfo"
)
}
opts = opts || {}
if (
opts.balanceWarningInfoVo === undefined ||
opts.balanceWarningInfoVo === null
) {
throw new Error(
"Missing the required parameter 'opts.balanceWarningInfoVo' when calling modifyBalanceWarningInfo"
)
}
let postBody = {}
if (
opts.balanceWarningInfoVo !== undefined &&
opts.balanceWarningInfoVo !== null
) {
postBody['balanceWarningInfoVo'] = opts.balanceWarningInfoVo
}
let queryParams = {}
let pathParams = {
regionId: regionId
}
let headerParams = {
'User-Agent': 'JdcloudSdkNode/1.0.0 asset/0.0.3'
}
let contentTypes = ['application/json']
let accepts = ['application/json']
// 扩展自定义头
if (opts['x-extra-header']) {
for (let extraHeader in opts['x-extra-header']) {
headerParams[extraHeader] = opts['x-extra-header'][extraHeader]
}
if (Array.isArray(opts['x-extra-header']['content-type'])) {
contentTypes = opts['x-extra-header']['content-type']
} else if (typeof opts['x-extra-header']['content-type'] === 'string') {
contentTypes = opts['x-extra-header']['content-type'].split(',')
}
if (Array.isArray(opts['x-extra-header']['accept'])) {
accepts = opts['x-extra-header']['accept']
} else if (typeof opts['x-extra-header']['accept'] === 'string') {
accepts = opts['x-extra-header']['accept'].split(',')
}
}
let formParams = {}
let returnType = null
this.config.logger(
`call modifyBalanceWarningInfo with params:\npathParams:${JSON.stringify(
pathParams
)},\nqueryParams:${JSON.stringify(
queryParams
)}, \nheaderParams:${JSON.stringify(
headerParams
)}, \nformParams:${JSON.stringify(
formParams
)}, \npostBody:${JSON.stringify(postBody)}`,
'DEBUG'
)
let request = super.makeRequest(
'/regions/{regionId}/assets:modifyBalanceWarningInfo',
'POST',
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType,
callback
)
return request.then(
function (result) {
if (callback && typeof callback === 'function') {
return callback(null, result)
}
return result
},
function (error) {
if (callback && typeof callback === 'function') {
return callback(error)
}
return Promise.reject(error)
}
)
}
}
module.exports = ASSET

View File

@@ -0,0 +1,919 @@
/*
* Copyright 2018 JDCLOUD.COM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:#www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* LogConfig
* 用户日志相关配置
*
* OpenAPI spec version: v1
* Contact:
*
* NOTE: This class is auto generated by the jdcloud code generator program.
*/
require('../../../lib/node_loader')
var JDCloud = require('../../../lib/core')
var Service = JDCloud.Service
var serviceId = 'assistant'
Service._services[serviceId] = true
/**
* assistant service.
* @version 1.0.2
*/
class ASSISTANT extends Service {
constructor (options = {}) {
options._defaultEndpoint = {}
options._defaultEndpoint.protocol =
options._defaultEndpoint.protocol || 'https'
options._defaultEndpoint.host =
options._defaultEndpoint.host || 'assistant.jdcloud-api.com'
options.basePath = '/v1' // 默认要设为空""
super(serviceId, options)
}
/**
*
保存用户自定义命令。
详细操作说明请参考帮助文档:[用户自定义命令概述](https://docs.jdcloud.com/cn/virtual-machines/assistant-overview)
## 接口说明
- 该接口用于保存用户自定义命令。
* @param {Object} opts - parameters
* @param {string} opts.commandName - 命令名称长度为1\~128个字符只允许中文、数字、大小写字母、英文下划线\_、连字符-)及点(.)。
* @param {string} [opts.commandType] - 命令类型可选值shell和powershell默认shell
optional
* @param {string} opts.commandContent - 以base64编码的命令内容编码后长度小于36KB
* @param {integer} [opts.timeout] - 超时时间,取值范围:[10, 86400], 超过该时间后尚未执行完的命令会置为失败。默认60s
optional
* @param {string} [opts.username] - 用户名执行该命令时的用户身份。在linux上默认是rootwindows上默认是administrator。长度小于256
optional
* @param {string} [opts.workdir] - 命令执行路径。在linux上默认是/rootwindows上默认是C:\Windows\System32。长度小于256。
optional
* @param {string} [opts.commandDescription] - 命令描述描述该命令详细信息如功能、使用注意事项等。长度小于256。
optional
* @param {boolean} [opts.enableParameter] - 是否使用参数, 默认false不使用参数 optional
* @param {string} regionId - ID of the region
* @param {string} callback - callback
@return {Object} result
* @param string commandId 命令Id。
*/
createCommand (opts, regionId = this.config.regionId, callback) {
if (typeof regionId === 'function') {
callback = regionId
regionId = this.config.regionId
}
if (regionId === undefined || regionId === null) {
throw new Error(
"Missing the required parameter 'regionId' when calling createCommand"
)
}
opts = opts || {}
if (opts.commandName === undefined || opts.commandName === null) {
throw new Error(
"Missing the required parameter 'opts.commandName' when calling createCommand"
)
}
if (opts.commandContent === undefined || opts.commandContent === null) {
throw new Error(
"Missing the required parameter 'opts.commandContent' when calling createCommand"
)
}
let postBody = {}
if (opts.commandName !== undefined && opts.commandName !== null) {
postBody['commandName'] = opts.commandName
}
if (opts.commandType !== undefined && opts.commandType !== null) {
postBody['commandType'] = opts.commandType
}
if (opts.commandContent !== undefined && opts.commandContent !== null) {
postBody['commandContent'] = opts.commandContent
}
if (opts.timeout !== undefined && opts.timeout !== null) {
postBody['timeout'] = opts.timeout
}
if (opts.username !== undefined && opts.username !== null) {
postBody['username'] = opts.username
}
if (opts.workdir !== undefined && opts.workdir !== null) {
postBody['workdir'] = opts.workdir
}
if (
opts.commandDescription !== undefined &&
opts.commandDescription !== null
) {
postBody['commandDescription'] = opts.commandDescription
}
if (opts.enableParameter !== undefined && opts.enableParameter !== null) {
postBody['enableParameter'] = opts.enableParameter
}
let queryParams = {}
let pathParams = {
regionId: regionId
}
let headerParams = {
'User-Agent': 'JdcloudSdkNode/1.0.0 assistant/1.0.2'
}
let contentTypes = ['application/json']
let accepts = ['application/json']
// 扩展自定义头
if (opts['x-extra-header']) {
for (let extraHeader in opts['x-extra-header']) {
headerParams[extraHeader] = opts['x-extra-header'][extraHeader]
}
if (Array.isArray(opts['x-extra-header']['content-type'])) {
contentTypes = opts['x-extra-header']['content-type']
} else if (typeof opts['x-extra-header']['content-type'] === 'string') {
contentTypes = opts['x-extra-header']['content-type'].split(',')
}
if (Array.isArray(opts['x-extra-header']['accept'])) {
accepts = opts['x-extra-header']['accept']
} else if (typeof opts['x-extra-header']['accept'] === 'string') {
accepts = opts['x-extra-header']['accept'].split(',')
}
}
let formParams = {}
let returnType = null
this.config.logger(
`call createCommand with params:\npathParams:${JSON.stringify(
pathParams
)},\nqueryParams:${JSON.stringify(
queryParams
)}, \nheaderParams:${JSON.stringify(
headerParams
)}, \nformParams:${JSON.stringify(
formParams
)}, \npostBody:${JSON.stringify(postBody)}`,
'DEBUG'
)
let request = super.makeRequest(
'/regions/{regionId}/createCommand',
'POST',
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType,
callback
)
return request.then(
function (result) {
if (callback && typeof callback === 'function') {
return callback(null, result)
}
return result
},
function (error) {
if (callback && typeof callback === 'function') {
return callback(error)
}
return Promise.reject(error)
}
)
}
/**
*
查询用户自定义命令。
详细操作说明请参考帮助文档:[用户自定义命令概述](https://docs.jdcloud.com/cn/virtual-machines/assistant-overview)
## 接口说明
- 该接口用于查询用户保存的自定义命令。
* @param {Object} opts - parameters
* @param {integer} [opts.pageNumber] - 页数,默认是第一页,取值为 1
optional
* @param {integer} [opts.pageSize] - 每页命令数默认为20最大为100
optional
* @param {array} [opts.commandIds] - 命令Id最多可传入100个命令Id
optional
* @param {array} [opts.commandNames] - 命令名称长度为1\~128个字符只允许中文、数字、大小写字母、英文下划线\_、连字符-)及点(.。最多可传入100个命令名称。
optional
* @param {array} [opts.commandTypes] - 命令类型可选值shell和powershell默认shell
optional
* @param {string} [opts.sourceType] - 命令来源可选值jdcloud官方和self默认self
optional
* @param {array} [opts.usernames] - 用户名执行该命令时的用户身份。在linux上默认是rootwindows上默认是administrator。长度小于256
optional
* @param {string} regionId - ID of the region
* @param {string} callback - callback
@return {Object} result
* @param integer totalCount 总的命令数,可以根据该字段来决定是否继续查找
* @param command commands
*/
describeCommands (opts, regionId = this.config.regionId, callback) {
if (typeof regionId === 'function') {
callback = regionId
regionId = this.config.regionId
}
if (regionId === undefined || regionId === null) {
throw new Error(
"Missing the required parameter 'regionId' when calling describeCommands"
)
}
opts = opts || {}
let postBody = {}
if (opts.pageNumber !== undefined && opts.pageNumber !== null) {
postBody['pageNumber'] = opts.pageNumber
}
if (opts.pageSize !== undefined && opts.pageSize !== null) {
postBody['pageSize'] = opts.pageSize
}
if (opts.commandIds !== undefined && opts.commandIds !== null) {
postBody['commandIds'] = opts.commandIds
}
if (opts.commandNames !== undefined && opts.commandNames !== null) {
postBody['commandNames'] = opts.commandNames
}
if (opts.commandTypes !== undefined && opts.commandTypes !== null) {
postBody['commandTypes'] = opts.commandTypes
}
if (opts.sourceType !== undefined && opts.sourceType !== null) {
postBody['sourceType'] = opts.sourceType
}
if (opts.usernames !== undefined && opts.usernames !== null) {
postBody['usernames'] = opts.usernames
}
let queryParams = {}
let pathParams = {
regionId: regionId
}
let headerParams = {
'User-Agent': 'JdcloudSdkNode/1.0.0 assistant/1.0.2'
}
let contentTypes = ['application/json']
let accepts = ['application/json']
// 扩展自定义头
if (opts['x-extra-header']) {
for (let extraHeader in opts['x-extra-header']) {
headerParams[extraHeader] = opts['x-extra-header'][extraHeader]
}
if (Array.isArray(opts['x-extra-header']['content-type'])) {
contentTypes = opts['x-extra-header']['content-type']
} else if (typeof opts['x-extra-header']['content-type'] === 'string') {
contentTypes = opts['x-extra-header']['content-type'].split(',')
}
if (Array.isArray(opts['x-extra-header']['accept'])) {
accepts = opts['x-extra-header']['accept']
} else if (typeof opts['x-extra-header']['accept'] === 'string') {
accepts = opts['x-extra-header']['accept'].split(',')
}
}
let formParams = {}
let returnType = null
this.config.logger(
`call describeCommands with params:\npathParams:${JSON.stringify(
pathParams
)},\nqueryParams:${JSON.stringify(
queryParams
)}, \nheaderParams:${JSON.stringify(
headerParams
)}, \nformParams:${JSON.stringify(
formParams
)}, \npostBody:${JSON.stringify(postBody)}`,
'DEBUG'
)
let request = super.makeRequest(
'/regions/{regionId}/describeCommands',
'POST',
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType,
callback
)
return request.then(
function (result) {
if (callback && typeof callback === 'function') {
return callback(null, result)
}
return result
},
function (error) {
if (callback && typeof callback === 'function') {
return callback(error)
}
return Promise.reject(error)
}
)
}
/**
*
删除用户自定义命令。
详细操作说明请参考帮助文档:[用户自定义命令概述](https://docs.jdcloud.com/cn/virtual-machines/assistant-overview)
## 接口说明
- 该接口用于删除用户自定义命令。
* @param {Object} opts - parameters
* @param {array} [opts.commandIds] - 用户创建的命令Id
optional
* @param {string} regionId - ID of the region
* @param {string} callback - callback
@return {Object} result
* @param string commandId 命令Id。
*/
deleteCommands (opts, regionId = this.config.regionId, callback) {
if (typeof regionId === 'function') {
callback = regionId
regionId = this.config.regionId
}
if (regionId === undefined || regionId === null) {
throw new Error(
"Missing the required parameter 'regionId' when calling deleteCommands"
)
}
opts = opts || {}
let postBody = {}
if (opts.commandIds !== undefined && opts.commandIds !== null) {
postBody['commandIds'] = opts.commandIds
}
let queryParams = {}
let pathParams = {
regionId: regionId
}
let headerParams = {
'User-Agent': 'JdcloudSdkNode/1.0.0 assistant/1.0.2'
}
let contentTypes = ['application/json']
let accepts = ['application/json']
// 扩展自定义头
if (opts['x-extra-header']) {
for (let extraHeader in opts['x-extra-header']) {
headerParams[extraHeader] = opts['x-extra-header'][extraHeader]
}
if (Array.isArray(opts['x-extra-header']['content-type'])) {
contentTypes = opts['x-extra-header']['content-type']
} else if (typeof opts['x-extra-header']['content-type'] === 'string') {
contentTypes = opts['x-extra-header']['content-type'].split(',')
}
if (Array.isArray(opts['x-extra-header']['accept'])) {
accepts = opts['x-extra-header']['accept']
} else if (typeof opts['x-extra-header']['accept'] === 'string') {
accepts = opts['x-extra-header']['accept'].split(',')
}
}
let formParams = {}
let returnType = null
this.config.logger(
`call deleteCommands with params:\npathParams:${JSON.stringify(
pathParams
)},\nqueryParams:${JSON.stringify(
queryParams
)}, \nheaderParams:${JSON.stringify(
headerParams
)}, \nformParams:${JSON.stringify(
formParams
)}, \npostBody:${JSON.stringify(postBody)}`,
'DEBUG'
)
let request = super.makeRequest(
'/regions/{regionId}/deleteCommands',
'POST',
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType,
callback
)
return request.then(
function (result) {
if (callback && typeof callback === 'function') {
return callback(null, result)
}
return result
},
function (error) {
if (callback && typeof callback === 'function') {
return callback(error)
}
return Promise.reject(error)
}
)
}
/**
*
执行用户保存的自定义命令。
详细操作说明请参考帮助文档:[用户自定义命令概述](https://docs.jdcloud.com/cn/virtual-machines/assistant-overview)
## 接口说明
- 该接口用于执行用户保存的自定义命令。
* @param {Object} opts - parameters
* @param {string} opts.commandId - 命令Id
* @param {array} [opts.instances] - 运行该命令的云主机与tags查到云主机取并集一次最多云主机数50
optional
* @param {array} [opts.tags] - 根据tags确定运行该命令的云主机与指定instances云主机取并集一次最多云主机数50
optional
* @param {string} [opts.execTime] - 配置运行该命令的时刻,格式&#x60;yyyy-MM-dd HH:mm:ss&#x60;。不传该参数,立即执行命令。默认为空,可配置的时间范围为&#x60;当前时间+10minute&#x60;~&#x60;当前时间+6month&#x60;。
optional
* @param {integer} [opts.timeout] - 超时时间,取值范围:[10, 86400], 超过该时间后尚未执行完的命令会置为失败。默认60s
optional
* @param {string} [opts.username] - 用户名执行该命令时的用户身份。在linux上默认是rootwindows上默认是administrator。长度小于256
optional
* @param {string} [opts.workdir] - 命令执行路径在linux上默认为用户的home目录/root, 在windows上默认为C:\Windows\system32
optional
* @param {string} [opts.windowsPassword] - 和用户名相匹配的密码仅适用于windows系统
optional
* @param {boolean} [opts.enableParameter] - 脚本中是否启用参数true启用false不启用。
optional
* @param {array} [opts.parameters] - 用户自定义参数和对应的参数值数量不超过20。
optional
* @param {string} regionId - ID of the region
* @param {string} callback - callback
@return {Object} result
* @param string invokeId 命令调用Id。
*/
invokeCommand (opts, regionId = this.config.regionId, callback) {
if (typeof regionId === 'function') {
callback = regionId
regionId = this.config.regionId
}
if (regionId === undefined || regionId === null) {
throw new Error(
"Missing the required parameter 'regionId' when calling invokeCommand"
)
}
opts = opts || {}
if (opts.commandId === undefined || opts.commandId === null) {
throw new Error(
"Missing the required parameter 'opts.commandId' when calling invokeCommand"
)
}
let postBody = {}
if (opts.commandId !== undefined && opts.commandId !== null) {
postBody['commandId'] = opts.commandId
}
if (opts.instances !== undefined && opts.instances !== null) {
postBody['instances'] = opts.instances
}
if (opts.tags !== undefined && opts.tags !== null) {
postBody['tags'] = opts.tags
}
if (opts.execTime !== undefined && opts.execTime !== null) {
postBody['execTime'] = opts.execTime
}
if (opts.timeout !== undefined && opts.timeout !== null) {
postBody['timeout'] = opts.timeout
}
if (opts.username !== undefined && opts.username !== null) {
postBody['username'] = opts.username
}
if (opts.workdir !== undefined && opts.workdir !== null) {
postBody['workdir'] = opts.workdir
}
if (opts.windowsPassword !== undefined && opts.windowsPassword !== null) {
postBody['windowsPassword'] = opts.windowsPassword
}
if (opts.enableParameter !== undefined && opts.enableParameter !== null) {
postBody['enableParameter'] = opts.enableParameter
}
if (opts.parameters !== undefined && opts.parameters !== null) {
postBody['parameters'] = opts.parameters
}
let queryParams = {}
let pathParams = {
regionId: regionId
}
let headerParams = {
'User-Agent': 'JdcloudSdkNode/1.0.0 assistant/1.0.2'
}
let contentTypes = ['application/json']
let accepts = ['application/json']
// 扩展自定义头
if (opts['x-extra-header']) {
for (let extraHeader in opts['x-extra-header']) {
headerParams[extraHeader] = opts['x-extra-header'][extraHeader]
}
if (Array.isArray(opts['x-extra-header']['content-type'])) {
contentTypes = opts['x-extra-header']['content-type']
} else if (typeof opts['x-extra-header']['content-type'] === 'string') {
contentTypes = opts['x-extra-header']['content-type'].split(',')
}
if (Array.isArray(opts['x-extra-header']['accept'])) {
accepts = opts['x-extra-header']['accept']
} else if (typeof opts['x-extra-header']['accept'] === 'string') {
accepts = opts['x-extra-header']['accept'].split(',')
}
}
let formParams = {}
let returnType = null
this.config.logger(
`call invokeCommand with params:\npathParams:${JSON.stringify(
pathParams
)},\nqueryParams:${JSON.stringify(
queryParams
)}, \nheaderParams:${JSON.stringify(
headerParams
)}, \nformParams:${JSON.stringify(
formParams
)}, \npostBody:${JSON.stringify(postBody)}`,
'DEBUG'
)
let request = super.makeRequest(
'/regions/{regionId}/invokeCommand',
'POST',
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType,
callback
)
return request.then(
function (result) {
if (callback && typeof callback === 'function') {
return callback(null, result)
}
return result
},
function (error) {
if (callback && typeof callback === 'function') {
return callback(error)
}
return Promise.reject(error)
}
)
}
/**
*
查询命令调用。
详细操作说明请参考帮助文档:[用户自定义命令概述](https://docs.jdcloud.com/cn/virtual-machines/assistant-overview)
## 接口说明
- 该接口用于查询命令调用。
* @param {Object} opts - parameters
* @param {integer} [opts.pageNumber] - 页数默认是第一页取值1
optional
* @param {integer} [opts.pageSize] - 每页大小默认20, 最大100
optional
* @param {array} [opts.invokeIds] - 命令执行Id
optional
* @param {array} [opts.commandIds] - 命令Id
optional
* @param {array} [opts.status] - 命令执行状态
optional
* @param {array} [opts.commandNames] - 命令名字
optional
* @param {array} [opts.commandTypes] - 命令类型, shell or powershell
optional
* @param {array} [opts.userNames] - 命令执行用户
optional
* @param {string} regionId - ID of the region
* @param {string} callback - callback
@return {Object} result
* @param integer totalCount 命令调用的总数
* @param invocation invocations
*/
describeInvocations (opts, regionId = this.config.regionId, callback) {
if (typeof regionId === 'function') {
callback = regionId
regionId = this.config.regionId
}
if (regionId === undefined || regionId === null) {
throw new Error(
"Missing the required parameter 'regionId' when calling describeInvocations"
)
}
opts = opts || {}
let postBody = {}
if (opts.pageNumber !== undefined && opts.pageNumber !== null) {
postBody['pageNumber'] = opts.pageNumber
}
if (opts.pageSize !== undefined && opts.pageSize !== null) {
postBody['pageSize'] = opts.pageSize
}
if (opts.invokeIds !== undefined && opts.invokeIds !== null) {
postBody['invokeIds'] = opts.invokeIds
}
if (opts.commandIds !== undefined && opts.commandIds !== null) {
postBody['commandIds'] = opts.commandIds
}
if (opts.status !== undefined && opts.status !== null) {
postBody['status'] = opts.status
}
if (opts.commandNames !== undefined && opts.commandNames !== null) {
postBody['commandNames'] = opts.commandNames
}
if (opts.commandTypes !== undefined && opts.commandTypes !== null) {
postBody['commandTypes'] = opts.commandTypes
}
if (opts.userNames !== undefined && opts.userNames !== null) {
postBody['userNames'] = opts.userNames
}
let queryParams = {}
let pathParams = {
regionId: regionId
}
let headerParams = {
'User-Agent': 'JdcloudSdkNode/1.0.0 assistant/1.0.2'
}
let contentTypes = ['application/json']
let accepts = ['application/json']
// 扩展自定义头
if (opts['x-extra-header']) {
for (let extraHeader in opts['x-extra-header']) {
headerParams[extraHeader] = opts['x-extra-header'][extraHeader]
}
if (Array.isArray(opts['x-extra-header']['content-type'])) {
contentTypes = opts['x-extra-header']['content-type']
} else if (typeof opts['x-extra-header']['content-type'] === 'string') {
contentTypes = opts['x-extra-header']['content-type'].split(',')
}
if (Array.isArray(opts['x-extra-header']['accept'])) {
accepts = opts['x-extra-header']['accept']
} else if (typeof opts['x-extra-header']['accept'] === 'string') {
accepts = opts['x-extra-header']['accept'].split(',')
}
}
let formParams = {}
let returnType = null
this.config.logger(
`call describeInvocations with params:\npathParams:${JSON.stringify(
pathParams
)},\nqueryParams:${JSON.stringify(
queryParams
)}, \nheaderParams:${JSON.stringify(
headerParams
)}, \nformParams:${JSON.stringify(
formParams
)}, \npostBody:${JSON.stringify(postBody)}`,
'DEBUG'
)
let request = super.makeRequest(
'/regions/{regionId}/describeInvocations',
'POST',
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType,
callback
)
return request.then(
function (result) {
if (callback && typeof callback === 'function') {
return callback(null, result)
}
return result
},
function (error) {
if (callback && typeof callback === 'function') {
return callback(error)
}
return Promise.reject(error)
}
)
}
/**
*
配置用户日志信息。
详细操作说明请参考帮助文档:[用户自定义命令概述](https://docs.jdcloud.com/cn/virtual-machines/assistant-overview)
## 接口说明
- 该接口用于配置用户日志信息。
* @param {Object} opts - parameters
* @param {string} [opts.logset] - 用户日志集名字
optional
* @param {string} opts.logtopic - 用户日志主题名字
* @param {string} regionId - ID of the region
* @param {string} callback - callback
@return {Object} result
*/
addLogConfig (opts, regionId = this.config.regionId, callback) {
if (typeof regionId === 'function') {
callback = regionId
regionId = this.config.regionId
}
if (regionId === undefined || regionId === null) {
throw new Error(
"Missing the required parameter 'regionId' when calling addLogConfig"
)
}
opts = opts || {}
if (opts.logtopic === undefined || opts.logtopic === null) {
throw new Error(
"Missing the required parameter 'opts.logtopic' when calling addLogConfig"
)
}
let postBody = {}
if (opts.logset !== undefined && opts.logset !== null) {
postBody['logset'] = opts.logset
}
if (opts.logtopic !== undefined && opts.logtopic !== null) {
postBody['logtopic'] = opts.logtopic
}
let queryParams = {}
let pathParams = {
regionId: regionId
}
let headerParams = {
'User-Agent': 'JdcloudSdkNode/1.0.0 assistant/1.0.2'
}
let contentTypes = ['application/json']
let accepts = ['application/json']
// 扩展自定义头
if (opts['x-extra-header']) {
for (let extraHeader in opts['x-extra-header']) {
headerParams[extraHeader] = opts['x-extra-header'][extraHeader]
}
if (Array.isArray(opts['x-extra-header']['content-type'])) {
contentTypes = opts['x-extra-header']['content-type']
} else if (typeof opts['x-extra-header']['content-type'] === 'string') {
contentTypes = opts['x-extra-header']['content-type'].split(',')
}
if (Array.isArray(opts['x-extra-header']['accept'])) {
accepts = opts['x-extra-header']['accept']
} else if (typeof opts['x-extra-header']['accept'] === 'string') {
accepts = opts['x-extra-header']['accept'].split(',')
}
}
let formParams = {}
let returnType = null
this.config.logger(
`call addLogConfig with params:\npathParams:${JSON.stringify(
pathParams
)},\nqueryParams:${JSON.stringify(
queryParams
)}, \nheaderParams:${JSON.stringify(
headerParams
)}, \nformParams:${JSON.stringify(
formParams
)}, \npostBody:${JSON.stringify(postBody)}`,
'DEBUG'
)
let request = super.makeRequest(
'/regions/{regionId}/addLogConfig',
'POST',
pathParams,
queryParams,
headerParams,
formParams,
postBody,
contentTypes,
accepts,
returnType,
callback
)
return request.then(
function (result) {
if (callback && typeof callback === 'function') {
return callback(null, result)
}
return result
},
function (error) {
if (callback && typeof callback === 'function') {
return callback(error)
}
return Promise.reject(error)
}
)
}
}
module.exports = ASSISTANT

Some files were not shown because too many files have changed in this diff Show More