Compare commits

...

140 Commits

Author SHA1 Message Date
xiaojunnuo
98a81385a6 v1.29.5 2025-01-07 23:16:46 +08:00
xiaojunnuo
7bdc277b58 build: prepare to build 2025-01-07 23:14:55 +08:00
xiaojunnuo
f57116d2be fix: 修复复制到本机插件,pfx格式复制时报错的bug 2025-01-07 23:13:44 +08:00
xiaojunnuo
85c99f7f80 fix: 修复授权管理,点击了查看原文按钮后,无法修改值的bug 2025-01-07 11:00:04 +08:00
xiaojunnuo
75081ceac3 build: publish 2025-01-07 00:02:42 +08:00
xiaojunnuo
65da3ca298 build: trigger build image 2025-01-07 00:02:21 +08:00
xiaojunnuo
94509c64b9 v1.29.4 2025-01-06 23:59:56 +08:00
xiaojunnuo
4f36d94726 build: prepare to build 2025-01-06 23:56:50 +08:00
xiaojunnuo
05c284b999 docs: 文档 2025-01-06 23:55:41 +08:00
xiaojunnuo
635b042690 perf: 优化腾讯云CLB插件,支持非sni情况,sni情况支持填写多个域名 2025-01-06 23:47:08 +08:00
xiaojunnuo
1cb4a539cc fix: 修复站点监控域名校验无法通过的bug 2025-01-06 23:08:16 +08:00
xiaojunnuo
46b87250b2 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2025-01-06 22:12:42 +08:00
xiaojunnuo
1a05355e54 docs: payments文档 2025-01-06 22:11:07 +08:00
xiaojunnuo
c81c17d17b chore: 2025-01-06 15:32:14 +08:00
xiaojunnuo
7b4f8d31e8 chore: db transform text改成longtext 2025-01-06 09:39:44 +08:00
xiaojunnuo
5cef28c5bd build: publish 2025-01-05 01:14:01 +08:00
xiaojunnuo
6e68da7936 build: trigger build image 2025-01-05 01:13:39 +08:00
xiaojunnuo
0c130f9596 v1.29.3 2025-01-05 01:11:06 +08:00
xiaojunnuo
f156f4cb4e build: prepare to build 2025-01-05 01:09:09 +08:00
xiaojunnuo
fa3bfa2ea8 chore: 2025-01-05 01:07:04 +08:00
xiaojunnuo
ab5c7bb75a chore: 2025-01-05 01:02:41 +08:00
xiaojunnuo
81b322cd60 chore: 2025-01-04 20:17:08 +08:00
xiaojunnuo
e6dd7cd54a perf: 优化站点证书检查页面,检查增加3次重试 2025-01-04 20:10:00 +08:00
xiaojunnuo
aa1da7c11a chore: 2025-01-04 01:46:49 +08:00
xiaojunnuo
3f74d4d9e5 perf: http校验方式,支持七牛云oss、阿里云oss、腾讯云cos 2025-01-04 01:45:24 +08:00
xiaojunnuo
297d09c5ad docs: 增加支付配置说明 2025-01-03 16:50:16 +08:00
xiaojunnuo
07e1dbb4cc chore: 2025-01-03 16:12:37 +08:00
xiaojunnuo
3c6618b4fc chore: 2025-01-03 09:27:51 +08:00
xiaojunnuo
54db744282 perf: 优化acme sdk 2025-01-03 01:17:20 +08:00
xiaojunnuo
03b751fa13 chore: 2025-01-03 00:12:15 +08:00
xiaojunnuo
ec342708b2 chore: 2025-01-02 17:48:54 +08:00
xiaojunnuo
405591c5d0 perf: 支持http校验方式申请证书 2025-01-02 00:28:13 +08:00
xiaojunnuo
67af67b92d chore: 2024-12-27 22:40:07 +08:00
xiaojunnuo
8644348fc4 fix: 修复系统级授权无法查看密钥的bug 2024-12-26 23:15:35 +08:00
xiaojunnuo
00dc226bd2 chore: auto-upgrade 2024-12-26 16:14:08 +08:00
xiaojunnuo
b6b7c3e2e0 chore: storage存储的数据量优化,去掉logs信息 2024-12-26 13:48:55 +08:00
xiaojunnuo
246ef348d3 chore: mysql text 改成longtext 2024-12-26 13:26:10 +08:00
xiaojunnuo
3e9ba1a30a docs: 2024-12-26 09:02:04 +08:00
xiaojunnuo
598cde4865 build: publish 2024-12-26 01:56:08 +08:00
xiaojunnuo
fc4a716b4e build: trigger build image 2024-12-26 01:55:50 +08:00
xiaojunnuo
ed5634ff83 v1.29.2 2024-12-26 01:53:32 +08:00
xiaojunnuo
884af1ea62 build: prepare to build 2024-12-26 01:51:48 +08:00
xiaojunnuo
01ad62df16 build: prepare to build 2024-12-26 01:49:48 +08:00
xiaojunnuo
512a667e44 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2024-12-26 01:47:50 +08:00
xiaojunnuo
d0e841f7de build: publish 2024-12-26 01:43:52 +08:00
xiaojunnuo
c04641d835 build: trigger build image 2024-12-26 01:43:35 +08:00
xiaojunnuo
f9128d4d45 v1.29.1 2024-12-26 01:41:20 +08:00
xiaojunnuo
2026211622 build: prepare to build 2024-12-26 01:39:23 +08:00
xiaojunnuo
9d0f21a9e5 chore: 2024-12-26 01:36:01 +08:00
xiaojunnuo
26adf7d437 perf: 优化插件名称显示 2024-12-26 01:32:52 +08:00
xiaojunnuo
d2d6f12218 fix: 修复某处金额转换丢失精度的bug 2024-12-26 01:01:28 +08:00
xiaojunnuo
b31c0b6a8d chore: 2024-12-25 23:24:42 +08:00
xiaojunnuo
472f06c2d1 perf: 用户创建证书流水线没有购买套餐或者超限时提前报错 2024-12-25 23:20:07 +08:00
xiaojunnuo
f5ec9870fd fix: 免费套餐支持购买 2024-12-25 17:05:24 +08:00
xiaojunnuo
66fb9e5f49 fix: 修复套餐关闭状态下,仍然限制用户流水线数量的bug 2024-12-25 11:42:42 +08:00
xiaojunnuo
a323f3aa2c chore: 2024-12-25 10:38:48 +08:00
xiaojunnuo
fe4786e168 fix: 修复新版本小红点显示错误问题 2024-12-25 09:25:27 +08:00
xiaojunnuo
83185c8c50 chore: 2024-12-25 01:16:32 +08:00
xiaojunnuo
83ae9db02d build: publish 2024-12-25 01:07:17 +08:00
xiaojunnuo
8bf328ca94 build: trigger build image 2024-12-25 01:06:48 +08:00
xiaojunnuo
36993cb6f8 v1.29.0 2024-12-25 01:04:12 +08:00
xiaojunnuo
c854415319 build: prepare to build 2024-12-25 01:02:01 +08:00
xiaojunnuo
aecc1cd979 build: prepare to build 2024-12-25 00:59:27 +08:00
xiaojunnuo
b2f3b0b584 build: prepare to build 2024-12-25 00:53:02 +08:00
xiaojunnuo
c937f5afc7 chore: 兼容数据库 2024-12-25 00:52:39 +08:00
xiaojunnuo
2d580a26af chore:menu.meta.show参数支持 2024-12-24 23:55:50 +08:00
xiaojunnuo
4a00a3cc1b chore: 2024-12-24 23:23:02 +08:00
xiaojunnuo
d3935219f2 perf: 调整创建证书表单字段的顺序 2024-12-24 23:14:12 +08:00
xiaojunnuo
040788c793 fix: 修复手机模式下,查询框被文字遮盖的bug 2024-12-24 17:52:03 +08:00
xiaojunnuo
005622307e fix: 修复左侧菜单收起时无法展开子菜单的bug 2024-12-24 17:09:06 +08:00
xiaojunnuo
8ebf95a222 perf: 同一时间只允许一个套餐生效 2024-12-24 10:39:54 +08:00
xiaojunnuo
7f596ed315 chore: 2024-12-24 01:16:27 +08:00
xiaojunnuo
ffa4de6911 chore: 2024-12-24 01:12:12 +08:00
xiaojunnuo
cb27d4b490 feat: 基础版不再限制流水线数量 2024-12-23 23:33:13 +08:00
xiaojunnuo
bb4910f4e5 perf: 站点证书监控通知发送,每天定时检查 2024-12-23 18:11:06 +08:00
xiaojunnuo
89c7f07034 perf: 用户名支持修改 2024-12-23 14:47:27 +08:00
xiaojunnuo
b150b2f034 chore: 2024-12-23 13:28:25 +08:00
xiaojunnuo
45d6347f5b feat: 支持微信支付 2024-12-23 13:27:04 +08:00
xiaojunnuo
67d762b6a5 perf: 优化证书申请跳过的状态显示,成功通知现在在跳过时不会发送 2024-12-23 00:49:56 +08:00
xiaojunnuo
faa28f88f9 feat: 套餐购买支持易支付、支付宝支付 2024-12-23 00:24:31 +08:00
xiaojunnuo
9c8c7a7812 feat: 站点证书监控 2024-12-22 14:01:10 +08:00
xiaojunnuo
a019956698 feat: 用户套餐,用户支付功能 2024-12-22 14:00:46 +08:00
xiaojunnuo
d70e2b66a3 chore: 2024-12-20 18:04:32 +08:00
xiaojunnuo
5d568efac3 chore: suite 2024-12-20 01:00:13 +08:00
xiaojunnuo
08111f1418 chore: 2024-12-19 22:37:27 +08:00
xiaojunnuo
45839f227a chore: suite 2024-12-19 01:21:55 +08:00
xiaojunnuo
8814ffeda6 Merge branch 'v2-dev' into v2-dev-suite 2024-12-18 21:28:38 +08:00
xiaojunnuo
d224c4c124 chore: 2024-12-18 21:25:39 +08:00
xiaojunnuo
549525fb37 chore: plesk ok 2024-12-18 10:22:22 +08:00
xiaojunnuo
1c8e25beb3 chore: suite 2024-12-18 09:07:52 +08:00
xiaojunnuo
eda45c1528 perf: 支持plesk网站证书部署 2024-12-18 00:38:27 +08:00
xiaojunnuo
53c38cf714 perf: 支持一体证书 2024-12-17 22:50:18 +08:00
xiaojunnuo
0e7578043e chore: 2024-12-17 22:45:14 +08:00
xiaojunnuo
21f50e0b38 Merge branch 'v2' into v2-dev 2024-12-17 22:22:19 +08:00
greper
515f00c7cd docs: 自动更新方法(@coolxitech)
Update README.md
2024-12-17 10:52:30 +08:00
xiaojunnuo
8057586dc1 chore: suite first 2024-12-17 10:27:35 +08:00
酷曦科技
b101ac7c7f Update README.md
Include the Docker compose configuration file content for automatic version updates.
2024-12-17 00:06:21 +08:00
xiaojunnuo
64319937a1 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2024-12-13 09:51:13 +08:00
xiaojunnuo
1c0cfd6769 build: publish 2024-12-13 00:19:43 +08:00
xiaojunnuo
f8e17d5285 build: trigger build image 2024-12-13 00:19:23 +08:00
xiaojunnuo
d4385ad8a5 v1.28.4 2024-12-13 00:17:10 +08:00
xiaojunnuo
da07ce419f build: prepare to build 2024-12-13 00:08:18 +08:00
xiaojunnuo
714e0206c4 build: prepare to build 2024-12-13 00:07:32 +08:00
xiaojunnuo
40da82666a chore: 2024-12-12 18:06:07 +08:00
xiaojunnuo
79f7ec4672 perf: 群晖支持6.x 2024-12-12 17:55:54 +08:00
xiaojunnuo
0f5c69040b fix: 修复证书成功通知发送失败的bug 2024-12-12 17:28:33 +08:00
xiaojunnuo
c9d1c45d97 docs: 证书成功同志 2024-12-12 16:49:40 +08:00
xiaojunnuo
ea8fdb120c docs: 证书说明 2024-12-12 16:45:40 +08:00
xiaojunnuo
f6fa830ffe docs: 2024-12-12 12:37:38 +08:00
xiaojunnuo
992e50c014 docs: 2024-12-12 12:30:26 +08:00
xiaojunnuo
bd705d91ba build: publish 2024-12-12 12:08:12 +08:00
xiaojunnuo
2656394195 build: trigger build image 2024-12-12 12:07:54 +08:00
xiaojunnuo
c8df9e698c v1.28.3 2024-12-12 12:06:46 +08:00
xiaojunnuo
19b78a1d2f build: prepare to build 2024-12-12 12:05:16 +08:00
xiaojunnuo
8039e8baf8 perf: 支持腾讯虚拟机开关机(@wujingke) 2024-12-12 11:50:01 +08:00
xiaojunnuo
9c5142c73c chore: 2024-12-12 11:42:46 +08:00
xiaojunnuo
8e3dcdde17 chore: tke挪出来 2024-12-11 22:17:11 +08:00
xiaojunnuo
34023adafb chore: 2024-12-11 17:40:34 +08:00
xiaojunnuo
79914e8d08 chore: 2024-12-11 15:06:02 +08:00
xiaojunnuo
454fbda581 perf: 点击版本红点按钮,跳转到升级帮助页面 2024-12-11 13:59:00 +08:00
xiaojunnuo
2c32703e6b chore: 2024-12-11 12:01:06 +08:00
xiaojunnuo
b561535626 Merge branch 'v2' into v2-dev 2024-12-11 11:48:34 +08:00
xiaojunnuo
1fc684d995 chore: 2024-12-11 11:48:05 +08:00
greper
7595d9fdfd pref: 腾讯云实例开机插件( @wujingke )
pr:  #265
2024-12-11 11:44:53 +08:00
w
3bf7732a21 腾讯云实例开机插件 2024-12-11 11:40:11 +08:00
xiaojunnuo
71b5aaf8ab chore: 2024-12-11 11:38:28 +08:00
xiaojunnuo
e1e5347476 chore: 2024-12-11 11:37:52 +08:00
xiaojunnuo
cdcdb6a2d9 chore: 2024-12-11 11:36:00 +08:00
xiaojunnuo
ec79104ad2 chore: 2024-12-11 11:33:33 +08:00
xiaojunnuo
ff083ce684 perf: 通知标题优化 2024-12-11 11:30:32 +08:00
xiaojunnuo
0f051e322e docs: upgrade 2024-12-11 10:25:16 +08:00
xiaojunnuo
657a2ae032 fix: 修复没有配置eab时,报order无法读取的问题 2024-12-11 09:30:21 +08:00
xiaojunnuo
0db3570026 chore: 2024-12-10 18:30:32 +08:00
xiaojunnuo
0ae39f160a perf: 支持aws cloudfront 2024-12-10 18:28:48 +08:00
xiaojunnuo
b45977c29a fix: 修复授权被删除后,无法清空的bug 2024-12-10 17:22:43 +08:00
xiaojunnuo
b7f5740c57 fix: mysql下access.setting字段改成text 2024-12-10 00:19:35 +08:00
xiaojunnuo
21e23369d3 chore: 2024-12-09 23:08:40 +08:00
xiaojunnuo
fca598991a chore: 2024-12-09 22:56:18 +08:00
xiaojunnuo
aa5b909486 build: publish 2024-12-09 22:53:08 +08:00
xiaojunnuo
0a888cf51a build: trigger build image 2024-12-09 22:52:44 +08:00
307 changed files with 10321 additions and 981 deletions

View File

@@ -91,14 +91,14 @@ jobs:
# greper/certd:armv7 # greper/certd:armv7
# greper/certd:${{steps.get_certd_version.outputs.result}}-armv7 # greper/certd:${{steps.get_certd_version.outputs.result}}-armv7
- name: Build agent # - name: Build agent
uses: docker/build-push-action@v6 # uses: docker/build-push-action@v6
with: # with:
platforms: linux/amd64,linux/arm64 # platforms: linux/amd64,linux/arm64
push: true # push: true
context: ./packages/ui/agent/ # context: ./packages/ui/agent/
tags: | # tags: |
registry.cn-shenzhen.aliyuncs.com/handsfree/certd-agent:latest # registry.cn-shenzhen.aliyuncs.com/handsfree/certd-agent:latest
registry.cn-shenzhen.aliyuncs.com/handsfree/certd-agent:${{steps.get_certd_version.outputs.result}} # registry.cn-shenzhen.aliyuncs.com/handsfree/certd-agent:${{steps.get_certd_version.outputs.result}}
greper/certd-agent:latest # greper/certd-agent:latest
greper/certd-agent:${{steps.get_certd_version.outputs.result}} # greper/certd-agent:${{steps.get_certd_version.outputs.result}}

View File

