2025-03-22 02:06:02 +08:00
|
|
|
|
import { NotificationBody, Step, TaskInput } from "@certd/pipeline";
|
2024-07-18 21:10:13 +08:00
|
|
|
|
import dayjs from "dayjs";
|
|
|
|
|
|
import { CertReader } from "./cert-reader.js";
|
2024-10-16 12:20:42 +08:00
|
|
|
|
import { pick } from "lodash-es";
|
2025-03-18 00:52:50 +08:00
|
|
|
|
import { CertApplyBaseConvertPlugin } from "./base-convert.js";
|
2024-07-18 21:10:13 +08:00
|
|
|
|
|
2025-03-18 00:52:50 +08:00
|
|
|
|
export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
2024-07-18 21:10:13 +08:00
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "邮箱",
|
|
|
|
|
|
component: {
|
2025-05-31 00:45:54 +08:00
|
|
|
|
name: "email-selector",
|
2024-07-18 21:10:13 +08:00
|
|
|
|
vModel: "value",
|
|
|
|
|
|
},
|
2024-10-25 21:47:28 +08:00
|
|
|
|
rules: [{ type: "email", message: "请输入正确的邮箱" }],
|
2024-07-18 21:10:13 +08:00
|
|
|
|
required: true,
|
2024-07-21 02:26:03 +08:00
|
|
|
|
order: -1,
|
2024-07-18 21:10:13 +08:00
|
|
|
|
helper: "请输入邮箱",
|
|
|
|
|
|
})
|
|
|
|
|
|
email!: string;
|
|
|
|
|
|
|
|
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "更新天数",
|
2024-10-28 15:31:45 +08:00
|
|
|
|
value: 35,
|
2024-07-18 21:10:13 +08:00
|
|
|
|
component: {
|
|
|
|
|
|
name: "a-input-number",
|
|
|
|
|
|
vModel: "value",
|
|
|
|
|
|
},
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
order: 100,
|
|
|
|
|
|
helper: "到期前多少天后更新证书,注意:流水线默认不会自动运行,请设置定时器,每天定时运行本流水线",
|
|
|
|
|
|
})
|
|
|
|
|
|
renewDays!: number;
|
|
|
|
|
|
|
|
|
|
|
|
@TaskInput({
|
2024-11-27 12:36:28 +08:00
|
|
|
|
title: "证书申请成功通知",
|
2024-07-18 21:10:13 +08:00
|
|
|
|
value: true,
|
|
|
|
|
|
component: {
|
|
|
|
|
|
name: "a-switch",
|
|
|
|
|
|
vModel: "checked",
|
|
|
|
|
|
},
|
|
|
|
|
|
order: 100,
|
2024-11-27 12:36:28 +08:00
|
|
|
|
helper: "证书申请成功后是否发送通知,优先使用默认通知渠道",
|
2024-07-18 21:10:13 +08:00
|
|
|
|
})
|
|
|
|
|
|
successNotify = true;
|
|
|
|
|
|
|
|
|
|
|
|
// @TaskInput({
|
|
|
|
|
|
// title: "CsrInfo",
|
|
|
|
|
|
// helper: "暂时没有用",
|
|
|
|
|
|
// })
|
|
|
|
|
|
csrInfo!: string;
|
|
|
|
|
|
|
|
|
|
|
|
async onInstance() {
|
|
|
|
|
|
this.userContext = this.ctx.userContext;
|
|
|
|
|
|
this.lastStatus = this.ctx.lastStatus as Step;
|
|
|
|
|
|
await this.onInit();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
abstract onInit(): Promise<void>;
|
|
|
|
|
|
|
2025-01-15 01:05:34 +08:00
|
|
|
|
abstract doCertApply(): Promise<CertReader>;
|
2024-07-18 21:10:13 +08:00
|
|
|
|
|
2024-12-23 00:49:56 +08:00
|
|
|
|
async execute(): Promise<string | void> {
|
2024-07-18 21:10:13 +08:00
|
|
|
|
const oldCert = await this.condition();
|
|
|
|
|
|
if (oldCert != null) {
|
2024-12-23 00:49:56 +08:00
|
|
|
|
await this.output(oldCert, false);
|
|
|
|
|
|
return "skip";
|
2024-07-18 21:10:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
const cert = await this.doCertApply();
|
|
|
|
|
|
if (cert != null) {
|
|
|
|
|
|
await this.output(cert, true);
|
2025-01-15 01:05:34 +08:00
|
|
|
|
|
2025-03-22 02:06:02 +08:00
|
|
|
|
await this.emitCertApplySuccess();
|
2024-07-18 21:10:13 +08:00
|
|
|
|
//清空后续任务的状态,让后续任务能够重新执行
|
|
|
|
|
|
this.clearLastStatus();
|
|
|
|
|
|
|
|
|
|
|
|
if (this.successNotify) {
|
2024-11-27 12:36:28 +08:00
|
|
|
|
await this.sendSuccessNotify();
|
2024-07-18 21:10:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error("申请证书失败");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-25 22:58:30 +08:00
|
|
|
|
getCheckChangeInputKeys() {
|
|
|
|
|
|
//插件哪些字段参与校验是否需要更新
|
|
|
|
|
|
return ["domains", "sslProvider", "privateKeyType", "dnsProviderType", "pfxPassword"];
|
|
|
|
|
|
}
|
2024-07-18 21:10:13 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 是否更新证书
|
|
|
|
|
|
*/
|
|
|
|
|
|
async condition() {
|
2024-11-12 10:12:10 +08:00
|
|
|
|
// if (this.forceUpdate) {
|
|
|
|
|
|
// this.logger.info("强制更新证书选项已勾选,准备申请新证书");
|
|
|
|
|
|
// this.logger.warn("申请完之后,切记取消强制更新,避免申请过多证书。");
|
|
|
|
|
|
// return null;
|
|
|
|
|
|
// }
|
2024-07-18 21:10:13 +08:00
|
|
|
|
|
2025-05-25 22:58:30 +08:00
|
|
|
|
const checkInputChanges = this.getCheckChangeInputKeys();
|
2024-12-24 17:09:06 +08:00
|
|
|
|
const oldInput = JSON.stringify(pick(this.lastStatus?.input, checkInputChanges));
|
|
|
|
|
|
const thisInput = JSON.stringify(pick(this, checkInputChanges));
|
2025-01-15 01:05:34 +08:00
|
|
|
|
const inputChanged = oldInput !== thisInput;
|
2024-10-16 12:20:42 +08:00
|
|
|
|
|
2024-12-24 17:09:06 +08:00
|
|
|
|
this.logger.info(`旧参数:${oldInput}`);
|
|
|
|
|
|
this.logger.info(`新参数:${thisInput}`);
|
|
|
|
|
|
if (inputChanged) {
|
|
|
|
|
|
this.logger.info("输入参数变更,准备申请新证书");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
} else {
|
2025-01-05 01:07:04 +08:00
|
|
|
|
this.logger.info("输入参数未变更,检查证书是否过期");
|
2024-07-18 21:10:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let oldCert: CertReader | undefined = undefined;
|
|
|
|
|
|
try {
|
2025-01-02 17:48:54 +08:00
|
|
|
|
this.logger.info("读取上次证书");
|
2024-07-18 21:10:13 +08:00
|
|
|
|
oldCert = await this.readLastCert();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
this.logger.warn("读取cert失败:", e);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (oldCert == null) {
|
|
|
|
|
|
this.logger.info("还未申请过,准备申请新证书");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const ret = this.isWillExpire(oldCert.expires, this.renewDays);
|
|
|
|
|
|
if (!ret.isWillExpire) {
|
|
|
|
|
|
this.logger.info(`证书还未过期:过期时间${dayjs(oldCert.expires).format("YYYY-MM-DD HH:mm:ss")},剩余${ret.leftDays}天`);
|
|
|
|
|
|
return oldCert;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.logger.info("即将过期,开始更新证书");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2024-11-18 13:32:03 +08:00
|
|
|
|
* 检查是否过期,默认提前35天
|
2024-07-18 21:10:13 +08:00
|
|
|
|
* @param expires
|
|
|
|
|
|
* @param maxDays
|
|
|
|
|
|
*/
|
|
|
|
|
|
isWillExpire(expires: number, maxDays = 20) {
|
|
|
|
|
|
if (expires == null) {
|
|
|
|
|
|
throw new Error("过期时间不能为空");
|
|
|
|
|
|
}
|
|
|
|
|
|
// 检查有效期
|
2025-05-20 23:19:50 +08:00
|
|
|
|
const leftDays = Math.floor((expires - dayjs().valueOf()) / (1000 * 60 * 60 * 24));
|
|
|
|
|
|
this.logger.info(`证书剩余天数:${leftDays}`);
|
2024-07-18 21:10:13 +08:00
|
|
|
|
return {
|
2024-12-27 22:40:07 +08:00
|
|
|
|
isWillExpire: leftDays <= maxDays,
|
2024-07-18 21:10:13 +08:00
|
|
|
|
leftDays,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2024-11-27 12:36:28 +08:00
|
|
|
|
async sendSuccessNotify() {
|
|
|
|
|
|
this.logger.info("发送证书申请成功通知");
|
|
|
|
|
|
const url = await this.ctx.urlService.getPipelineDetailUrl(this.pipeline.id, this.ctx.runtime.id);
|
|
|
|
|
|
const body: NotificationBody = {
|
2024-12-11 11:30:32 +08:00
|
|
|
|
title: `证书申请成功【${this.pipeline.title}】`,
|
2024-11-27 12:36:28 +08:00
|
|
|
|
content: `域名:${this.domains.join(",")}`,
|
|
|
|
|
|
url: url,
|
|
|
|
|
|
};
|
2024-07-18 21:10:13 +08:00
|
|
|
|
try {
|
2024-12-11 11:30:32 +08:00
|
|
|
|
await this.ctx.notificationService.send({
|
|
|
|
|
|
useDefault: true,
|
|
|
|
|
|
useEmail: true,
|
|
|
|
|
|
emailAddress: this.email,
|
2024-12-12 16:49:40 +08:00
|
|
|
|
logger: this.logger,
|
2024-12-11 11:30:32 +08:00
|
|
|
|
body,
|
|
|
|
|
|
});
|
2024-07-18 21:10:13 +08:00
|
|
|
|
} catch (e) {
|
2024-11-27 12:36:28 +08:00
|
|
|
|
this.logger.error("证书申请成功通知发送失败", e);
|
2024-07-18 21:10:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|