mirror of
https://github.com/certd/certd.git
synced 2026-04-23 19:57:27 +08:00
Merge remote-tracking branch 'origin/v2-dev' into v2-dev
This commit is contained in:
@@ -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();
|
||||
Reference in New Issue
Block a user