Merge remote-tracking branch 'origin/v2-dev' into v2-dev

This commit is contained in:
xiaojunnuo
2024-10-10 21:44:49 +08:00
45 changed files with 684 additions and 101 deletions
@@ -10,3 +10,4 @@ export * from './plugin-west/index.js';
export * from './plugin-doge/index.js';
export * from './plugin-qiniu/index.js';
export * from './plugin-jdcloud/index.js';
export * from './plugin-woai/index.js';
@@ -1,5 +1,5 @@
// @ts-ignore
import ssh2, { ConnectConfig } from 'ssh2';
import ssh2, { ConnectConfig, ExecOptions } from 'ssh2';
import path from 'path';
import * as _ from 'lodash-es';
import { ILogger } from '@certd/pipeline';
@@ -269,7 +269,7 @@ export class SshClient {
* Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\cmd.exe"
* @param options
*/
async exec(options: { connectConf: SshAccess; script: string | Array<string> }) {
async exec(options: { connectConf: SshAccess; script: string | Array<string>; env?: any }): Promise<string[]> {
let { script } = options;
const { connectConf } = options;
@@ -278,14 +278,32 @@ export class SshClient {
connectConf,
callable: async (conn: AsyncSsh2Client) => {
let isWinCmd = false;
const isLinux = !connectConf.windows;
const envScripts = [];
if (connectConf.windows) {
isWinCmd = await this.isCmd(conn);
}
if (options.env) {
for (const key in options.env) {
if (isLinux) {
envScripts.push(`export ${key}=${options.env[key]}`);
} else if (isWinCmd) {
//win cmd
envScripts.push(`set ${key}=${options.env[key]}`);
} else {
//powershell
envScripts.push(`$env:${key}="${options.env[key]}"`);
}
}
}
if (isWinCmd) {
//组合成&&的形式
if (typeof script === 'string') {
script = script.split('\n');
}
script = envScripts.concat(script);
script = script as Array<string>;
script = script.join(' && ');
} else {
@@ -293,6 +311,9 @@ export class SshClient {
script = script as Array<string>;
script = script.join('\n');
}
if (envScripts.length > 0) {
script = envScripts.join('\n') + '\n' + script;
}
}
await conn.exec(script);
},
@@ -3,6 +3,7 @@ import { SshClient } from '../../lib/ssh.js';
import { CertInfo, CertReader, CertReaderHandleContext } from '@certd/plugin-cert';
import * as fs from 'fs';
import { SshAccess } from '../../access/index.js';
import dayjs from 'dayjs';
@IsTaskPlugin({
name: 'uploadCertToHost',
@@ -106,6 +107,18 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
})
script!: string;
@TaskInput({
title: '注入环境变量',
value: false,
component: {
name: 'a-switch',
vModel: 'checked',
},
helper: '是否将证书域名、路径等信息注入脚本执行环境变量中,具体的变量名称,可以运行后从日志中查看',
required: false,
})
injectEnv!: string;
@TaskOutput({
title: '证书保存路径',
})
@@ -233,10 +246,28 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
const connectConf: SshAccess = await this.accessService.getById(accessId);
const sshClient = new SshClient(this.logger);
this.logger.info('执行脚本命令');
//环境变量
const env = {};
if (this.injectEnv) {
const domains = certReader.getAllDomains();
for (let i = 0; i < domains.length; i++) {
env[`CERT_DOMAIN_${i}`] = domains[i];
}
env['CERT_EXPIRES'] = dayjs(certReader.getCrtDetail().expires).unix();
env['HOST_CRT_PATH'] = this.hostCrtPath || '';
env['HOST_KEY_PATH'] = this.hostKeyPath || '';
env['HOST_IC_PATH'] = this.hostIcPath || '';
env['HOST_PFX_PATH'] = this.hostPfxPath || '';
env['HOST_DER_PATH'] = this.hostDerPath || '';
}
const scripts = this.script.split('\n');
await sshClient.exec({
connectConf,
script: scripts,
env,
});
}
}
@@ -18,9 +18,17 @@ export class QiniuDeployCertToCDN extends AbstractTaskPlugin {
@TaskInput({
title: 'CDN加速域名',
helper: '你在七牛云上配置的CDN加速域名,比如:certd.handsfree.work',
component: {
name: 'a-select',
vModel: 'value',
mode: 'tags',
open: false,
tokenSeparators: [',', ' ', '', '、', '|'],
},
rules: [{ type: 'domains' }],
required: true,
})
domainName!: string;
domainName!: string | string[];
@TaskInput({
title: '域名证书',
@@ -52,7 +60,7 @@ export class QiniuDeployCertToCDN extends AbstractTaskPlugin {
http: this.ctx.http,
access,
});
const url = `https://api.qiniu.com/domain/${this.domainName}/httpsconf`;
let certId = null;
if (typeof this.cert !== 'string') {
// 是证书id,直接上传即可
@@ -62,13 +70,17 @@ export class QiniuDeployCertToCDN extends AbstractTaskPlugin {
certId = this.cert;
}
//开始修改证书
this.logger.info(`开始修改证书,certId:${certId},domain:${this.domainName}`);
const body = {
certID: certId,
};
await qiniuClient.doRequest(url, 'put', body);
const domains: string[] = typeof this.domainName === 'string' ? [this.domainName] : this.domainName;
for (const domain of domains) {
//开始修改证书
this.logger.info(`开始修改证书,certId:${certId},domain:${domain}`);
const body = {
certID: certId,
};
const url = `https://api.qiniu.com/domain/${domain}/httpsconf`;
await qiniuClient.doRequest(url, 'put', body);
this.logger.info(`修改证书成功,certId:${certId},domain:${domain}`);
}
this.logger.info('部署完成');
}
@@ -0,0 +1,28 @@
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
@IsAccess({
name: 'woai',
title: '我爱云授权',
desc: '我爱云CDN',
})
export class WoaiAccess extends BaseAccess {
@AccessInput({
title: '账号',
component: {
placeholder: '我爱云的账号',
},
required: true,
})
username = '';
@AccessInput({
title: '密码',
component: {
placeholder: '我爱云的密码',
},
required: true,
encrypt: true,
})
password = '';
}
new WoaiAccess();
@@ -0,0 +1,2 @@
export * from './plugins/index.js';
export * from './access.js';
@@ -0,0 +1 @@
export * from './plugin-deploy-to-cdn.js';
@@ -0,0 +1,94 @@
import { AbstractTaskPlugin, HttpClient, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo } from '@certd/plugin-cert';
import { WoaiAccess } from '../access.js';
@IsTaskPlugin({
name: 'WoaiCDN',
title: '部署证书到我爱云 CDN',
desc: '部署证书到我爱云CDN',
icon: 'clarity:plugin-line',
group: pluginGroups.cdn.key,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class WoaiCdnPlugin extends AbstractTaskPlugin {
@TaskInput({
title: '证书ID',
helper: '请填写 [证书列表](https://console.edge.51vs.club/site/certificate) 中的证书的ID',
component: { name: 'a-input' },
required: true,
})
certId!: string;
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: ['CertApply', 'CertApplyLego'],
},
required: true,
})
cert!: CertInfo;
@TaskInput({
title: 'Access授权',
helper: '我爱云的用户、密码授权',
component: {
name: 'access-selector',
type: 'woai',
},
required: true,
})
accessId!: string;
http!: HttpClient;
private readonly baseApi = 'https://console.edeg.51vs.club';
async onInstance() {
this.http = this.ctx.http;
}
private async doRequestApi(url: string, data: any = null, method = 'post', token: string | null = null) {
const headers = {
'Content-Type': 'application/json',
...(token ? { Token: token } : {}),
};
const res = await this.http.request<any, any>({
url,
method,
data,
headers,
});
if (res.code !== 200) {
throw new Error(`${JSON.stringify(res.message)}`);
}
return res;
}
async execute(): Promise<void> {
const { certId, cert, accessId } = this;
const access = (await this.accessService.getById(accessId)) as WoaiAccess;
// 登录获取token
const loginResponse = await this.doRequestApi(`${this.baseApi}/account/login`, {
username: access.username,
password: access.password,
});
const token = loginResponse.data.token;
this.logger.info('登录成功,获取到Token:', token);
// 更新证书
const editCertResponse = await this.doRequestApi(
`${this.baseApi}/certificate/edit`,
{
id: certId,
cert: cert.crt,
key: cert.key,
},
'post',
token
);
this.logger.info('证书更新成功:', editCertResponse.message);
}
}
new WoaiCdnPlugin();