@@ -3,6 +3,105 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.5](https://github.com/certd/certd/compare/v1.29.4...v1.29.5) (2025-01-07)
### Bug Fixes
* 修复复制到本机插件pfx格式复制时报错的bug ([f57116d](https://github.com/certd/certd/commit/f57116d2bebf33e47ad93e0b39c4efe8e4aea25c))
* 修复授权管理点击了查看原文按钮后无法修改值的bug ([85c99f7](https://github.com/certd/certd/commit/85c99f7f80761ac6efaf3255c03b933442db1686))
## [1.29.4](https://github.com/certd/certd/compare/v1.29.3...v1.29.4) (2025-01-06)
### Bug Fixes
* 修复站点监控域名校验无法通过的bug ([1cb4a53](https://github.com/certd/certd/commit/1cb4a539cc523721ffd4b22d40d0e3d2d68cd915))
### Performance Improvements
* 优化腾讯云CLB插件支持非sni情况sni情况支持填写多个域名 ([635b042](https://github.com/certd/certd/commit/635b042690637bff85e97e07c7aac4b87a8a124b))
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
### Bug Fixes
* 修复系统级授权无法查看密钥的bug ([8644348](https://github.com/certd/certd/commit/8644348fc41ae2e1672f946ca37e5d3a674e0218))
### Performance Improvements
* 优化站点证书检查页面检查增加3次重试 ([e6dd7cd](https://github.com/certd/certd/commit/e6dd7cd54a3e23897031b5df6e0c3cdc0545d35a))
* 优化acme sdk ([54db744](https://github.com/certd/certd/commit/54db74428259de64d12230c2ab7353ae11197bbc))
* 支持http校验方式申请证书 ([405591c](https://github.com/certd/certd/commit/405591c5d08fa1a3b228ee3980199e7731cfec4a))
* http校验方式支持七牛云oss、阿里云oss、腾讯云cos ([3f74d4d](https://github.com/certd/certd/commit/3f74d4d9e5f5d0e629b44cff1895b3f7a8fbcafc))
## [1.29.2](https://github.com/certd/certd/compare/v1.29.1...v1.29.2) (2024-12-25)
### Bug Fixes
* 修复套餐关闭状态下仍然限制用户流水线数量的bug ([66fb9e5](https://github.com/certd/certd/commit/66fb9e5f49491f9c159363b48af14720a37673b1))
## [1.29.1](https://github.com/certd/certd/compare/v1.29.0...v1.29.1) (2024-12-25)
### Bug Fixes
* 免费套餐支持购买 ([f5ec987](https://github.com/certd/certd/commit/f5ec9870fd6af1f0c9099852bbdb4d07813ccce8))
* 修复某处金额转换丢失精度的bug ([d2d6f12](https://github.com/certd/certd/commit/d2d6f12218cbe7bd55f4ae082b93084be85f0a7b))
* 修复新版本小红点显示错误问题 ([fe4786e](https://github.com/certd/certd/commit/fe4786e168afe03a5243dd67971476c348339809))
### Performance Improvements
* 用户创建证书流水线没有购买套餐或者超限时提前报错 ([472f06c](https://github.com/certd/certd/commit/472f06c2d190d0ae48e8b53c18bc278437656a1c))
* 优化插件名称显示 ([26adf7d](https://github.com/certd/certd/commit/26adf7d437e674385f26a8f92fded6521a620671))
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Bug Fixes
* 修复手机模式下查询框被文字遮盖的bug ([040788c](https://github.com/certd/certd/commit/040788c793642c3bb2a3ede87fe30fcf3be471bd))
* 修复左侧菜单收起时无法展开子菜单的bug ([0056223](https://github.com/certd/certd/commit/005622307e612717a5408aa1484717ef03003a22))
### Features
* 基础版不再限制流水线数量 ([cb27d4b](https://github.com/certd/certd/commit/cb27d4b4906b2782eaceb0a95bbdc5d0534370d2))
* 套餐购买支持易支付、支付宝支付 ([faa28f8](https://github.com/certd/certd/commit/faa28f88f954cba4c1dd29125562e5acd2fd99af))
* 用户套餐,用户支付功能 ([a019956](https://github.com/certd/certd/commit/a019956698acaf2c4beb620b5ad8c18918ead6a1))
* 站点证书监控 ([9c8c7a7](https://github.com/certd/certd/commit/9c8c7a781223f4217f45510db1e89495600e3cd5))
* 支持微信支付 ([45d6347](https://github.com/certd/certd/commit/45d6347f5b6199493b11aabdd74177f6dca2cea4))
### Performance Improvements
* 调整创建证书表单字段的顺序 ([d393521](https://github.com/certd/certd/commit/d3935219f2aa50d6662c5b5ebf7ee25ad696ab2b))
* 同一时间只允许一个套餐生效 ([8ebf95a](https://github.com/certd/certd/commit/8ebf95a222a900d1707716c7b1f3b39f8a6d8f94))
* 用户名支持修改 ([89c7f07](https://github.com/certd/certd/commit/89c7f070343e86453c84677ebe1669f9b266d871))
* 优化证书申请跳过的状态显示,成功通知现在在跳过时不会发送 ([67d762b](https://github.com/certd/certd/commit/67d762b6a520f1fa24719a124e5ae975a81f5f82))
* 站点证书监控通知发送,每天定时检查 ([bb4910f](https://github.com/certd/certd/commit/bb4910f4e57234e42b44505f4620ae7af66025c5))
* 支持一体证书 ([53c38cf](https://github.com/certd/certd/commit/53c38cf714a6f7486abbf1d71c9f48f56a790100))
* 支持plesk网站证书部署 ([eda45c1](https://github.com/certd/certd/commit/eda45c1528199648b3970505e87f492d398226cd))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
### Bug Fixes
* 修复证书成功通知发送失败的bug ([0f5c690](https://github.com/certd/certd/commit/0f5c69040ba77340c909813220a26bc7ddada3ea))
### Performance Improvements
* 群晖支持6.x ([79f7ec4](https://github.com/certd/certd/commit/79f7ec4672f4fd5744cc45e4a6f104da943f4026))
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
### Bug Fixes
* 修复没有配置eab时报order无法读取的问题 ([657a2ae](https://github.com/certd/certd/commit/657a2ae032e6f61ac27fbdd26c7bf169c041219e))
* 修复授权被删除后无法清空的bug ([b45977c](https://github.com/certd/certd/commit/b45977c29a29084c11e496bec3415eaaebafdd74))
* mysql下access.setting字段改成text ([b7f5740](https://github.com/certd/certd/commit/b7f5740c57743914f754f3b4fdd94b59a2e8338c))
### Performance Improvements
* 点击版本红点按钮,跳转到升级帮助页面 ([454fbda](https://github.com/certd/certd/commit/454fbda581bbe22abca5b91e5086ea9d9d58a020))
* 通知标题优化 ([ff083ce](https://github.com/certd/certd/commit/ff083ce6848a8bee3c8248e4b881086ae1517c28))
* 支持腾讯虚拟机开关机([@wujingke](https://github.com/wujingke)) ([8039e8b](https://github.com/certd/certd/commit/8039e8baf83c82d03f1a6198cf61c372026b962b))
* 支持aws cloudfront ([0ae39f1](https://github.com/certd/certd/commit/0ae39f160a7c6b6696b3bf513d68aa28905810ad))
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09) ## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
### Bug Fixes ### Bug Fixes

View File

@@ -9,15 +9,23 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。 本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
* 全自动申请证书(支持所有注册商注册的域名) * 全自动申请证书(支持所有注册商注册的域名)
* 全自动部署更新证书(目前支持部署到主机、部署到阿里云、腾讯云等,目前已支持30+部署插件) * 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等,目前已支持40+部署插件)
* 支持DNS-01、HTTP-01、CNAME代理等多种域名验证方式
* 支持通配符域名/泛域名支持多个域名打到一个证书上支持pem、pfx、der、jks等多种证书格式 * 支持通配符域名/泛域名支持多个域名打到一个证书上支持pem、pfx、der、jks等多种证书格式
* 邮件通知 * 邮件通知、webhook通知
* 私有化部署数据保存本地镜像由Github Actions构建过程公开透明 * 私有化部署,数据保存本地,授权信息加密存储,镜像由Github Actions构建过程公开透明
* 支持sqlitepostgresql数据库 * 支持SQLitePostgreSQL、MySQL数据库
>
> 流水线数量现已调整为无限制,欢迎大家使用
>
> 关于证书续期:
>* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
>* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
## 二、在线体验 ## 二、在线体验
官方Demo地址自助注册后体验 官方Demo地址自助注册后体验
@@ -86,11 +94,13 @@ https://certd.handfree.work/
## 五、 升级 ## 五、 升级
如果使用固定版本号
### docker-compose方式部署
#### 1. 如果使用固定版本号
1. 修改`docker-compose.yaml`中的镜像版本号 1. 修改`docker-compose.yaml`中的镜像版本号
2. 运行`docker compose up -d` 即可 2. 运行`docker compose up -d` 即可
如果使用`latest`版本 #### 2. 如果需要使用最新版本
```shell ```shell
#重新拉取镜像 #重新拉取镜像
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
@@ -98,19 +108,54 @@ docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
docker compose down docker compose down
docker compose up -d docker compose up -d
``` ```
> 数据默认存在`/data/certd`目录下,不用担心数据丢失
> 数据默认存在`/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
更新日志: [CHANGELOG](./CHANGELOG.md)
### 更新日志:
[CHANGELOG](./CHANGELOG.md)
## 六、一些说明 ## 六、一些说明
* 本项目ssl证书提供商为letencrypt/Google/ZeroSSL * 本项目ssl证书提供商为letencrypt/Google/ZeroSSL
* 申请过程遵循acme协议 * 申请过程遵循acme协议
* 需要验证域名所有权一般有两种方式目前本项目仅支持dns-01
* http-01 在网站根目录下放置一份txt文件
* dns-01 需要给域名添加txt解析记录通配符域名只能用这种方式
* 证书续期: * 证书续期:
* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。 * 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。 * 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
@@ -155,12 +200,14 @@ https://afdian.com/a/greper
专业版特权对比 专业版特权对比
| 功能 | 免费版 | 专业版 | | 功能 | 基础版 | 专业版 |
|---------|-------------------|-----------------------| |------|-----------------|-------------------|
| 免费证书申请 | 免费无限制 | 免费无限制 | | 免费证书申请 | 免费无限制 | 无限制 |
| 自动部署插件 | 阿里云、腾讯云、七牛云、主机部署等 | 支持群晖、宝塔、1Panel等持续开发中 | | 域名数量 | 免费无限制 | 无限制 |
| 发邮件功能 | 需要配置 | 免配置 | | 证书流水线条数 | 免费无限制 | 无限制 |
| 证书流水线条数 | 10条 | 无限制 | | 站点证书监控 | 1 | 无限制 |
| 自动部署插件 | 阿里云、腾讯云、七牛云、SSH | 支持群晖、宝塔、1Panel等持续开发中 |
| 通知 | 邮件、webhook | server酱、企微、anpush等 |
************************ ************************

View File

@@ -1 +1 @@
01:55 00:02

View File

@@ -1,7 +1,7 @@
version: '3.3' # 兼容旧版docker-compose version: '3.3' # 兼容旧版docker-compose
services: services:
certd: certd:
# 镜像 # ↓↓↓↓↓ ---- 镜像版本号,建议改成固定版本号 # 镜像 # ↓↓↓↓↓ ---- 镜像版本号,建议改成固定版本号,例如certd:1.29.0
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
container_name: certd # 容器名 container_name: certd # 容器名
restart: unless-stopped # 自动重启 restart: unless-stopped # 自动重启
@@ -25,14 +25,22 @@ services:
# - 8.8.4.4 # - 8.8.4.4
# extra_hosts: # extra_hosts:
# # ↓↓↓↓ -------------------------------------------------------- 这里可以配置自定义hosts外网域名可以指向本地局域网ip地址 # # ↓↓↓↓ -------------------------------------------------------- 这里可以配置自定义hosts外网域名可以指向本地局域网ip地址
# - "localdomain.comm:192.168.1.3" # - "localdomain.com:192.168.1.3"
labels:
com.centurylinklabs.watchtower.enable: "true"
# ↓↓↓↓ -------------------------------------------------------------- 启用ipv6网络还需要把下面networks的注释放开
# networks:
# - ip6net
environment: environment:
# 设置环境变量即可自定义certd配置 # 设置环境变量即可自定义certd配置
# 配置项见: packages/ui/certd-server/src/config/config.default.ts # 配置项见: packages/ui/certd-server/src/config/config.default.ts
# 配置规则: certd_ + 配置项, 点号用_代替 # 配置规则: certd_ + 配置项, 点号用_代替
# #↓↓↓↓ ----------------------------- 如果忘记管理员密码可以设置为true重启之后管理员密码将改成123456然后请及时修改回false # #↓↓↓↓ ----------------------------- 如果忘记管理员密码可以设置为true重启之后管理员密码将改成123456然后请及时修改回false
- certd_system_resetAdminPasswd=false - certd_system_resetAdminPasswd=false
# 默认使用sqlite文件数据库如果需要使用其他数据库请设置以下环境变量
# 注意: 选定使用一种数据库之后,不支持更换数据库。
# 数据库迁移方法1、使用新数据库重新部署一套然后将旧数据同步过去注意flyway_history表的数据不要同步
# #↓↓↓↓ ----------------------------- 使用postgresql数据库需要提前创建数据库 # #↓↓↓↓ ----------------------------- 使用postgresql数据库需要提前创建数据库
# - certd_flyway_scriptDir=./db/migration-pg # 升级脚本目录 # - certd_flyway_scriptDir=./db/migration-pg # 升级脚本目录
# - certd_typeorm_dataSource_default_type=postgres # 数据库类型 # - certd_typeorm_dataSource_default_type=postgres # 数据库类型
@@ -51,13 +59,22 @@ services:
# - certd_typeorm_dataSource_default_password=yourpasswd # 密码 # - certd_typeorm_dataSource_default_password=yourpasswd # 密码
# - certd_typeorm_dataSource_default_database=certd # 数据库名 # - certd_typeorm_dataSource_default_database=certd # 数据库名
# ↓↓↓↓ --------------------------------------------------------- 自动升级上面certd的版本号要保持为latest
# 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=600 # 每 10 分钟检查一次更新
# ↓↓↓↓ -------------------------------------------------------------- 启用ipv6网络还需要把上面networks的注释放开
# #↓↓↓↓ ------------------------------------------------------------- 启用ipv6网络
# networks:
# - ip6net
#networks: #networks:
# ip6net: # ip6net:
# enable_ipv6: true # enable_ipv6: true

View File

@@ -57,6 +57,7 @@ export default defineConfig({
nav: [ nav: [
{ text: "首页", link: "/" }, { text: "首页", link: "/" },
{ text: "指南", link: "/guide/" }, { text: "指南", link: "/guide/" },
{ text: "商业版", link: "/comm/" },
{ text: "Demo体验", link: "https://certd.handfree.work" } { text: "Demo体验", link: "https://certd.handfree.work" }
], ],
sidebar: { sidebar: {
@@ -76,8 +77,8 @@ export default defineConfig({
{ text: "源码部署", link: "/guide/install/source/" } { text: "源码部署", link: "/guide/install/source/" }
] ]
}, },
{ text: "演示教程", link: "/guide/tutorial.md" } { text: "演示教程", link: "/guide/tutorial.md" },
{ text: "版本升级", link: "/guide/install/upgrade.md" }
] ]
}, },
{ {
@@ -116,8 +117,20 @@ export default defineConfig({
] ]
} }
],
"/comm/": [
{
text: "商业版",
items: [
{ text: "支付宝配置", link: "/comm/payments/alipay.md" },
{ text: "微信支付配置", link: "/comm/payments/wxpay.md" },
{ text: "彩虹易支付配置", link: "/comm/payments/yizhifu.md" },
]
}
] ]
,
}, },
socialLinks: [ socialLinks: [
{ icon: "github", link: "https://github.com/certd/certd" } { icon: "github", link: "https://github.com/certd/certd" }
], ],

BIN
docs/comm/images/index.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

9
docs/comm/index.md Normal file
View File

@@ -0,0 +1,9 @@
# 商业版文档
![](./images/index.png)
## 支付方式配置
* [支付宝支付配置](./payments/alipay.md)
* [微信支付配置](./payments/wxpay.md)
* [彩虹易支付配置](./payments/yizhifu.md)

View File

@@ -0,0 +1,21 @@
# 支付宝配置
## 配置步骤
1. 创建应用获取APPID
* 登录支付宝开放平台进入开发者中心创建网页应用获取应用的AppId左上角复制
* 开发者中心https://open.alipay.com/develop/manage
2. 进入应用详情,选择开发设置,配置接口加签方式 (选择密钥类型)
* 参考文档https://opendocs.alipay.com/common/02kdnc?pathHash=fb0c752a
* 此步骤完成后,可以获取应用的私钥、支付宝公钥。
* 注意:支付宝不会保存应用的私钥,你需要自己保管好私钥。
3. 在Certd后台配置支付宝
* 进入“系统”->"设置"->“支付设置”
* 启用支付宝,选择“支付宝配置”,点击添加
* 填写支付宝AppId、应用私钥、支付宝公钥等信息即可。

View File

@@ -0,0 +1,27 @@
# 微信支付配置
## 配置步骤
1. 开通Native支付
* 登录微信支付平台
* 进入产品中心: https://pay.weixin.qq.com/index.php/extend/product/lists?tid=3
* 选择开通Native支付
2. 申请证书
* 进入“账户中心”->“API安全”->“商户API证书”->“管理证书”
* 根据指引生成证书
* 得到私钥和公钥
3. 填写APIv3密钥
* 进入“账户中心”->“API安全”->“解密回调”
* 填写APIv3密钥
* 参考文档 https://kf.qq.com/faq/180830E36vyQ180830AZFZvu.html
4. 在Certd后台配置微信支付
* 进入“系统”->"设置"->“支付设置”
* 启用微信支付,选择“微信支付配置”,点击添加
* 填写微信支付商户号、证书私钥、证书公钥、APIv3密钥即可。

View File

@@ -0,0 +1,19 @@
# 彩虹易支付配置
彩虹易支付是一款非常流行的php聚合支付系统。
## 配置步骤
1. 获取商户ID、商户密钥
* 登录彩虹易支付平台
* 进入用户中心https://xxxxxx.com/user/userinfo.php?mod=api
* 点击API信息
* 可以复制接口地址、商户ID、商户密钥key
* 点击查看文档了解支持的签名类型一般为MD5
2. 进入Certd后台配置彩虹易支付
* 进入“系统”->"设置"->“支付设置”
* 启用彩虹易支付,选择“彩虹易支付配置”,点击添加
* 填写接口地址、商户ID、商户密钥、签名方式等信息即可。

View File

@@ -3,6 +3,110 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.4](https://github.com/certd/certd/compare/v1.29.3...v1.29.4) (2025-01-06)
### Bug Fixes
* 修复站点监控域名校验无法通过的bug ([1cb4a53](https://github.com/certd/certd/commit/1cb4a539cc523721ffd4b22d40d0e3d2d68cd915))
### Performance Improvements
* 优化腾讯云CLB插件支持非sni情况sni情况支持填写多个域名 ([635b042](https://github.com/certd/certd/commit/635b042690637bff85e97e07c7aac4b87a8a124b))
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
### Bug Fixes
* 修复系统级授权无法查看密钥的bug ([8644348](https://github.com/certd/certd/commit/8644348fc41ae2e1672f946ca37e5d3a674e0218))
### Performance Improvements
* 优化站点证书检查页面检查增加3次重试 ([e6dd7cd](https://github.com/certd/certd/commit/e6dd7cd54a3e23897031b5df6e0c3cdc0545d35a))
* 优化acme sdk ([54db744](https://github.com/certd/certd/commit/54db74428259de64d12230c2ab7353ae11197bbc))
* 支持http校验方式申请证书 ([405591c](https://github.com/certd/certd/commit/405591c5d08fa1a3b228ee3980199e7731cfec4a))
* http校验方式支持七牛云oss、阿里云oss、腾讯云cos ([3f74d4d](https://github.com/certd/certd/commit/3f74d4d9e5f5d0e629b44cff1895b3f7a8fbcafc))
## [1.29.2](https://github.com/certd/certd/compare/v1.29.1...v1.29.2) (2024-12-25)
### Bug Fixes
* 修复套餐关闭状态下仍然限制用户流水线数量的bug ([66fb9e5](https://github.com/certd/certd/commit/66fb9e5f49491f9c159363b48af14720a37673b1))
## [1.29.1](https://github.com/certd/certd/compare/v1.29.0...v1.29.1) (2024-12-25)
### Bug Fixes
* 免费套餐支持购买 ([f5ec987](https://github.com/certd/certd/commit/f5ec9870fd6af1f0c9099852bbdb4d07813ccce8))
* 修复某处金额转换丢失精度的bug ([d2d6f12](https://github.com/certd/certd/commit/d2d6f12218cbe7bd55f4ae082b93084be85f0a7b))
* 修复新版本小红点显示错误问题 ([fe4786e](https://github.com/certd/certd/commit/fe4786e168afe03a5243dd67971476c348339809))
### Performance Improvements
* 用户创建证书流水线没有购买套餐或者超限时提前报错 ([472f06c](https://github.com/certd/certd/commit/472f06c2d190d0ae48e8b53c18bc278437656a1c))
* 优化插件名称显示 ([26adf7d](https://github.com/certd/certd/commit/26adf7d437e674385f26a8f92fded6521a620671))
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Bug Fixes
* 修复手机模式下查询框被文字遮盖的bug ([040788c](https://github.com/certd/certd/commit/040788c793642c3bb2a3ede87fe30fcf3be471bd))
* 修复左侧菜单收起时无法展开子菜单的bug ([0056223](https://github.com/certd/certd/commit/005622307e612717a5408aa1484717ef03003a22))
### Features
* 基础版不再限制流水线数量 ([cb27d4b](https://github.com/certd/certd/commit/cb27d4b4906b2782eaceb0a95bbdc5d0534370d2))
* 套餐购买支持易支付、支付宝支付 ([faa28f8](https://github.com/certd/certd/commit/faa28f88f954cba4c1dd29125562e5acd2fd99af))
* 用户套餐,用户支付功能 ([a019956](https://github.com/certd/certd/commit/a019956698acaf2c4beb620b5ad8c18918ead6a1))
* 站点证书监控 ([9c8c7a7](https://github.com/certd/certd/commit/9c8c7a781223f4217f45510db1e89495600e3cd5))
* 支持微信支付 ([45d6347](https://github.com/certd/certd/commit/45d6347f5b6199493b11aabdd74177f6dca2cea4))
### Performance Improvements
* 调整创建证书表单字段的顺序 ([d393521](https://github.com/certd/certd/commit/d3935219f2aa50d6662c5b5ebf7ee25ad696ab2b))
* 同一时间只允许一个套餐生效 ([8ebf95a](https://github.com/certd/certd/commit/8ebf95a222a900d1707716c7b1f3b39f8a6d8f94))
* 用户名支持修改 ([89c7f07](https://github.com/certd/certd/commit/89c7f070343e86453c84677ebe1669f9b266d871))
* 优化证书申请跳过的状态显示,成功通知现在在跳过时不会发送 ([67d762b](https://github.com/certd/certd/commit/67d762b6a520f1fa24719a124e5ae975a81f5f82))
* 站点证书监控通知发送,每天定时检查 ([bb4910f](https://github.com/certd/certd/commit/bb4910f4e57234e42b44505f4620ae7af66025c5))
* 支持一体证书 ([53c38cf](https://github.com/certd/certd/commit/53c38cf714a6f7486abbf1d71c9f48f56a790100))
* 支持plesk网站证书部署 ([eda45c1](https://github.com/certd/certd/commit/eda45c1528199648b3970505e87f492d398226cd))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
### Bug Fixes
* 修复证书成功通知发送失败的bug ([0f5c690](https://github.com/certd/certd/commit/0f5c69040ba77340c909813220a26bc7ddada3ea))
### Performance Improvements
* 群晖支持6.x ([79f7ec4](https://github.com/certd/certd/commit/79f7ec4672f4fd5744cc45e4a6f104da943f4026))
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
### Bug Fixes
* 修复没有配置eab时报order无法读取的问题 ([657a2ae](https://github.com/certd/certd/commit/657a2ae032e6f61ac27fbdd26c7bf169c041219e))
* 修复授权被删除后无法清空的bug ([b45977c](https://github.com/certd/certd/commit/b45977c29a29084c11e496bec3415eaaebafdd74))
* mysql下access.setting字段改成text ([b7f5740](https://github.com/certd/certd/commit/b7f5740c57743914f754f3b4fdd94b59a2e8338c))
### Performance Improvements
* 点击版本红点按钮,跳转到升级帮助页面 ([454fbda](https://github.com/certd/certd/commit/454fbda581bbe22abca5b91e5086ea9d9d58a020))
* 通知标题优化 ([ff083ce](https://github.com/certd/certd/commit/ff083ce6848a8bee3c8248e4b881086ae1517c28))
* 支持腾讯虚拟机开关机([@wujingke](https://github.com/wujingke)) ([8039e8b](https://github.com/certd/certd/commit/8039e8baf83c82d03f1a6198cf61c372026b962b))
* 支持aws cloudfront ([0ae39f1](https://github.com/certd/certd/commit/0ae39f160a7c6b6696b3bf513d68aa28905810ad))
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
### Bug Fixes
* 修复创建流水线通知设置无效的bug ([498cf34](https://github.com/certd/certd/commit/498cf34999fddfa24ce088e2e678469fa669abb8))
* 修复流水线分组可以被所有人看见的bug ([a0e838d](https://github.com/certd/certd/commit/a0e838d1eec918e5dc92fe95dc72ac14facb930e))
### Performance Improvements
* 优化数据表索引 ([228fdf0](https://github.com/certd/certd/commit/228fdf0a0d28013f5dd156a97bbde80537e8e97e))
* 支持mysql ([7cde1fd](https://github.com/certd/certd/commit/7cde1fdc4a9ed851900d231a5460c8dbfbcd148e))
## [1.28.1](https://github.com/certd/certd/compare/v1.28.0...v1.28.1) (2024-12-08) ## [1.28.1](https://github.com/certd/certd/compare/v1.28.0...v1.28.1) (2024-12-08)
### Bug Fixes ### Bug Fixes

View File

@@ -106,4 +106,4 @@ throw new Error("错误信息")
## 五、贡献插件送激活码 ## 五、贡献插件送激活码
- PR要求插件功能完整代码规范 - PR要求插件功能完整代码规范
- PR通过后联系我们送您一个专业版激活码 - PR通过后联系我们送您一个半年期专业版激活码

View File

@@ -14,7 +14,7 @@ Certd 是一款开源、免费、全自动申请和部署更新SSL证书的工
* 支持通配符域名/泛域名,支持多个域名打到一个证书上 * 支持通配符域名/泛域名,支持多个域名打到一个证书上
* 邮件通知 * 邮件通知
* 私有化部署,保障数据安全 * 私有化部署,保障数据安全
* 支持sqlitepostgresql数据库 * 支持SQLite、Postgresql、MySQL数据库
## 二、一些说明 ## 二、一些说明

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -31,15 +31,12 @@ admin/123456
## 三、升级 ## 三、升级
1. 找到容器,点击编辑 1. 找到容器,点击更多->升级
![](./images/edit1.png) ![](./images/upgrade-1.png)
2. 将latest修改为最新版本号 2. 选择强制拉取镜像,点击确认即可
![](https://img.shields.io/npm/v/%40certd%2Fpipeline) ![img.png](./images/upgrade-2.png)
![img.png](./images/edit2.png)
3. 点击确定,重启容器
## 四、数据备份 ## 四、数据备份

View File

@@ -38,28 +38,12 @@ admin/123456
登录后请及时修改密码 登录后请及时修改密码
## 三、如何升级 ## 三、如何升级
宝塔升级certd非常简单
### 1. 应用商店安装,直接更新镜像即可 `docker`->`容器编排`->`左侧选择Certd`->`更新镜像`
`docker`->`容器编排`->`左侧选择Certd-xxxx`->`更新镜像`
![img.png](./images/upgrade.png) ![img.png](./images/upgrade.png)
### 2. latest更新方式
在主机上拉取最新镜像,然后面板上重启容器
```shell
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
```
### 3. 固定版本号方式
修改容器编排模版中的镜像版本号,然后面板上重启容器
```shell
services:
certd:
# 镜像 # 修改最新版本号 ---- ↓↓↓↓↓
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:v1.xx.x
```
## 四、数据备份 ## 四、数据备份
### 4.1 应用商店部署方式 ### 4.1 应用商店部署方式

View File

@@ -8,7 +8,7 @@
```shell ```shell
# 克隆代码 # 克隆代码
git clone https://github.com/certd/certd git clone https://github.com/certd/certd
# git checkout v1.x.x # 1.x.x换成最新版本号当v2主干分支代码无法正常启动时可以尝试此命令 # git checkout v1.x.x # 当v2主干分支代码无法正常启动时可以尝试此命令1.x.x换成最新版本号
cd certd cd certd
# 启动服务 # 启动服务
./start.sh ./start.sh
@@ -29,9 +29,15 @@ https://your_server_ip:7002
## 二、升级 ## 二、升级
```shell ```shell
# 更新代码并启动
cd certd cd certd
# 确保数据安全,备份一下数据
cp -rf ./packages/ui/certd-server/data ../certd-data-backup
git pull git pull
# 如果提示pull失败可以尝试强制更新
# git checkout v2 -f && git pull
# 先停止旧的服务,7001是certd的默认端口 # 先停止旧的服务,7001是certd的默认端口
kill -9 $(lsof -t -i:7001) kill -9 $(lsof -t -i:7001)
# 重新编译启动 # 重新编译启动

View File

@@ -0,0 +1,12 @@
# 版本升级
## 升级方法
根据不同部署方式查看升级方法
1. [Docker方式部署升级](./docker/#二、升级)
2. [宝塔面板方式部署升级](./baota/#三、如何升级)
3. [1Panel面板方式部署升级](./1panel/#三、升级)
4. [源码方式部署](./source/#二、升级)
## 升级日志
[CHANGELOG](../changelogs/CHANGELOG.md)

View File

@@ -0,0 +1,10 @@
# 证书申请失败情况
## DNS记录问题
1. DNS 不要设置CAA记录删除即可
2. DNSSEC相关报错DNSSEC管理中删除即可
3. DNS 有其他平台申请过的_acme-challenge记录删除即可

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,8 +1,12 @@
# 群晖部署和证书更新 # 群晖部署和证书更新
支持群晖`6.x``7.x`
## 一、群晖部署Certd ## 一、群晖部署Certd
以下是群晖`7.x`的部署`certd`步骤。
群晖`6.x`请参考[docker部署](./../../install/docker/)
### 1. 打开Container Manager ### 1. 打开Container Manager
![](./images/1.png) ![](./images/1.png)
@@ -32,6 +36,8 @@
## 二、更新群晖证书 ## 二、更新群晖证书
证书部署插件支持群晖`6.x``7.x`
## 1. 前提条件 ## 1. 前提条件
* 已经部署了certd * 已经部署了certd
* 群晖上已经设置好了证书(证书建议设置好描述,插件需要根据描述查找证书) * 群晖上已经设置好了证书(证书建议设置好描述,插件需要根据描述查找证书)

View File

@@ -30,7 +30,7 @@ features:
- title: 多证书格式支持 - title: 多证书格式支持
details: 支持pem、pfx、der、jks等多种证书格式支持Google、Letsencrypt、ZeroSSL证书颁发机构 details: 支持pem、pfx、der、jks等多种证书格式支持Google、Letsencrypt、ZeroSSL证书颁发机构
- title: 支持私有化部署 - title: 支持私有化部署
details: 保障数据安全 details: 授权数据加密存储,保障数据安全
- title: 多数据库支持 - title: 多数据库支持
details: 支持sqlitepostgresql数据库 details: 支持SQLite、Postgresql、MySQL数据库
--- ---

View File

@@ -9,5 +9,5 @@
} }
}, },
"npmClient": "pnpm", "npmClient": "pnpm",
"version": "1.28.2" "version": "1.29.5"
} }

View File

@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.5](https://github.com/publishlab/node-acme-client/compare/v1.29.4...v1.29.5) (2025-01-07)
**Note:** Version bump only for package @certd/acme-client
## [1.29.4](https://github.com/publishlab/node-acme-client/compare/v1.29.3...v1.29.4) (2025-01-06)
**Note:** Version bump only for package @certd/acme-client
## [1.29.3](https://github.com/publishlab/node-acme-client/compare/v1.29.2...v1.29.3) (2025-01-04)
### Performance Improvements
* 优化acme sdk ([54db744](https://github.com/publishlab/node-acme-client/commit/54db74428259de64d12230c2ab7353ae11197bbc))
## [1.29.2](https://github.com/publishlab/node-acme-client/compare/v1.29.1...v1.29.2) (2024-12-25)
**Note:** Version bump only for package @certd/acme-client
## [1.29.1](https://github.com/publishlab/node-acme-client/compare/v1.29.0...v1.29.1) (2024-12-25)
**Note:** Version bump only for package @certd/acme-client
# [1.29.0](https://github.com/publishlab/node-acme-client/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/acme-client
## [1.28.4](https://github.com/publishlab/node-acme-client/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/acme-client
## [1.28.3](https://github.com/publishlab/node-acme-client/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/acme-client
## [1.28.2](https://github.com/publishlab/node-acme-client/compare/v1.28.1...v1.28.2) (2024-12-09) ## [1.28.2](https://github.com/publishlab/node-acme-client/compare/v1.28.1...v1.28.2) (2024-12-09)
### Performance Improvements ### Performance Improvements

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client", "description": "Simple and unopinionated ACME client",
"private": false, "private": false,
"author": "nmorsman", "author": "nmorsman",
"version": "1.28.2", "version": "1.29.5",
"type": "module", "type": "module",
"module": "scr/index.js", "module": "scr/index.js",
"main": "src/index.js", "main": "src/index.js",
@@ -18,7 +18,7 @@
"types" "types"
], ],
"dependencies": { "dependencies": {
"@certd/basic": "^1.28.2", "@certd/basic": "^1.29.5",
"@peculiar/x509": "^1.11.0", "@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5", "asn1js": "^3.0.5",
"axios": "^1.7.2", "axios": "^1.7.2",
@@ -65,5 +65,5 @@
"bugs": { "bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues" "url": "https://github.com/publishlab/node-acme-client/issues"
}, },
"gitHead": "a6cd532035f55a7ce122ea1229bb65f9d41e7125" "gitHead": "94509c64b954e17e4842b02c5c9fa0649bce13d4"
} }

View File

@@ -99,31 +99,14 @@ export default async (client, userOpts) => {
return; return;
} }
const keyAuthorizationGetter = async (challenge) => {
return await client.getChallengeKeyAuthorization(challenge);
}
try { try {
/* Select challenge based on priority */
const challenge = authz.challenges.sort((a, b) => {
const aidx = opts.challengePriority.indexOf(a.type);
const bidx = opts.challengePriority.indexOf(b.type);
if (aidx === -1) return 1;
if (bidx === -1) return -1;
return aidx - bidx;
}).slice(0, 1)[0];
if (!challenge) {
throw new Error(`Unable to select challenge for ${d}, no challenge found`);
}
log(`[auto] [${d}] Found ${authz.challenges.length} challenges, selected type: ${challenge.type}`);
/* Trigger challengeCreateFn() */
log(`[auto] [${d}] Trigger challengeCreateFn()`); log(`[auto] [${d}] Trigger challengeCreateFn()`);
const keyAuthorization = await client.getChallengeKeyAuthorization(challenge);
try { try {
const { recordReq, recordRes, dnsProvider } = await opts.challengeCreateFn(authz, challenge, keyAuthorization); const { recordReq, recordRes, dnsProvider,challenge ,keyAuthorization} = await opts.challengeCreateFn(authz, keyAuthorizationGetter);
log(`[auto] [${d}] challengeCreateFn success`);
log(`[auto] [${d}] add challengeRemoveFn()`);
clearTasks.push(async () => { clearTasks.push(async () => {
/* Trigger challengeRemoveFn(), suppress errors */ /* Trigger challengeRemoveFn(), suppress errors */
log(`[auto] [${d}] Trigger challengeRemoveFn()`); log(`[auto] [${d}] Trigger challengeRemoveFn()`);
@@ -141,7 +124,7 @@ export default async (client, userOpts) => {
await wait(60 * 1000); await wait(60 * 1000);
} }
else { else {
log(`[auto] [${d}] Running challenge verification`); log(`[auto] [${d}] Running challenge verification, type = ${challenge.type}`);
try { try {
await client.verifyChallenge(authz, challenge); await client.verifyChallenge(authz, challenge);
} }

View File

@@ -5,3 +5,5 @@ export class CancelError extends Error {
} }
} }

View File

@@ -4,6 +4,8 @@
import { AxiosInstance } from 'axios'; import { AxiosInstance } from 'axios';
import * as rfc8555 from './rfc8555'; import * as rfc8555 from './rfc8555';
import {CancelError} from '../src/error.js'
export * from '../src/error.js'
export type PrivateKeyBuffer = Buffer; export type PrivateKeyBuffer = Buffer;
export type PublicKeyBuffer = Buffer; export type PublicKeyBuffer = Buffer;
@@ -56,7 +58,7 @@ export interface ClientExternalAccountBindingOptions {
export interface ClientAutoOptions { export interface ClientAutoOptions {
csr: CsrBuffer | CsrString; csr: CsrBuffer | CsrString;
challengeCreateFn: (authz: Authorization, challenge: rfc8555.Challenge, keyAuthorization: string) => Promise<{recordReq:any,recordRes:any,dnsProvider:any}>; 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) => Promise<any>;
email?: string; email?: string;
termsOfServiceAgreed?: boolean; termsOfServiceAgreed?: boolean;
@@ -202,4 +204,4 @@ export function setLogger(fn: (message: any, ...args: any[]) => void): void;
export function walkTxtRecord(record: any): Promise<string[]>; export function walkTxtRecord(record: any): Promise<string[]>;
export const CancelError: Error; export const CancelError: typeof CancelError;

View File

@@ -3,6 +3,48 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.5](https://github.com/certd/certd/compare/v1.29.4...v1.29.5) (2025-01-07)
**Note:** Version bump only for package @certd/basic
## [1.29.4](https://github.com/certd/certd/compare/v1.29.3...v1.29.4) (2025-01-06)
**Note:** Version bump only for package @certd/basic
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
**Note:** Version bump only for package @certd/basic
## [1.29.2](https://github.com/certd/certd/compare/v1.29.1...v1.29.2) (2024-12-25)
**Note:** Version bump only for package @certd/basic
## [1.29.1](https://github.com/certd/certd/compare/v1.29.0...v1.29.1) (2024-12-25)
### Bug Fixes
* 修复某处金额转换丢失精度的bug ([d2d6f12](https://github.com/certd/certd/commit/d2d6f12218cbe7bd55f4ae082b93084be85f0a7b))
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Features
* 用户套餐,用户支付功能 ([a019956](https://github.com/certd/certd/commit/a019956698acaf2c4beb620b5ad8c18918ead6a1))
* 支持微信支付 ([45d6347](https://github.com/certd/certd/commit/45d6347f5b6199493b11aabdd74177f6dca2cea4))
### Performance Improvements
* 站点证书监控通知发送,每天定时检查 ([bb4910f](https://github.com/certd/certd/commit/bb4910f4e57234e42b44505f4620ae7af66025c5))
* 支持plesk网站证书部署 ([eda45c1](https://github.com/certd/certd/commit/eda45c1528199648b3970505e87f492d398226cd))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/basic
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/basic
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09) ## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
**Note:** Version bump only for package @certd/basic **Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
22:42 23:14

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/basic", "name": "@certd/basic",
"private": false, "private": false,
"version": "1.28.2", "version": "1.29.5",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@@ -23,6 +23,7 @@
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"lru-cache": "^10.0.0", "lru-cache": "^10.0.0",
"mitt": "^3.0.1",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
"nodemailer": "^6.9.3" "nodemailer": "^6.9.3"
@@ -43,5 +44,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "a6cd532035f55a7ce122ea1229bb65f9d41e7125" "gitHead": "94509c64b954e17e4842b02c5c9fa0649bce13d4"
} }

View File

@@ -8,6 +8,11 @@ export * from './util.hash.js';
export * from './util.merge.js'; export * from './util.merge.js';
export * from './util.cache.js'; export * from './util.cache.js';
export * from './util.string.js'; export * from './util.string.js';
export * from './util.lock.js';
export * from './util.mitter.js';
export * from './util.id.js';
export * from './util.domain.js';
export * from './util.amount.js';
import { stringUtils } from './util.string.js'; import { stringUtils } from './util.string.js';
import sleep from './util.sleep.js'; import sleep from './util.sleep.js';
import { http, download } from './util.request.js'; import { http, download } from './util.request.js';
@@ -22,8 +27,11 @@ import { cache } from './util.cache.js';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { domainUtils } from './util.domain.js'; import { domainUtils } from './util.domain.js';
import { optionsUtils } from './util.options.js'; import { optionsUtils } from './util.options.js';
import { amountUtils } from './util.amount.js';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import * as id from './util.id.js'; import * as id from './util.id.js';
import { locker } from './util.lock.js';
import { mitter } from './util.mitter.js';
export const utils = { export const utils = {
sleep, sleep,
http, http,
@@ -41,4 +49,7 @@ export const utils = {
domain: domainUtils, domain: domainUtils,
options: optionsUtils, options: optionsUtils,
string: stringUtils, string: stringUtils,
locker,
mitter,
amount: amountUtils,
}; };

View File

@@ -0,0 +1,9 @@
export const amountUtils = {
toCent(amount: number): number {
return parseInt((amount * 100).toFixed(0));
},
toYuan(amount: number): number {
return parseFloat((amount / 100).toFixed(2));
},
};

View File

@@ -1,4 +1,4 @@
export function isDev() { export function isDev() {
const nodeEnv = process.env.NODE_ENV || ''; const nodeEnv = process.env.NODE_ENV || '';
return nodeEnv === 'development' || nodeEnv.indexOf('local') >= 0; return nodeEnv === 'development' || nodeEnv.includes('local') || nodeEnv.startsWith('dev');
} }

View File

@@ -3,7 +3,10 @@ import crypto from 'crypto';
function md5(data: string) { function md5(data: string) {
return crypto.createHash('md5').update(data).digest('hex'); return crypto.createHash('md5').update(data).digest('hex');
} }
function sha256(data: string) {
return crypto.createHash('sha256').update(data).digest('hex');
}
export const hashUtils = { export const hashUtils = {
md5, md5,
sha256,
}; };

View File

@@ -0,0 +1,47 @@
import { logger, utils } from './index.js';
export class Locker {
locked: Record<string, any> = {};
async execute(lockStr: string, callback: any) {
await this.lock(lockStr);
const timeoutId = setTimeout(() => {
logger.warn('Lock timeout,自动解锁', lockStr);
this.unlock(lockStr);
}, 20000);
try {
return await callback();
} finally {
clearTimeout(timeoutId);
this.unlock(lockStr);
}
}
async lock(str: string) {
const isLocked = this.isLocked(str);
if (isLocked) {
let count = 0;
while (true) {
await utils.sleep(100);
if (!this.isLocked(str)) {
break;
}
count++;
if (count > 20) {
throw new Error('Lock timeout');
}
}
}
this.locked[str] = true;
}
unlock(str: string) {
delete this.locked[str];
}
isLocked(str: string) {
return this.locked[str] ?? false;
}
}
export const locker = new Locker();

View File

@@ -0,0 +1,2 @@
import mitt from 'mitt';
export const mitter = mitt();

View File

@@ -37,6 +37,8 @@ function buildGroupOptions(options: any[], inDomains: string[]) {
} }
export const optionsUtils = { export const optionsUtils = {
//获取分组
groupByDomain, groupByDomain,
//构建分组后的选项列表,常用
buildGroupOptions, buildGroupOptions,
}; };

View File

@@ -1,4 +1,4 @@
import axios, { AxiosRequestConfig } from 'axios'; import axios, { AxiosHeaders, AxiosRequestConfig } from 'axios';
import { ILogger, logger } from './util.log.js'; import { ILogger, logger } from './util.log.js';
import { Logger } from 'log4js'; import { Logger } from 'log4js';
import { HttpProxyAgent } from 'http-proxy-agent'; import { HttpProxyAgent } from 'http-proxy-agent';
@@ -13,7 +13,7 @@ export class HttpError extends Error {
statusText?: string; statusText?: string;
code?: string; code?: string;
request?: { baseURL: string; url: string; method: string; params?: any; data?: any }; request?: { baseURL: string; url: string; method: string; params?: any; data?: any };
response?: { data: any }; response?: { data: any; headers: AxiosHeaders };
cause?: any; cause?: any;
constructor(error: any) { constructor(error: any) {
if (!error) { if (!error) {
@@ -55,6 +55,7 @@ export class HttpError extends Error {
this.response = { this.response = {
data: error.response?.data, data: error.response?.data,
headers: error.response?.headers,
}; };
const { stack, cause } = error; const { stack, cause } = error;
@@ -156,13 +157,13 @@ export function createAxiosService({ logger }: { logger: Logger }) {
error.message = '请求错误'; error.message = '请求错误';
break; break;
case 401: case 401:
error.message = '未授权,请登录'; error.message = '认证/登录失败';
break; break;
case 403: case 403:
error.message = '拒绝访问'; error.message = '拒绝访问';
break; break;
case 404: case 404:
error.message = `请求地址出错: ${error.response.config.url}`; error.message = `请求地址出错`;
break; break;
case 408: case 408:
error.message = '请求超时'; error.message = '请求超时';
@@ -216,6 +217,7 @@ export type HttpRequestConfig<D = any> = {
logParams?: boolean; logParams?: boolean;
logRes?: boolean; logRes?: boolean;
httpProxy?: string; httpProxy?: string;
returnResponse?: boolean;
} & AxiosRequestConfig<D>; } & AxiosRequestConfig<D>;
export type HttpClient = { export type HttpClient = {
request<D = any, R = any>(config: HttpRequestConfig<D>): Promise<HttpClientResponse<R>>; request<D = any, R = any>(config: HttpRequestConfig<D>): Promise<HttpClientResponse<R>>;

View File

@@ -3,6 +3,56 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.5](https://github.com/certd/certd/compare/v1.29.4...v1.29.5) (2025-01-07)
**Note:** Version bump only for package @certd/pipeline
## [1.29.4](https://github.com/certd/certd/compare/v1.29.3...v1.29.4) (2025-01-06)
**Note:** Version bump only for package @certd/pipeline
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
### Performance Improvements
* 支持http校验方式申请证书 ([405591c](https://github.com/certd/certd/commit/405591c5d08fa1a3b228ee3980199e7731cfec4a))
## [1.29.2](https://github.com/certd/certd/compare/v1.29.1...v1.29.2) (2024-12-25)
**Note:** Version bump only for package @certd/pipeline
## [1.29.1](https://github.com/certd/certd/compare/v1.29.0...v1.29.1) (2024-12-25)
### Performance Improvements
* 用户创建证书流水线没有购买套餐或者超限时提前报错 ([472f06c](https://github.com/certd/certd/commit/472f06c2d190d0ae48e8b53c18bc278437656a1c))
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Features
* 套餐购买支持易支付、支付宝支付 ([faa28f8](https://github.com/certd/certd/commit/faa28f88f954cba4c1dd29125562e5acd2fd99af))
* 支持微信支付 ([45d6347](https://github.com/certd/certd/commit/45d6347f5b6199493b11aabdd74177f6dca2cea4))
### Performance Improvements
* 同一时间只允许一个套餐生效 ([8ebf95a](https://github.com/certd/certd/commit/8ebf95a222a900d1707716c7b1f3b39f8a6d8f94))
* 优化证书申请跳过的状态显示,成功通知现在在跳过时不会发送 ([67d762b](https://github.com/certd/certd/commit/67d762b6a520f1fa24719a124e5ae975a81f5f82))
* 支持plesk网站证书部署 ([eda45c1](https://github.com/certd/certd/commit/eda45c1528199648b3970505e87f492d398226cd))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
### Bug Fixes
* 修复证书成功通知发送失败的bug ([0f5c690](https://github.com/certd/certd/commit/0f5c69040ba77340c909813220a26bc7ddada3ea))
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
### Performance Improvements
* 通知标题优化 ([ff083ce](https://github.com/certd/certd/commit/ff083ce6848a8bee3c8248e4b881086ae1517c28))
* 支持aws cloudfront ([0ae39f1](https://github.com/certd/certd/commit/0ae39f160a7c6b6696b3bf513d68aa28905810ad))
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09) ## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
**Note:** Version bump only for package @certd/pipeline **Note:** Version bump only for package @certd/pipeline

View File

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

View File

@@ -10,7 +10,8 @@ import { Decorator } from "../decorator/index.js";
import { ICnameProxyService, IEmailService, IPluginConfigService, IUrlService } from "../service/index.js"; import { ICnameProxyService, IEmailService, IPluginConfigService, IUrlService } from "../service/index.js";
import { FileStore } from "./file-store.js"; import { FileStore } from "./file-store.js";
import { cloneDeep, forEach, merge } from "lodash-es"; import { cloneDeep, forEach, merge } from "lodash-es";
import { INotificationService, NotificationBody, NotificationContext, notificationRegistry } from "../notification/index.js"; import { INotificationService } from "../notification/index.js";
export type SysInfo = { export type SysInfo = {
//系统标题 //系统标题
title?: string; title?: string;
@@ -91,20 +92,30 @@ export class Executor {
await this.onChanged(this.runtime); await this.onChanged(this.runtime);
}, 5000); }, 5000);
await this.runWithHistory(this.pipeline, "pipeline", async () => { const result = await this.runWithHistory(this.pipeline, "pipeline", async () => {
return await this.runStages(this.pipeline); return await this.runStages(this.pipeline);
}); });
if (this.lastRuntime && this.lastRuntime.pipeline.status?.status === ResultType.error) { if (result === ResultType.success) {
await this.notification("turnToSuccess"); if (this.lastRuntime && this.lastRuntime.pipeline.status?.status === ResultType.error) {
await this.notification("turnToSuccess");
} else {
await this.notification("success");
}
} }
await this.notification("success"); return result;
} catch (e: any) { } catch (e: any) {
await this.notification("error", e); await this.notification("error", e);
this.logger.error("pipeline 执行失败", e); this.logger.error("pipeline 执行失败", e);
} finally { } finally {
clearInterval(intervalFlushLogId); clearInterval(intervalFlushLogId);
await this.onChanged(this.runtime); await this.onChanged(this.runtime);
await this.pipelineContext.setObj("lastRuntime", this.runtime); //保存之前移除logs
const lastRuntime: any = {
...this.runtime,
};
delete lastRuntime.logs;
delete lastRuntime._loggers;
await this.pipelineContext.setObj("lastRuntime", lastRuntime);
this.logger.info(`pipeline.${this.pipeline.id} end`); this.logger.info(`pipeline.${this.pipeline.id} end`);
} }
} }
@@ -335,7 +346,7 @@ export class Executor {
instance.setCtx(taskCtx); instance.setCtx(taskCtx);
await instance.onInstance(); await instance.onInstance();
await instance.execute(); const result = await instance.execute();
//执行结果处理 //执行结果处理
if (instance._result.clearLastStatus) { if (instance._result.clearLastStatus) {
//是否需要清除所有状态 //是否需要清除所有状态
@@ -363,6 +374,8 @@ export class Executor {
merge(vars, instance._result.pipelinePrivateVars); merge(vars, instance._result.pipelinePrivateVars);
await this.pipelineContext.setObj("privateVars", vars); await this.pipelineContext.setObj("privateVars", vars);
} }
return result;
} }
async notification(when: NotificationWhen, error?: any) { async notification(when: NotificationWhen, error?: any) {
@@ -373,19 +386,18 @@ export class Executor {
let subject = ""; let subject = "";
let content = ""; let content = "";
const errorMessage = error?.message; const errorMessage = error?.message;
const sysTitle = this.options.sysInfo?.title || "Certd";
if (when === "start") { if (when === "start") {
subject = `${sysTitle}开始执行,${this.pipeline.title}${this.pipeline.id}`; subject = `开始执行,${this.pipeline.title}${this.pipeline.id}`;
content = `流水线ID:${this.pipeline.id}运行ID:${this.runtime.id}`; content = `流水线ID:${this.pipeline.id}运行ID:${this.runtime.id}`;
} else if (when === "success") { } else if (when === "success") {
subject = `${sysTitle}执行成功,${this.pipeline.title}${this.pipeline.id}`; subject = `执行成功,${this.pipeline.title}${this.pipeline.id}`;
content = `流水线ID:${this.pipeline.id}运行ID:${this.runtime.id}`; content = `流水线ID:${this.pipeline.id}运行ID:${this.runtime.id}`;
} else if (when === "turnToSuccess") { } else if (when === "turnToSuccess") {
subject = `${sysTitle}执行成功(失败转成功),${this.pipeline.title}${this.pipeline.id}`; subject = `执行成功(失败转成功),${this.pipeline.title}${this.pipeline.id}`;
content = `流水线ID:${this.pipeline.id}运行ID:${this.runtime.id}`; content = `流水线ID:${this.pipeline.id}运行ID:${this.runtime.id}`;
} else if (when === "error") { } else if (when === "error") {
subject = `${sysTitle}执行失败,${this.pipeline.title}${this.pipeline.id}`; subject = `执行失败,${this.pipeline.title}${this.pipeline.id}`;
content = `流水线ID:${this.pipeline.id}运行ID:${this.runtime.id}\n错误详情:${error.message}`; content = `流水线ID:${this.pipeline.id}运行ID:${this.runtime.id}\n\n${this.currentStatusMap?.currentStep?.title} 执行失败\n\n错误详情:${error.message}`;
} else { } else {
return; return;
} }
@@ -407,43 +419,24 @@ export class Executor {
} }
} else { } else {
try { try {
//构建notification插件发送通知
let notifyConfig: any;
if (notification.notificationId === 0) {
notifyConfig = await this.options.notificationService.getDefault();
} else {
notifyConfig = await this.options.notificationService.getById(notification.notificationId);
}
if (notifyConfig == null) {
throw new Error(`通知配置<id:${notification.notificationId}>不存在`);
}
const notificationPlugin = notificationRegistry.get(notifyConfig.type);
const notificationCls: any = notificationPlugin.target;
const notificationSender = new notificationCls();
const notificationCtx: NotificationContext = {
http: utils.http,
logger,
utils,
emailService: this.options.emailService,
};
//设置参数
merge(notificationSender, notifyConfig.setting);
notificationSender.setCtx(notificationCtx);
await notificationSender.onInstance();
const body: NotificationBody = {
title: subject,
content,
userId: this.pipeline.userId,
pipeline: this.pipeline,
result: this.lastRuntime.pipeline.status,
pipelineId: this.pipeline.id,
historyId: this.runtime.id,
errorMessage,
url,
};
//发送通知 //发送通知
await notificationSender.send(body); await this.options.notificationService.send({
id: notification.notificationId,
useDefault: true,
useEmail: false,
logger: this.logger,
body: {
title: subject,
content,
userId: this.pipeline.userId,
pipeline: this.pipeline,
result: this.lastRuntime.pipeline.status,
pipelineId: this.pipeline.id,
historyId: this.runtime.id,
errorMessage,
url,
},
});
} catch (e) { } catch (e) {
logger.error("send notification error", e); logger.error("send notification error", e);
} }

View File

@@ -134,6 +134,7 @@ export class RunHistory {
export class RunnableCollection { export class RunnableCollection {
private collection: RunnableMap = {}; private collection: RunnableMap = {};
private pipeline!: Pipeline; private pipeline!: Pipeline;
currentStep!: Step;
constructor(pipeline?: Pipeline) { constructor(pipeline?: Pipeline) {
if (!pipeline) { if (!pipeline) {
return; return;
@@ -143,6 +144,23 @@ export class RunnableCollection {
this.collection = map; this.collection = map;
} }
static initPipelineRunnableType(pipeline: Pipeline) {
pipeline.runnableType = "pipeline";
if (pipeline.stages === undefined) {
pipeline.stages = [];
return;
}
pipeline.stages.forEach((stage) => {
stage.runnableType = "stage";
stage.tasks.forEach((task) => {
task.runnableType = "task";
task.steps.forEach((step) => {
step.runnableType = "step";
});
});
});
}
static each<T extends Runnable>(list: T[], exec: (item: Runnable) => void) { static each<T extends Runnable>(list: T[], exec: (item: Runnable) => void) {
list.forEach((item) => { list.forEach((item) => {
exec(item); exec(item);
@@ -193,5 +211,8 @@ export class RunnableCollection {
add(runnable: Runnable) { add(runnable: Runnable) {
this.collection[runnable.id] = runnable; this.collection[runnable.id] = runnable;
if (runnable.runnableType === "step") {
this.currentStep = runnable as Step;
}
} }
} }

View File

@@ -48,9 +48,18 @@ export type NotificationInstanceConfig = {
}; };
}; };
export type NotificationSendReq = {
id?: number;
useDefault?: boolean;
useEmail?: boolean;
emailAddress?: string;
logger: ILogger;
body: NotificationBody;
};
export interface INotificationService { export interface INotificationService {
getById(id: number): Promise<NotificationInstanceConfig>; getById(id: number): Promise<NotificationInstanceConfig>;
getDefault(): Promise<NotificationInstanceConfig>; getDefault(): Promise<NotificationInstanceConfig>;
send(req: NotificationSendReq): Promise<void>;
} }
export interface INotification { export interface INotification {

View File

@@ -1,9 +1,9 @@
// src/decorator/memoryCache.decorator.ts // src/decorator/memoryCache.decorator.ts
import { Decorator } from "../decorator/index.js"; import { Decorator } from "../decorator/index.js";
import * as _ from "lodash-es"; import * as _ from "lodash-es";
import { merge } from "lodash-es";
import { notificationRegistry } from "./registry.js"; import { notificationRegistry } from "./registry.js";
import { BaseNotification, NotificationBody, NotificationContext, NotificationDefine, NotificationInputDefine, NotificationInstanceConfig } from "./api.js"; import { BaseNotification, NotificationBody, NotificationContext, NotificationDefine, NotificationInputDefine, NotificationInstanceConfig } from "./api.js";
import { isPlus } from "@certd/plus-core";
// 提供一个唯一 key // 提供一个唯一 key
export const NOTIFICATION_CLASS_KEY = "pipeline:notification"; export const NOTIFICATION_CLASS_KEY = "pipeline:notification";
@@ -47,9 +47,7 @@ export async function newNotification(type: string, input: any, ctx: Notificatio
// @ts-ignore // @ts-ignore
const plugin = new register.target(); const plugin = new register.target();
for (const key in input) { merge(plugin, input);
plugin[key] = input[key];
}
if (!ctx) { if (!ctx) {
throw new Error("ctx is required"); throw new Error("ctx is required");
} }
@@ -61,8 +59,5 @@ export async function newNotification(type: string, input: any, ctx: Notificatio
export async function sendNotification(opts: { config: NotificationInstanceConfig; ctx: NotificationContext; body: NotificationBody }) { export async function sendNotification(opts: { config: NotificationInstanceConfig; ctx: NotificationContext; body: NotificationBody }) {
const notification: BaseNotification = await newNotification(opts.config.type, opts.config.setting, opts.ctx); const notification: BaseNotification = await newNotification(opts.config.type, opts.config.setting, opts.ctx);
if (notification.define.needPlus && !isPlus()) {
opts.body.content = `${opts.body.content}\n\n注意此通知渠道已调整为专业版功能后续版本将不再支持发送请尽快修改或升级为专业版`;
}
await notification.doSend(opts.body); await notification.doSend(opts.body);
} }

View File

@@ -59,7 +59,7 @@ export type PluginDefine = Registrable & {
export type ITaskPlugin = { export type ITaskPlugin = {
onInstance(): Promise<void>; onInstance(): Promise<void>;
execute(): Promise<void>; execute(): Promise<void | string>;
onRequest(req: PluginRequestHandleReq<any>): Promise<any>; onRequest(req: PluginRequestHandleReq<any>): Promise<any>;
[key: string]: any; [key: string]: any;
}; };
@@ -184,7 +184,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
return; return;
} }
abstract execute(): Promise<void>; abstract execute(): Promise<void | string>;
appendTimeSuffix(name?: string) { appendTimeSuffix(name?: string) {
if (name == null) { if (name == null) {

View File

@@ -21,8 +21,9 @@ export const pluginGroups = {
huawei: new PluginGroup("huawei", "华为云", 3), huawei: new PluginGroup("huawei", "华为云", 3),
tencent: new PluginGroup("tencent", "腾讯云", 4), tencent: new PluginGroup("tencent", "腾讯云", 4),
qiniu: new PluginGroup("qiniu", "七牛云", 5), qiniu: new PluginGroup("qiniu", "七牛云", 5),
host: new PluginGroup("host", "主机", 6), aws: new PluginGroup("aws", "亚马逊云", 6),
cdn: new PluginGroup("cdn", "CDN", 7), host: new PluginGroup("host", "主机", 7),
panel: new PluginGroup("panel", "面板", 8), cdn: new PluginGroup("cdn", "CDN", 8),
other: new PluginGroup("other", "其他", 9), panel: new PluginGroup("panel", "面板", 9),
other: new PluginGroup("other", "其他", 10),
}; };

View File

@@ -18,6 +18,7 @@ export type CnameRecord = {
status: string; status: string;
commonDnsProvider?: any; commonDnsProvider?: any;
}; };
export type ICnameProxyService = { export type ICnameProxyService = {
getByDomain: (domain: string) => Promise<CnameRecord>; getByDomain: (domain: string) => Promise<CnameRecord>;
}; };

View File

@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.5](https://github.com/certd/certd/compare/v1.29.4...v1.29.5) (2025-01-07)
**Note:** Version bump only for package @certd/lib-huawei
## [1.29.4](https://github.com/certd/certd/compare/v1.29.3...v1.29.4) (2025-01-06)
**Note:** Version bump only for package @certd/lib-huawei
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
**Note:** Version bump only for package @certd/lib-huawei
## [1.29.2](https://github.com/certd/certd/compare/v1.29.1...v1.29.2) (2024-12-25)
**Note:** Version bump only for package @certd/lib-huawei
## [1.29.1](https://github.com/certd/certd/compare/v1.29.0...v1.29.1) (2024-12-25)
**Note:** Version bump only for package @certd/lib-huawei
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/lib-huawei
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/lib-huawei
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/lib-huawei
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09) ## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
**Note:** Version bump only for package @certd/lib-huawei **Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-huawei", "name": "@certd/lib-huawei",
"private": false, "private": false,
"version": "1.28.2", "version": "1.29.5",
"main": "./dist/bundle.js", "main": "./dist/bundle.js",
"module": "./dist/bundle.js", "module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts", "types": "./dist/d/index.d.ts",
@@ -21,5 +21,5 @@
"prettier": "^2.8.8", "prettier": "^2.8.8",
"tslib": "^2.8.1" "tslib": "^2.8.1"
}, },
"gitHead": "a6cd532035f55a7ce122ea1229bb65f9d41e7125" "gitHead": "94509c64b954e17e4842b02c5c9fa0649bce13d4"
} }

View File

@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.5](https://github.com/certd/certd/compare/v1.29.4...v1.29.5) (2025-01-07)
**Note:** Version bump only for package @certd/lib-iframe
## [1.29.4](https://github.com/certd/certd/compare/v1.29.3...v1.29.4) (2025-01-06)
**Note:** Version bump only for package @certd/lib-iframe
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
**Note:** Version bump only for package @certd/lib-iframe
## [1.29.2](https://github.com/certd/certd/compare/v1.29.1...v1.29.2) (2024-12-25)
**Note:** Version bump only for package @certd/lib-iframe
## [1.29.1](https://github.com/certd/certd/compare/v1.29.0...v1.29.1) (2024-12-25)
**Note:** Version bump only for package @certd/lib-iframe
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/lib-iframe
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/lib-iframe
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/lib-iframe
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09) ## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
**Note:** Version bump only for package @certd/lib-iframe **Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-iframe", "name": "@certd/lib-iframe",
"private": false, "private": false,
"version": "1.28.2", "version": "1.29.5",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@@ -30,5 +30,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "a6cd532035f55a7ce122ea1229bb65f9d41e7125" "gitHead": "94509c64b954e17e4842b02c5c9fa0649bce13d4"
} }

View File

@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.5](https://github.com/certd/certd/compare/v1.29.4...v1.29.5) (2025-01-07)
**Note:** Version bump only for package @certd/lib-k8s
## [1.29.4](https://github.com/certd/certd/compare/v1.29.3...v1.29.4) (2025-01-06)
**Note:** Version bump only for package @certd/lib-k8s
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
**Note:** Version bump only for package @certd/lib-k8s
## [1.29.2](https://github.com/certd/certd/compare/v1.29.1...v1.29.2) (2024-12-25)
**Note:** Version bump only for package @certd/lib-k8s
## [1.29.1](https://github.com/certd/certd/compare/v1.29.0...v1.29.1) (2024-12-25)
**Note:** Version bump only for package @certd/lib-k8s
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/lib-k8s
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/lib-k8s
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/lib-k8s
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09) ## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
**Note:** Version bump only for package @certd/lib-k8s **Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-k8s", "name": "@certd/lib-k8s",
"private": false, "private": false,
"version": "1.28.2", "version": "1.29.5",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@@ -16,7 +16,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@certd/basic": "^1.28.2", "@certd/basic": "^1.29.5",
"@kubernetes/client-node": "0.21.0" "@kubernetes/client-node": "0.21.0"
}, },
"devDependencies": { "devDependencies": {
@@ -31,5 +31,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "a6cd532035f55a7ce122ea1229bb65f9d41e7125" "gitHead": "94509c64b954e17e4842b02c5c9fa0649bce13d4"
} }

View File

@@ -3,6 +3,48 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.5](https://github.com/certd/certd/compare/v1.29.4...v1.29.5) (2025-01-07)
**Note:** Version bump only for package @certd/lib-server
## [1.29.4](https://github.com/certd/certd/compare/v1.29.3...v1.29.4) (2025-01-06)
**Note:** Version bump only for package @certd/lib-server
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
### Bug Fixes
* 修复系统级授权无法查看密钥的bug ([8644348](https://github.com/certd/certd/commit/8644348fc41ae2e1672f946ca37e5d3a674e0218))
## [1.29.2](https://github.com/certd/certd/compare/v1.29.1...v1.29.2) (2024-12-25)
**Note:** Version bump only for package @certd/lib-server
## [1.29.1](https://github.com/certd/certd/compare/v1.29.0...v1.29.1) (2024-12-25)
**Note:** Version bump only for package @certd/lib-server
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Features
* 基础版不再限制流水线数量 ([cb27d4b](https://github.com/certd/certd/commit/cb27d4b4906b2782eaceb0a95bbdc5d0534370d2))
* 套餐购买支持易支付、支付宝支付 ([faa28f8](https://github.com/certd/certd/commit/faa28f88f954cba4c1dd29125562e5acd2fd99af))
* 用户套餐,用户支付功能 ([a019956](https://github.com/certd/certd/commit/a019956698acaf2c4beb620b5ad8c18918ead6a1))
### Performance Improvements
* 站点证书监控通知发送,每天定时检查 ([bb4910f](https://github.com/certd/certd/commit/bb4910f4e57234e42b44505f4620ae7af66025c5))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/lib-server
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/lib-server
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09) ## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
**Note:** Version bump only for package @certd/lib-server **Note:** Version bump only for package @certd/lib-server

View File

@@ -1,6 +1,6 @@
{ {
"name": "@certd/lib-server", "name": "@certd/lib-server",
"version": "1.28.2", "version": "1.29.5",
"description": "midway with flyway, sql upgrade way ", "description": "midway with flyway, sql upgrade way ",
"private": false, "private": false,
"type": "module", "type": "module",
@@ -27,10 +27,10 @@
], ],
"license": "AGPL", "license": "AGPL",
"dependencies": { "dependencies": {
"@certd/acme-client": "^1.28.2", "@certd/acme-client": "^1.29.5",
"@certd/basic": "^1.28.2", "@certd/basic": "^1.29.5",
"@certd/pipeline": "^1.28.2", "@certd/pipeline": "^1.29.5",
"@certd/plus-core": "^1.28.2", "@certd/plus-core": "^1.29.5",
"@midwayjs/cache": "~3.14.0", "@midwayjs/cache": "~3.14.0",
"@midwayjs/core": "~3.17.1", "@midwayjs/core": "~3.17.1",
"@midwayjs/i18n": "~3.17.3", "@midwayjs/i18n": "~3.17.3",
@@ -61,5 +61,5 @@
"typeorm": "^0.3.11", "typeorm": "^0.3.11",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "a6cd532035f55a7ce122ea1229bb65f9d41e7125" "gitHead": "94509c64b954e17e4842b02c5c9fa0649bce13d4"
} }

View File

@@ -25,7 +25,7 @@ export abstract class BaseController {
* @param msg * @param msg
* @param code * @param code
*/ */
fail(msg: string, code: any) { fail(msg: string, code?: any) {
return { return {
code: code ? code : Constants.res.error.code, code: code ? code : Constants.res.error.code,
msg: msg ? msg : Constants.res.error.code, msg: msg ? msg : Constants.res.error.code,
@@ -39,4 +39,12 @@ export abstract class BaseController {
} }
return userId; return userId;
} }
getLoginUser() {
const user = this.ctx.user;
if (user == null) {
throw new Error('Token已过期');
}
return user;
}
} }

View File

@@ -3,6 +3,7 @@ import { In, Repository, SelectQueryBuilder } from 'typeorm';
import { Inject } from '@midwayjs/core'; import { Inject } from '@midwayjs/core';
import { TypeORMDataSourceManager } from '@midwayjs/typeorm'; import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { EntityManager } from 'typeorm/entity-manager/EntityManager.js'; import { EntityManager } from 'typeorm/entity-manager/EntityManager.js';
import { FindManyOptions } from 'typeorm';
export type PageReq<T = any> = { export type PageReq<T = any> = {
page?: { offset: number; limit: number }; page?: { offset: number; limit: number };
@@ -15,6 +16,7 @@ export type ListReq<T = any> = {
asc: boolean; asc: boolean;
}; };
buildQuery?: (bq: SelectQueryBuilder<any>) => void; buildQuery?: (bq: SelectQueryBuilder<any>) => void;
select?: any;
}; };
/** /**
@@ -53,7 +55,7 @@ export abstract class BaseService<T> {
* 非分页查询 * 非分页查询
* @param options * @param options
*/ */
async find(options) { async find(options: FindManyOptions<T>) {
return await this.getRepository().find(options); return await this.getRepository().find(options);
} }
@@ -99,7 +101,7 @@ export abstract class BaseService<T> {
* 新增|修改 * 新增|修改
* @param param 数据 * @param param 数据
*/ */
async addOrUpdate(param) { async addOrUpdate(param: any) {
await this.getRepository().save(param); await this.getRepository().save(param);
} }
@@ -107,7 +109,7 @@ export abstract class BaseService<T> {
* 新增 * 新增
* @param param 数据 * @param param 数据
*/ */
async add(param) { async add(param: any) {
const now = new Date(); const now = new Date();
param.createTime = now; param.createTime = now;
param.updateTime = now; param.updateTime = now;
@@ -122,7 +124,7 @@ export abstract class BaseService<T> {
* 修改 * 修改
* @param param 数据 * @param param 数据
*/ */
async update(param) { async update(param: any) {
if (!param.id) throw new ValidateException('id 不能为空'); if (!param.id) throw new ValidateException('id 不能为空');
param.updateTime = new Date(); param.updateTime = new Date();
await this.addOrUpdate(param); await this.addOrUpdate(param);
@@ -148,6 +150,7 @@ export abstract class BaseService<T> {
page.limit = 20; page.limit = 20;
} }
const qb = this.buildListQuery(pageReq); const qb = this.buildListQuery(pageReq);
qb.offset(page.offset).limit(page.limit); qb.offset(page.offset).limit(page.limit);
const list = await qb.getMany(); const list = await qb.getMany();
const total = await qb.getCount(); const total = await qb.getCount();

View File

@@ -36,6 +36,10 @@ export const Constants = {
code: 88, code: 88,
message: '需要VIP', message: '需要VIP',
}, },
needsuite: {
code: 89,
message: '需要购买或升级套餐',
},
loginError: { loginError: {
code: 2, code: 2,
message: '登录失败', message: '登录失败',

View File

@@ -49,4 +49,10 @@ export abstract class CrudController<T> extends BaseController {
await this.getService().delete([id]); await this.getService().delete([id]);
return this.ok(null); return this.ok(null);
} }
@Post('/deleteByIds')
async deleteByIds(@Body('ids') ids: number[]) {
await this.getService().delete(ids);
return this.ok(null);
}
} }

View File

@@ -8,3 +8,9 @@ export class NeedVIPException extends BaseException {
super('NeedVIPException', Constants.res.needvip.code, message ? message : Constants.res.needvip.message); super('NeedVIPException', Constants.res.needvip.code, message ? message : Constants.res.needvip.message);
} }
} }
export class NeedSuiteException extends BaseException {
constructor(message) {
super('NeedSuiteException', Constants.res.needsuite.code, message ? message : Constants.res.needsuite.message);
}
}

View File

@@ -1,7 +1,8 @@
import { SysSettingsEntity } from './system/index.js'; import { SysSettingsEntity } from './system/index.js';
import { AccessEntity } from './user/access/entity/access.js';
export * from './basic/index.js'; export * from './basic/index.js';
export * from './system/index.js'; export * from './system/index.js';
export * from './user/index.js';
export { LibServerConfiguration as Configuration } from './configuration.js'; export { LibServerConfiguration as Configuration } from './configuration.js';
export const libServerEntities = [SysSettingsEntity]; export const libServerEntities = [SysSettingsEntity, AccessEntity];

View File

@@ -14,7 +14,7 @@ export const uploadTmpFileCacheKey = 'tmpfile_key_';
/** /**
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class FileService { export class FileService {
async saveFile(userId: number, tmpCacheKey: any, permission: 'public' | 'private') { async saveFile(userId: number, tmpCacheKey: any, permission: 'public' | 'private') {
if (tmpCacheKey.startsWith(`/${permission}`)) { if (tmpCacheKey.startsWith(`/${permission}`)) {

View File

@@ -5,7 +5,7 @@ import { SysInstallInfo, SysLicenseInfo, SysSettingsService } from '../../settin
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class PlusService { export class PlusService {
@Inject() @Inject()
sysSettingsService: SysSettingsService; sysSettingsService: SysSettingsService;

View File

@@ -112,6 +112,17 @@ export class SysSecretBackup extends BaseSettings {
encryptSecret?: string; encryptSecret?: string;
} }
/**
* 不要修改
*/
export class SysSecret extends BaseSettings {
static __title__ = '密钥信息';
static __key__ = 'sys.secret';
static __access__ = 'private';
siteId?: string;
encryptSecret?: string;
}
export class SysSiteEnv { export class SysSiteEnv {
agent?: { agent?: {
enabled?: boolean; enabled?: boolean;
@@ -136,3 +147,35 @@ export class SysHeaderMenus extends BaseSettings {
menus: MenuItem[]; menus: MenuItem[];
} }
export type PaymentItem = {
enabled: boolean;
accessId?: number;
};
export class SysPaymentSetting extends BaseSettings {
static __title__ = '支付设置';
static __key__ = 'sys.payment';
static __access__ = 'private';
yizhifu?: PaymentItem = { enabled: false };
alipay?: PaymentItem = { enabled: false };
wxpay?: PaymentItem = { enabled: false };
}
export class SysSuiteSetting extends BaseSettings {
static __title__ = '套餐设置';
static __key__ = 'sys.suite';
static __access__ = 'private';
enabled = false;
registerGift?: {
productId: number;
duration: number;
};
intro?: string;
}

View File

@@ -1,13 +1,13 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { SysSettingsEntity } from '../entity/sys-settings.js'; import { SysSettingsEntity } from '../entity/sys-settings.js';
import { CacheManager } from '@midwayjs/cache'; import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecretBackup } from './models.js';
import * as _ from 'lodash-es'; import * as _ from 'lodash-es';
import { BaseService } from '../../../basic/index.js'; import { BaseService } from '../../../basic/index.js';
import { logger, setGlobalProxy } from '@certd/basic'; import { cache, logger, setGlobalProxy } from '@certd/basic';
import * as dns from 'node:dns'; import * as dns from 'node:dns';
/** /**
* 设置 * 设置
*/ */
@@ -17,9 +17,6 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
@InjectEntityModel(SysSettingsEntity) @InjectEntityModel(SysSettingsEntity)
repository: Repository<SysSettingsEntity>; repository: Repository<SysSettingsEntity>;
@Inject()
cache: CacheManager; // 依赖注入CacheManager
getRepository() { getRepository() {
return this.repository; return this.repository;
} }
@@ -72,7 +69,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
async getSetting<T>(type: any): Promise<T> { async getSetting<T>(type: any): Promise<T> {
const key = type.__key__; const key = type.__key__;
const cacheKey = type.getCacheKey(); const cacheKey = type.getCacheKey();
const settings: T = await this.cache.get(cacheKey); const settings: T = cache.get(cacheKey);
if (settings) { if (settings) {
return settings; return settings;
} }
@@ -80,7 +77,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
const savedSettings = await this.getSettingByKey(key); const savedSettings = await this.getSettingByKey(key);
newSetting = _.merge(newSetting, savedSettings); newSetting = _.merge(newSetting, savedSettings);
await this.saveSetting(newSetting); await this.saveSetting(newSetting);
await this.cache.set(cacheKey, newSetting); cache.set(cacheKey, newSetting);
return newSetting; return newSetting;
} }
@@ -93,6 +90,12 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
if (entity) { if (entity) {
entity.setting = JSON.stringify(bean); entity.setting = JSON.stringify(bean);
entity.access = type.__access__; entity.access = type.__access__;
if (key === SysSecretBackup.__key__) {
//备份密钥不允许更新
return;
}
await this.repository.save(entity); await this.repository.save(entity);
} else { } else {
const newEntity = new SysSettingsEntity(); const newEntity = new SysSettingsEntity();
@@ -103,7 +106,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
await this.repository.save(newEntity); await this.repository.save(newEntity);
} }
await this.cache.set(cacheKey, bean); cache.set(cacheKey, bean);
} }
async getPublicSettings(): Promise<SysPublicSettings> { async getPublicSettings(): Promise<SysPublicSettings> {
@@ -146,7 +149,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
} else { } else {
throw new Error('该设置不存在'); throw new Error('该设置不存在');
} }
await this.cache.del(`settings.${key}`); cache.delete(`settings.${key}`);
} }
async backupSecret() { async backupSecret() {
@@ -173,4 +176,20 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
} }
} }
} }
async getSecret() {
const sysSecret = await this.getSetting<SysSecret>(SysSecret);
if (sysSecret.encryptSecret) {
return sysSecret;
}
//从备份中读取
const settings = await this.getSettingByKey(SysSecretBackup.__key__);
if (settings == null || !settings.encryptSecret) {
throw new Error('密钥备份不存在');
}
sysSecret.siteId = settings.siteId;
sysSecret.encryptSecret = settings.encryptSecret;
await this.saveSetting(sysSecret);
logger.info('密钥恢复成功');
return sysSecret;
}
} }

View File

@@ -0,0 +1,5 @@
export * from './entity/access.js';
export * from './service/access-service.js';
export * from './service/access-sys-getter.js';
export * from './service/access-getter.js';
export * from './service/encrypt-service.js';

View File

@@ -1,7 +1,7 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { BaseService, PageReq, PermissionException, ValidateException } from '@certd/lib-server'; import { BaseService, PageReq, PermissionException, ValidateException } from '../../../index.js';
import { AccessEntity } from '../entity/access.js'; import { AccessEntity } from '../entity/access.js';
import { AccessDefine, accessRegistry, newAccess } from '@certd/pipeline'; import { AccessDefine, accessRegistry, newAccess } from '@certd/pipeline';
import { EncryptService } from './encrypt-service.js'; import { EncryptService } from './encrypt-service.js';
@@ -10,7 +10,7 @@ import { EncryptService } from './encrypt-service.js';
* *
*/ */
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Request, { allowDowngrade: true })
export class AccessService extends BaseService<AccessEntity> { export class AccessService extends BaseService<AccessEntity> {
@InjectEntityModel(AccessEntity) @InjectEntityModel(AccessEntity)
repository: Repository<AccessEntity>; repository: Repository<AccessEntity>;
@@ -18,6 +18,7 @@ export class AccessService extends BaseService<AccessEntity> {
@Inject() @Inject()
encryptService: EncryptService; encryptService: EncryptService;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore //@ts-ignore
getRepository() { getRepository() {
return this.repository; return this.repository;

View File

@@ -1,7 +1,6 @@
import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import crypto from 'crypto'; import crypto from 'crypto';
import { SysSettingsService } from '@certd/lib-server'; import { SysSecret, SysSettingsService } from '../../../system/index.js';
import { SysPrivateSettings } from '@certd/lib-server';
/** /**
* *
@@ -16,8 +15,8 @@ export class EncryptService {
@Init() @Init()
async init() { async init() {
const privateInfo: SysPrivateSettings = await this.sysSettingService.getSetting(SysPrivateSettings); const secret: SysSecret = await this.sysSettingService.getSecret();
this.secretKey = Buffer.from(privateInfo.encryptSecret, 'base64'); this.secretKey = Buffer.from(secret.encryptSecret, 'base64');
} }
// 加密函数 // 加密函数

View File

@@ -0,0 +1 @@
export * from './access/index.js';

View File

@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.5](https://github.com/certd/certd/compare/v1.29.4...v1.29.5) (2025-01-07)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.29.4](https://github.com/certd/certd/compare/v1.29.3...v1.29.4) (2025-01-06)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.29.2](https://github.com/certd/certd/compare/v1.29.1...v1.29.2) (2024-12-25)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.29.1](https://github.com/certd/certd/compare/v1.29.0...v1.29.1) (2024-12-25)
**Note:** Version bump only for package @certd/midway-flyway-js
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09) ## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
**Note:** Version bump only for package @certd/midway-flyway-js **Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{ {
"name": "@certd/midway-flyway-js", "name": "@certd/midway-flyway-js",
"version": "1.28.2", "version": "1.29.5",
"description": "midway with flyway, sql upgrade way ", "description": "midway with flyway, sql upgrade way ",
"private": false, "private": false,
"type": "module", "type": "module",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11", "typeorm": "^0.3.11",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "a6cd532035f55a7ce122ea1229bb65f9d41e7125" "gitHead": "94509c64b954e17e4842b02c5c9fa0649bce13d4"
} }

View File

@@ -3,6 +3,59 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.5](https://github.com/certd/certd/compare/v1.29.4...v1.29.5) (2025-01-07)
### Bug Fixes
* 修复复制到本机插件pfx格式复制时报错的bug ([f57116d](https://github.com/certd/certd/commit/f57116d2bebf33e47ad93e0b39c4efe8e4aea25c))
## [1.29.4](https://github.com/certd/certd/compare/v1.29.3...v1.29.4) (2025-01-06)
**Note:** Version bump only for package @certd/plugin-cert
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
### Performance Improvements
* 优化acme sdk ([54db744](https://github.com/certd/certd/commit/54db74428259de64d12230c2ab7353ae11197bbc))
* 支持http校验方式申请证书 ([405591c](https://github.com/certd/certd/commit/405591c5d08fa1a3b228ee3980199e7731cfec4a))
* http校验方式支持七牛云oss、阿里云oss、腾讯云cos ([3f74d4d](https://github.com/certd/certd/commit/3f74d4d9e5f5d0e629b44cff1895b3f7a8fbcafc))
## [1.29.2](https://github.com/certd/certd/compare/v1.29.1...v1.29.2) (2024-12-25)
**Note:** Version bump only for package @certd/plugin-cert
## [1.29.1](https://github.com/certd/certd/compare/v1.29.0...v1.29.1) (2024-12-25)
**Note:** Version bump only for package @certd/plugin-cert
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
### Bug Fixes
* 修复左侧菜单收起时无法展开子菜单的bug ([0056223](https://github.com/certd/certd/commit/005622307e612717a5408aa1484717ef03003a22))
### Performance Improvements
* 调整创建证书表单字段的顺序 ([d393521](https://github.com/certd/certd/commit/d3935219f2aa50d6662c5b5ebf7ee25ad696ab2b))
* 优化证书申请跳过的状态显示,成功通知现在在跳过时不会发送 ([67d762b](https://github.com/certd/certd/commit/67d762b6a520f1fa24719a124e5ae975a81f5f82))
* 站点证书监控通知发送,每天定时检查 ([bb4910f](https://github.com/certd/certd/commit/bb4910f4e57234e42b44505f4620ae7af66025c5))
* 支持一体证书 ([53c38cf](https://github.com/certd/certd/commit/53c38cf714a6f7486abbf1d71c9f48f56a790100))
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/plugin-cert
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
### Bug Fixes
* 修复没有配置eab时报order无法读取的问题 ([657a2ae](https://github.com/certd/certd/commit/657a2ae032e6f61ac27fbdd26c7bf169c041219e))
### Performance Improvements
* 通知标题优化 ([ff083ce](https://github.com/certd/certd/commit/ff083ce6848a8bee3c8248e4b881086ae1517c28))
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09) ## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
**Note:** Version bump only for package @certd/plugin-cert **Note:** Version bump only for package @certd/plugin-cert

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/plugin-cert", "name": "@certd/plugin-cert",
"private": false, "private": false,
"version": "1.28.2", "version": "1.29.5",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -15,9 +15,10 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@certd/acme-client": "^1.28.2", "@certd/acme-client": "^1.29.5",
"@certd/basic": "^1.28.2", "@certd/basic": "^1.29.5",
"@certd/pipeline": "^1.28.2", "@certd/pipeline": "^1.29.5",
"@certd/plugin-lib": "^1.29.5",
"@google-cloud/publicca": "^1.3.0", "@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"jszip": "^3.10.1", "jszip": "^3.10.1",
@@ -40,5 +41,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "a6cd532035f55a7ce122ea1229bb65f9d41e7125" "gitHead": "94509c64b954e17e4842b02c5c9fa0649bce13d4"
} }

View File

@@ -6,31 +6,47 @@ import { Challenge } from "@certd/acme-client/types/rfc8555";
import { IContext } from "@certd/pipeline"; import { IContext } from "@certd/pipeline";
import { ILogger, utils } from "@certd/basic"; import { ILogger, utils } from "@certd/basic";
import { IDnsProvider, parseDomain } from "../../dns-provider/index.js"; import { IDnsProvider, parseDomain } from "../../dns-provider/index.js";
import { HttpChallengeUploader } from "./uploads/api.js";
export type CnameVerifyPlan = { export type CnameVerifyPlan = {
type?: string;
domain: string; domain: string;
fullRecord: string; fullRecord: string;
dnsProvider: IDnsProvider; dnsProvider: IDnsProvider;
}; };
export type HttpVerifyPlan = {
type: string;
domain: string;
httpUploader: HttpChallengeUploader;
};
export type DomainVerifyPlan = { export type DomainVerifyPlan = {
domain: string; domain: string;
type: "cname" | "dns"; type: "cname" | "dns" | "http";
dnsProvider?: IDnsProvider; dnsProvider?: IDnsProvider;
cnameVerifyPlan?: Record<string, CnameVerifyPlan>; cnameVerifyPlan?: Record<string, CnameVerifyPlan>;
httpVerifyPlan?: Record<string, HttpVerifyPlan>;
}; };
export type DomainsVerifyPlan = { export type DomainsVerifyPlan = {
[key: string]: DomainVerifyPlan; [key: string]: DomainVerifyPlan;
}; };
export type Providers = {
dnsProvider?: IDnsProvider;
domainsVerifyPlan?: DomainsVerifyPlan;
httpUploader?: HttpChallengeUploader;
};
export type CertInfo = { export type CertInfo = {
crt: string; crt: string; //fullchain证书
key: string; key: string; //私钥
csr: string; csr: string; //csr
ic?: string; oc?: string; //仅证书非fullchain证书
ic?: string; //中间证书
pfx?: string; pfx?: string;
der?: string; der?: string;
jks?: string; jks?: string;
one?: string;
}; };
export type SSLProvider = "letsencrypt" | "google" | "zerossl"; export type SSLProvider = "letsencrypt" | "google" | "zerossl";
export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521"; export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521";
@@ -153,58 +169,37 @@ export class AcmeService {
return key.toString(); return key.toString();
} }
async challengeCreateFn(authz: any, challenge: any, keyAuthorization: string, dnsProvider: IDnsProvider, domainsVerifyPlan: DomainsVerifyPlan) { async challengeCreateFn(authz: any, keyAuthorizationGetter: (challenge: Challenge) => Promise<string>, providers: Providers) {
this.logger.info("Triggered challengeCreateFn()"); this.logger.info("Triggered challengeCreateFn()");
/* http-01 */
const fullDomain = authz.identifier.value; const fullDomain = authz.identifier.value;
if (challenge.type === "http-01") { let domain = parseDomain(fullDomain);
const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`; this.logger.info("主域名为:" + domain);
const getChallenge = (type: string) => {
return authz.challenges.find((c: any) => c.type === type);
};
const doHttpVerify = async (challenge: any, httpUploader: HttpChallengeUploader) => {
const keyAuthorization = await keyAuthorizationGetter(challenge);
this.logger.info("http校验");
const filePath = `.well-known/acme-challenge/${challenge.token}`;
const fileContents = keyAuthorization; const fileContents = keyAuthorization;
this.logger.info(`校验 ${fullDomain} ,准备上传文件:${filePath}`);
await httpUploader.upload(filePath, Buffer.from(fileContents));
this.logger.info(`上传文件【${filePath}】成功`);
return {
challenge,
keyAuthorization,
httpUploader,
};
};
this.logger.info(`Creating challenge response for ${fullDomain} at path: ${filePath}`); const doDnsVerify = async (challenge: any, fullRecord: string, dnsProvider: IDnsProvider) => {
this.logger.info("dns校验");
const keyAuthorization = await keyAuthorizationGetter(challenge);
/* Replace this */
this.logger.info(`Would write "${fileContents}" to path "${filePath}"`);
// await fs.writeFileAsync(filePath, fileContents);
} else if (challenge.type === "dns-01") {
/* dns-01 */
let fullRecord = `_acme-challenge.${fullDomain}`;
const recordValue = keyAuthorization; const recordValue = keyAuthorization;
this.logger.info(`Creating TXT record for ${fullDomain}: ${fullRecord}`);
/* Replace this */
this.logger.info(`Would create TXT record "${fullRecord}" with value "${recordValue}"`);
let domain = parseDomain(fullDomain);
this.logger.info("解析到域名domain=" + domain);
if (domainsVerifyPlan) {
//按照计划执行
const domainVerifyPlan = domainsVerifyPlan[domain];
if (domainVerifyPlan) {
if (domainVerifyPlan.type === "dns") {
dnsProvider = domainVerifyPlan.dnsProvider;
} else if (domainVerifyPlan.type === "cname") {
const cnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan;
if (cnameVerifyPlan) {
const cname = cnameVerifyPlan[fullDomain];
if (cname) {
dnsProvider = cname.dnsProvider;
domain = parseDomain(cname.domain);
fullRecord = cname.fullRecord;
}
} else {
this.logger.error("未找到域名Cname校验计划使用默认的dnsProvider");
}
} else {
this.logger.error("不支持的校验类型", domainVerifyPlan.type);
}
} else {
this.logger.info("未找到域名校验计划使用默认的dnsProvider");
}
}
let hostRecord = fullRecord.replace(`${domain}`, ""); let hostRecord = fullRecord.replace(`${domain}`, "");
if (hostRecord.endsWith(".")) { if (hostRecord.endsWith(".")) {
hostRecord = hostRecord.substring(0, hostRecord.length - 1); hostRecord = hostRecord.substring(0, hostRecord.length - 1);
@@ -224,8 +219,54 @@ export class AcmeService {
recordReq, recordReq,
recordRes, recordRes,
dnsProvider, dnsProvider,
challenge,
keyAuthorization,
}; };
};
let dnsProvider = providers.dnsProvider;
let fullRecord = `_acme-challenge.${fullDomain}`;
if (providers.domainsVerifyPlan) {
//按照计划执行
const domainVerifyPlan = providers.domainsVerifyPlan[domain];
if (domainVerifyPlan) {
if (domainVerifyPlan.type === "dns") {
dnsProvider = domainVerifyPlan.dnsProvider;
} else if (domainVerifyPlan.type === "cname") {
const cnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan;
if (cnameVerifyPlan) {
const cname = cnameVerifyPlan[fullDomain];
if (cname) {
dnsProvider = cname.dnsProvider;
domain = parseDomain(cname.domain);
fullRecord = cname.fullRecord;
}
} else {
this.logger.error("未找到域名Cname校验计划使用默认的dnsProvider");
}
} else if (domainVerifyPlan.type === "http") {
const httpVerifyPlan = domainVerifyPlan.httpVerifyPlan;
if (httpVerifyPlan) {
const httpChallenge = getChallenge("http-01");
if (httpChallenge == null) {
throw new Error("该域名不支持http-01方式校验");
}
const plan = httpVerifyPlan[fullDomain];
return await doHttpVerify(httpChallenge, plan.httpUploader);
} else {
throw new Error("未找到域名【" + fullDomain + "】的http校验配置");
}
} else {
throw new Error("不支持的校验类型", domainVerifyPlan.type);
}
} else {
this.logger.info("未找到域名校验计划使用默认的dnsProvider");
}
} }
const dnsChallenge = getChallenge("dns-01");
return await doDnsVerify(dnsChallenge, fullRecord, dnsProvider);
} }
/** /**
@@ -237,22 +278,28 @@ export class AcmeService {
* @param recordReq * @param recordReq
* @param recordRes * @param recordRes
* @param dnsProvider dnsProvider * @param dnsProvider dnsProvider
* @param httpUploader
* @returns {Promise} * @returns {Promise}
*/ */
async challengeRemoveFn(authz: any, challenge: any, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider: IDnsProvider) { async challengeRemoveFn(
this.logger.info("Triggered challengeRemoveFn()"); authz: any,
challenge: any,
keyAuthorization: string,
recordReq: any,
recordRes: any,
dnsProvider?: IDnsProvider,
httpUploader?: HttpChallengeUploader
) {
this.logger.info("执行清理");
/* http-01 */ /* http-01 */
const fullDomain = authz.identifier.value; const fullDomain = authz.identifier.value;
if (challenge.type === "http-01") { if (challenge.type === "http-01") {
const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`; const filePath = `.well-known/acme-challenge/${challenge.token}`;
this.logger.info(`Removing challenge response for ${fullDomain} at file: ${filePath}`);
this.logger.info(`Removing challenge response for ${fullDomain} at path: ${filePath}`); await httpUploader.remove(filePath);
this.logger.info(`删除文件【${filePath}】成功`);
/* Replace this */
this.logger.info(`Would remove file on path "${filePath}"`);
// await fs.unlinkAsync(filePath);
} else if (challenge.type === "dns-01") { } else if (challenge.type === "dns-01") {
this.logger.info(`删除 TXT 解析记录:${JSON.stringify(recordReq)} ,recordRes = ${JSON.stringify(recordRes)}`); this.logger.info(`删除 TXT 解析记录:${JSON.stringify(recordReq)} ,recordRes = ${JSON.stringify(recordRes)}`);
try { try {
@@ -273,11 +320,12 @@ export class AcmeService {
domains: string | string[]; domains: string | string[];
dnsProvider?: any; dnsProvider?: any;
domainsVerifyPlan?: DomainsVerifyPlan; domainsVerifyPlan?: DomainsVerifyPlan;
httpUploader?: any;
csrInfo: any; csrInfo: any;
isTest?: boolean; isTest?: boolean;
privateKeyType?: string; privateKeyType?: string;
}): Promise<CertInfo> { }): Promise<CertInfo> {
const { email, isTest, domains, csrInfo, dnsProvider, domainsVerifyPlan } = options; const { email, isTest, domains, csrInfo, dnsProvider, domainsVerifyPlan, httpUploader } = options;
const client: acme.Client = await this.getAcmeClient(email, isTest); const client: acme.Client = await this.getAcmeClient(email, isTest);
/* Create CSR */ /* Create CSR */
@@ -317,22 +365,27 @@ export class AcmeService {
privateKey privateKey
); );
if (dnsProvider == null && domainsVerifyPlan == null) { if (dnsProvider == null && domainsVerifyPlan == null && httpUploader == null) {
throw new Error("dnsProvider 、 domainsVerifyPlan 不能都为空"); throw new Error("dnsProvider 、 domainsVerifyPlan 、 httpUploader不能都为空");
} }
const providers: Providers = {
dnsProvider,
domainsVerifyPlan,
httpUploader,
};
/* 自动申请证书 */ /* 自动申请证书 */
const crt = await client.auto({ const crt = await client.auto({
csr, csr,
email: email, email: email,
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
skipChallengeVerification: this.skipLocalVerify, skipChallengeVerification: this.skipLocalVerify,
challengePriority: ["dns-01"], challengePriority: ["dns-01", "http-01"],
challengeCreateFn: async ( challengeCreateFn: async (
authz: acme.Authorization, authz: acme.Authorization,
challenge: Challenge, keyAuthorizationGetter: (challenge: Challenge) => Promise<string>
keyAuthorization: string ): Promise<{ recordReq?: any; recordRes?: any; dnsProvider?: any; challenge: Challenge; keyAuthorization: string }> => {
): Promise<{ recordReq: any; recordRes: any; dnsProvider: any }> => { return await this.challengeCreateFn(authz, keyAuthorizationGetter, providers);
return await this.challengeCreateFn(authz, challenge, keyAuthorization, dnsProvider, domainsVerifyPlan);
}, },
challengeRemoveFn: async ( challengeRemoveFn: async (
authz: acme.Authorization, authz: acme.Authorization,
@@ -342,7 +395,7 @@ export class AcmeService {
recordRes: any, recordRes: any,
dnsProvider: IDnsProvider dnsProvider: IDnsProvider
): Promise<any> => { ): Promise<any> => {
return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider); return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider, httpUploader);
}, },
signal: this.options.signal, signal: this.options.signal,
}); });

View File

@@ -1,4 +1,4 @@
import { AbstractTaskPlugin, IContext, NotificationBody, sendNotification, Step, TaskInput, TaskOutput } from "@certd/pipeline"; import { AbstractTaskPlugin, IContext, NotificationBody, Step, TaskInput, TaskOutput } from "@certd/pipeline";
import dayjs from "dayjs"; import dayjs from "dayjs";
import type { CertInfo } from "./acme.js"; import type { CertInfo } from "./acme.js";
import { CertReader } from "./cert-reader.js"; import { CertReader } from "./cert-reader.js";
@@ -17,6 +17,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
vModel: "value", vModel: "value",
mode: "tags", mode: "tags",
open: false, open: false,
placeholder: "foo.com / *.foo.com / *.bar.com",
tokenSeparators: [",", " ", "", "、", "|"], tokenSeparators: [",", " ", "", "、", "|"],
}, },
rules: [{ type: "domains" }], rules: [{ type: "domains" }],
@@ -26,9 +27,9 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
}, },
order: -999, order: -999,
helper: helper:
"1、支持通配符域名,例如: *.foo.comfoo.com*.test.handsfree.work\n" + "1、支持多个域名打到一个证书上,例如: foo.com*.foo.com*.bar.com\n" +
"2、支持多个域名、多个子域名、多个通配符域名打到一个证书上域名必须是在同一个DNS提供商解析\n" + "2、子域名被通配符包含的不要填写例如www.foo.com已经被*.foo.com包含不要填写www.foo.com\n" +
"3、多级子域名要分成多个域名输入*.foo.com的证书不能用于xxx.yyy.foo.com、foo.com\n" + "3、泛域名只能通配*号那一级*.foo.com的证书不能用于xxx.yyy.foo.com、不能用于foo.com\n" +
"4、输入一个空格之后再输入下一个", "4、输入一个空格之后再输入下一个",
}) })
domains!: string[]; domains!: string[];
@@ -120,10 +121,11 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
abstract doCertApply(): Promise<any>; abstract doCertApply(): Promise<any>;
async execute(): Promise<void> { async execute(): Promise<string | void> {
const oldCert = await this.condition(); const oldCert = await this.condition();
if (oldCert != null) { if (oldCert != null) {
return await this.output(oldCert, false); await this.output(oldCert, false);
return "skip";
} }
const cert = await this.doCertApply(); const cert = await this.doCertApply();
if (cert != null) { if (cert != null) {
@@ -191,7 +193,8 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
zip.file("cert.crt", cert.crt); zip.file("cert.crt", cert.crt);
zip.file("cert.key", cert.key); zip.file("cert.key", cert.key);
zip.file("intermediate.crt", cert.ic); zip.file("intermediate.crt", cert.ic);
zip.file("origin.crt", cert.oc);
zip.file("one.pem", cert.one);
if (cert.pfx) { if (cert.pfx) {
zip.file("cert.pfx", Buffer.from(cert.pfx, "base64")); zip.file("cert.pfx", Buffer.from(cert.pfx, "base64"));
} }
@@ -201,6 +204,21 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
if (cert.jks) { if (cert.jks) {
zip.file("cert.jks", Buffer.from(cert.jks, "base64")); zip.file("cert.jks", Buffer.from(cert.jks, "base64"));
} }
zip.file(
"说明.txt",
`证书文件说明
cert.crt证书文件包含证书链pem格式
cert.key私钥文件pem格式
intermediate.crt中间证书文件pem格式
origin.crt原始证书文件不含证书链pem格式
one.pem 证书和私钥简单合并成一个文件pem格式crt正文+key正文
cert.pfxpfx格式证书文件iis服务器使用
cert.derder格式证书文件
cert.jksjks格式证书文件java服务器使用
`
);
const content = await zip.generateAsync({ type: "nodebuffer" }); const content = await zip.generateAsync({ type: "nodebuffer" });
this.saveFile(filename, content); this.saveFile(filename, content);
this.logger.info(`已保存文件:${filename}`); this.logger.info(`已保存文件:${filename}`);
@@ -216,39 +234,41 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
// return null; // return null;
// } // }
let inputChanged = this.ctx.inputChanged; let inputChanged = false;
if (inputChanged) { //判断域名有没有变更
this.logger.info("input hash 有变更,检查是否需要重新申请证书"); /**
//判断域名有没有变更 * "renewDays": 35,
/** * "certApplyPlugin": "CertApply",
* "renewDays": 35, * "sslProvider": "letsencrypt",
* "certApplyPlugin": "CertApply", * "privateKeyType": "rsa_2048_pkcs1",
* "sslProvider": "letsencrypt", * "dnsProviderType": "aliyun",
* "privateKeyType": "rsa_2048_pkcs1", * "domains": [
* "dnsProviderType": "aliyun", * "*.handsfree.work"
* "domains": [ * ],
* "*.handsfree.work" * "email": "xiaojunnuo@qq.com",
* ], * "dnsProviderAccess": 3,
* "email": "xiaojunnuo@qq.com", * "useProxy": false,
* "dnsProviderAccess": 3, * "skipLocalVerify": false,
* "useProxy": false, * "successNotify": true,
* "skipLocalVerify": false, * "pfxPassword": "123456"
* "successNotify": true, */
* "pfxPassword": "123456" const checkInputChanges = ["domains", "sslProvider", "privateKeyType", "dnsProviderType", "pfxPassword"];
*/ const oldInput = JSON.stringify(pick(this.lastStatus?.input, checkInputChanges));
const checkInputChanges = ["domains", "sslProvider", "privateKeyType", "dnsProviderType", "pfxPassword"]; const thisInput = JSON.stringify(pick(this, checkInputChanges));
const oldInput = JSON.stringify(pick(this.lastStatus?.input, checkInputChanges)); inputChanged = oldInput !== thisInput;
const thisInput = JSON.stringify(pick(this, checkInputChanges));
inputChanged = oldInput !== thisInput;
if (inputChanged) { this.logger.info(`旧参数:${oldInput}`);
this.logger.info("输入参数变更,准备申请新证书"); this.logger.info(`新参数:${thisInput}`);
return null; if (inputChanged) {
} this.logger.info("输入参数变更,准备申请新证书");
return null;
} else {
this.logger.info("输入参数未变更,检查证书是否过期");
} }
let oldCert: CertReader | undefined = undefined; let oldCert: CertReader | undefined = undefined;
try { try {
this.logger.info("读取上次证书");
oldCert = await this.readLastCert(); oldCert = await this.readLastCert();
} catch (e) { } catch (e) {
this.logger.warn("读取cert失败", e); this.logger.warn("读取cert失败", e);
@@ -286,6 +306,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
async readLastCert(): Promise<CertReader | undefined> { async readLastCert(): Promise<CertReader | undefined> {
const cert = this.lastStatus?.status?.output?.cert; const cert = this.lastStatus?.status?.output?.cert;
if (cert == null) { if (cert == null) {
this.logger.info("没有找到上次的证书");
return undefined; return undefined;
} }
return new CertReader(cert); return new CertReader(cert);
@@ -295,7 +316,6 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
* 检查是否过期默认提前35天 * 检查是否过期默认提前35天
* @param expires * @param expires
* @param maxDays * @param maxDays
* @returns {boolean}
*/ */
isWillExpire(expires: number, maxDays = 20) { isWillExpire(expires: number, maxDays = 20) {
if (expires == null) { if (expires == null) {
@@ -304,7 +324,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
// 检查有效期 // 检查有效期
const leftDays = dayjs(expires).diff(dayjs(), "day"); const leftDays = dayjs(expires).diff(dayjs(), "day");
return { return {
isWillExpire: leftDays < maxDays, isWillExpire: leftDays <= maxDays,
leftDays, leftDays,
}; };
} }
@@ -312,39 +332,20 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
this.logger.info("发送证书申请成功通知"); this.logger.info("发送证书申请成功通知");
const url = await this.ctx.urlService.getPipelineDetailUrl(this.pipeline.id, this.ctx.runtime.id); const url = await this.ctx.urlService.getPipelineDetailUrl(this.pipeline.id, this.ctx.runtime.id);
const body: NotificationBody = { const body: NotificationBody = {
title: `【Certd】证书申请成功【${this.pipeline.title}`, title: `证书申请成功【${this.pipeline.title}`,
content: `域名:${this.domains.join(",")}`, content: `域名:${this.domains.join(",")}`,
url: url, url: url,
}; };
try { try {
const defNotification = await this.ctx.notificationService.getDefault(); await this.ctx.notificationService.send({
if (defNotification) { useDefault: true,
this.logger.info(`通知渠道:${defNotification.name}`); useEmail: true,
const notificationCtx = { emailAddress: this.email,
http: this.ctx.http, logger: this.logger,
logger: this.logger, body,
utils: this.ctx.utils, });
emailService: this.ctx.emailService,
};
await sendNotification({
config: defNotification,
ctx: notificationCtx,
body,
});
return;
}
this.logger.warn("未配置默认通知,将发送邮件通知");
await this.sendSuccessEmail(body);
} catch (e) { } catch (e) {
this.logger.error("证书申请成功通知发送失败", e); this.logger.error("证书申请成功通知发送失败", e);
} }
} }
async sendSuccessEmail(body: NotificationBody) {
this.logger.info("发送邮件通知:" + this.email);
await this.ctx.emailService.send({
receivers: [this.email],
subject: body.title,
content: body.content,
});
}
} }

View File

@@ -10,19 +10,23 @@ export type CertReaderHandleContext = {
reader: CertReader; reader: CertReader;
tmpCrtPath: string; tmpCrtPath: string;
tmpKeyPath: string; tmpKeyPath: string;
tmpOcPath?: string;
tmpPfxPath?: string; tmpPfxPath?: string;
tmpDerPath?: string; tmpDerPath?: string;
tmpIcPath?: string; tmpIcPath?: string;
tmpJksPath?: string; tmpJksPath?: string;
tmpOnePath?: string;
}; };
export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise<void>; export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise<void>;
export type HandleOpts = { logger: ILogger; handle: CertReaderHandle }; export type HandleOpts = { logger: ILogger; handle: CertReaderHandle };
export class CertReader { export class CertReader {
cert: CertInfo; cert: CertInfo;
oc: string; //仅证书非fullchain证书
crt: string; crt: string;
key: string; key: string;
csr: string; csr: string;
ic: string; //中间证书 ic: string; //中间证书
one: string; //crt + key 合成一个pem文件
detail: any; detail: any;
expires: number; expires: number;
@@ -38,6 +42,18 @@ export class CertReader {
this.cert.ic = this.ic; this.cert.ic = this.ic;
} }
this.oc = certInfo.oc;
if (!this.oc) {
this.oc = this.getOc();
this.cert.oc = this.oc;
}
this.one = certInfo.one;
if (!this.one) {
this.one = this.crt + "\n" + this.key;
this.cert.one = this.one;
}
const { detail, expires } = this.getCrtDetail(this.cert.crt); const { detail, expires } = this.getCrtDetail(this.cert.crt);
this.detail = detail; this.detail = detail;
this.expires = expires.getTime(); this.expires = expires.getTime();
@@ -53,14 +69,28 @@ export class CertReader {
return ""; return "";
} }
const ic = this.crt.substring(start); const ic = this.crt.substring(start);
if (ic == null) {
return "";
}
return ic.trim(); return ic.trim();
} }
getOc() {
//原始证书 就是crt的第一个 -----END CERTIFICATE----- 之前的内容
const endStr = "-----END CERTIFICATE-----";
const arr = this.crt.split(endStr);
return arr[0] + endStr;
}
toCertInfo(): CertInfo { toCertInfo(): CertInfo {
return this.cert; return this.cert;
} }
getCrtDetail(crt: string = this.cert.crt) { getCrtDetail(crt: string = this.cert.crt) {
return CertReader.readCertDetail(crt);
}
static readCertDetail(crt: string) {
const detail = crypto.readCertificateInfo(crt.toString()); const detail = crypto.readCertificateInfo(crt.toString());
const expires = detail.notAfter; const expires = detail.notAfter;
return { detail, expires }; return { detail, expires };
@@ -73,7 +103,7 @@ export class CertReader {
return domains; return domains;
} }
saveToFile(type: "crt" | "key" | "pfx" | "der" | "ic" | "jks", filepath?: string) { saveToFile(type: "crt" | "key" | "pfx" | "der" | "oc" | "one" | "ic" | "jks", filepath?: string) {
if (!this.cert[type]) { if (!this.cert[type]) {
return; return;
} }
@@ -87,7 +117,7 @@ export class CertReader {
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
} }
if (type === "crt" || type === "key" || type === "ic") { if (type === "crt" || type === "key" || type === "ic" || type === "oc" || type === "one") {
fs.writeFileSync(filepath, this.cert[type]); fs.writeFileSync(filepath, this.cert[type]);
} else { } else {
fs.writeFileSync(filepath, Buffer.from(this.cert[type], "base64")); fs.writeFileSync(filepath, Buffer.from(this.cert[type], "base64"));
@@ -102,9 +132,11 @@ export class CertReader {
const tmpKeyPath = this.saveToFile("key"); const tmpKeyPath = this.saveToFile("key");
const tmpPfxPath = this.saveToFile("pfx"); const tmpPfxPath = this.saveToFile("pfx");
const tmpIcPath = this.saveToFile("ic"); const tmpIcPath = this.saveToFile("ic");
logger.info("本地文件写入成功"); const tmpOcPath = this.saveToFile("oc");
const tmpDerPath = this.saveToFile("der"); const tmpDerPath = this.saveToFile("der");
const tmpJksPath = this.saveToFile("jks"); const tmpJksPath = this.saveToFile("jks");
const tmpOnePath = this.saveToFile("one");
logger.info("本地文件写入成功");
try { try {
return await opts.handle({ return await opts.handle({
reader: this, reader: this,
@@ -114,12 +146,15 @@ export class CertReader {
tmpDerPath: tmpDerPath, tmpDerPath: tmpDerPath,
tmpIcPath: tmpIcPath, tmpIcPath: tmpIcPath,
tmpJksPath: tmpJksPath, tmpJksPath: tmpJksPath,
tmpOcPath: tmpOcPath,
tmpOnePath,
}); });
} catch (err) { } catch (err) {
logger.error("处理失败", err);
throw err; throw err;
} finally { } finally {
//删除临时文件 //删除临时文件
logger.info("删除临时文件"); logger.info("清理临时文件");
function removeFile(filepath?: string) { function removeFile(filepath?: string) {
if (filepath) { if (filepath) {
fs.unlinkSync(filepath); fs.unlinkSync(filepath);
@@ -128,9 +163,11 @@ export class CertReader {
removeFile(tmpCrtPath); removeFile(tmpCrtPath);
removeFile(tmpKeyPath); removeFile(tmpKeyPath);
removeFile(tmpPfxPath); removeFile(tmpPfxPath);
removeFile(tmpOcPath);
removeFile(tmpDerPath); removeFile(tmpDerPath);
removeFile(tmpIcPath); removeFile(tmpIcPath);
removeFile(tmpJksPath); removeFile(tmpJksPath);
removeFile(tmpOnePath);
} }
} }

View File

@@ -1,7 +1,7 @@
import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CancelError, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { utils } from "@certd/basic"; import { utils } from "@certd/basic";
import type { CertInfo, CnameVerifyPlan, DomainsVerifyPlan, PrivateKeyType, SSLProvider } from "./acme.js"; import type { CertInfo, CnameVerifyPlan, DomainsVerifyPlan, HttpVerifyPlan, PrivateKeyType, SSLProvider } from "./acme.js";
import { AcmeService } from "./acme.js"; import { AcmeService } from "./acme.js";
import * as _ from "lodash-es"; import * as _ from "lodash-es";
import { createDnsProvider, DnsProviderContext, IDnsProvider } from "../../dns-provider/index.js"; import { createDnsProvider, DnsProviderContext, IDnsProvider } from "../../dns-provider/index.js";
@@ -9,7 +9,7 @@ import { CertReader } from "./cert-reader.js";
import { CertApplyBasePlugin } from "./base.js"; import { CertApplyBasePlugin } from "./base.js";
import { GoogleClient } from "../../libs/google.js"; import { GoogleClient } from "../../libs/google.js";
import { EabAccess } from "../../access"; import { EabAccess } from "../../access";
import { CancelError } from "@certd/pipeline"; import { httpChallengeUploaderFactory } from "./uploads/factory.js";
export type { CertInfo }; export type { CertInfo };
export * from "./cert-reader.js"; export * from "./cert-reader.js";
@@ -17,12 +17,20 @@ export type CnameRecordInput = {
id: number; id: number;
status: string; status: string;
}; };
export type HttpRecordInput = {
domain: string;
httpUploaderType: string;
httpUploaderAccess: number;
httpUploadRootDir: string;
};
export type DomainVerifyPlanInput = { export type DomainVerifyPlanInput = {
domain: string; domain: string;
type: "cname" | "dns"; type: "cname" | "dns" | "http";
dnsProviderType?: string; dnsProviderType?: string;
dnsProviderAccessId?: number; dnsProviderAccessId?: number;
cnameVerifyPlan?: Record<string, CnameRecordInput>; cnameVerifyPlan?: Record<string, CnameRecordInput>;
httpVerifyPlan?: Record<string, HttpRecordInput>;
}; };
export type DomainsVerifyPlanInput = { export type DomainsVerifyPlanInput = {
[key: string]: DomainVerifyPlanInput; [key: string]: DomainVerifyPlanInput;
@@ -54,14 +62,33 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
options: [ options: [
{ value: "dns", label: "DNS直接验证" }, { value: "dns", label: "DNS直接验证" },
{ value: "cname", label: "CNAME代理验证" }, { value: "cname", label: "CNAME代理验证" },
{ value: "http", label: "HTTP文件验证" },
], ],
}, },
required: true, required: true,
helper: helper: `DNS直接验证域名是在阿里云、腾讯云、华为云、Cloudflare、NameSilo、西数注册的选它
"DNS直接验证域名是在阿里云、腾讯云、华为云、Cloudflare、NameSilo、西数注册的选它。\nCNAME代理验证支持任何注册商注册的域名但第一次需要手动添加CNAME记录", CNAME代理验证支持任何注册商注册的域名但第一次需要手动添加CNAME记录
HTTP文件验证不支持泛域名需要配置网站文件上传`,
}) })
challengeType!: string; challengeType!: string;
@TaskInput({
title: "证书颁发机构",
value: "letsencrypt",
component: {
name: "icon-select",
vModel: "value",
options: [
{ value: "letsencrypt", label: "Let's Encrypt", icon: "simple-icons:letsencrypt" },
{ value: "google", label: "Google", icon: "flat-color-icons:google" },
{ value: "zerossl", label: "ZeroSSL", icon: "emojione:digit-zero" },
],
},
helper: "Let's Encrypt申请最简单\nGoogle大厂光环兼容性好仅首次需要翻墙获取EAB授权\nZeroSSL需要EAB授权无需翻墙",
required: true,
})
sslProvider!: SSLProvider;
@TaskInput({ @TaskInput({
title: "DNS解析服务商", title: "DNS解析服务商",
component: { component: {
@@ -105,9 +132,8 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
component: { component: {
name: "domains-verify-plan-editor", name: "domains-verify-plan-editor",
}, },
rules: [{ type: "checkCnameVerifyPlan" }], rules: [{ type: "checkDomainVerifyPlan" }],
required: true, required: true,
helper: "如果选择CNAME方式请按照上面的显示给要申请证书的域名添加CNAME记录添加后点击验证验证成功后不要删除记录申请和续期证书会一直用它",
col: { col: {
span: 24, span: 24,
}, },
@@ -115,33 +141,26 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
component:{ component:{
domains: ctx.compute(({form})=>{ domains: ctx.compute(({form})=>{
return form.domains return form.domains
}),
defaultType: ctx.compute(({form})=>{
return form.challengeType || 'cname'
}) })
}, },
show: ctx.compute(({form})=>{ show: ctx.compute(({form})=>{
return form.challengeType === 'cname' return form.challengeType === 'cname' || form.challengeType === 'http'
}),
helper: ctx.compute(({form})=>{
if(form.challengeType === 'cname' ){
return '请按照上面的提示给要申请证书的域名添加CNAME记录添加后点击验证验证成功后不要删除记录申请和续期证书会一直用它'
}else if (form.challengeType === 'http'){
return '请按照上面的提示,给每个域名设置文件上传配置,证书申请过程中会上传校验文件到网站根目录下'
}
}) })
} }
`, `,
}) })
domainsVerifyPlan!: DomainsVerifyPlanInput; domainsVerifyPlan!: DomainsVerifyPlanInput;
@TaskInput({
title: "证书颁发机构",
value: "letsencrypt",
component: {
name: "icon-select",
vModel: "value",
options: [
{ value: "letsencrypt", label: "Let's Encrypt", icon: "simple-icons:letsencrypt" },
{ value: "google", label: "Google", icon: "flat-color-icons:google" },
{ value: "zerossl", label: "ZeroSSL", icon: "emojione:digit-zero" },
],
},
helper: "Let's Encrypt申请最简单\nGoogle大厂光环兼容性好仅首次需要翻墙获取EAB授权\nZeroSSL需要EAB授权无需翻墙",
required: true,
})
sslProvider!: SSLProvider;
@TaskInput({ @TaskInput({
title: "Google公共EAB授权", title: "Google公共EAB授权",
isSys: true, isSys: true,
@@ -271,8 +290,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
this.logger.info("当前正在使用 google公共EAB授权"); this.logger.info("当前正在使用 google公共EAB授权");
eab = await this.ctx.accessService.getCommonById(this.googleCommonEabAccessId); eab = await this.ctx.accessService.getCommonById(this.googleCommonEabAccessId);
} else { } else {
this.logger.error("google需要配置EAB授权或服务账号授权"); throw new Error("google需要配置EAB授权或服务账号授权");
return;
} }
} else if (this.sslProvider === "zerossl") { } else if (this.sslProvider === "zerossl") {
if (this.eabAccessId) { if (this.eabAccessId) {
@@ -282,8 +300,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
this.logger.info("当前正在使用 zerossl 公共EAB授权"); this.logger.info("当前正在使用 zerossl 公共EAB授权");
eab = await this.ctx.accessService.getCommonById(this.zerosslCommonEabAccessId); eab = await this.ctx.accessService.getCommonById(this.zerosslCommonEabAccessId);
} else { } else {
this.logger.error("zerossl需要配置EAB授权"); throw new Error("zerossl需要配置EAB授权");
return;
} }
} }
this.eab = eab; this.eab = eab;
@@ -322,9 +339,9 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
); );
this.logger.info("开始申请证书,", email, domains); this.logger.info("开始申请证书,", email, domains);
let dnsProvider: any = null; let dnsProvider: IDnsProvider = null;
let domainsVerifyPlan: DomainsVerifyPlan = null; let domainsVerifyPlan: DomainsVerifyPlan = null;
if (this.challengeType === "cname") { if (this.challengeType === "cname" || this.challengeType === "http") {
domainsVerifyPlan = await this.createDomainsVerifyPlan(); domainsVerifyPlan = await this.createDomainsVerifyPlan();
} else { } else {
const dnsProviderType = this.dnsProviderType; const dnsProviderType = this.dnsProviderType;
@@ -372,10 +389,11 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
const domainVerifyPlan = this.domainsVerifyPlan[domain]; const domainVerifyPlan = this.domainsVerifyPlan[domain];
let dnsProvider = null; let dnsProvider = null;
const cnameVerifyPlan: Record<string, CnameVerifyPlan> = {}; const cnameVerifyPlan: Record<string, CnameVerifyPlan> = {};
const httpVerifyPlan: Record<string, HttpVerifyPlan> = {};
if (domainVerifyPlan.type === "dns") { if (domainVerifyPlan.type === "dns") {
const access = await this.ctx.accessService.getById(domainVerifyPlan.dnsProviderAccessId); const access = await this.ctx.accessService.getById(domainVerifyPlan.dnsProviderAccessId);
dnsProvider = await this.createDnsProvider(domainVerifyPlan.dnsProviderType, access); dnsProvider = await this.createDnsProvider(domainVerifyPlan.dnsProviderType, access);
} else { } else if (domainVerifyPlan.type === "cname") {
for (const key in domainVerifyPlan.cnameVerifyPlan) { for (const key in domainVerifyPlan.cnameVerifyPlan) {
const cnameRecord = await this.ctx.cnameProxyService.getByDomain(key); const cnameRecord = await this.ctx.cnameProxyService.getByDomain(key);
let dnsProvider = cnameRecord.commonDnsProvider; let dnsProvider = cnameRecord.commonDnsProvider;
@@ -383,17 +401,44 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
dnsProvider = await this.createDnsProvider(cnameRecord.cnameProvider.dnsProviderType, cnameRecord.cnameProvider.access); dnsProvider = await this.createDnsProvider(cnameRecord.cnameProvider.dnsProviderType, cnameRecord.cnameProvider.access);
} }
cnameVerifyPlan[key] = { cnameVerifyPlan[key] = {
type: "cname",
domain: cnameRecord.cnameProvider.domain, domain: cnameRecord.cnameProvider.domain,
fullRecord: cnameRecord.recordValue, fullRecord: cnameRecord.recordValue,
dnsProvider, dnsProvider,
}; };
} }
} else if (domainVerifyPlan.type === "http") {
const httpUploaderContext = {
accessService: this.ctx.accessService,
logger: this.logger,
utils,
};
for (const key in domainVerifyPlan.httpVerifyPlan) {
const httpRecord = domainVerifyPlan.httpVerifyPlan[key];
const access = await this.ctx.accessService.getById(httpRecord.httpUploaderAccess);
let rootDir = httpRecord.httpUploadRootDir;
if (!rootDir.endsWith("/") && !rootDir.endsWith("\\")) {
rootDir = rootDir + "/";
}
this.logger.info("上传方式", httpRecord.httpUploaderType);
const httpUploader = await httpChallengeUploaderFactory.createUploaderByType(httpRecord.httpUploaderType, {
access,
rootDir: rootDir,
ctx: httpUploaderContext,
});
httpVerifyPlan[key] = {
type: "http",
domain: key,
httpUploader,
};
}
} }
plan[domain] = { plan[domain] = {
domain, domain,
type: domainVerifyPlan.type, type: domainVerifyPlan.type,
dnsProvider, dnsProvider,
cnameVerifyPlan, cnameVerifyPlan,
httpVerifyPlan,
}; };
} }
return plan; return plan;

View File

@@ -0,0 +1,35 @@
import { IAccessService } from "@certd/pipeline";
import { ILogger, utils } from "@certd/basic";
export type HttpChallengeUploader = {
upload: (fileName: string, fileContent: Buffer) => Promise<void>;
remove: (fileName: string) => Promise<void>;
};
export type HttpChallengeUploadContext = {
accessService: IAccessService;
logger: ILogger;
utils: typeof utils;
};
export abstract class BaseHttpChallengeUploader<A> implements HttpChallengeUploader {
rootDir: string;
access: A = null;
logger: ILogger;
utils: typeof utils;
ctx: HttpChallengeUploadContext;
protected constructor(opts: { rootDir: string; access: A }) {
this.rootDir = opts.rootDir;
this.access = opts.access;
}
async setCtx(ctx: any) {
// set context
this.ctx = ctx;
this.logger = ctx.logger;
this.utils = ctx.utils;
}
abstract remove(fileName: string): Promise<void>;
abstract upload(fileName: string, fileContent: Buffer): Promise<void>;
}

View File

@@ -0,0 +1,35 @@
import { HttpChallengeUploadContext } from "./api";
export class HttpChallengeUploaderFactory {
async getClassByType(type: string) {
if (type === "alioss") {
const module = await import("./impls/alioss.js");
return module.AliossHttpChallengeUploader;
} else if (type === "ssh") {
const module = await import("./impls/ssh.js");
return module.SshHttpChallengeUploader;
} else if (type === "ftp") {
const module = await import("./impls/ftp.js");
return module.FtpHttpChallengeUploader;
} else if (type === "tencentcos") {
const module = await import("./impls/tencentcos.js");
return module.TencentCosHttpChallengeUploader;
} else if (type === "qiniuoss") {
const module = await import("./impls/qiniuoss.js");
return module.QiniuOssHttpChallengeUploader;
} else {
throw new Error(`暂不支持此文件上传方式: ${type}`);
}
}
async createUploaderByType(type: string, opts: { rootDir: string; access: any; ctx: HttpChallengeUploadContext }) {
const cls = await this.getClassByType(type);
if (cls) {
// @ts-ignore
const instance = new cls(opts);
await instance.setCtx(opts.ctx);
return instance;
}
}
}
export const httpChallengeUploaderFactory = new HttpChallengeUploaderFactory();

View File

@@ -0,0 +1,39 @@
import { BaseHttpChallengeUploader } from "../api.js";
import { AliossAccess, AliyunAccess } from "@certd/plugin-lib";
import { AliossClient } from "@certd/plugin-lib";
export class AliossHttpChallengeUploader extends BaseHttpChallengeUploader<AliossAccess> {
async upload(filePath: string, fileContent: Buffer) {
const aliyunAccess = await this.ctx.accessService.getById<AliyunAccess>(this.access.accessId);
const client = new AliossClient({
access: aliyunAccess,
bucket: this.access.bucket,
region: this.access.region,
});
const key = this.rootDir + filePath;
this.logger.info(`开始上传文件: ${key}`);
await client.uploadFile(key, fileContent);
this.logger.info(`校验文件上传成功: ${filePath}`);
}
async remove(filePath: string) {
const key = this.rootDir + filePath;
// remove file from alioss
const client = await this.getAliossClient();
await client.removeFile(key);
this.logger.info(`文件删除成功: ${key}`);
}
private async getAliossClient() {
const aliyunAccess = await this.ctx.accessService.getById<AliyunAccess>(this.access.accessId);
const client = new AliossClient({
access: aliyunAccess,
bucket: this.access.bucket,
region: this.access.region,
});
await client.init();
return client;
}
}

View File

@@ -0,0 +1,41 @@
import { BaseHttpChallengeUploader } from "../api.js";
import { FtpAccess, FtpClient } from "@certd/plugin-lib";
import path from "path";
import os from "os";
import fs from "fs";
export class FtpHttpChallengeUploader extends BaseHttpChallengeUploader<FtpAccess> {
async upload(filePath: string, fileContent: Buffer) {
const client = new FtpClient({
access: this.access,
logger: this.logger,
});
await client.connect(async (client) => {
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
const dir = path.dirname(tmpFilePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(tmpFilePath, fileContent);
try {
// Write file to temp path
const path = this.rootDir + filePath;
await client.upload(path, tmpFilePath);
} finally {
// Remove temp file
fs.unlinkSync(tmpFilePath);
}
});
}
async remove(filePath: string) {
const client = new FtpClient({
access: this.access,
logger: this.logger,
});
await client.connect(async (client) => {
const path = this.rootDir + filePath;
await client.client.remove(path);
});
}
}

View File

@@ -0,0 +1,31 @@
import { BaseHttpChallengeUploader } from "../api.js";
import { QiniuOssAccess, QiniuClient, QiniuAccess } from "@certd/plugin-lib";
export class QiniuOssHttpChallengeUploader extends BaseHttpChallengeUploader<QiniuOssAccess> {
async upload(filePath: string, fileContent: Buffer) {
const qiniuAccess = await this.ctx.accessService.getById<QiniuAccess>(this.access.accessId);
const client = new QiniuClient({
access: qiniuAccess,
logger: this.logger,
http: this.ctx.utils.http,
});
if (this.rootDir.endsWith("/")) {
this.rootDir = this.rootDir.slice(0, -1);
}
await client.uploadFile(this.access.bucket, this.rootDir + filePath, fileContent);
}
async remove(filePath: string) {
const qiniuAccess = await this.ctx.accessService.getById<QiniuAccess>(this.access.accessId);
const client = new QiniuClient({
access: qiniuAccess,
logger: this.logger,
http: this.ctx.utils.http,
});
if (this.rootDir.endsWith("/")) {
this.rootDir = this.rootDir.slice(0, -1);
}
await client.removeFile(this.access.bucket, this.rootDir + filePath);
}
}

View File

@@ -0,0 +1,45 @@
import { BaseHttpChallengeUploader } from "../api.js";
import { SshAccess, SshClient } from "@certd/plugin-lib";
import path from "path";
import os from "os";
import fs from "fs";
export class SshHttpChallengeUploader extends BaseHttpChallengeUploader<SshAccess> {
async upload(filePath: string, fileContent: Buffer) {
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
// Write file to temp path
const dir = path.dirname(tmpFilePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(tmpFilePath, fileContent);
const key = this.rootDir + filePath;
try {
const client = new SshClient(this.logger);
await client.uploadFiles({
connectConf: this.access,
mkdirs: true,
transports: [
{
localPath: tmpFilePath,
remotePath: key,
},
],
});
} finally {
// Remove temp file
fs.unlinkSync(tmpFilePath);
}
}
async remove(filePath: string) {
const client = new SshClient(this.logger);
const key = this.rootDir + filePath;
await client.removeFiles({
connectConf: this.access,
files: [key],
});
}
}

View File

@@ -0,0 +1,28 @@
import { BaseHttpChallengeUploader } from "../api.js";
import { TencentAccess, TencentCosAccess, TencentCosClient } from "@certd/plugin-lib";
export class TencentCosHttpChallengeUploader extends BaseHttpChallengeUploader<TencentCosAccess> {
async upload(filePath: string, fileContent: Buffer) {
const access = await this.ctx.accessService.getById<TencentAccess>(this.access.accessId);
const client = new TencentCosClient({
access: access,
logger: this.logger,
region: this.access.region,
bucket: this.access.bucket,
});
const key = this.rootDir + filePath;
await client.uploadFile(key, fileContent);
}
async remove(filePath: string) {
const access = await this.ctx.accessService.getById<TencentAccess>(this.access.accessId);
const client = new TencentCosClient({
access: access,
logger: this.logger,
region: this.access.region,
bucket: this.access.bucket,
});
const key = this.rootDir + filePath;
await client.removeFile(key);
}
}

View File

@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.29.5](https://github.com/certd/certd/compare/v1.29.4...v1.29.5) (2025-01-07)
**Note:** Version bump only for package @certd/plugin-lib
## [1.29.4](https://github.com/certd/certd/compare/v1.29.3...v1.29.4) (2025-01-06)
**Note:** Version bump only for package @certd/plugin-lib
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
### Performance Improvements
* 优化acme sdk ([54db744](https://github.com/certd/certd/commit/54db74428259de64d12230c2ab7353ae11197bbc))
* 支持http校验方式申请证书 ([405591c](https://github.com/certd/certd/commit/405591c5d08fa1a3b228ee3980199e7731cfec4a))
* http校验方式支持七牛云oss、阿里云oss、腾讯云cos ([3f74d4d](https://github.com/certd/certd/commit/3f74d4d9e5f5d0e629b44cff1895b3f7a8fbcafc))
## [1.29.2](https://github.com/certd/certd/compare/v1.29.1...v1.29.2) (2024-12-25)
**Note:** Version bump only for package @certd/plugin-lib
## [1.29.1](https://github.com/certd/certd/compare/v1.29.0...v1.29.1) (2024-12-25)
**Note:** Version bump only for package @certd/plugin-lib
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
**Note:** Version bump only for package @certd/plugin-lib
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
**Note:** Version bump only for package @certd/plugin-lib
## [1.28.3](https://github.com/certd/certd/compare/v1.28.2...v1.28.3) (2024-12-12)
**Note:** Version bump only for package @certd/plugin-lib
## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09) ## [1.28.2](https://github.com/certd/certd/compare/v1.28.1...v1.28.2) (2024-12-09)
**Note:** Version bump only for package @certd/plugin-lib **Note:** Version bump only for package @certd/plugin-lib

View File

@@ -1,7 +1,7 @@
{ {
"name": "@certd/plugin-lib", "name": "@certd/plugin-lib",
"private": false, "private": false,
"version": "1.28.2", "version": "1.29.5",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -16,18 +16,22 @@
}, },
"dependencies": { "dependencies": {
"@alicloud/pop-core": "^1.7.10", "@alicloud/pop-core": "^1.7.10",
"@certd/basic": "^1.28.2", "@certd/basic": "^1.29.5",
"@certd/pipeline": "^1.28.2", "@certd/pipeline": "^1.29.5",
"@certd/plugin-cert": "^1.28.2",
"@kubernetes/client-node": "0.21.0", "@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.21.0",
"basic-ftp": "^5.0.5",
"cos-nodejs-sdk-v5": "^2.14.6",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"qiniu": "^7.12.0",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"socks": "^2.8.3", "socks": "^2.8.3",
"socks-proxy-agent": "^8.0.4", "socks-proxy-agent": "^8.0.4",
"ssh2": "^1.15.0", "ssh2": "^1.15.0",
"strip-ansi": "^7.1.0" "strip-ansi": "^7.1.0",
"tencentcloud-sdk-nodejs": "^4.0.1005"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.3", "@types/chai": "^4.3.3",
@@ -44,5 +48,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "a6cd532035f55a7ce122ea1229bb65f9d41e7125" "gitHead": "94509c64b954e17e4842b02c5c9fa0649bce13d4"
} }

View File

@@ -0,0 +1,71 @@
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
@IsAccess({
name: "alioss",
title: "阿里云OSS授权",
desc: "包含地域和Bucket",
icon: "ant-design:aliyun-outlined",
})
export class AliossAccess extends BaseAccess {
@AccessInput({
title: "阿里云授权",
component: {
name: "access-selector",
vModel: "modelValue",
type: "aliyun",
},
helper: "请选择阿里云授权",
required: true,
})
accessId = "";
@AccessInput({
title: "大区",
component: {
name: "a-auto-complete",
vModel: "value",
options: [
{ value: "oss-cn-hangzhou", label: "华东1杭州" },
{ value: "oss-cn-shanghai", label: "华东2上海" },
{ value: "oss-cn-nanjing", label: "华东5南京-本地地域)" },
{ value: "oss-cn-fuzhou", label: "华东6福州-本地地域)" },
{ value: "oss-cn-wuhan-lr", label: "华中1武汉-本地地域)" },
{ value: "oss-cn-qingdao", label: "华北1青岛" },
{ value: "oss-cn-beijing", label: "华北2北京" },
{ value: "oss-cn-zhangjiakou", label: "华北 3张家口" },
{ value: "oss-cn-huhehaote", label: "华北5呼和浩特" },
{ value: "oss-cn-wulanchabu", label: "华北6乌兰察布" },
{ value: "oss-cn-shenzhen", label: "华南1深圳" },
{ value: "oss-cn-heyuan", label: "华南2河源" },
{ value: "oss-cn-guangzhou", label: "华南3广州" },
{ value: "oss-cn-chengdu", label: "西南1成都" },
{ value: "oss-cn-hongkong", label: "中国香港" },
{ value: "oss-us-west-1", label: "美国(硅谷)①" },
{ value: "oss-us-east-1", label: "美国(弗吉尼亚)①" },
{ value: "oss-ap-northeast-1", label: "日本(东京)①" },
{ value: "oss-ap-northeast-2", label: "韩国(首尔)" },
{ value: "oss-ap-southeast-1", label: "新加坡①" },
{ value: "oss-ap-southeast-2", label: "澳大利亚(悉尼)①" },
{ value: "oss-ap-southeast-3", label: "马来西亚(吉隆坡)①" },
{ value: "oss-ap-southeast-5", label: "印度尼西亚(雅加达)①" },
{ value: "oss-ap-southeast-6", label: "菲律宾(马尼拉)" },
{ value: "oss-ap-southeast-7", label: "泰国(曼谷)" },
{ value: "oss-eu-central-1", label: "德国(法兰克福)①" },
{ value: "oss-eu-west-1", label: "英国(伦敦)" },
{ value: "oss-me-east-1", label: "阿联酋(迪拜)①" },
{ value: "oss-rg-china-mainland", label: "无地域属性(中国内地)" },
],
},
required: true,
})
region!: string;
@AccessInput({
title: "Bucket",
helper: "存储桶名称",
required: true,
})
bucket!: string;
}
new AliossAccess();

View File

@@ -1 +1,2 @@
export * from './aliyun-access.js'; export * from "./aliyun-access.js";
export * from "./alioss-access.js";

View File

@@ -1,2 +1,3 @@
export * from "./base-client.js"; export * from "./base-client.js";
export * from "./ssl-client.js"; export * from "./ssl-client.js";
export * from "./oss-client.js";

View File

@@ -0,0 +1,64 @@
import { AliyunAccess } from "../access";
export class AliossClient {
access: AliyunAccess;
region: string;
bucket: string;
client: any;
constructor(opts: { access: AliyunAccess; bucket: string; region: string }) {
this.access = opts.access;
this.bucket = opts.bucket;
this.region = opts.region;
}
async init() {
if (this.client) {
return;
}
// @ts-ignore
const OSS = await import("ali-oss");
const ossClient = new OSS.default({
accessKeyId: this.access.accessKeyId,
accessKeySecret: this.access.accessKeySecret,
// yourRegion填写Bucket所在地域。以华东1杭州为例Region填写为oss-cn-hangzhou。
region: this.region,
//@ts-ignore
authorizationV4: true,
// yourBucketName填写Bucket名称。
bucket: this.bucket,
});
// oss
this.client = ossClient;
}
async doRequest(bucket: string, xml: string, params: any) {
await this.init();
params = this.client._bucketRequestParams("POST", bucket, {
...params,
});
params.content = xml;
params.mime = "xml";
params.successStatuses = [200];
const res = await this.client.request(params);
this.checkRet(res);
return res;
}
checkRet(ret: any) {
if (ret.code != null) {
throw new Error("执行失败:" + ret.Message);
}
}
async uploadFile(filePath: string, content: Buffer) {
await this.init();
return await this.client.put(filePath, content);
}
async removeFile(filePath: string) {
await this.init();
return await this.client.delete(filePath);
}
}

View File

@@ -1,8 +1,11 @@
import { ILogger } from "@certd/basic"; import { ILogger } from "@certd/basic";
import { AliyunAccess } from "../access/index.js"; import { AliyunAccess } from "../access/index.js";
import { AliyunClient } from "./index.js"; import { AliyunClient } from "./index.js";
import { CertInfo } from "@certd/plugin-cert";
export type AliyunCertInfo = {
crt: string; //fullchain证书
key: string; //私钥
};
export type AliyunSslClientOpts = { export type AliyunSslClientOpts = {
access: AliyunAccess; access: AliyunAccess;
logger: ILogger; logger: ILogger;
@@ -23,7 +26,7 @@ export type AliyunSslCreateDeploymentJobReq = {
export type AliyunSslUploadCertReq = { export type AliyunSslUploadCertReq = {
name: string; name: string;
cert: CertInfo; cert: AliyunCertInfo;
}; };
export class AliyunSslClient { export class AliyunSslClient {

View File

@@ -0,0 +1,77 @@
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
/**
* 这个注解将注册一个授权配置
* 在certd的后台管理系统中用户可以选择添加此类型的授权
*/
@IsAccess({
name: "ftp",
title: "FTP授权",
desc: "",
icon: "mdi:folder-upload-outline",
})
export class FtpAccess extends BaseAccess {
@AccessInput({
title: "host",
component: {
placeholder: "ip / 域名",
name: "a-input",
vModel: "value",
},
helper: "FTP地址",
required: true,
})
host!: string;
@AccessInput({
title: "host",
value: 21,
component: {
placeholder: "21",
name: "a-input-number",
vModel: "value",
},
helper: "FTP端口",
required: true,
})
port!: string;
@AccessInput({
title: "user",
component: {
placeholder: "用户名",
},
helper: "FTP用户名",
required: true,
})
user!: string;
@AccessInput({
title: "password",
component: {
placeholder: "密码",
component: {
name: "a-input-password",
vModel: "value",
},
},
encrypt: true,
helper: "FTP密码",
required: true,
})
password!: string;
@AccessInput({
title: "secure",
value: false,
component: {
name: "a-switch",
vModel: "checked",
},
helper: "是否使用SSL",
required: true,
})
secure?: boolean = false;
}
new FtpAccess();

View File

@@ -0,0 +1,47 @@
import { FtpAccess } from "./access";
import { ILogger } from "@certd/basic";
import path from "node:path";
export class FtpClient {
access: FtpAccess = null;
logger: ILogger = null;
client: any;
constructor(opts: { access: FtpAccess; logger: ILogger }) {
this.access = opts.access;
this.logger = opts.logger;
}
async connect(callback: (client: FtpClient) => Promise<void>) {
const ftp = await import("basic-ftp");
const Client = ftp.Client;
const client = new Client();
client.ftp.verbose = true;
this.logger.info("开始连接FTP");
await client.access(this.access as any);
this.logger.info("FTP连接成功");
this.client = client;
try {
await callback(this);
} finally {
if (client) {
client.close();
}
}
}
async upload(filePath: string, remotePath: string): Promise<void> {
if (!remotePath) {
return;
}
const dirname = path.dirname(remotePath);
this.logger.info(`确保目录存在:${dirname}`);
await this.client.ensureDir(dirname);
this.logger.info(`开始上传文件${filePath} -> ${remotePath}`);
await this.client.uploadFrom(filePath, remotePath);
}
async remove(filePath: string): Promise<void> {
this.logger.info(`开始删除文件${filePath}`);
await this.client.remove(filePath, true);
}
}

View File

@@ -0,0 +1,2 @@
export * from "./access.js";
export * from "./client.js";

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