mirror of
https://github.com/certd/certd.git
synced 2026-04-04 23:10:56 +08:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19a6b94680 | ||
|
|
65a72b8d60 | ||
|
|
7f61cab101 | ||
|
|
37caef38ad | ||
|
|
9cc01db1d5 | ||
|
|
9172440f79 | ||
|
|
e0eb3a4413 | ||
|
|
ae0f16bf35 | ||
|
|
6c9ed162e3 | ||
|
|
3849b52cdf | ||
|
|
9ecfcb5814 | ||
|
|
54ad09f755 | ||
|
|
6ee4dc165b | ||
|
|
8e2eb89696 | ||
|
|
9d397cc8be | ||
|
|
cbfb0755b3 | ||
|
|
d8d127ee9d | ||
|
|
0ed5430e80 | ||
|
|
878c1f52fa |
11
CHANGELOG.md
11
CHANGELOG.md
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复无法强制取消任务的bug ([9cc01db](https://github.com/certd/certd/commit/9cc01db1d569a5c45bb3e731f35d85df324a8e62))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云dns provider 支持腾讯云的accessId ([e0eb3a4](https://github.com/certd/certd/commit/e0eb3a441384d474fe2923c69b25318264bdc9df))
|
||||
* 支持windows文件上传 ([7f61cab](https://github.com/certd/certd/commit/7f61cab101fa13b4e88234e9ad47434e6130fed2))
|
||||
|
||||
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
35
README.md
35
README.md
@@ -49,9 +49,8 @@ https://certd.handsfree.work/
|
||||
|
||||
1.2 安装docker
|
||||
https://docs.docker.com/engine/install/
|
||||
选择对应的操作系统,按照官方文档执行命令即可
|
||||
|
||||
1.3 安装docker-compose
|
||||
https://docs.docker.com/compose/install/linux/
|
||||
|
||||
### 2. 下载docker-compose.yaml文件
|
||||
|
||||
@@ -82,12 +81,12 @@ https://github.com/certd/certd/releases
|
||||
# 如果docker compose是插件化安装
|
||||
export CERTD_VERSION=latest
|
||||
docker compose up -d
|
||||
|
||||
#如果docker compose是独立安装
|
||||
export CERTD_VERSION=latest
|
||||
docker-compose up -d
|
||||
|
||||
```
|
||||
如果提示 没有compose命令,请安装docker-compose
|
||||
https://docs.docker.com/compose/install/linux/
|
||||
|
||||
|
||||
|
||||
### 4. 访问
|
||||
|
||||
http://your_server_ip:7001
|
||||
@@ -102,8 +101,7 @@ http://your_server_ip:7001
|
||||
|
||||
|
||||
|
||||
## 五、一些说明及问题处理
|
||||
### 1. 一些说明
|
||||
## 五、一些说明
|
||||
* 本项目ssl证书提供商为letencrypt
|
||||
* 申请过程遵循acme协议
|
||||
* 需要验证域名所有权,一般有两种方式(目前本项目仅支持dns-01)
|
||||
@@ -115,8 +113,15 @@ http://your_server_ip:7001
|
||||
* 免费证书过期时间90天,以后可能还会缩短,所以自动化部署必不可少
|
||||
* 设置每天自动运行,当证书过期前20天,会自动重新申请证书并部署
|
||||
|
||||
### 2. 问题处理
|
||||
#### 2.1 忘记管理员密码
|
||||
## 六、不同平台的设置说明
|
||||
|
||||
* [Cloudflare](./doc/cf/cf.md)
|
||||
* [腾讯云](./doc/tencent/tencent.md)
|
||||
* [windows主机](./doc/host/host.md)
|
||||
|
||||
|
||||
## 七、问题处理
|
||||
### 7.1 忘记管理员密码
|
||||
解决方法如下:
|
||||
1. 修改docker-compose.yaml文件,将环境变量`certd_system_resetAdminPassword`改为`true`
|
||||
```yaml
|
||||
@@ -138,7 +143,7 @@ docker compose up -d
|
||||
```
|
||||
5. 使用admin/123456登录系统,请及时修改管理员密码
|
||||
|
||||
## 六、联系作者
|
||||
## 八、联系作者
|
||||
如有疑问,欢迎加入群聊(请备注certd)
|
||||
* QQ群:141236433
|
||||
* 微信群:
|
||||
@@ -150,7 +155,7 @@ docker compose up -d
|
||||
<img height="230" src="./doc/images/me.png">
|
||||
</p>
|
||||
|
||||
## 捐赠
|
||||
## 九、捐赠
|
||||
媳妇儿说:“一天到晚搞开源,也不管管老婆孩子!😡😡😡”
|
||||
拜托各位捐赠支持一下,让媳妇儿开心开心,我也能有更多时间进行开源项目,感谢🙏🙏🙏
|
||||
<p align="center">
|
||||
@@ -158,12 +163,12 @@ docker compose up -d
|
||||
</p>
|
||||
|
||||
|
||||
## 七、贡献代码
|
||||
## 十、贡献代码
|
||||
|
||||
[贡献插件教程](./plugin.md)
|
||||
|
||||
|
||||
## 八、我的其他项目
|
||||
## 十一、我的其他项目
|
||||
* [袖手GPT](https://ai.handsfree.work/) ChatGPT,国内可用,无需FQ,每日免费额度
|
||||
* [fast-crud](https://gitee.com/fast-crud/fast-crud/) 基于vue3的crud快速开发框架
|
||||
* [dev-sidecar](https://github.com/docmirror/dev-sidecar/) 直连访问github工具,无需FQ,解决github无法访问的问题
|
||||
|
||||
15
doc/cf/cf.md
Normal file
15
doc/cf/cf.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Cloudflare
|
||||
|
||||
|
||||
## CF Token申请
|
||||
|
||||
### 申请地址:
|
||||
https://dash.cloudflare.com/profile/api-tokens
|
||||
|
||||
### 权限设置:
|
||||
需要设置权限和资源范围
|
||||
权限包括:Zone.Zone.edit, Zone.DNS.edit
|
||||
资源范围:要包含对应域名,推荐直接设置为All Zones
|
||||
最终效果如下,可以切换语言为英文对比如下图检查
|
||||
|
||||

|
||||
BIN
doc/cf/cf_token.png
Normal file
BIN
doc/cf/cf_token.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
24
doc/host/host.md
Normal file
24
doc/host/host.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# 远程主机
|
||||
|
||||
远程主机基于ssh协议,通过ssh连接远程主机,执行命令。
|
||||
|
||||
## windows开启OpenSSH Server
|
||||
1. 安装OpenSSH Server
|
||||
请前往Microsoft官方文档查看如何开启openSSH
|
||||
https://learn.microsoft.com/zh-cn/windows-server/administration/openssh/openssh_install_firstuse?tabs=gui#install-openssh-for-windows
|
||||
|
||||
2. 启动OpenSSH Server服务
|
||||
```
|
||||
win+R 弹出运行对话框,输入 services.msc 打开服务管理器
|
||||
找到 OpenSSH SSH Server
|
||||
启动ssh server服务,并且设置为自动启动
|
||||
```
|
||||
|
||||
3. 测试ssh登录
|
||||
使用你常用的ssh客户端,连接你的windows主机,进行测试
|
||||
|
||||
```cmd
|
||||
# 如何确定你用户名
|
||||
C:\Users\xiaoj>
|
||||
↑↑↑↑---------这个就是windows ssh的登录用户名
|
||||
```
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 145 KiB |
BIN
doc/tencent/dnspod-token.png
Normal file
BIN
doc/tencent/dnspod-token.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
BIN
doc/tencent/tencent-access.png
Normal file
BIN
doc/tencent/tencent-access.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
16
doc/tencent/tencent.md
Normal file
16
doc/tencent/tencent.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# 腾讯云
|
||||
|
||||
|
||||
## DNSPOD 授权设置
|
||||
目前腾讯云管理的域名的dns暂时只支持从DNSPOD进行设置
|
||||
打开 https://console.dnspod.cn/account/token/apikey
|
||||
然后按如下方式获取DNSPOD的授权
|
||||

|
||||
|
||||
|
||||
## 腾讯云API密钥设置
|
||||
|
||||
腾讯云其他部署需要API密钥,需要在腾讯云控制台进行设置
|
||||
打开 https://console.cloud.tencent.com/cam/capi
|
||||
然后按如下方式获取腾讯云的API密钥
|
||||

|
||||
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.20.14"
|
||||
"version": "1.20.15"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.15](https://github.com/publishlab/node-acme-client/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云dns provider 支持腾讯云的accessId ([e0eb3a4](https://github.com/publishlab/node-acme-client/commit/e0eb3a441384d474fe2923c69b25318264bdc9df))
|
||||
|
||||
## [1.20.14](https://github.com/publishlab/node-acme-client/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
@@ -1 +1 @@
|
||||
01:04
|
||||
14:27
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.20.14",
|
||||
"version": "1.20.15",
|
||||
"main": "src/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
const { readCsrDomains } = require('./crypto');
|
||||
const { log } = require('./logger');
|
||||
const { wait } = require('./wait');
|
||||
|
||||
const defaultOpts = {
|
||||
csr: null,
|
||||
@@ -118,7 +119,18 @@ module.exports = async function(client, userOpts) {
|
||||
let recordItem = null;
|
||||
try {
|
||||
recordItem = await opts.challengeCreateFn(authz, challenge, keyAuthorization);
|
||||
|
||||
log(`[auto] [${d}] challengeCreateFn success`);
|
||||
log(`[auto] [${d}] add challengeRemoveFn()`);
|
||||
clearTasks.push(async () => {
|
||||
/* Trigger challengeRemoveFn(), suppress errors */
|
||||
log(`[auto] [${d}] Trigger challengeRemoveFn()`);
|
||||
try {
|
||||
await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem);
|
||||
}
|
||||
catch (e) {
|
||||
log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);
|
||||
}
|
||||
});
|
||||
// throw new Error('测试异常');
|
||||
/* Challenge verification */
|
||||
if (opts.skipChallengeVerification === true) {
|
||||
@@ -140,19 +152,6 @@ module.exports = async function(client, userOpts) {
|
||||
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
log(`[auto] [${d}] add challengeRemoveFn()`);
|
||||
clearTasks.push(async () => {
|
||||
/* Trigger challengeRemoveFn(), suppress errors */
|
||||
log(`[auto] [${d}] Trigger challengeRemoveFn()`);
|
||||
try {
|
||||
await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem);
|
||||
}
|
||||
catch (e) {
|
||||
log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
/* Deactivate pending authz when unable to complete challenge */
|
||||
@@ -186,14 +185,21 @@ module.exports = async function(client, userOpts) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
// function runPromisePa(tasks) {
|
||||
// return Promise.all(tasks.map((task) => task()));
|
||||
// }
|
||||
async function runPromisePa(tasks) {
|
||||
const results = [];
|
||||
// eslint-disable-next-line no-await-in-loop,no-restricted-syntax
|
||||
for (const task of tasks) {
|
||||
results.push(task());
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await wait(10000);
|
||||
}
|
||||
return Promise.all(results);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
log('开始challenge');
|
||||
await runAllPromise(challengePromises);
|
||||
await runPromisePa(challengePromises);
|
||||
|
||||
log('challenge结束');
|
||||
|
||||
|
||||
9
packages/core/acme-client/src/wait.js
Normal file
9
packages/core/acme-client/src/wait.js
Normal file
@@ -0,0 +1,9 @@
|
||||
async function wait(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
wait
|
||||
};
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.20.14",
|
||||
"version": "1.20.15",
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
@@ -23,7 +23,7 @@
|
||||
"qs": "^6.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/acme-client": "workspace:^1.20.14",
|
||||
"@certd/acme-client": "workspace:^1.20.15",
|
||||
"@rollup/plugin-commonjs": "^23.0.4",
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
|
||||
@@ -52,6 +52,8 @@ export function createAxiosService({ logger }: { logger: Logger }) {
|
||||
// }
|
||||
logger.error(`请求出错:url:${error?.response?.config.url},method:${error.response.config.method},status:${error?.response?.status}`);
|
||||
logger.info("返回数据:", JSON.stringify(error?.response?.data));
|
||||
delete error.config;
|
||||
delete error.response;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云dns provider 支持腾讯云的accessId ([e0eb3a4](https://github.com/certd/certd/commit/e0eb3a441384d474fe2923c69b25318264bdc9df))
|
||||
|
||||
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-cert",
|
||||
"private": false,
|
||||
"version": "1.20.14",
|
||||
"version": "1.20.15",
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
@@ -17,8 +17,8 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "workspace:^1.20.14",
|
||||
"@certd/pipeline": "workspace:^1.20.14",
|
||||
"@certd/acme-client": "workspace:^1.20.15",
|
||||
"@certd/pipeline": "workspace:^1.20.15",
|
||||
"jszip": "^3.10.1",
|
||||
"node-forge": "^0.10.0",
|
||||
"psl": "^1.9.0"
|
||||
|
||||
@@ -45,7 +45,7 @@ export class AcmeService {
|
||||
directoryUrl: isTest ? acme.directory.letsencrypt.staging : acme.directory.letsencrypt.production,
|
||||
accountKey: conf.key,
|
||||
accountUrl: conf.accountUrl,
|
||||
backoffAttempts: 20,
|
||||
backoffAttempts: 70,
|
||||
backoffMin: 5000,
|
||||
backoffMax: 10000,
|
||||
});
|
||||
|
||||
@@ -90,7 +90,7 @@ export class CertApplyPlugin extends AbstractTaskPlugin {
|
||||
vModel: "value",
|
||||
},
|
||||
required: true,
|
||||
helper: "到期前多少天后更新证书",
|
||||
helper: "到期前多少天后更新证书,注意:流水线默认不会自动运行,请设置定时器,每天定时运行本流水线",
|
||||
})
|
||||
renewDays!: number;
|
||||
|
||||
@@ -106,9 +106,16 @@ export class CertApplyPlugin extends AbstractTaskPlugin {
|
||||
|
||||
@TaskInput({
|
||||
title: "CsrInfo",
|
||||
helper: "暂时没有用",
|
||||
})
|
||||
csrInfo!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: "配置说明",
|
||||
helper: "运行策略请选择总是运行,其他证书部署任务请选择成功后跳过;当证书快到期前将会自动重新申请证书,然后会清空后续任务的成功状态,部署任务将会重新运行",
|
||||
})
|
||||
intro!: string;
|
||||
|
||||
acme!: AcmeService;
|
||||
logger!: Logger;
|
||||
userContext!: IContext;
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-util
|
||||
|
||||
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-util
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-util",
|
||||
"private": false,
|
||||
"version": "1.20.14",
|
||||
"version": "1.20.15",
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
@@ -21,7 +21,7 @@
|
||||
"shelljs": "^0.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/pipeline": "workspace:^1.20.14",
|
||||
"@certd/pipeline": "workspace:^1.20.15",
|
||||
"@rollup/plugin-commonjs": "^23.0.4",
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复无法强制取消任务的bug ([9cc01db](https://github.com/certd/certd/commit/9cc01db1d569a5c45bb3e731f35d85df324a8e62))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 支持windows文件上传 ([7f61cab](https://github.com/certd/certd/commit/7f61cab101fa13b4e88234e9ad47434e6130fed2))
|
||||
|
||||
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.20.14",
|
||||
"version": "1.20.15",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
@@ -59,7 +59,7 @@
|
||||
"vuedraggable": "^2.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/pipeline": "^1.20.14",
|
||||
"@certd/pipeline": "^1.20.15",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/chai": "^4.3.12",
|
||||
|
||||
@@ -28,6 +28,9 @@ export default defineComponent({
|
||||
return {};
|
||||
}
|
||||
},
|
||||
historyId: {
|
||||
type: Number
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "icon"
|
||||
@@ -59,7 +62,7 @@ export default defineComponent({
|
||||
okText: "确认",
|
||||
cancelText: "取消",
|
||||
onOk: async () => {
|
||||
await api.Cancel(props.runnable.id);
|
||||
await api.Cancel(props.historyId);
|
||||
notification.success({
|
||||
message: "任务取消成功"
|
||||
});
|
||||
|
||||
@@ -81,11 +81,11 @@
|
||||
<fs-icon v-if="editMode" class="add-stage-btn" title="添加新阶段" icon="ion:add-circle" @click="stageAdd(index)"></fs-icon>
|
||||
</div>
|
||||
<div class="task">
|
||||
<a-button shape="round" @click="taskEdit(stage, index, task, taskIndex)">
|
||||
{{ task.title }}
|
||||
<pi-status-show :status="task.status?.result"></pi-status-show>
|
||||
</a-button>
|
||||
<fs-icon class="copy" v-if="editMode" title="复制" icon="ion:copy-outline" @click="taskCopy(stage, index, task)"></fs-icon>
|
||||
<a-button shape="round" @click="taskEdit(stage, index, task, taskIndex)">
|
||||
{{ task.title }}
|
||||
<pi-status-show :status="task.status?.result"></pi-status-show>
|
||||
</a-button>
|
||||
<fs-icon v-if="editMode" class="copy" title="复制" icon="ion:copy-outline" @click="taskCopy(stage, index, task)"></fs-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="editMode" class="task-container is-add">
|
||||
@@ -99,7 +99,6 @@
|
||||
并行任务
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,7 +187,14 @@
|
||||
<a-page-header title="运行历史" sub-title="点任务可查看日志" class="logs-block">
|
||||
<a-timeline class="mt-10">
|
||||
<template v-for="item of histories" :key="item.id">
|
||||
<pi-history-timeline-item :runnable="item.pipeline" :is-current="currentHistory?.id === item.id" :edit-mode="editMode" @view="historyView(item)" @cancel="historyCancel()"></pi-history-timeline-item>
|
||||
<pi-history-timeline-item
|
||||
:runnable="item.pipeline"
|
||||
:history-id="item.id"
|
||||
:is-current="currentHistory?.id === item.id"
|
||||
:edit-mode="editMode"
|
||||
@view="historyView(item)"
|
||||
@cancel="historyCancel()"
|
||||
></pi-history-timeline-item>
|
||||
</template>
|
||||
<a-empty v-if="histories.length === 0"> </a-empty>
|
||||
</a-timeline>
|
||||
@@ -373,7 +379,7 @@ export default defineComponent({
|
||||
|
||||
const taskView = useTaskView();
|
||||
|
||||
const taskAdd = (stage: any, stageIndex: number, onSuccess?: any,taskDef?:any) => {
|
||||
const taskAdd = (stage: any, stageIndex: number, onSuccess?: any, taskDef?: any) => {
|
||||
currentStageIndex.value = stageIndex;
|
||||
currentTaskIndex.value = stage.tasks.length;
|
||||
taskFormRef.value.taskAdd((type: any, value: any) => {
|
||||
@@ -383,17 +389,17 @@ export default defineComponent({
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
},taskDef);
|
||||
}, taskDef);
|
||||
};
|
||||
|
||||
const taskCopy = (stage: any, stageIndex: number, task: any ) => {
|
||||
task = _.cloneDeep(task)
|
||||
task.id = nanoid()
|
||||
task.title= task.title+"_copy"
|
||||
const taskCopy = (stage: any, stageIndex: number, task: any) => {
|
||||
task = _.cloneDeep(task);
|
||||
task.id = nanoid();
|
||||
task.title = task.title + "_copy";
|
||||
for (const step of task.steps) {
|
||||
step.id = nanoid()
|
||||
step.id = nanoid();
|
||||
}
|
||||
taskAdd(stage,stageIndex,null,task)
|
||||
taskAdd(stage, stageIndex, null, task);
|
||||
};
|
||||
|
||||
const taskEdit = (stage: any, stageIndex: number, task: any, taskIndex: number, onSuccess?: any) => {
|
||||
@@ -423,7 +429,7 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
return { taskAdd, taskEdit, taskCopy,taskFormRef, ...taskView };
|
||||
return { taskAdd, taskEdit, taskCopy, taskFormRef, ...taskView };
|
||||
}
|
||||
|
||||
function useStage(useTaskRet: any) {
|
||||
@@ -760,10 +766,10 @@ export default defineComponent({
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
|
||||
.copy{
|
||||
.copy {
|
||||
position: absolute;
|
||||
right:60px;
|
||||
top:18px;
|
||||
right: 60px;
|
||||
top: 18px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
|
||||
@@ -36,6 +36,13 @@ const StatusEnum: StatusEnumType = {
|
||||
spin: true,
|
||||
icon: "ant-design:sync-outlined"
|
||||
},
|
||||
canceled: {
|
||||
value: "canceled",
|
||||
label: "已取消",
|
||||
color: "yellow",
|
||||
spin: true,
|
||||
icon: "ant-design:minus-circle-twotone"
|
||||
},
|
||||
none: {
|
||||
value: "none",
|
||||
label: "未运行",
|
||||
|
||||
@@ -18,12 +18,14 @@
|
||||
</a-form-item>
|
||||
<a-form-item label="密码" :name="['auth', 'pass']" :rules="[{ required: true, message: '请输入密码' }]">
|
||||
<a-input-password v-model:value="formState.auth.pass" />
|
||||
<div class="helper">如果是qq邮箱,需要到qq邮箱的设置里面申请授权码作为密码</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="发件邮箱" name="sender" :rules="[{ required: true, message: '请输入发件邮箱' }]">
|
||||
<a-input v-model:value="formState.sender" />
|
||||
</a-form-item>
|
||||
<a-form-item label="是否ssl" name="secure">
|
||||
<a-switch v-model:checked="formState.secure" />
|
||||
<div class="helper">ssl和非ssl的smtp端口是不一样的,注意修改端口</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="忽略证书校验" name="tls.rejectUnauthorized">
|
||||
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
|
||||
@@ -124,5 +126,12 @@ async function onTestSend() {
|
||||
width: 500px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.helper{
|
||||
padding:1px;
|
||||
margin:0px;
|
||||
color: #999;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="d2-page-cover">
|
||||
<div class="d2-page-cover__title" @click="$open('https://github.com/certd/certd')">
|
||||
<div class="title-line">Certd v{{ version }}</div>
|
||||
<div class="title-line">
|
||||
<span>Certd v{{ version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="d2-page-cover__sub-title">让你的证书永不过期</p>
|
||||
<div class="warnning">
|
||||
@@ -73,7 +75,7 @@ export default defineComponent({
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
width: 80%;
|
||||
width: 70%;
|
||||
.preview_img {
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.15](https://github.com/fast-crud/fast-server-js/compare/v1.20.14...v1.20.15) (2024-06-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复无法强制取消任务的bug ([9cc01db](https://github.com/fast-crud/fast-server-js/commit/9cc01db1d569a5c45bb3e731f35d85df324a8e62))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云dns provider 支持腾讯云的accessId ([e0eb3a4](https://github.com/fast-crud/fast-server-js/commit/e0eb3a441384d474fe2923c69b25318264bdc9df))
|
||||
* 支持windows文件上传 ([7f61cab](https://github.com/fast-crud/fast-server-js/commit/7f61cab101fa13b4e88234e9ad47434e6130fed2))
|
||||
|
||||
## [1.20.14](https://github.com/fast-crud/fast-server-js/compare/v1.20.13...v1.20.14) (2024-06-23)
|
||||
|
||||
**Note:** Version bump only for package @certd/ui-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.20.14",
|
||||
"version": "1.20.15",
|
||||
"description": "fast-server base midway",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -24,10 +24,10 @@
|
||||
"@alicloud/cs20151215": "^3.0.3",
|
||||
"@alicloud/openapi-client": "^0.4.0",
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@certd/acme-client": "^1.20.14",
|
||||
"@certd/pipeline": "^1.20.14",
|
||||
"@certd/plugin-cert": "^1.20.14",
|
||||
"@certd/plugin-util": "^1.20.14",
|
||||
"@certd/acme-client": "^1.20.15",
|
||||
"@certd/pipeline": "^1.20.15",
|
||||
"@certd/plugin-cert": "^1.20.15",
|
||||
"@certd/plugin-util": "^1.20.15",
|
||||
"@koa/cors": "^3.4.3",
|
||||
"@midwayjs/bootstrap": "^3.15.0",
|
||||
"@midwayjs/cache": "^3.14.0",
|
||||
@@ -46,6 +46,7 @@
|
||||
"dayjs": "^1.11.7",
|
||||
"glob": "^7.2.3",
|
||||
"https-proxy-agent": "^7.0.4",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"koa-send": "^5.0.1",
|
||||
@@ -62,6 +63,7 @@
|
||||
"ssh2": "^0.8.9",
|
||||
"svg-captcha": "^1.4.0",
|
||||
"tencentcloud-sdk-nodejs": "^4.0.44",
|
||||
"tencentcloud-sdk-nodejs-dnspod": "^4.0.866",
|
||||
"typeorm": "^0.3.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -236,14 +236,18 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
async cancel(historyId: number) {
|
||||
async cancel(historyId) {
|
||||
const executor = runningTasks.get(historyId);
|
||||
if (executor) {
|
||||
await executor.cancel();
|
||||
} else {
|
||||
const entity = await this.historyService.info(historyId);
|
||||
if(entity == null){
|
||||
return
|
||||
}
|
||||
const pipeline: Pipeline = JSON.parse(entity.pipeline);
|
||||
pipeline.status.status = ResultType.canceled;
|
||||
pipeline.status.result = ResultType.canceled;
|
||||
const runtime = new RunHistory(historyId, null, pipeline);
|
||||
await this.saveHistory(runtime);
|
||||
}
|
||||
@@ -291,6 +295,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
const entity: HistoryEntity = new HistoryEntity();
|
||||
entity.id = parseInt(history.id);
|
||||
entity.userId = history.pipeline.userId;
|
||||
entity.status = pipelineEntity.status
|
||||
entity.pipeline = JSON.stringify(history.pipeline);
|
||||
entity.pipelineId = parseInt(history.pipeline.id);
|
||||
await this.historyService.save(entity);
|
||||
|
||||
@@ -60,6 +60,32 @@ export class SshAccess implements IAccess, ConnectConfig {
|
||||
},
|
||||
})
|
||||
passphrase!: string;
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: '是否Windows',
|
||||
helper: '如果是Windows主机,请勾选此项',
|
||||
component: {
|
||||
name: 'a-switch',
|
||||
vModel: 'checked',
|
||||
},
|
||||
})
|
||||
windows: boolean = false;
|
||||
|
||||
@AccessInput({
|
||||
title: '命令编码',
|
||||
helper: '如果是Windows主机,且出现乱码了,请尝试设置为GBK',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
options:[
|
||||
{value:"","label":"默认"},
|
||||
{value:"GBK","label":"GBK"},
|
||||
{value:"UTF8","label":"UTF-8"},
|
||||
]
|
||||
},
|
||||
})
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
new SshAccess();
|
||||
|
||||
@@ -3,6 +3,138 @@ import ssh2, { ConnectConfig } from 'ssh2';
|
||||
import path from 'path';
|
||||
import _ from 'lodash';
|
||||
import { ILogger } from '@certd/pipeline';
|
||||
import iconv from 'iconv-lite';
|
||||
import {SshAccess} from "../access";
|
||||
export class AsyncSsh2Client {
|
||||
conn: ssh2.Client;
|
||||
logger: ILogger;
|
||||
connConf: ssh2.ConnectConfig;
|
||||
windows:boolean = false;
|
||||
encoding:string;
|
||||
constructor(connConf: SshAccess, logger: ILogger) {
|
||||
this.connConf = connConf;
|
||||
this.logger = logger;
|
||||
this.windows = connConf.windows || false;
|
||||
this.encoding = connConf.encoding;
|
||||
}
|
||||
|
||||
convert(buffer: Buffer) {
|
||||
if(this.encoding){
|
||||
return iconv.decode(buffer, this.encoding);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.logger.info(`开始连接,${this.connConf.host}:${this.connConf.port}`);
|
||||
return new Promise((resolve, reject) => {
|
||||
const conn = new ssh2.Client();
|
||||
conn
|
||||
.on('error', (err: any) => {
|
||||
reject(err);
|
||||
})
|
||||
.on('ready', () => {
|
||||
this.logger.info('连接成功');
|
||||
this.conn = conn;
|
||||
resolve(this.conn);
|
||||
})
|
||||
.connect(this.connConf);
|
||||
});
|
||||
}
|
||||
async getSftp() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.logger.info('获取sftp');
|
||||
this.conn.sftp((err: any, sftp: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(sftp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async fastPut(options: { sftp: any; localPath: string; remotePath: string }) {
|
||||
const { sftp, localPath, remotePath } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.logger.info(`开始上传:${localPath} => ${remotePath}`);
|
||||
sftp.fastPut(localPath, remotePath, (err: Error) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
this.logger.info(`上传文件成功:${localPath} => ${remotePath}`);
|
||||
resolve({});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async exec(script: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.logger.info(`执行命令:[${this.connConf.host}][exec]: ` + script);
|
||||
this.conn.exec(script, (err: Error, stream: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
let data: string = null;
|
||||
stream
|
||||
.on('close', (code: any, signal: any) => {
|
||||
this.logger.info(`[${this.connConf.host}][close]:code:${code}`);
|
||||
if (code === 0) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(new Error(data));
|
||||
}
|
||||
})
|
||||
.on('data', (ret: Buffer) => {
|
||||
data = this.convert(ret)
|
||||
this.logger.info(`[${this.connConf.host}][info]: ` + data);
|
||||
})
|
||||
.stderr.on('data', (ret:Buffer) => {
|
||||
data = this.convert(ret)
|
||||
this.logger.info(`[${this.connConf.host}][error]: ` + data);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async shell(script: string | string[]): Promise<string[]> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.logger.info(
|
||||
`执行shell脚本:[${this.connConf.host}][shell]: ` + script
|
||||
);
|
||||
this.conn.shell((err: Error, stream: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const output: string[] = [];
|
||||
stream
|
||||
.on('close', () => {
|
||||
this.logger.info('Stream :: close');
|
||||
resolve(output);
|
||||
})
|
||||
.on('data', (ret: Buffer) => {
|
||||
const data = this.convert(ret)
|
||||
this.logger.info('' + data);
|
||||
output.push(data);
|
||||
})
|
||||
.stderr.on('data', (ret:Buffer) => {
|
||||
const data = this.convert(ret)
|
||||
output.push(data);
|
||||
this.logger.info(`[${this.connConf.host}][error]: ` + data);
|
||||
});
|
||||
stream.end(script + '\nexit\n');
|
||||
});
|
||||
});
|
||||
}
|
||||
end() {
|
||||
if (this.conn) {
|
||||
this.conn.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
export class SshClient {
|
||||
logger: ILogger;
|
||||
constructor(logger: ILogger) {
|
||||
@@ -19,42 +151,34 @@ export class SshClient {
|
||||
}
|
||||
* @param options
|
||||
*/
|
||||
uploadFiles(options: { connectConf: ConnectConfig; transports: any }) {
|
||||
async uploadFiles(options: { connectConf: SshAccess; transports: any }) {
|
||||
const { connectConf, transports } = options;
|
||||
const conn = new ssh2.Client();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
conn
|
||||
.on('ready', () => {
|
||||
this.logger.info('连接服务器成功');
|
||||
conn.sftp(async (err: any, sftp: any) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
await this._call({
|
||||
connectConf,
|
||||
callable: async (conn: AsyncSsh2Client) => {
|
||||
const sftp = await conn.getSftp();
|
||||
this.logger.info('开始上传');
|
||||
for (const transport of transports) {
|
||||
let filePath = path.dirname(transport.remotePath);
|
||||
let mkdirCmd = `mkdir -p ${filePath} `;
|
||||
if(conn.windows){
|
||||
if(filePath.indexOf("/") > -1){
|
||||
this.logger.info("--------------------------")
|
||||
this.logger.info("请注意:windows下,文件目录分隔应该写成\\而不是/")
|
||||
this.logger.info("--------------------------")
|
||||
}
|
||||
|
||||
try {
|
||||
for (const transport of transports) {
|
||||
this.logger.info('上传文件:', JSON.stringify(transport));
|
||||
await this.exec({
|
||||
connectConf,
|
||||
script: `mkdir -p ${path.dirname(transport.remotePath)} `,
|
||||
});
|
||||
await this.fastPut({ sftp, ...transport });
|
||||
}
|
||||
resolve({});
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
});
|
||||
})
|
||||
.connect(connectConf);
|
||||
mkdirCmd = `if not exist "${filePath}" mkdir ${filePath} `
|
||||
}
|
||||
await conn.exec(mkdirCmd);
|
||||
await conn.fastPut({ sftp, ...transport });
|
||||
}
|
||||
this.logger.info('文件全部上传成功');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
exec(options: {
|
||||
connectConf: ConnectConfig;
|
||||
async exec(options: {
|
||||
connectConf: SshAccess;
|
||||
script: string | Array<string>;
|
||||
}) {
|
||||
let { script } = options;
|
||||
@@ -64,102 +188,38 @@ export class SshClient {
|
||||
script = script.join('\n');
|
||||
}
|
||||
this.logger.info('执行命令:', script);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.connect({
|
||||
connectConf,
|
||||
onError(err: any) {
|
||||
reject(err);
|
||||
},
|
||||
onReady: (conn: any) => {
|
||||
conn.exec(script, (err: Error, stream: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
let data: any = null;
|
||||
stream
|
||||
.on('close', (code: any, signal: any) => {
|
||||
this.logger.info(`[${connectConf.host}][close]:code:${code}`);
|
||||
data = data ? data.toString() : null;
|
||||
if (code === 0) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(new Error(data));
|
||||
}
|
||||
conn.end();
|
||||
})
|
||||
.on('data', (ret: any) => {
|
||||
this.logger.info(`[${connectConf.host}][info]: ` + ret);
|
||||
data = ret;
|
||||
})
|
||||
.stderr.on('data', (err: Error) => {
|
||||
this.logger.info(`[${connectConf.host}][error]: ` + err);
|
||||
data = err;
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
return await this._call({
|
||||
connectConf,
|
||||
callable: async (conn: AsyncSsh2Client) => {
|
||||
return await conn.exec(script as string);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
shell(options: { connectConf: ConnectConfig; script: string }) {
|
||||
async shell(options: {
|
||||
connectConf: SshAccess;
|
||||
script: string;
|
||||
}): Promise<string[]> {
|
||||
const { connectConf, script } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.connect({
|
||||
connectConf,
|
||||
onError: (err: any) => {
|
||||
this.logger.error(err);
|
||||
reject(err);
|
||||
},
|
||||
onReady: (conn: any) => {
|
||||
conn.shell((err: Error, stream: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const output: any = [];
|
||||
stream
|
||||
.on('close', () => {
|
||||
this.logger.info('Stream :: close');
|
||||
conn.end();
|
||||
resolve(output);
|
||||
})
|
||||
.on('data', (data: any) => {
|
||||
this.logger.info('' + data);
|
||||
output.push('' + data);
|
||||
});
|
||||
stream.end(script + '\nexit\n');
|
||||
});
|
||||
},
|
||||
});
|
||||
return await this._call({
|
||||
connectConf,
|
||||
callable: async (conn: AsyncSsh2Client) => {
|
||||
return await conn.shell(script as string);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
connect(options: { connectConf: ConnectConfig; onReady: any; onError: any }) {
|
||||
const { connectConf, onReady, onError } = options;
|
||||
const conn = new ssh2.Client();
|
||||
conn
|
||||
.on('error', (err: any) => {
|
||||
onError(err);
|
||||
})
|
||||
.on('ready', () => {
|
||||
this.logger.info('Client :: ready');
|
||||
onReady(conn);
|
||||
})
|
||||
.connect(connectConf);
|
||||
return conn;
|
||||
}
|
||||
|
||||
fastPut(options: { sftp: any; localPath: string; remotePath: string }) {
|
||||
const { sftp, localPath, remotePath } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
sftp.fastPut(localPath, remotePath, (err: Error) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve({});
|
||||
});
|
||||
});
|
||||
async _call(options: {
|
||||
connectConf: SshAccess;
|
||||
callable: any;
|
||||
}): Promise<string[]> {
|
||||
const { connectConf, callable } = options;
|
||||
const conn = new AsyncSsh2Client(connectConf, this.logger);
|
||||
await conn.connect();
|
||||
try {
|
||||
return await callable(conn);
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { SshClient } from '../../lib/ssh';
|
||||
import { CertInfo, CertReader } from '@certd/plugin-cert';
|
||||
import * as fs from 'fs';
|
||||
import {SshAccess} from "../../access";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'uploadCertToHost',
|
||||
@@ -24,7 +25,7 @@ import * as fs from 'fs';
|
||||
export class UploadCertToHostPlugin extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: '证书保存路径',
|
||||
helper: '需要有写入权限,路径要包含证书文件名',
|
||||
helper: '需要有写入权限,路径要包含证书文件名,文件名不能用*?!等特殊符号',
|
||||
component: {
|
||||
placeholder: '/root/deploy/nginx/cert.crt',
|
||||
},
|
||||
@@ -32,7 +33,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
|
||||
crtPath!: string;
|
||||
@TaskInput({
|
||||
title: '私钥保存路径',
|
||||
helper: '需要有写入权限,路径要包含私钥文件名',
|
||||
helper: '需要有写入权限,路径要包含私钥文件名,文件名不能用*?!等特殊符号',
|
||||
component: {
|
||||
placeholder: '/root/deploy/nginx/cert.key',
|
||||
},
|
||||
@@ -59,9 +60,9 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
|
||||
accessId!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: '复制到当前主机',
|
||||
title: '仅复制到当前主机',
|
||||
helper:
|
||||
'开启后,将直接复制到当前主机某个目录,由于是docker启动,实际上复制到的是docker容器内的目录,你需要事先在docker-compose.yaml中配置主机目录映射: volumes: /your_target_path:/your_target_path',
|
||||
'开启后,将直接复制到当前主机某个目录,不上传到主机,由于是docker启动,实际上是复制到docker容器内的“证书保存路径”,你需要事先在docker-compose.yaml中配置主机目录映射: volumes: /your_target_path:/your_target_path',
|
||||
component: {
|
||||
name: 'a-switch',
|
||||
value: false,
|
||||
@@ -98,11 +99,12 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
|
||||
async execute(): Promise<void> {
|
||||
const { crtPath, keyPath, cert, accessId } = this;
|
||||
const certReader = new CertReader(cert);
|
||||
|
||||
this.logger.info('将证书写入本地缓存文件');
|
||||
const saveCrtPath = certReader.saveToFile('crt');
|
||||
const saveKeyPath = certReader.saveToFile('key');
|
||||
|
||||
if (this.copyToThisHost) {
|
||||
this.logger.info('复制到目标路径');
|
||||
this.copyFile(saveCrtPath, crtPath);
|
||||
this.copyFile(saveKeyPath, keyPath);
|
||||
this.logger.info('证书复制成功:crtPath=', crtPath, ',keyPath=', keyPath);
|
||||
@@ -110,7 +112,8 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
|
||||
if (!accessId) {
|
||||
throw new Error('主机登录授权配置不能为空');
|
||||
}
|
||||
const connectConf = await this.accessService.getById(accessId);
|
||||
this.logger.info('准备上传到服务器');
|
||||
const connectConf:SshAccess = await this.accessService.getById(accessId);
|
||||
const sshClient = new SshClient(this.logger);
|
||||
await sshClient.uploadFiles({
|
||||
connectConf,
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import {Autowire, HttpClient, ILogger} from "@certd/pipeline";
|
||||
import {AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions} from "@certd/plugin-cert";
|
||||
import {TencentAccess} from "../access";
|
||||
import tencentcloud from 'tencentcloud-sdk-nodejs/index';
|
||||
|
||||
const DnspodClient = tencentcloud.dnspod.v20210323.Client;
|
||||
@IsDnsProvider({
|
||||
name: 'tencent',
|
||||
title: '腾讯云',
|
||||
desc: '腾讯云域名DNS解析提供者',
|
||||
accessType: 'tencent',
|
||||
})
|
||||
export class TencentDnsProvider extends AbstractDnsProvider {
|
||||
@Autowire()
|
||||
http!: HttpClient;
|
||||
|
||||
@Autowire()
|
||||
access!: TencentAccess;
|
||||
@Autowire()
|
||||
logger!: ILogger;
|
||||
|
||||
client!: any;
|
||||
|
||||
endpoint = 'dnspod.tencentcloudapi.com';
|
||||
|
||||
async onInstance() {
|
||||
|
||||
const clientConfig = {
|
||||
credential: this.access,
|
||||
region: "",
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint: this.endpoint,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
||||
this.client = new DnspodClient(clientConfig);
|
||||
}
|
||||
|
||||
async createRecord(options: CreateRecordOptions): Promise<any> {
|
||||
const { fullRecord, value, type,domain } = options;
|
||||
this.logger.info('添加域名解析:', fullRecord, value);
|
||||
const rr = fullRecord.replace('.' + domain, '');
|
||||
|
||||
const params = {
|
||||
"Domain": domain,
|
||||
"RecordType": type,
|
||||
"RecordLine": "默认",
|
||||
"Value": value,
|
||||
"SubDomain": rr
|
||||
};
|
||||
|
||||
const ret = await this.client.CreateRecord(params)
|
||||
/*
|
||||
{
|
||||
"RecordId": 162,
|
||||
"RequestId": "ab4f1426-ea15-42ea-8183-dc1b44151166"
|
||||
}
|
||||
*/
|
||||
this.logger.info(
|
||||
'添加域名解析成功:',
|
||||
fullRecord,
|
||||
value,
|
||||
JSON.stringify(ret)
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
|
||||
async removeRecord(options: RemoveRecordOptions<any>) {
|
||||
const { fullRecord, value, domain,record } = options;
|
||||
|
||||
const params = {
|
||||
"Domain": domain,
|
||||
"RecordId": record.RecordId
|
||||
};
|
||||
const ret = await this.client.DeleteRecord(params)
|
||||
this.logger.info('删除域名解析成功:', fullRecord, value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
new TencentDnsProvider();
|
||||
Reference in New Issue
Block a user