mirror of
https://github.com/certd/certd.git
synced 2026-04-14 12:30:54 +08:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
089825d360 | ||
|
|
333629caff | ||
|
|
d715cd1129 | ||
|
|
15d6eaf553 | ||
|
|
ae5dfc3bee | ||
|
|
6ab83b662a | ||
|
|
52ae6902d2 | ||
|
|
c30adb2671 | ||
|
|
e95d29f446 | ||
|
|
c20bb38b06 | ||
|
|
d0213d275d | ||
|
|
9a78dad576 | ||
|
|
880f1aeb66 | ||
|
|
e764eabd97 | ||
|
|
235f9cf854 | ||
|
|
d10795ecd9 | ||
|
|
a7e45dace0 | ||
|
|
7e482f798c | ||
|
|
c085bac5d8 | ||
|
|
653940a0ca | ||
|
|
417d37b199 | ||
|
|
3b2107a4f1 | ||
|
|
7f6d03c02a | ||
|
|
5fc07d4dd4 | ||
|
|
3fb9524cbd | ||
|
|
e79703e49b | ||
|
|
b829bd1341 | ||
|
|
8cbab7525a | ||
|
|
93b37a89c9 | ||
|
|
87620b9072 | ||
|
|
6877b865a7 | ||
|
|
d6b3142a02 | ||
|
|
14cdb54212 | ||
|
|
91e7f45a1c | ||
|
|
709105120c | ||
|
|
865f26d75c | ||
|
|
52a4fd3318 | ||
|
|
c6c269f9e4 | ||
|
|
2a8eeaf240 | ||
|
|
f7dcff5113 | ||
|
|
98a81385a6 | ||
|
|
7bdc277b58 | ||
|
|
f57116d2be | ||
|
|
85c99f7f80 | ||
|
|
75081ceac3 | ||
|
|
65da3ca298 | ||
|
|
94509c64b9 | ||
|
|
4f36d94726 | ||
|
|
05c284b999 | ||
|
|
635b042690 | ||
|
|
1cb4a539cc | ||
|
|
46b87250b2 | ||
|
|
1a05355e54 | ||
|
|
c81c17d17b | ||
|
|
7b4f8d31e8 | ||
|
|
5cef28c5bd | ||
|
|
6e68da7936 | ||
|
|
0c130f9596 | ||
|
|
f156f4cb4e | ||
|
|
fa3bfa2ea8 | ||
|
|
ab5c7bb75a | ||
|
|
81b322cd60 | ||
|
|
e6dd7cd54a | ||
|
|
aa1da7c11a | ||
|
|
3f74d4d9e5 | ||
|
|
297d09c5ad | ||
|
|
07e1dbb4cc | ||
|
|
3c6618b4fc | ||
|
|
54db744282 | ||
|
|
03b751fa13 | ||
|
|
ec342708b2 | ||
|
|
405591c5d0 | ||
|
|
67af67b92d | ||
|
|
8644348fc4 | ||
|
|
00dc226bd2 | ||
|
|
b6b7c3e2e0 | ||
|
|
246ef348d3 | ||
|
|
3e9ba1a30a | ||
|
|
598cde4865 | ||
|
|
fc4a716b4e | ||
|
|
ed5634ff83 | ||
|
|
884af1ea62 | ||
|
|
01ad62df16 | ||
|
|
512a667e44 | ||
|
|
d0e841f7de | ||
|
|
c04641d835 | ||
|
|
f9128d4d45 | ||
|
|
2026211622 | ||
|
|
9d0f21a9e5 | ||
|
|
26adf7d437 | ||
|
|
d2d6f12218 | ||
|
|
b31c0b6a8d | ||
|
|
472f06c2d1 | ||
|
|
f5ec9870fd | ||
|
|
66fb9e5f49 | ||
|
|
a323f3aa2c | ||
|
|
fe4786e168 | ||
|
|
83185c8c50 | ||
|
|
83ae9db02d | ||
|
|
8bf328ca94 |
22
.github/workflows/build-image.yml
vendored
22
.github/workflows/build-image.yml
vendored
@@ -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}}
|
||||||
|
|||||||
81
CHANGELOG.md
81
CHANGELOG.md
@@ -3,6 +3,87 @@
|
|||||||
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.30.1](https://github.com/certd/certd/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复部署到阿里云ALB、NLB插件加载混乱的bug ([6ab83b6](https://github.com/certd/certd/commit/6ab83b662a2c5e715b9cb7eb1244de2ebb7f47b0))
|
||||||
|
* 修复腾讯clb重复执行会报错的bug ([e95d29f](https://github.com/certd/certd/commit/e95d29f446d06eced315a3087fc9e105a30b20bd))
|
||||||
|
* 修复tg消息内容中存在.和*就会发送失败的bug ([ae5dfc3](https://github.com/certd/certd/commit/ae5dfc3bee950267123ae2fbd1c11e7ce36626ea))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 创建流水线时,默认成功时也发送通知 ([52ae690](https://github.com/certd/certd/commit/52ae6902d203ca56e0312692b50c55cb6ddd3e39))
|
||||||
|
* http方式校验,选择sftp时,支持修改文件访问权限比如777 ([15d6eaf](https://github.com/certd/certd/commit/15d6eaf5532ed25acd4f8d58c429353a2f44206c))
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复查看任务日志偶发性无法自动滚动底部的bug ([7e482f7](https://github.com/certd/certd/commit/7e482f798c0142bce1866f84676cb40210f9638a))
|
||||||
|
* 修复namesilo ttl太短的问题 ([865f26d](https://github.com/certd/certd/commit/865f26d75c0d3dd4dc8b41448f8830068e45957c))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 支持open api接口,根据域名获取证书 ([52a4fd3](https://github.com/certd/certd/commit/52a4fd33180e9b3f71b8dc9f7671d7cd8e448c3b))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 证书仓库 ([91e7f45](https://github.com/certd/certd/commit/91e7f45a1c5ea1e0ec0aa3236b80028f03a6d0aa))
|
||||||
|
* 支持部署到阿里云ALB ([653940a](https://github.com/certd/certd/commit/653940a0ca64fc380178c1b0b58ae0af64dfaf07))
|
||||||
|
* 支持部署到阿里云NLB、SLB ([c085bac](https://github.com/certd/certd/commit/c085bac5d877c4250a8a79e17eb8673b8e4fc89c))
|
||||||
|
* 支持部署到腾讯云直播 ([417d37b](https://github.com/certd/certd/commit/417d37b199b79a42f790f9edab8f178eedf8fbf7))
|
||||||
|
* 支持部署证书到proxmox ([d10795e](https://github.com/certd/certd/commit/d10795ecd97eb8cf2ffa46aabfdbfc6812636396))
|
||||||
|
|
||||||
|
## [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)
|
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
44
README.md
44
README.md
@@ -10,6 +10,7 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
|
|||||||
|
|
||||||
* 全自动申请证书(支持所有注册商注册的域名)
|
* 全自动申请证书(支持所有注册商注册的域名)
|
||||||
* 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等,目前已支持40+部署插件)
|
* 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等,目前已支持40+部署插件)
|
||||||
|
* 支持DNS-01、HTTP-01、CNAME代理等多种域名验证方式
|
||||||
* 支持通配符域名/泛域名,支持多个域名打到一个证书上,支持pem、pfx、der、jks等多种证书格式
|
* 支持通配符域名/泛域名,支持多个域名打到一个证书上,支持pem、pfx、der、jks等多种证书格式
|
||||||
* 邮件通知、webhook通知
|
* 邮件通知、webhook通知
|
||||||
* 私有化部署,数据保存本地,授权信息加密存储,镜像由Github Actions构建,过程公开透明
|
* 私有化部署,数据保存本地,授权信息加密存储,镜像由Github Actions构建,过程公开透明
|
||||||
@@ -20,6 +21,11 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
|
|||||||
> 流水线数量现已调整为无限制,欢迎大家使用
|
> 流水线数量现已调整为无限制,欢迎大家使用
|
||||||
>
|
>
|
||||||
|
|
||||||
|
> 关于证书续期:
|
||||||
|
>* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
|
||||||
|
>* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
|
||||||
|
|
||||||
|
|
||||||
## 二、在线体验
|
## 二、在线体验
|
||||||
|
|
||||||
官方Demo地址,自助注册后体验
|
官方Demo地址,自助注册后体验
|
||||||
@@ -88,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` 即可
|
||||||
|
|
||||||
如果需要使用最新版本
|
#### 2. 如果需要使用最新版本
|
||||||
```shell
|
```shell
|
||||||
#重新拉取镜像
|
#重新拉取镜像
|
||||||
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
@@ -100,7 +108,9 @@ 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`目录下,不用担心数据丢失
|
||||||
|
|
||||||
|
### 自动升级(仅限尝鲜建议非生产使用)
|
||||||
```yaml
|
```yaml
|
||||||
version: '3.3'
|
version: '3.3'
|
||||||
services:
|
services:
|
||||||
@@ -113,16 +123,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "7001:7001"
|
- "7001:7001"
|
||||||
- "7002:7002"
|
- "7002:7002"
|
||||||
# 如果需要修改系统配置,可以通过环境变量传递;初次运行请保持默认配置
|
|
||||||
environment:
|
environment:
|
||||||
- certd_system_resetAdminPasswd=false
|
- certd_system_resetAdminPasswd=false
|
||||||
# 如果需要切换数据库类型,可以在此处设置为mysql或postgres
|
|
||||||
# - certd_typeorm_dataSource_default_type=mysql
|
|
||||||
# - certd_typeorm_dataSource_default_host=localhost
|
|
||||||
# - certd_typeorm_dataSource_default_port=3306
|
|
||||||
# - certd_typeorm_dataSource_default_username=root
|
|
||||||
# - certd_typeorm_dataSource_default_password=123456
|
|
||||||
# - certd_typeorm_dataSource_default_database=certd
|
|
||||||
labels:
|
labels:
|
||||||
com.centurylinklabs.watchtower.enable: "true"
|
com.centurylinklabs.watchtower.enable: "true"
|
||||||
|
|
||||||
@@ -139,27 +141,21 @@ services:
|
|||||||
- WATCHTOWER_LABEL_ENABLE=true # 根据容器标签进行更新
|
- WATCHTOWER_LABEL_ENABLE=true # 根据容器标签进行更新
|
||||||
- WATCHTOWER_POLL_INTERVAL=300 # 每 5 分钟检查一次更新
|
- WATCHTOWER_POLL_INTERVAL=300 # 每 5 分钟检查一次更新
|
||||||
|
|
||||||
# 如果需要支持 IPv6,请取消以下注释
|
|
||||||
# networks:
|
|
||||||
# ip6net:
|
|
||||||
# enable_ipv6: true
|
|
||||||
# ipam:
|
|
||||||
# config:
|
|
||||||
# - subnet: 2001:db8::/64
|
|
||||||
|
|
||||||
```
|
```
|
||||||
> 数据默认存在`/data/certd`目录下,不用担心数据丢失
|
|
||||||
|
### 其他部署方式升级方法
|
||||||
|
请参考 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解析记录,通配符域名只能用这种方式
|
|
||||||
* 证书续期:
|
* 证书续期:
|
||||||
* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
|
* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
|
||||||
* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
|
* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
00:19
|
00:38
|
||||||
|
|||||||
@@ -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,15 +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文件数据库,如果需要使用其他数据库,请设置以下环境变量
|
# 默认使用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 # 数据库类型
|
||||||
@@ -52,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
|
||||||
|
|||||||
@@ -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: {
|
||||||
@@ -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
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
9
docs/comm/index.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# 商业版文档
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 支付方式配置
|
||||||
|
|
||||||
|
* [支付宝支付配置](./payments/alipay.md)
|
||||||
|
* [微信支付配置](./payments/wxpay.md)
|
||||||
|
* [彩虹易支付配置](./payments/yizhifu.md)
|
||||||
21
docs/comm/payments/alipay.md
Normal file
21
docs/comm/payments/alipay.md
Normal 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、应用私钥、支付宝公钥等信息即可。
|
||||||
27
docs/comm/payments/wxpay.md
Normal file
27
docs/comm/payments/wxpay.md
Normal 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密钥即可。
|
||||||
19
docs/comm/payments/yizhifu.md
Normal file
19
docs/comm/payments/yizhifu.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 彩虹易支付配置
|
||||||
|
|
||||||
|
彩虹易支付是一款非常流行的php聚合支付系统。
|
||||||
|
|
||||||
|
## 配置步骤
|
||||||
|
|
||||||
|
1. 获取商户ID、商户密钥
|
||||||
|
|
||||||
|
* 登录彩虹易支付平台
|
||||||
|
* 进入用户中心:https://xxxxxx.com/user/userinfo.php?mod=api
|
||||||
|
* 点击API信息
|
||||||
|
* 可以复制:接口地址、商户ID、商户密钥(key)
|
||||||
|
* 点击查看文档,了解支持的签名类型,一般为MD5
|
||||||
|
|
||||||
|
2. 进入Certd后台配置彩虹易支付
|
||||||
|
|
||||||
|
* 进入“系统”->"设置"->“支付设置”
|
||||||
|
* 启用彩虹易支付,选择“彩虹易支付配置”,点击添加
|
||||||
|
* 填写接口地址、商户ID、商户密钥、签名方式等信息即可。
|
||||||
@@ -3,6 +3,99 @@
|
|||||||
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.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复查看任务日志偶发性无法自动滚动底部的bug ([7e482f7](https://github.com/certd/certd/commit/7e482f798c0142bce1866f84676cb40210f9638a))
|
||||||
|
* 修复namesilo ttl太短的问题 ([865f26d](https://github.com/certd/certd/commit/865f26d75c0d3dd4dc8b41448f8830068e45957c))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 支持open api接口,根据域名获取证书 ([52a4fd3](https://github.com/certd/certd/commit/52a4fd33180e9b3f71b8dc9f7671d7cd8e448c3b))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 证书仓库 ([91e7f45](https://github.com/certd/certd/commit/91e7f45a1c5ea1e0ec0aa3236b80028f03a6d0aa))
|
||||||
|
* 支持部署到阿里云ALB ([653940a](https://github.com/certd/certd/commit/653940a0ca64fc380178c1b0b58ae0af64dfaf07))
|
||||||
|
* 支持部署到阿里云NLB、SLB ([c085bac](https://github.com/certd/certd/commit/c085bac5d877c4250a8a79e17eb8673b8e4fc89c))
|
||||||
|
* 支持部署到腾讯云直播 ([417d37b](https://github.com/certd/certd/commit/417d37b199b79a42f790f9edab8f178eedf8fbf7))
|
||||||
|
* 支持部署证书到proxmox ([d10795e](https://github.com/certd/certd/commit/d10795ecd97eb8cf2ffa46aabfdbfc6812636396))
|
||||||
|
|
||||||
|
## [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)
|
## [1.28.4](https://github.com/certd/certd/compare/v1.28.3...v1.28.4) (2024-12-12)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"version": "1.29.0"
|
"version": "1.30.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.30.1](https://github.com/publishlab/node-acme-client/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/publishlab/node-acme-client/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复查看任务日志偶发性无法自动滚动底部的bug ([7e482f7](https://github.com/publishlab/node-acme-client/commit/7e482f798c0142bce1866f84676cb40210f9638a))
|
||||||
|
|
||||||
|
## [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)
|
# [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
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|||||||
@@ -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.29.0",
|
"version": "1.30.1",
|
||||||
"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.29.0",
|
"@certd/basic": "^1.30.1",
|
||||||
"@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": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
|
"gitHead": "9a78dad57619e02d1390cf5a013695a2aaf20e64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ instance.interceptors.response.use(null, async (error) => {
|
|||||||
|
|
||||||
/* Wait and retry the request */
|
/* Wait and retry the request */
|
||||||
await new Promise((resolve) => { setTimeout(resolve, (retryAfter * 1000)); });
|
await new Promise((resolve) => { setTimeout(resolve, (retryAfter * 1000)); });
|
||||||
|
log(`Retrying request to URL ${config.url}`);
|
||||||
return instance(config);
|
return instance(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,3 +5,5 @@ export class CancelError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
6
packages/core/acme-client/types/index.d.ts
vendored
6
packages/core/acme-client/types/index.d.ts
vendored
@@ -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;
|
||||||
@@ -3,6 +3,36 @@
|
|||||||
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.30.1](https://github.com/certd/certd/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
|
## [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)
|
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
01:02
|
23:35
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/basic",
|
"name": "@certd/basic",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.29.0",
|
"version": "1.30.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -44,5 +44,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
|
"gitHead": "9a78dad57619e02d1390cf5a013695a2aaf20e64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ 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.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';
|
||||||
@@ -23,6 +27,7 @@ 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 { locker } from './util.lock.js';
|
||||||
@@ -46,4 +51,5 @@ export const utils = {
|
|||||||
string: stringUtils,
|
string: stringUtils,
|
||||||
locker,
|
locker,
|
||||||
mitter,
|
mitter,
|
||||||
|
amount: amountUtils,
|
||||||
};
|
};
|
||||||
|
|||||||
9
packages/core/basic/src/utils/util.amount.ts
Normal file
9
packages/core/basic/src/utils/util.amount.ts
Normal 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));
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -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.30.1](https://github.com/certd/certd/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复tg消息内容中存在.和*就会发送失败的bug ([ae5dfc3](https://github.com/certd/certd/commit/ae5dfc3bee950267123ae2fbd1c11e7ce36626ea))
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 证书仓库 ([91e7f45](https://github.com/certd/certd/commit/91e7f45a1c5ea1e0ec0aa3236b80028f03a6d0aa))
|
||||||
|
|
||||||
|
## [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)
|
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/pipeline",
|
"name": "@certd/pipeline",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.29.0",
|
"version": "1.30.1",
|
||||||
"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.29.0",
|
"@certd/basic": "^1.30.1",
|
||||||
"@certd/plus-core": "^1.29.0",
|
"@certd/plus-core": "^1.30.1",
|
||||||
"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": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
|
"gitHead": "9a78dad57619e02d1390cf5a013695a2aaf20e64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { ICnameProxyService, IEmailService, IPluginConfigService, IUrlService }
|
|||||||
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 } from "../notification/index.js";
|
import { INotificationService } from "../notification/index.js";
|
||||||
|
import { taskEmitterCreate } from "../service/emit.js";
|
||||||
|
|
||||||
export type SysInfo = {
|
export type SysInfo = {
|
||||||
//系统标题
|
//系统标题
|
||||||
@@ -109,7 +110,13 @@ export class Executor {
|
|||||||
} 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`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,6 +343,10 @@ export class Executor {
|
|||||||
signal: this.abort.signal,
|
signal: this.abort.signal,
|
||||||
utils,
|
utils,
|
||||||
user: this.options.user,
|
user: this.options.user,
|
||||||
|
emitter: taskEmitterCreate({
|
||||||
|
step,
|
||||||
|
pipeline: this.pipeline,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
instance.setCtx(taskCtx);
|
instance.setCtx(taskCtx);
|
||||||
|
|
||||||
@@ -424,7 +435,7 @@ export class Executor {
|
|||||||
content,
|
content,
|
||||||
userId: this.pipeline.userId,
|
userId: this.pipeline.userId,
|
||||||
pipeline: this.pipeline,
|
pipeline: this.pipeline,
|
||||||
result: this.lastRuntime.pipeline.status,
|
result: this.lastRuntime?.pipeline?.status,
|
||||||
pipelineId: this.pipeline.id,
|
pipelineId: this.pipeline.id,
|
||||||
historyId: this.runtime.id,
|
historyId: this.runtime.id,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
|
|||||||
@@ -146,6 +146,10 @@ export class RunnableCollection {
|
|||||||
|
|
||||||
static initPipelineRunnableType(pipeline: Pipeline) {
|
static initPipelineRunnableType(pipeline: Pipeline) {
|
||||||
pipeline.runnableType = "pipeline";
|
pipeline.runnableType = "pipeline";
|
||||||
|
if (pipeline.stages === undefined) {
|
||||||
|
pipeline.stages = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
pipeline.stages.forEach((stage) => {
|
pipeline.stages.forEach((stage) => {
|
||||||
stage.runnableType = "stage";
|
stage.runnableType = "stage";
|
||||||
stage.tasks.forEach((task) => {
|
stage.tasks.forEach((task) => {
|
||||||
|
|||||||
@@ -121,11 +121,11 @@ export abstract class BaseNotification implements INotification {
|
|||||||
async onTestRequest() {
|
async onTestRequest() {
|
||||||
await this.doSend({
|
await this.doSend({
|
||||||
userId: 0,
|
userId: 0,
|
||||||
title: "【Certd】测试通知,标题长度测试、测试、测试",
|
title: "【Certd】测试通知【*.foo.com】,标题长度测试、测试、测试",
|
||||||
content: "测试通知",
|
content: "测试通知,*.foo.com",
|
||||||
pipeline: {
|
pipeline: {
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "测试流水线",
|
title: "证书申请成功【测试流水线】",
|
||||||
} as any,
|
} as any,
|
||||||
pipelineId: 1,
|
pipelineId: 1,
|
||||||
historyId: 1,
|
historyId: 1,
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/i
|
|||||||
import { HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
|
import { HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
|
||||||
import { HttpClient } from "@certd/basic";
|
import { HttpClient } from "@certd/basic";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { IPluginConfigService } from "../service/config";
|
import { IPluginConfigService } from "../service/config.js";
|
||||||
import { upperFirst } from "lodash-es";
|
import { upperFirst } from "lodash-es";
|
||||||
import { INotificationService } from "../notification";
|
import { INotificationService } from "../notification/index.js";
|
||||||
|
import { TaskEmitter } from "../service/emit.js";
|
||||||
|
|
||||||
export type PluginRequestHandleReq<T = any> = {
|
export type PluginRequestHandleReq<T = any> = {
|
||||||
typeName: string;
|
typeName: string;
|
||||||
@@ -111,6 +112,8 @@ export type TaskInstanceContext = {
|
|||||||
utils: typeof utils;
|
utils: typeof utils;
|
||||||
//用户信息
|
//用户信息
|
||||||
user: UserInfo;
|
user: UserInfo;
|
||||||
|
|
||||||
|
emitter: TaskEmitter;
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
||||||
|
|||||||
@@ -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>;
|
||||||
};
|
};
|
||||||
|
|||||||
68
packages/core/pipeline/src/service/emit.ts
Normal file
68
packages/core/pipeline/src/service/emit.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { logger } from "@certd/basic";
|
||||||
|
import { Pipeline, Runnable } from "../dt";
|
||||||
|
|
||||||
|
export type PipelineEventListener = (...args: any[]) => Promise<void>;
|
||||||
|
export type PipelineEvent<T> = {
|
||||||
|
pipeline: Pipeline;
|
||||||
|
step: Runnable;
|
||||||
|
event: T;
|
||||||
|
};
|
||||||
|
export class PipelineEmitter {
|
||||||
|
events: Record<string, PipelineEventListener[]>;
|
||||||
|
constructor() {
|
||||||
|
this.events = {};
|
||||||
|
}
|
||||||
|
on(event: string, listener: PipelineEventListener) {
|
||||||
|
if (!this.events[event]) {
|
||||||
|
this.events[event] = [];
|
||||||
|
}
|
||||||
|
this.events[event].push(listener);
|
||||||
|
}
|
||||||
|
async emit<T>(name: string, event: PipelineEvent<T>) {
|
||||||
|
const listeners = this.events[name];
|
||||||
|
if (listeners) {
|
||||||
|
for (const listener of listeners) {
|
||||||
|
try {
|
||||||
|
await listener(event);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`事件<${name}>监听器执行失败:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
off(event: string, listener: PipelineEventListener) {
|
||||||
|
if (this.events[event]) {
|
||||||
|
this.events[event] = this.events[event].filter((l) => l !== listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
once(event: string, listener: PipelineEventListener) {
|
||||||
|
const onceListener = async (...args: any[]) => {
|
||||||
|
this.off(event, onceListener);
|
||||||
|
await listener(...args);
|
||||||
|
};
|
||||||
|
this.on(event, onceListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pipelineEmitter = new PipelineEmitter();
|
||||||
|
|
||||||
|
export type TaskEmitterCreateReq = {
|
||||||
|
step: Runnable;
|
||||||
|
pipeline: Pipeline;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TaskEmitter = {
|
||||||
|
emit: <T>(name: string, event: T) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function taskEmitterCreate(req: TaskEmitterCreateReq) {
|
||||||
|
return {
|
||||||
|
emit: async <T>(name: string, event: T) => {
|
||||||
|
await pipelineEmitter.emit(name, {
|
||||||
|
pipeline: req.pipeline,
|
||||||
|
step: req.step,
|
||||||
|
event,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
} as TaskEmitter;
|
||||||
|
}
|
||||||
@@ -2,3 +2,4 @@ export * from "./email.js";
|
|||||||
export * from "./cname.js";
|
export * from "./cname.js";
|
||||||
export * from "./config.js";
|
export * from "./config.js";
|
||||||
export * from "./url.js";
|
export * from "./url.js";
|
||||||
|
export * from "./emit.js";
|
||||||
|
|||||||
@@ -3,6 +3,34 @@
|
|||||||
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.30.1](https://github.com/certd/certd/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
|
## [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)
|
# [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
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-huawei",
|
"name": "@certd/lib-huawei",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.29.0",
|
"version": "1.30.1",
|
||||||
"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": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
|
"gitHead": "9a78dad57619e02d1390cf5a013695a2aaf20e64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,34 @@
|
|||||||
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.30.1](https://github.com/certd/certd/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
|
## [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)
|
# [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
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-iframe",
|
"name": "@certd/lib-iframe",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.29.0",
|
"version": "1.30.1",
|
||||||
"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": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
|
"gitHead": "9a78dad57619e02d1390cf5a013695a2aaf20e64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,34 @@
|
|||||||
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.30.1](https://github.com/certd/certd/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
|
## [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)
|
# [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
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-k8s",
|
"name": "@certd/lib-k8s",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.29.0",
|
"version": "1.30.1",
|
||||||
"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.29.0",
|
"@certd/basic": "^1.30.1",
|
||||||
"@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": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
|
"gitHead": "9a78dad57619e02d1390cf5a013695a2aaf20e64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.30.1](https://github.com/certd/certd/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-server
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 支持open api接口,根据域名获取证书 ([52a4fd3](https://github.com/certd/certd/commit/52a4fd33180e9b3f71b8dc9f7671d7cd8e448c3b))
|
||||||
|
|
||||||
|
## [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)
|
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-server",
|
"name": "@certd/lib-server",
|
||||||
"version": "1.29.0",
|
"version": "1.30.1",
|
||||||
"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.29.0",
|
"@certd/acme-client": "^1.30.1",
|
||||||
"@certd/basic": "^1.29.0",
|
"@certd/basic": "^1.30.1",
|
||||||
"@certd/pipeline": "^1.29.0",
|
"@certd/pipeline": "^1.30.1",
|
||||||
"@certd/plus-core": "^1.29.0",
|
"@certd/plus-core": "^1.30.1",
|
||||||
"@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": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
|
"gitHead": "9a78dad57619e02d1390cf5a013695a2aaf20e64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export const Constants = {
|
|||||||
authOnly: '_authOnly_',
|
authOnly: '_authOnly_',
|
||||||
//仅需要登录
|
//仅需要登录
|
||||||
loginOnly: '_authOnly_',
|
loginOnly: '_authOnly_',
|
||||||
|
|
||||||
|
open: '_open_',
|
||||||
},
|
},
|
||||||
res: {
|
res: {
|
||||||
serverError(message: string) {
|
serverError(message: string) {
|
||||||
@@ -68,5 +70,33 @@ export const Constants = {
|
|||||||
code: 10001,
|
code: 10001,
|
||||||
message: '对不起,预览环境不允许修改此数据',
|
message: '对不起,预览环境不允许修改此数据',
|
||||||
},
|
},
|
||||||
|
openKeyError: {
|
||||||
|
code: 20000,
|
||||||
|
message: 'ApiToken错误',
|
||||||
|
},
|
||||||
|
openKeySignError: {
|
||||||
|
code: 20001,
|
||||||
|
message: 'ApiToken签名错误',
|
||||||
|
},
|
||||||
|
openKeyExpiresError: {
|
||||||
|
code: 20002,
|
||||||
|
message: 'ApiToken时间戳错误',
|
||||||
|
},
|
||||||
|
openKeySignTypeError: {
|
||||||
|
code: 20003,
|
||||||
|
message: 'ApiToken签名类型不支持',
|
||||||
|
},
|
||||||
|
openParamError: {
|
||||||
|
code: 20010,
|
||||||
|
message: '请求参数错误',
|
||||||
|
},
|
||||||
|
openCertNotFound: {
|
||||||
|
code: 20011,
|
||||||
|
message: '证书不存在',
|
||||||
|
},
|
||||||
|
openCertNotReady: {
|
||||||
|
code: 20012,
|
||||||
|
message: '证书还未生成',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,10 +5,6 @@ import { BaseException } from './base-exception.js';
|
|||||||
*/
|
*/
|
||||||
export class AuthException extends BaseException {
|
export class AuthException extends BaseException {
|
||||||
constructor(message) {
|
constructor(message) {
|
||||||
super(
|
super('AuthException', Constants.res.auth.code, message ? message : Constants.res.auth.message);
|
||||||
'AuthException',
|
|
||||||
Constants.res.auth.code,
|
|
||||||
message ? message : Constants.res.auth.message
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
* 异常基类
|
* 异常基类
|
||||||
*/
|
*/
|
||||||
export class BaseException extends Error {
|
export class BaseException extends Error {
|
||||||
status: number;
|
code: number;
|
||||||
constructor(name, code, message) {
|
constructor(name, code, message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.status = code;
|
this.code = code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,3 +8,9 @@ export class CommonException extends BaseException {
|
|||||||
super('CommonException', Constants.res.error.code, message ? message : Constants.res.error.message);
|
super('CommonException', Constants.res.error.code, message ? message : Constants.res.error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CodeException extends BaseException {
|
||||||
|
constructor(res: { code: number; message: string }) {
|
||||||
|
super('CodeException', res.code, res.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class Result<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static error(code = 1, msg) {
|
static error(code = 1, msg) {
|
||||||
return new Result(code, msg, null);
|
return new Result(code, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static success(msg, data?) {
|
static success(msg, data?) {
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './service/plus-service.js';
|
export * from './service/plus-service.js';
|
||||||
export * from './service/file-service.js';
|
export * from './service/file-service.js';
|
||||||
|
export * from './service/encryptor.js';
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
export class Encryptor {
|
||||||
|
secretKey: Buffer;
|
||||||
|
constructor(encryptSecret: string, encoding: BufferEncoding = 'base64') {
|
||||||
|
this.secretKey = Buffer.from(encryptSecret, encoding);
|
||||||
|
}
|
||||||
|
// 加密函数
|
||||||
|
encrypt(text: string) {
|
||||||
|
const iv = crypto.randomBytes(16); // 初始化向量
|
||||||
|
// const secretKey = crypto.randomBytes(32);
|
||||||
|
// const key = Buffer.from(secretKey);
|
||||||
|
const cipher = crypto.createCipheriv('aes-256-cbc', this.secretKey, iv);
|
||||||
|
let encrypted = cipher.update(text);
|
||||||
|
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||||
|
return iv.toString('hex') + ':' + encrypted.toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密函数
|
||||||
|
decrypt(encryptedText: string) {
|
||||||
|
const textParts = encryptedText.split(':');
|
||||||
|
const iv = Buffer.from(textParts.shift(), 'hex');
|
||||||
|
const encrypted = Buffer.from(textParts.join(':'), 'hex');
|
||||||
|
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(this.secretKey), iv);
|
||||||
|
let decrypted = decipher.update(encrypted);
|
||||||
|
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||||
|
return decrypted.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,25 +1,22 @@
|
|||||||
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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置
|
* 设置
|
||||||
*/
|
*/
|
||||||
@Provide()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Singleton)
|
||||||
export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import crypto from 'crypto';
|
import { Encryptor, SysSecret, SysSettingsService } from '../../../system/index.js';
|
||||||
import { SysPrivateSettings, SysSettingsService } from '../../../system/index.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 授权
|
* 授权
|
||||||
@@ -8,36 +7,24 @@ import { SysPrivateSettings, SysSettingsService } from '../../../system/index.js
|
|||||||
@Provide()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Singleton)
|
@Scope(ScopeEnum.Singleton)
|
||||||
export class EncryptService {
|
export class EncryptService {
|
||||||
secretKey: Buffer;
|
encryptor: Encryptor;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
sysSettingService: SysSettingsService;
|
sysSettingService: SysSettingsService;
|
||||||
|
|
||||||
@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.encryptor = new Encryptor(secret.encryptSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加密函数
|
// 加密函数
|
||||||
encrypt(text: string) {
|
encrypt(text: string) {
|
||||||
const iv = crypto.randomBytes(16); // 初始化向量
|
return this.encryptor.encrypt(text);
|
||||||
// const secretKey = crypto.randomBytes(32);
|
|
||||||
// const key = Buffer.from(secretKey);
|
|
||||||
const cipher = crypto.createCipheriv('aes-256-cbc', this.secretKey, iv);
|
|
||||||
let encrypted = cipher.update(text);
|
|
||||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
||||||
return iv.toString('hex') + ':' + encrypted.toString('hex');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解密函数
|
// 解密函数
|
||||||
decrypt(encryptedText: string) {
|
decrypt(encryptedText: string) {
|
||||||
const textParts = encryptedText.split(':');
|
return this.encryptor.decrypt(encryptedText);
|
||||||
const iv = Buffer.from(textParts.shift(), 'hex');
|
|
||||||
const encrypted = Buffer.from(textParts.join(':'), 'hex');
|
|
||||||
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(this.secretKey), iv);
|
|
||||||
let decrypted = decipher.update(encrypted);
|
|
||||||
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
||||||
return decrypted.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,34 @@
|
|||||||
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.30.1](https://github.com/certd/certd/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
|
## [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)
|
# [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
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/midway-flyway-js",
|
"name": "@certd/midway-flyway-js",
|
||||||
"version": "1.29.0",
|
"version": "1.30.1",
|
||||||
"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": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
|
"gitHead": "9a78dad57619e02d1390cf5a013695a2aaf20e64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.30.1](https://github.com/certd/certd/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* http方式校验,选择sftp时,支持修改文件访问权限比如777 ([15d6eaf](https://github.com/certd/certd/commit/15d6eaf5532ed25acd4f8d58c429353a2f44206c))
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复namesilo ttl太短的问题 ([865f26d](https://github.com/certd/certd/commit/865f26d75c0d3dd4dc8b41448f8830068e45957c))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 证书仓库 ([91e7f45](https://github.com/certd/certd/commit/91e7f45a1c5ea1e0ec0aa3236b80028f03a6d0aa))
|
||||||
|
|
||||||
|
## [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)
|
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-cert",
|
"name": "@certd/plugin-cert",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.29.0",
|
"version": "1.30.1",
|
||||||
"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.29.0",
|
"@certd/acme-client": "^1.30.1",
|
||||||
"@certd/basic": "^1.29.0",
|
"@certd/basic": "^1.30.1",
|
||||||
"@certd/pipeline": "^1.29.0",
|
"@certd/pipeline": "^1.30.1",
|
||||||
|
"@certd/plugin-lib": "^1.30.1",
|
||||||
"@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": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
|
"gitHead": "9a78dad57619e02d1390cf5a013695a2aaf20e64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,23 +6,37 @@ 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; //fullchain证书
|
crt: string; //fullchain证书
|
||||||
key: string; //私钥
|
key: string; //私钥
|
||||||
@@ -155,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);
|
||||||
@@ -226,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -239,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 {
|
||||||
@@ -275,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 */
|
||||||
@@ -319,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,
|
||||||
@@ -344,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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AbstractTaskPlugin, IContext, NotificationBody, Step, TaskInput, TaskOutput } from "@certd/pipeline";
|
import { AbstractTaskPlugin, IContext, NotificationBody, Step, TaskEmitter, 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";
|
||||||
@@ -6,8 +6,11 @@ import JSZip from "jszip";
|
|||||||
import { CertConverter } from "./convert.js";
|
import { CertConverter } from "./convert.js";
|
||||||
import { pick } from "lodash-es";
|
import { pick } from "lodash-es";
|
||||||
|
|
||||||
export { CertReader };
|
export const EVENT_CERT_APPLY_SUCCESS = "CertApply.success";
|
||||||
export type { CertInfo };
|
|
||||||
|
export async function emitCertApplySuccess(emitter: TaskEmitter, cert: CertReader) {
|
||||||
|
await emitter.emit(EVENT_CERT_APPLY_SUCCESS, cert);
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
@@ -17,6 +20,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 +30,9 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
|||||||
},
|
},
|
||||||
order: -999,
|
order: -999,
|
||||||
helper:
|
helper:
|
||||||
"1、支持通配符域名,例如: *.foo.com、foo.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[];
|
||||||
@@ -54,7 +58,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
|||||||
},
|
},
|
||||||
required: false,
|
required: false,
|
||||||
order: 100,
|
order: 100,
|
||||||
helper: "PFX、jks格式证书是否加密;jks必须设置密码,不传则默认123456",
|
helper: "PFX、jks格式证书是否加密\njks必须设置密码,不传则默认123456",
|
||||||
})
|
})
|
||||||
pfxPassword!: string;
|
pfxPassword!: string;
|
||||||
|
|
||||||
@@ -62,12 +66,17 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
|||||||
title: "PFX证书转换参数",
|
title: "PFX证书转换参数",
|
||||||
value: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES",
|
value: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES",
|
||||||
component: {
|
component: {
|
||||||
name: "a-input",
|
name: "a-auto-complete",
|
||||||
vModel: "value",
|
vModel: "value",
|
||||||
|
options: [
|
||||||
|
{ value: "", label: "兼容 Windows Server 最新" },
|
||||||
|
{ value: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES", label: "兼容 Windows Server 2016" },
|
||||||
|
{ value: "-nomac -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES", label: "兼容 Windows Server 2008" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
required: false,
|
required: false,
|
||||||
order: 100,
|
order: 100,
|
||||||
helper: "兼容Server 2016,如果导入证书失败,请删除此参数",
|
helper: "兼容Windows Server各个版本",
|
||||||
})
|
})
|
||||||
pfxArgs = "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES";
|
pfxArgs = "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES";
|
||||||
|
|
||||||
@@ -118,7 +127,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
|||||||
|
|
||||||
abstract onInit(): Promise<void>;
|
abstract onInit(): Promise<void>;
|
||||||
|
|
||||||
abstract doCertApply(): Promise<any>;
|
abstract doCertApply(): Promise<CertReader>;
|
||||||
|
|
||||||
async execute(): Promise<string | void> {
|
async execute(): Promise<string | void> {
|
||||||
const oldCert = await this.condition();
|
const oldCert = await this.condition();
|
||||||
@@ -129,6 +138,8 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
|||||||
const cert = await this.doCertApply();
|
const cert = await this.doCertApply();
|
||||||
if (cert != null) {
|
if (cert != null) {
|
||||||
await this.output(cert, true);
|
await this.output(cert, true);
|
||||||
|
|
||||||
|
await emitCertApplySuccess(this.ctx.emitter, cert);
|
||||||
//清空后续任务的状态,让后续任务能够重新执行
|
//清空后续任务的状态,让后续任务能够重新执行
|
||||||
this.clearLastStatus();
|
this.clearLastStatus();
|
||||||
|
|
||||||
@@ -233,28 +244,10 @@ cert.jks:jks格式证书文件,java服务器使用
|
|||||||
// return null;
|
// return null;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let inputChanged = false;
|
|
||||||
//判断域名有没有变更
|
|
||||||
/**
|
|
||||||
* "renewDays": 35,
|
|
||||||
* "certApplyPlugin": "CertApply",
|
|
||||||
* "sslProvider": "letsencrypt",
|
|
||||||
* "privateKeyType": "rsa_2048_pkcs1",
|
|
||||||
* "dnsProviderType": "aliyun",
|
|
||||||
* "domains": [
|
|
||||||
* "*.handsfree.work"
|
|
||||||
* ],
|
|
||||||
* "email": "xiaojunnuo@qq.com",
|
|
||||||
* "dnsProviderAccess": 3,
|
|
||||||
* "useProxy": false,
|
|
||||||
* "skipLocalVerify": false,
|
|
||||||
* "successNotify": true,
|
|
||||||
* "pfxPassword": "123456"
|
|
||||||
*/
|
|
||||||
const checkInputChanges = ["domains", "sslProvider", "privateKeyType", "dnsProviderType", "pfxPassword"];
|
const checkInputChanges = ["domains", "sslProvider", "privateKeyType", "dnsProviderType", "pfxPassword"];
|
||||||
const oldInput = JSON.stringify(pick(this.lastStatus?.input, checkInputChanges));
|
const oldInput = JSON.stringify(pick(this.lastStatus?.input, checkInputChanges));
|
||||||
const thisInput = JSON.stringify(pick(this, checkInputChanges));
|
const thisInput = JSON.stringify(pick(this, checkInputChanges));
|
||||||
inputChanged = oldInput !== thisInput;
|
const inputChanged = oldInput !== thisInput;
|
||||||
|
|
||||||
this.logger.info(`旧参数:${oldInput}`);
|
this.logger.info(`旧参数:${oldInput}`);
|
||||||
this.logger.info(`新参数:${thisInput}`);
|
this.logger.info(`新参数:${thisInput}`);
|
||||||
@@ -262,11 +255,12 @@ cert.jks:jks格式证书文件,java服务器使用
|
|||||||
this.logger.info("输入参数变更,准备申请新证书");
|
this.logger.info("输入参数变更,准备申请新证书");
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
this.logger.info("输入参数未变更,不需要更新证书");
|
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);
|
||||||
@@ -304,6 +298,7 @@ cert.jks:jks格式证书文件,java服务器使用
|
|||||||
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);
|
||||||
@@ -321,7 +316,7 @@ cert.jks:jks格式证书文件,java服务器使用
|
|||||||
// 检查有效期
|
// 检查有效期
|
||||||
const leftDays = dayjs(expires).diff(dayjs(), "day");
|
const leftDays = dayjs(expires).diff(dayjs(), "day");
|
||||||
return {
|
return {
|
||||||
isWillExpire: leftDays < maxDays,
|
isWillExpire: leftDays <= maxDays,
|
||||||
leftDays,
|
leftDays,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { CertInfo } from "./acme.js";
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { crypto } from "@certd/acme-client";
|
import { CertificateInfo, crypto } from "@certd/acme-client";
|
||||||
import { ILogger } from "@certd/basic";
|
import { ILogger } from "@certd/basic";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
@@ -21,37 +21,22 @@ 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;
|
|
||||||
key: string;
|
|
||||||
csr: string;
|
|
||||||
ic: string; //中间证书
|
|
||||||
one: string; //crt + key 合成一个pem文件
|
|
||||||
|
|
||||||
detail: any;
|
detail: CertificateInfo;
|
||||||
expires: number;
|
expires: number;
|
||||||
constructor(certInfo: CertInfo) {
|
constructor(certInfo: CertInfo) {
|
||||||
this.cert = certInfo;
|
this.cert = certInfo;
|
||||||
this.crt = certInfo.crt;
|
|
||||||
this.key = certInfo.key;
|
|
||||||
this.csr = certInfo.csr;
|
|
||||||
|
|
||||||
this.ic = certInfo.ic;
|
if (!certInfo.ic) {
|
||||||
if (!this.ic) {
|
this.cert.ic = this.getIc();
|
||||||
this.ic = this.getIc();
|
|
||||||
this.cert.ic = this.ic;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.oc = certInfo.oc;
|
if (!certInfo.oc) {
|
||||||
if (!this.oc) {
|
this.cert.oc = this.getOc();
|
||||||
this.oc = this.getOc();
|
|
||||||
this.cert.oc = this.oc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.one = certInfo.one;
|
if (!certInfo.one) {
|
||||||
if (!this.one) {
|
this.cert.one = this.cert.crt + "\n" + this.cert.key;
|
||||||
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);
|
||||||
@@ -62,20 +47,23 @@ export class CertReader {
|
|||||||
getIc() {
|
getIc() {
|
||||||
//中间证书ic, 就是crt的第一个 -----END CERTIFICATE----- 之后的内容
|
//中间证书ic, 就是crt的第一个 -----END CERTIFICATE----- 之后的内容
|
||||||
const endStr = "-----END CERTIFICATE-----";
|
const endStr = "-----END CERTIFICATE-----";
|
||||||
const firstBlockEndIndex = this.crt.indexOf(endStr);
|
const firstBlockEndIndex = this.cert.crt.indexOf(endStr);
|
||||||
|
|
||||||
const start = firstBlockEndIndex + endStr.length + 1;
|
const start = firstBlockEndIndex + endStr.length + 1;
|
||||||
if (this.crt.length <= start) {
|
if (this.cert.crt.length <= start) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
const ic = this.crt.substring(start);
|
const ic = this.cert.crt.substring(start);
|
||||||
return ic.trim();
|
if (ic == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return ic?.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
getOc() {
|
getOc() {
|
||||||
//原始证书 就是crt的第一个 -----END CERTIFICATE----- 之前的内容
|
//原始证书 就是crt的第一个 -----END CERTIFICATE----- 之前的内容
|
||||||
const endStr = "-----END CERTIFICATE-----";
|
const endStr = "-----END CERTIFICATE-----";
|
||||||
const arr = this.crt.split(endStr);
|
const arr = this.cert.crt.split(endStr);
|
||||||
return arr[0] + endStr;
|
return arr[0] + endStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,10 +135,11 @@ export class CertReader {
|
|||||||
tmpOnePath,
|
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);
|
||||||
@@ -167,7 +156,7 @@ export class CertReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCertFileName(suffix: string, applyTime: number, prefix = "cert") {
|
buildCertFileName(suffix: string, applyTime: any, prefix = "cert") {
|
||||||
const detail = this.getCrtDetail();
|
const detail = this.getCrtDetail();
|
||||||
let domain = detail.detail.domains.commonName;
|
let domain = detail.detail.domains.commonName;
|
||||||
domain = domain.replace(".", "_").replace("*", "_");
|
domain = domain.replace(".", "_").replace("*", "_");
|
||||||
|
|||||||
@@ -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,20 +9,28 @@ 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 * from "./base.js";
|
||||||
export type { CertInfo };
|
export type { CertInfo };
|
||||||
export * from "./cert-reader.js";
|
export * from "./cert-reader.js";
|
||||||
export type CnameRecordInput = {
|
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,11 +62,13 @@ 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;
|
||||||
|
|
||||||
@@ -122,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,
|
||||||
},
|
},
|
||||||
@@ -132,10 +141,20 @@ 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 '请按照上面的提示,给每个域名设置文件上传配置,证书申请过程中会上传校验文件到网站根目录下'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -213,7 +232,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||||||
// { value: "ec_521", label: "EC 521" },
|
// { value: "ec_521", label: "EC 521" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
helper: "如无特殊需求,默认即可",
|
helper: "如无特殊需求,默认即可\n选择RSA 2048 pkcs1可以获得旧版RSA证书",
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
privateKeyType!: PrivateKeyType;
|
privateKeyType!: PrivateKeyType;
|
||||||
@@ -320,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;
|
||||||
@@ -370,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;
|
||||||
@@ -381,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;
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
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 === "sftp") {
|
||||||
|
const module = await import("./impls/sftp.js");
|
||||||
|
return module.SftpHttpChallengeUploader;
|
||||||
|
} 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();
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { BaseHttpChallengeUploader } from "../api.js";
|
||||||
|
import { SshAccess, SshClient } from "@certd/plugin-lib";
|
||||||
|
import path from "path";
|
||||||
|
import os from "os";
|
||||||
|
import fs from "fs";
|
||||||
|
import { SftpAccess } from "@certd/plugin-lib";
|
||||||
|
|
||||||
|
export class SftpHttpChallengeUploader extends BaseHttpChallengeUploader<SftpAccess> {
|
||||||
|
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 access = await this.ctx.accessService.getById<SshAccess>(this.access.sshAccess);
|
||||||
|
const key = this.rootDir + filePath;
|
||||||
|
try {
|
||||||
|
const client = new SshClient(this.logger);
|
||||||
|
await client.uploadFiles({
|
||||||
|
connectConf: access,
|
||||||
|
mkdirs: true,
|
||||||
|
transports: [
|
||||||
|
{
|
||||||
|
localPath: tmpFilePath,
|
||||||
|
remotePath: key,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
opts: {
|
||||||
|
mode: this.access?.fileMode ?? undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
// Remove temp file
|
||||||
|
fs.unlinkSync(tmpFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(filePath: string) {
|
||||||
|
const access = await this.ctx.accessService.getById<SshAccess>(this.access.sshAccess);
|
||||||
|
const client = new SshClient(this.logger);
|
||||||
|
const key = this.rootDir + filePath;
|
||||||
|
await client.removeFiles({
|
||||||
|
connectConf: access,
|
||||||
|
files: [key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,47 @@
|
|||||||
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.30.1](https://github.com/certd/certd/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* http方式校验,选择sftp时,支持修改文件访问权限比如777 ([15d6eaf](https://github.com/certd/certd/commit/15d6eaf5532ed25acd4f8d58c429353a2f44206c))
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复查看任务日志偶发性无法自动滚动底部的bug ([7e482f7](https://github.com/certd/certd/commit/7e482f798c0142bce1866f84676cb40210f9638a))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 支持部署到阿里云ALB ([653940a](https://github.com/certd/certd/commit/653940a0ca64fc380178c1b0b58ae0af64dfaf07))
|
||||||
|
* 支持部署到阿里云NLB、SLB ([c085bac](https://github.com/certd/certd/commit/c085bac5d877c4250a8a79e17eb8673b8e4fc89c))
|
||||||
|
|
||||||
|
## [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)
|
# [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
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-lib",
|
"name": "@certd/plugin-lib",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.29.0",
|
"version": "1.30.1",
|
||||||
"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.29.0",
|
"@certd/basic": "^1.30.1",
|
||||||
"@certd/pipeline": "^1.29.0",
|
"@certd/pipeline": "^1.30.1",
|
||||||
"@certd/plugin-cert": "^1.29.0",
|
|
||||||
"@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": "d4385ad8a5f6eb5793dcde5281d5e05b3a3d1714"
|
"gitHead": "9a78dad57619e02d1390cf5a013695a2aaf20e64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * from './aliyun-access.js';
|
export * from "./aliyun-access.js";
|
||||||
|
export * from "./alioss-access.js";
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class AliyunClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkRet(ret: any) {
|
checkRet(ret: any) {
|
||||||
if (ret.code != null) {
|
if (ret.Code != null) {
|
||||||
throw new Error("执行失败:" + ret.Message);
|
throw new Error("执行失败:" + ret.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
64
packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts
Normal file
64
packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,9 +26,11 @@ export type AliyunSslCreateDeploymentJobReq = {
|
|||||||
|
|
||||||
export type AliyunSslUploadCertReq = {
|
export type AliyunSslUploadCertReq = {
|
||||||
name: string;
|
name: string;
|
||||||
cert: CertInfo;
|
cert: AliyunCertInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CasCertInfo = { certId: number; certName: string; certIdentifier: string };
|
||||||
|
|
||||||
export class AliyunSslClient {
|
export class AliyunSslClient {
|
||||||
opts: AliyunSslClientOpts;
|
opts: AliyunSslClientOpts;
|
||||||
constructor(opts: AliyunSslClientOpts) {
|
constructor(opts: AliyunSslClientOpts) {
|
||||||
@@ -33,7 +38,7 @@ export class AliyunSslClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkRet(ret: any) {
|
checkRet(ret: any) {
|
||||||
if (ret.code != null) {
|
if (ret.Code != null) {
|
||||||
throw new Error("执行失败:" + ret.Message);
|
throw new Error("执行失败:" + ret.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +55,22 @@ export class AliyunSslClient {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCertInfo(certId: number): Promise<CasCertInfo> {
|
||||||
|
const client = await this.getClient();
|
||||||
|
const params = {
|
||||||
|
CertId: certId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await client.request("GetUserCertificateDetail", params);
|
||||||
|
this.checkRet(res);
|
||||||
|
|
||||||
|
return {
|
||||||
|
certId: certId,
|
||||||
|
certName: res.Name,
|
||||||
|
certIdentifier: res.CertIdentifier,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async uploadCert(req: AliyunSslUploadCertReq) {
|
async uploadCert(req: AliyunSslUploadCertReq) {
|
||||||
const client = await this.getClient();
|
const client = await this.getClient();
|
||||||
const params = {
|
const params = {
|
||||||
|
|||||||
@@ -46,7 +46,13 @@ export function createRemoteSelectInputDefine(opts?: {
|
|||||||
const type = opts?.type || "plugin";
|
const type = opts?.type || "plugin";
|
||||||
const watches = opts?.watches || [];
|
const watches = opts?.watches || [];
|
||||||
const helper = opts?.helper || "请选择";
|
const helper = opts?.helper || "请选择";
|
||||||
const mode = opts?.mode || "tags";
|
let mode = "tags";
|
||||||
|
if (opts.multi === false) {
|
||||||
|
mode = undefined;
|
||||||
|
} else {
|
||||||
|
mode = opts?.mode ?? "tags";
|
||||||
|
}
|
||||||
|
|
||||||
const item = {
|
const item = {
|
||||||
title,
|
title,
|
||||||
component: {
|
component: {
|
||||||
|
|||||||
77
packages/plugins/plugin-lib/src/ftp/access.ts
Normal file
77
packages/plugins/plugin-lib/src/ftp/access.ts
Normal 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();
|
||||||
47
packages/plugins/plugin-lib/src/ftp/client.ts
Normal file
47
packages/plugins/plugin-lib/src/ftp/client.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
packages/plugins/plugin-lib/src/ftp/index.ts
Normal file
2
packages/plugins/plugin-lib/src/ftp/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./access.js";
|
||||||
|
export * from "./client.js";
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
export * from "./ssh/index.js";
|
export * from "./ssh/index.js";
|
||||||
export * from "./aliyun/index.js";
|
export * from "./aliyun/index.js";
|
||||||
export * from "./common/index.js";
|
export * from "./common/index.js";
|
||||||
|
export * from "./ftp/index.js";
|
||||||
|
export * from "./tencent/index.js";
|
||||||
|
export * from "./qiniu/index.js";
|
||||||
|
|||||||
31
packages/plugins/plugin-lib/src/qiniu/access-oss.ts
Normal file
31
packages/plugins/plugin-lib/src/qiniu/access-oss.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
||||||
|
|
||||||
|
@IsAccess({
|
||||||
|
name: "qiniuoss",
|
||||||
|
title: "七牛OSS授权",
|
||||||
|
desc: "",
|
||||||
|
icon: "svg:icon-qiniuyun",
|
||||||
|
input: {},
|
||||||
|
})
|
||||||
|
export class QiniuOssAccess extends BaseAccess {
|
||||||
|
@AccessInput({
|
||||||
|
title: "七牛云授权",
|
||||||
|
component: {
|
||||||
|
name: "access-selector",
|
||||||
|
vModel: "modelValue",
|
||||||
|
type: "qiniu",
|
||||||
|
},
|
||||||
|
helper: "请选择七牛云授权",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
accessId = "";
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: "Bucket",
|
||||||
|
helper: "存储桶名称",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
bucket = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
new QiniuOssAccess();
|
||||||
25
packages/plugins/plugin-lib/src/qiniu/access.ts
Normal file
25
packages/plugins/plugin-lib/src/qiniu/access.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
||||||
|
|
||||||
|
@IsAccess({
|
||||||
|
name: "qiniu",
|
||||||
|
title: "七牛云授权",
|
||||||
|
desc: "",
|
||||||
|
icon: "svg:icon-qiniuyun",
|
||||||
|
input: {},
|
||||||
|
})
|
||||||
|
export class QiniuAccess extends BaseAccess {
|
||||||
|
@AccessInput({
|
||||||
|
title: "AccessKey",
|
||||||
|
rules: [{ required: true, message: "此项必填" }],
|
||||||
|
helper: "AK,前往[密钥管理](https://portal.qiniu.com/developer/user/key)获取",
|
||||||
|
})
|
||||||
|
accessKey!: string;
|
||||||
|
@AccessInput({
|
||||||
|
title: "SecretKey",
|
||||||
|
encrypt: true,
|
||||||
|
helper: "SK",
|
||||||
|
})
|
||||||
|
secretKey!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
new QiniuAccess();
|
||||||
3
packages/plugins/plugin-lib/src/qiniu/index.ts
Normal file
3
packages/plugins/plugin-lib/src/qiniu/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./access.js";
|
||||||
|
export * from "./access-oss.js";
|
||||||
|
export * from "./lib/sdk.js";
|
||||||
142
packages/plugins/plugin-lib/src/qiniu/lib/sdk.ts
Normal file
142
packages/plugins/plugin-lib/src/qiniu/lib/sdk.ts
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import { HttpClient, ILogger } from "@certd/basic";
|
||||||
|
import { QiniuAccess } from "../access.js";
|
||||||
|
|
||||||
|
export type QiniuCertInfo = {
|
||||||
|
key: string;
|
||||||
|
crt: string;
|
||||||
|
};
|
||||||
|
export class QiniuClient {
|
||||||
|
http: HttpClient;
|
||||||
|
access: QiniuAccess;
|
||||||
|
logger: ILogger;
|
||||||
|
constructor(opts: { http: HttpClient; access: QiniuAccess; logger: ILogger }) {
|
||||||
|
this.http = opts.http;
|
||||||
|
this.access = opts.access;
|
||||||
|
this.logger = opts.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadCert(cert: QiniuCertInfo, certName?: string) {
|
||||||
|
const url = "https://api.qiniu.com/sslcert";
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
name: certName,
|
||||||
|
common_name: "certd",
|
||||||
|
pri: cert.key,
|
||||||
|
ca: cert.crt,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await this.doRequest(url, "post", body);
|
||||||
|
|
||||||
|
return res.certID;
|
||||||
|
}
|
||||||
|
|
||||||
|
async bindCert(body: { certid: string; domain: string }) {
|
||||||
|
const url = "https://api.qiniu.com/cert/bind";
|
||||||
|
return await this.doRequest(url, "post", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCertBindings() {
|
||||||
|
const url = "https://api.qiniu.com/cert/bindings";
|
||||||
|
const res = await this.doRequest(url, "get");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async doRequest(url: string, method: string, body?: any) {
|
||||||
|
const { generateAccessToken } = await import("qiniu/qiniu/util.js");
|
||||||
|
const token = generateAccessToken(this.access, url);
|
||||||
|
const res = await this.http.request({
|
||||||
|
url,
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
Authorization: token,
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
logRes: false,
|
||||||
|
});
|
||||||
|
if (res && res.error) {
|
||||||
|
if (res.error.includes("domaintype")) {
|
||||||
|
throw new Error("请求失败:" + res.error + ",该域名属于CDN域名,请使用部署到七牛云CDN插件");
|
||||||
|
}
|
||||||
|
throw new Error("请求失败:" + res.error);
|
||||||
|
}
|
||||||
|
console.log("res", res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async doRequestV2(opts: { url: string; method: string; body?: any; contentType: string }) {
|
||||||
|
const { HttpClient } = await import("qiniu/qiniu/httpc/client.js");
|
||||||
|
const { QiniuAuthMiddleware } = await import("qiniu/qiniu/httpc/middleware/qiniuAuth.js");
|
||||||
|
// X-Qiniu-Date: 20060102T150405Z
|
||||||
|
const auth = new QiniuAuthMiddleware({
|
||||||
|
mac: {
|
||||||
|
...this.access,
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const http = new HttpClient({ timeout: 10000, middlewares: [auth] });
|
||||||
|
console.log("http", http);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
http.get({
|
||||||
|
url: opts.url,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": opts.contentType,
|
||||||
|
},
|
||||||
|
callback: (nullable, res) => {
|
||||||
|
console.log("nullable", nullable, "res", res);
|
||||||
|
if (res?.error) {
|
||||||
|
reject(res);
|
||||||
|
} else {
|
||||||
|
resolve(res);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFile(bucket: string, key: string, content: Buffer) {
|
||||||
|
const sdk = await import("qiniu");
|
||||||
|
const qiniu = sdk.default;
|
||||||
|
const mac = new qiniu.auth.digest.Mac(this.access.accessKey, this.access.secretKey);
|
||||||
|
const options = {
|
||||||
|
scope: bucket,
|
||||||
|
};
|
||||||
|
const putPolicy = new qiniu.rs.PutPolicy(options);
|
||||||
|
const uploadToken = putPolicy.uploadToken(mac);
|
||||||
|
|
||||||
|
const config = new qiniu.conf.Config();
|
||||||
|
const formUploader = new qiniu.form_up.FormUploader(config);
|
||||||
|
const putExtra = new qiniu.form_up.PutExtra();
|
||||||
|
// 文件上传
|
||||||
|
const { data, resp } = await formUploader.put(uploadToken, key, content, putExtra);
|
||||||
|
if (resp.statusCode === 200) {
|
||||||
|
this.logger.info("文件上传成功:" + key);
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
console.log(resp.statusCode);
|
||||||
|
throw new Error("上传失败:" + JSON.stringify(resp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeFile(bucket: string, key: string) {
|
||||||
|
const sdk = await import("qiniu");
|
||||||
|
const qiniu = sdk.default;
|
||||||
|
const mac = new qiniu.auth.digest.Mac(this.access.accessKey, this.access.secretKey);
|
||||||
|
const config = new qiniu.conf.Config();
|
||||||
|
config.useHttpsDomain = true;
|
||||||
|
const bucketManager = new qiniu.rs.BucketManager(mac, config);
|
||||||
|
|
||||||
|
const { resp } = await bucketManager.delete(bucket, key);
|
||||||
|
|
||||||
|
if (resp.statusCode === 200) {
|
||||||
|
this.logger.info("文件删除成功:" + key);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Error("删除失败:" + JSON.stringify(resp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./ssh.js";
|
export * from "./ssh.js";
|
||||||
export * from "./ssh-access.js";
|
export * from "./ssh-access.js";
|
||||||
|
export * from "./sftp-access.js";
|
||||||
|
|||||||
34
packages/plugins/plugin-lib/src/ssh/sftp-access.ts
Normal file
34
packages/plugins/plugin-lib/src/ssh/sftp-access.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
||||||
|
|
||||||
|
@IsAccess({
|
||||||
|
name: "sftp",
|
||||||
|
title: "SFTP授权",
|
||||||
|
desc: "",
|
||||||
|
icon: "clarity:host-line",
|
||||||
|
input: {},
|
||||||
|
})
|
||||||
|
export class SftpAccess extends BaseAccess {
|
||||||
|
@AccessInput({
|
||||||
|
title: "SSH授权",
|
||||||
|
component: {
|
||||||
|
name: "access-selector",
|
||||||
|
type: "ssh",
|
||||||
|
vModel: "modelValue",
|
||||||
|
},
|
||||||
|
helper: "请选择一个SSH授权",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
sshAccess!: string;
|
||||||
|
@AccessInput({
|
||||||
|
title: "文件权限",
|
||||||
|
component: {
|
||||||
|
name: "a-input",
|
||||||
|
vModel: "value",
|
||||||
|
placeholder: "777",
|
||||||
|
},
|
||||||
|
helper: "文件上传后是否修改文件权限",
|
||||||
|
})
|
||||||
|
fileMode!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
new SftpAccess();
|
||||||
@@ -7,6 +7,7 @@ import { SshAccess } from "./ssh-access.js";
|
|||||||
import stripAnsi from "strip-ansi";
|
import stripAnsi from "strip-ansi";
|
||||||
import { SocksClient } from "socks";
|
import { SocksClient } from "socks";
|
||||||
import { SocksProxy, SocksProxyType } from "socks/typings/common/constants.js";
|
import { SocksProxy, SocksProxyType } from "socks/typings/common/constants.js";
|
||||||
|
export type TransportItem = { localPath: string; remotePath: string };
|
||||||
|
|
||||||
export class AsyncSsh2Client {
|
export class AsyncSsh2Client {
|
||||||
conn: ssh2.Client;
|
conn: ssh2.Client;
|
||||||
@@ -79,11 +80,11 @@ export class AsyncSsh2Client {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fastPut(options: { sftp: any; localPath: string; remotePath: string }) {
|
async fastPut(options: { sftp: any; localPath: string; remotePath: string; opts?: { mode?: string } }) {
|
||||||
const { sftp, localPath, remotePath } = options;
|
const { sftp, localPath, remotePath, opts } = options;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.logger.info(`开始上传:${localPath} => ${remotePath}`);
|
this.logger.info(`开始上传:${localPath} => ${remotePath}`);
|
||||||
sftp.fastPut(localPath, remotePath, (err: Error) => {
|
sftp.fastPut(localPath, remotePath, { ...(opts ?? {}) }, (err: Error) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
this.logger.error("请确认路径是否包含文件名,路径本身不能是目录,路径不能有*?之类的特殊符号,要有写入权限");
|
this.logger.error("请确认路径是否包含文件名,路径本身不能是目录,路径不能有*?之类的特殊符号,要有写入权限");
|
||||||
@@ -95,6 +96,21 @@ export class AsyncSsh2Client {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async unlink(options: { sftp: any; remotePath: string }) {
|
||||||
|
const { sftp, remotePath } = options;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.logger.info(`开始删除远程文件:${remotePath}`);
|
||||||
|
sftp.unlink(remotePath, (err: Error) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.logger.info(`删除文件成功:${remotePath}`);
|
||||||
|
resolve({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async exec(
|
async exec(
|
||||||
script: string,
|
script: string,
|
||||||
opts: {
|
opts: {
|
||||||
@@ -239,8 +255,8 @@ export class SshClient {
|
|||||||
}
|
}
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
async uploadFiles(options: { connectConf: SshAccess; transports: any; mkdirs: boolean }) {
|
async uploadFiles(options: { connectConf: SshAccess; transports: TransportItem[]; mkdirs: boolean; opts?: { mode?: string } }) {
|
||||||
const { connectConf, transports, mkdirs } = options;
|
const { connectConf, transports, mkdirs, opts } = options;
|
||||||
await this._call({
|
await this._call({
|
||||||
connectConf,
|
connectConf,
|
||||||
callable: async (conn: AsyncSsh2Client) => {
|
callable: async (conn: AsyncSsh2Client) => {
|
||||||
@@ -265,13 +281,31 @@ export class SshClient {
|
|||||||
}
|
}
|
||||||
await conn.exec(mkdirCmd);
|
await conn.exec(mkdirCmd);
|
||||||
}
|
}
|
||||||
await conn.fastPut({ sftp, ...transport });
|
await conn.fastPut({ sftp, ...transport, opts });
|
||||||
}
|
}
|
||||||
this.logger.info("文件全部上传成功");
|
this.logger.info("文件全部上传成功");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async removeFiles(opts: { connectConf: SshAccess; files: string[] }) {
|
||||||
|
const { connectConf, files } = opts;
|
||||||
|
await this._call({
|
||||||
|
connectConf,
|
||||||
|
callable: async (conn: AsyncSsh2Client) => {
|
||||||
|
const sftp = await conn.getSftp();
|
||||||
|
this.logger.info("开始删除");
|
||||||
|
for (const file of files) {
|
||||||
|
await conn.unlink({
|
||||||
|
sftp,
|
||||||
|
remotePath: file,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.logger.info("文件全部删除成功");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async isCmd(conn: AsyncSsh2Client) {
|
async isCmd(conn: AsyncSsh2Client) {
|
||||||
const spec = await conn.exec("echo %COMSPEC% ");
|
const spec = await conn.exec("echo %COMSPEC% ");
|
||||||
if (spec.toString().trim() === "%COMSPEC%") {
|
if (spec.toString().trim() === "%COMSPEC%") {
|
||||||
|
|||||||
65
packages/plugins/plugin-lib/src/tencent/access-cos.ts
Normal file
65
packages/plugins/plugin-lib/src/tencent/access-cos.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
||||||
|
|
||||||
|
@IsAccess({
|
||||||
|
name: "tencentcos",
|
||||||
|
title: "腾讯云COS授权",
|
||||||
|
icon: "svg:icon-tencentcloud",
|
||||||
|
desc: "腾讯云对象存储授权,包含地域和存储桶",
|
||||||
|
})
|
||||||
|
export class TencentCosAccess extends BaseAccess {
|
||||||
|
@AccessInput({
|
||||||
|
title: "腾讯云授权",
|
||||||
|
component: {
|
||||||
|
name: "access-selector",
|
||||||
|
vModel: "modelValue",
|
||||||
|
type: "tencent",
|
||||||
|
},
|
||||||
|
helper: "请选择腾讯云授权",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
accessId = "";
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: "所在地域",
|
||||||
|
helper: "存储桶所在地域",
|
||||||
|
component: {
|
||||||
|
name: "a-auto-complete",
|
||||||
|
vModel: "value",
|
||||||
|
options: [
|
||||||
|
{ value: "", label: "--------中国大陆地区-------", disabled: true },
|
||||||
|
{ value: "ap-beijing-1", label: "北京1区" },
|
||||||
|
{ value: "ap-beijing", label: "北京" },
|
||||||
|
{ value: "ap-nanjing", label: "南京" },
|
||||||
|
{ value: "ap-shanghai", label: "上海" },
|
||||||
|
{ value: "ap-guangzhou", label: "广州" },
|
||||||
|
{ value: "ap-chengdu", label: "成都" },
|
||||||
|
{ value: "ap-chongqing", label: "重庆" },
|
||||||
|
{ value: "ap-shenzhen-fsi", label: "深圳金融" },
|
||||||
|
{ value: "ap-shanghai-fsi", label: "上海金融" },
|
||||||
|
{ value: "ap-beijing-fsi", label: "北京金融" },
|
||||||
|
{ value: "", label: "--------中国香港及境外-------", disabled: true },
|
||||||
|
{ value: "ap-hongkong", label: "中国香港" },
|
||||||
|
{ value: "ap-singapore", label: "新加坡" },
|
||||||
|
{ value: "ap-mumbai", label: "孟买" },
|
||||||
|
{ value: "ap-jakarta", label: "雅加达" },
|
||||||
|
{ value: "ap-seoul", label: "首尔" },
|
||||||
|
{ value: "ap-bangkok", label: "曼谷" },
|
||||||
|
{ value: "ap-tokyo", label: "东京" },
|
||||||
|
{ value: "na-siliconvalley", label: "硅谷" },
|
||||||
|
{ value: "na-ashburn", label: "弗吉尼亚" },
|
||||||
|
{ value: "sa-saopaulo", label: "圣保罗" },
|
||||||
|
{ value: "eu-frankfurt", label: "法兰克福" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
region!: string;
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: "Bucket",
|
||||||
|
helper: "存储桶名称",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
bucket = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
new TencentCosAccess();
|
||||||
28
packages/plugins/plugin-lib/src/tencent/access.ts
Normal file
28
packages/plugins/plugin-lib/src/tencent/access.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
|
||||||
|
|
||||||
|
@IsAccess({
|
||||||
|
name: "tencent",
|
||||||
|
title: "腾讯云",
|
||||||
|
icon: "svg:icon-tencentcloud",
|
||||||
|
})
|
||||||
|
export class TencentAccess extends BaseAccess {
|
||||||
|
@AccessInput({
|
||||||
|
title: "secretId",
|
||||||
|
helper:
|
||||||
|
"使用对应的插件需要有对应的权限,比如上传证书,需要证书管理权限;部署到clb需要clb相关权限\n前往[密钥管理](https://console.cloud.tencent.com/cam/capi)进行创建",
|
||||||
|
component: {
|
||||||
|
placeholder: "secretId",
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: "该项必填" }],
|
||||||
|
})
|
||||||
|
secretId = "";
|
||||||
|
@AccessInput({
|
||||||
|
title: "secretKey",
|
||||||
|
component: {
|
||||||
|
placeholder: "secretKey",
|
||||||
|
},
|
||||||
|
encrypt: true,
|
||||||
|
rules: [{ required: true, message: "该项必填" }],
|
||||||
|
})
|
||||||
|
secretKey = "";
|
||||||
|
}
|
||||||
3
packages/plugins/plugin-lib/src/tencent/index.ts
Normal file
3
packages/plugins/plugin-lib/src/tencent/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./access.js";
|
||||||
|
export * from "./access-cos.js";
|
||||||
|
export * from "./lib/index.js";
|
||||||
69
packages/plugins/plugin-lib/src/tencent/lib/cos-client.ts
Normal file
69
packages/plugins/plugin-lib/src/tencent/lib/cos-client.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { TencentAccess } from "../access.js";
|
||||||
|
import { ILogger } from "@certd/basic";
|
||||||
|
|
||||||
|
export class TencentCosClient {
|
||||||
|
access: TencentAccess;
|
||||||
|
logger: ILogger;
|
||||||
|
region: string;
|
||||||
|
bucket: string;
|
||||||
|
|
||||||
|
constructor(opts: { access: TencentAccess; logger: ILogger; region: string; bucket: string }) {
|
||||||
|
this.access = opts.access;
|
||||||
|
this.logger = opts.logger;
|
||||||
|
this.bucket = opts.bucket;
|
||||||
|
this.region = opts.region;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCosClient() {
|
||||||
|
const sdk = await import("cos-nodejs-sdk-v5");
|
||||||
|
const clientConfig = {
|
||||||
|
SecretId: this.access.secretId,
|
||||||
|
SecretKey: this.access.secretKey,
|
||||||
|
};
|
||||||
|
return new sdk.default(clientConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFile(key: string, file: Buffer) {
|
||||||
|
const cos = await this.getCosClient();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
cos.putObject(
|
||||||
|
{
|
||||||
|
Bucket: this.bucket /* 必须 */,
|
||||||
|
Region: this.region /* 必须 */,
|
||||||
|
Key: key /* 必须 */,
|
||||||
|
Body: file, // 上传文件对象
|
||||||
|
onProgress: function (progressData) {
|
||||||
|
console.log(JSON.stringify(progressData));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeFile(key: string) {
|
||||||
|
const cos = await this.getCosClient();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
cos.deleteObject(
|
||||||
|
{
|
||||||
|
Bucket: this.bucket,
|
||||||
|
Region: this.region,
|
||||||
|
Key: key,
|
||||||
|
},
|
||||||
|
function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
2
packages/plugins/plugin-lib/src/tencent/lib/index.ts
Normal file
2
packages/plugins/plugin-lib/src/tencent/lib/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./ssl-client.js";
|
||||||
|
export * from "./cos-client.js";
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import { TencentAccess } from '@certd/plugin-plus';
|
import { ILogger } from "@certd/basic";
|
||||||
import { CertInfo } from '@certd/plugin-cert';
|
import { TencentAccess } from "../access.js";
|
||||||
import { ILogger } from '@certd/basic';
|
|
||||||
|
export type TencentCertInfo = {
|
||||||
|
key: string;
|
||||||
|
crt: string;
|
||||||
|
};
|
||||||
export class TencentSslClient {
|
export class TencentSslClient {
|
||||||
access: TencentAccess;
|
access: TencentAccess;
|
||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
@@ -11,7 +15,7 @@ export class TencentSslClient {
|
|||||||
this.region = opts.region;
|
this.region = opts.region;
|
||||||
}
|
}
|
||||||
async getSslClient(): Promise<any> {
|
async getSslClient(): Promise<any> {
|
||||||
const sdk = await import('tencentcloud-sdk-nodejs/tencentcloud/services/ssl/v20191205/index.js');
|
const sdk = await import("tencentcloud-sdk-nodejs/tencentcloud/services/ssl/v20191205/index.js");
|
||||||
const SslClient = sdk.v20191205.Client;
|
const SslClient = sdk.v20191205.Client;
|
||||||
|
|
||||||
const clientConfig = {
|
const clientConfig = {
|
||||||
@@ -22,7 +26,7 @@ export class TencentSslClient {
|
|||||||
region: this.region,
|
region: this.region,
|
||||||
profile: {
|
profile: {
|
||||||
httpProfile: {
|
httpProfile: {
|
||||||
endpoint: 'ssl.tencentcloudapi.com',
|
endpoint: "ssl.tencentcloudapi.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -32,11 +36,11 @@ export class TencentSslClient {
|
|||||||
|
|
||||||
checkRet(ret: any) {
|
checkRet(ret: any) {
|
||||||
if (!ret || ret.Error) {
|
if (!ret || ret.Error) {
|
||||||
throw new Error('请求失败:' + ret.Error.Code + ',' + ret.Error.Message);
|
throw new Error("请求失败:" + ret.Error.Code + "," + ret.Error.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadToTencent(opts: { certName: string; cert: CertInfo }): Promise<string> {
|
async uploadToTencent(opts: { certName: string; cert: TencentCertInfo }): Promise<string> {
|
||||||
const client = await this.getSslClient();
|
const client = await this.getSslClient();
|
||||||
const params = {
|
const params = {
|
||||||
CertificatePublicKey: opts.cert.crt,
|
CertificatePublicKey: opts.cert.crt,
|
||||||
@@ -45,7 +49,7 @@ export class TencentSslClient {
|
|||||||
};
|
};
|
||||||
const ret = await client.UploadCertificate(params);
|
const ret = await client.UploadCertificate(params);
|
||||||
this.checkRet(ret);
|
this.checkRet(ret);
|
||||||
this.logger.info('证书上传成功:tencentCertId=', ret.CertificateId);
|
this.logger.info("证书上传成功:tencentCertId=", ret.CertificateId);
|
||||||
return ret.CertificateId;
|
return ret.CertificateId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,6 +3,60 @@
|
|||||||
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.30.1](https://github.com/certd/certd/compare/v1.30.0...v1.30.1) (2025-01-20)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 创建流水线时,默认成功时也发送通知 ([52ae690](https://github.com/certd/certd/commit/52ae6902d203ca56e0312692b50c55cb6ddd3e39))
|
||||||
|
* http方式校验,选择sftp时,支持修改文件访问权限比如777 ([15d6eaf](https://github.com/certd/certd/commit/15d6eaf5532ed25acd4f8d58c429353a2f44206c))
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/certd/certd/compare/v1.29.5...v1.30.0) (2025-01-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复查看任务日志偶发性无法自动滚动底部的bug ([7e482f7](https://github.com/certd/certd/commit/7e482f798c0142bce1866f84676cb40210f9638a))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 证书仓库 ([91e7f45](https://github.com/certd/certd/commit/91e7f45a1c5ea1e0ec0aa3236b80028f03a6d0aa))
|
||||||
|
|
||||||
|
## [1.29.5](https://github.com/certd/certd/compare/v1.29.4...v1.29.5) (2025-01-07)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复授权管理,点击了查看原文按钮后,无法修改值的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))
|
||||||
|
|
||||||
|
## [1.29.3](https://github.com/certd/certd/compare/v1.29.2...v1.29.3) (2025-01-04)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 优化站点证书检查页面,检查增加3次重试 ([e6dd7cd](https://github.com/certd/certd/commit/e6dd7cd54a3e23897031b5df6e0c3cdc0545d35a))
|
||||||
|
* 支持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/ui-client
|
||||||
|
|
||||||
|
## [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)
|
# [1.29.0](https://github.com/certd/certd/compare/v1.28.4...v1.29.0) (2024-12-24)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-client",
|
"name": "@certd/ui-client",
|
||||||
"version": "1.29.0",
|
"version": "1.30.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --open",
|
"dev": "vite --open",
|
||||||
@@ -26,10 +26,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/colors": "^7.0.2",
|
"@ant-design/colors": "^7.0.2",
|
||||||
"@ant-design/icons-vue": "^6.1.0",
|
"@ant-design/icons-vue": "^6.1.0",
|
||||||
"@fast-crud/fast-crud": "^1.23.4",
|
"@fast-crud/fast-crud": "^1.25.0",
|
||||||
"@fast-crud/fast-extends": "^1.23.4",
|
"@fast-crud/fast-extends": "^1.25.0",
|
||||||
"@fast-crud/ui-antdv4": "^1.23.4",
|
"@fast-crud/ui-antdv4": "^1.25.0",
|
||||||
"@fast-crud/ui-interface": "^1.23.4",
|
"@fast-crud/ui-interface": "^1.25.0",
|
||||||
"@iconify/vue": "^4.1.1",
|
"@iconify/vue": "^4.1.1",
|
||||||
"@soerenmartius/vue3-clipboard": "^0.1.2",
|
"@soerenmartius/vue3-clipboard": "^0.1.2",
|
||||||
"@vue-js-cron/light": "^4.0.5",
|
"@vue-js-cron/light": "^4.0.5",
|
||||||
@@ -66,8 +66,8 @@
|
|||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@certd/lib-iframe": "^1.29.0",
|
"@certd/lib-iframe": "^1.30.1",
|
||||||
"@certd/pipeline": "^1.29.0",
|
"@certd/pipeline": "^1.30.1",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@types/chai": "^4.3.12",
|
"@types/chai": "^4.3.12",
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ export type CnameRecord = {
|
|||||||
recordValue?: string;
|
recordValue?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DomainGroupItem = {
|
||||||
|
domain: string;
|
||||||
|
domains?: string[];
|
||||||
|
keySubDomains?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export async function GetList() {
|
export async function GetList() {
|
||||||
return await request({
|
return await request({
|
||||||
url: apiPrefix + "/list",
|
url: apiPrefix + "/list",
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ defineOptions({
|
|||||||
name: "CnameVerifyPlan"
|
name: "CnameVerifyPlan"
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits(["update:modelValue", "change"]);
|
||||||
"update:modelValue": any;
|
|
||||||
change: Record<string, any>;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: Record<string, any>;
|
modelValue: Record<string, any>;
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<table class="http-verify-plan">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 160px">网站域名</td>
|
||||||
|
<td style="width: 100px; text-align: center">上传方式</td>
|
||||||
|
<td style="width: 150px">上传授权</td>
|
||||||
|
<td style="width: 200px">网站根目录路径</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody v-if="records" class="http-record-body">
|
||||||
|
<template v-for="(item, key) of records" :key="key">
|
||||||
|
<tr>
|
||||||
|
<td class="domain">
|
||||||
|
{{ item.domain }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<fs-dict-select v-model:value="item.httpUploaderType" :dict="uploaderTypeDict" @change="onRecordChange"></fs-dict-select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<access-selector v-model="item.httpUploaderAccess" :type="item.httpUploaderType" @change="onRecordChange"></access-selector>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-input v-model:value="item.httpUploadRootDir" placeholder="网站根目录,如:/www/wwwroot" @change="onRecordChange"></a-input>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Ref, ref, watch, nextTick } from "vue";
|
||||||
|
import { HttpRecord } from "/@/components/plugins/cert/domains-verify-plan-editor/type";
|
||||||
|
import { dict } from "@fast-crud/fast-crud";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "HttpVerifyPlan"
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:modelValue", "change"]);
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: Record<string, any>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const records: Ref<Record<string, HttpRecord>> = ref({});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => {
|
||||||
|
return props.modelValue;
|
||||||
|
},
|
||||||
|
(value: any) => {
|
||||||
|
if (value) {
|
||||||
|
records.value = {
|
||||||
|
...value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function onRecordChange() {
|
||||||
|
await nextTick();
|
||||||
|
emit("update:modelValue", records.value);
|
||||||
|
emit("change", records.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploaderTypeDict = dict({
|
||||||
|
data: [
|
||||||
|
{ label: "SFTP", value: "sftp" },
|
||||||
|
{ label: "FTP", value: "ftp" },
|
||||||
|
{ label: "阿里云OSS", value: "alioss" },
|
||||||
|
{ label: "腾讯云COS", value: "tencentcos" },
|
||||||
|
{ label: "七牛OSS", value: "qiniuoss" },
|
||||||
|
{ label: "SSH(已废弃,请选择SFTP方式)", value: "ssh", disabled: true }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.http-verify-plan {
|
||||||
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
tbody tr td {
|
||||||
|
border-top: 1px solid #e8e8e8 !important;
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td {
|
||||||
|
border: 0 !important;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//&:last-child {
|
||||||
|
// td {
|
||||||
|
// border-bottom: 0 !important;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<table class="plan-table">
|
<table class="plan-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>域名</th>
|
<th style="min-width: 100px">主域名</th>
|
||||||
<th>验证方式</th>
|
<th>验证方式</th>
|
||||||
<th>验证计划</th>
|
<th>验证计划</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -58,6 +58,9 @@
|
|||||||
<div v-if="item.type === 'cname'" class="plan-cname">
|
<div v-if="item.type === 'cname'" class="plan-cname">
|
||||||
<cname-verify-plan v-model="item.cnameVerifyPlan" @change="onPlanChanged" />
|
<cname-verify-plan v-model="item.cnameVerifyPlan" @change="onPlanChanged" />
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="item.type === 'http'" class="plan-http">
|
||||||
|
<http-verify-plan v-model="item.httpVerifyPlan" @change="onPlanChanged" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -76,10 +79,12 @@ import { ref, watch } from "vue";
|
|||||||
import { dict, FsDictSelect } from "@fast-crud/fast-crud";
|
import { dict, FsDictSelect } from "@fast-crud/fast-crud";
|
||||||
import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
|
import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
|
||||||
import CnameVerifyPlan from "./cname-verify-plan.vue";
|
import CnameVerifyPlan from "./cname-verify-plan.vue";
|
||||||
|
import HttpVerifyPlan from "./http-verify-plan.vue";
|
||||||
|
//@ts-ignore
|
||||||
import psl from "psl";
|
import psl from "psl";
|
||||||
import { Form } from "ant-design-vue";
|
import { Form } from "ant-design-vue";
|
||||||
import { DomainsVerifyPlanInput } from "./type";
|
import { DomainsVerifyPlanInput } from "./type";
|
||||||
import { CnameRecord } from "./api";
|
import { CnameRecord, DomainGroupItem } from "./api";
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "DomainsVerifyPlanEditor"
|
name: "DomainsVerifyPlanEditor"
|
||||||
});
|
});
|
||||||
@@ -92,12 +97,17 @@ const challengeTypeOptions = ref<any[]>([
|
|||||||
{
|
{
|
||||||
label: "CNAME验证",
|
label: "CNAME验证",
|
||||||
value: "cname"
|
value: "cname"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "HTTP验证",
|
||||||
|
value: "http"
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue?: DomainsVerifyPlanInput;
|
modelValue?: DomainsVerifyPlanInput;
|
||||||
domains?: string[];
|
domains?: string[];
|
||||||
|
defaultType?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -127,23 +137,17 @@ function showError(error: string) {
|
|||||||
errorMessageRef.value = error;
|
errorMessageRef.value = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
type DomainGroup = Record<
|
type DomainGroup = Record<string, DomainGroupItem>;
|
||||||
string,
|
|
||||||
{
|
|
||||||
[key: string]: CnameRecord;
|
|
||||||
}
|
|
||||||
>[];
|
|
||||||
|
|
||||||
function onDomainsChanged(domains: string[]) {
|
function onDomainsChanged(domains: string[]) {
|
||||||
console.log("域名变化", domains);
|
|
||||||
if (domains == null) {
|
if (domains == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domainGroups: DomainGroup = {};
|
const domainGroups: DomainGroup = {};
|
||||||
for (let domain of domains) {
|
for (let domain of domains) {
|
||||||
domain = domain.replace("*.", "");
|
const keyDomain = domain.replace("*.", "");
|
||||||
const parsed = psl.parse(domain);
|
const parsed = psl.parse(keyDomain);
|
||||||
if (parsed.error) {
|
if (parsed.error) {
|
||||||
showError(`域名${domain}解析失败: ${JSON.stringify(parsed.error)}`);
|
showError(`域名${domain}解析失败: ${JSON.stringify(parsed.error)}`);
|
||||||
continue;
|
continue;
|
||||||
@@ -154,39 +158,83 @@ function onDomainsChanged(domains: string[]) {
|
|||||||
}
|
}
|
||||||
let group = domainGroups[mainDomain];
|
let group = domainGroups[mainDomain];
|
||||||
if (!group) {
|
if (!group) {
|
||||||
group = {};
|
group = {
|
||||||
|
domain: mainDomain,
|
||||||
|
domains: [],
|
||||||
|
keySubDomains: []
|
||||||
|
} as DomainGroupItem;
|
||||||
domainGroups[mainDomain] = group;
|
domainGroups[mainDomain] = group;
|
||||||
}
|
}
|
||||||
group[domain] = {
|
group.domains.push(domain);
|
||||||
id: 0
|
group.keySubDomains.push(keyDomain);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const domain in domainGroups) {
|
for (const domain in domainGroups) {
|
||||||
let planItem = planRef.value[domain];
|
let planItem = planRef.value[domain];
|
||||||
const subDomains = domainGroups[domain];
|
const domainGroupItem = domainGroups[domain];
|
||||||
if (!planItem) {
|
if (!planItem) {
|
||||||
planItem = {
|
planItem = {
|
||||||
domain,
|
domain,
|
||||||
type: "cname",
|
//@ts-ignore
|
||||||
cnameVerifyPlan: {
|
type: props.defaultType || "cname",
|
||||||
...subDomains
|
//@ts-ignore
|
||||||
}
|
cnameVerifyPlan: {},
|
||||||
|
//@ts-ignore
|
||||||
|
httpVerifyPlan: {}
|
||||||
};
|
};
|
||||||
planRef.value[domain] = planItem;
|
planRef.value[domain] = planItem;
|
||||||
} else {
|
}
|
||||||
const cnamePlan = planItem.cnameVerifyPlan;
|
planItem.domains = domainGroupItem.domains;
|
||||||
for (const subDomain in subDomains) {
|
|
||||||
if (!cnamePlan[subDomain]) {
|
const cnameOrigin = planItem.cnameVerifyPlan;
|
||||||
cnamePlan[subDomain] = {
|
const httpOrigin = planItem.httpVerifyPlan;
|
||||||
id: 0
|
planItem.cnameVerifyPlan = {};
|
||||||
};
|
planItem.httpVerifyPlan = {};
|
||||||
}
|
const cnamePlan = planItem.cnameVerifyPlan;
|
||||||
|
const httpPlan = planItem.httpVerifyPlan;
|
||||||
|
for (const subDomain of domainGroupItem.keySubDomains) {
|
||||||
|
if (!cnameOrigin[subDomain]) {
|
||||||
|
//@ts-ignore
|
||||||
|
planItem.cnameVerifyPlan[subDomain] = {
|
||||||
|
id: 0
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
planItem.cnameVerifyPlan[subDomain] = cnameOrigin[subDomain];
|
||||||
}
|
}
|
||||||
for (const subDomain of Object.keys(cnamePlan)) {
|
|
||||||
if (!subDomains[subDomain]) {
|
if (!cnamePlan[subDomain]) {
|
||||||
delete cnamePlan[subDomain];
|
//@ts-ignore
|
||||||
}
|
cnamePlan[subDomain] = {
|
||||||
|
id: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!httpOrigin[subDomain]) {
|
||||||
|
//@ts-ignore
|
||||||
|
planItem.httpVerifyPlan[subDomain] = {
|
||||||
|
domain: subDomain
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
planItem.httpVerifyPlan[subDomain] = httpOrigin[subDomain];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!httpPlan[subDomain]) {
|
||||||
|
//@ts-ignore
|
||||||
|
httpPlan[subDomain] = {
|
||||||
|
domain: subDomain
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const subDomain of Object.keys(cnamePlan)) {
|
||||||
|
if (!domainGroupItem.keySubDomains.includes(subDomain)) {
|
||||||
|
delete cnamePlan[subDomain];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const subDomain of Object.keys(httpPlan)) {
|
||||||
|
if (!domainGroupItem.keySubDomains.includes(subDomain)) {
|
||||||
|
delete httpPlan[subDomain];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,10 +248,10 @@ function onDomainsChanged(domains: string[]) {
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => {
|
() => {
|
||||||
return props.domains;
|
return props.domains && props.defaultType;
|
||||||
},
|
},
|
||||||
(domains: string[]) => {
|
() => {
|
||||||
onDomainsChanged(domains);
|
onDomainsChanged(props.domains);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
@@ -277,12 +325,15 @@ watch(
|
|||||||
padding: 10px 6px;
|
padding: 10px 6px;
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
border-bottom: 1px solid #e8e8e8;
|
border-bottom: 2px solid #d8d8d8;
|
||||||
border-left: 1px solid #e8e8e8;
|
border-left: 1px solid #e8e8e8;
|
||||||
padding: 6px 6px;
|
padding: 6px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan {
|
.plan {
|
||||||
|
td {
|
||||||
|
border-right: 1px solid #e8e8e8 !important;
|
||||||
|
}
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
.ant-select {
|
.ant-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
import { CnameRecord } from "@certd/pipeline";
|
import { CnameRecord } from "@certd/pipeline";
|
||||||
|
|
||||||
|
export type HttpRecord = {
|
||||||
|
domain: string;
|
||||||
|
httpUploaderType: string;
|
||||||
|
httpUploaderAccess: number;
|
||||||
|
httpUploadRootDir: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type DomainVerifyPlanInput = {
|
export type DomainVerifyPlanInput = {
|
||||||
domain: string;
|
domain: string;
|
||||||
type: "cname" | "dns";
|
domains: string[];
|
||||||
|
type: "cname" | "dns" | "http";
|
||||||
dnsProviderType?: string;
|
dnsProviderType?: string;
|
||||||
dnsProviderAccessId?: number;
|
dnsProviderAccessId?: number;
|
||||||
cnameVerifyPlan?: Record<string, CnameRecord>;
|
cnameVerifyPlan?: Record<string, CnameRecord>;
|
||||||
|
httpVerifyPlan?: Record<string, HttpRecord>;
|
||||||
};
|
};
|
||||||
export type DomainsVerifyPlanInput = {
|
export type DomainsVerifyPlanInput = {
|
||||||
[key: string]: DomainVerifyPlanInput;
|
[key: string]: DomainVerifyPlanInput;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user