mirror of
https://github.com/certd/certd.git
synced 2026-07-01 08:57:33 +08:00
perf: 支持全自动匹配部署宝塔网站证书
This commit is contained in:
@@ -19,6 +19,8 @@
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-this-alias": "off"
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
// 允许any
|
||||
"@typescript-eslint/no-unsafe-anyassignment": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export class FileController extends BaseController {
|
||||
const key = await this.fileService.saveFile(this.getUserId(), cacheKey, "public");
|
||||
return this.ok({
|
||||
key,
|
||||
url: `/api/basic/file/download?key=${encodeURIComponent(key)}`,
|
||||
url: `/api/basic/file/download?key=${encodeURIComponent(key as string)}`,
|
||||
});
|
||||
}
|
||||
return this.ok({
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ export class CertInfoWildcardDomainCountFix {
|
||||
},
|
||||
});
|
||||
let fixedCount = 0;
|
||||
for (const item of list) {
|
||||
for (const item of list as any[]) {
|
||||
if (!item.domains) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ export class CommonEabToAcmeAccountFix {
|
||||
return null;
|
||||
}
|
||||
const email = eabAccess.email || `${caType}@common.certd.local`;
|
||||
const exists = await this.accessService.findOne({
|
||||
const exists: any = await this.accessService.findOne({
|
||||
where: {
|
||||
userId: 0,
|
||||
projectId: null,
|
||||
|
||||
@@ -36,7 +36,7 @@ export class RoleService extends BaseService<RoleEntity> {
|
||||
}
|
||||
|
||||
async getRoleIdsByUserId(id: any) {
|
||||
const userRoles = await this.userRoleService.find({
|
||||
const userRoles: any = await this.userRoleService.find({
|
||||
where: { userId: id },
|
||||
});
|
||||
return userRoles.map(item => item.roleId);
|
||||
@@ -131,7 +131,7 @@ export class RoleService extends BaseService<RoleEntity> {
|
||||
async delete(id: any) {
|
||||
const idArr = this.resolveIdArr(id);
|
||||
//@ts-ignore
|
||||
const urs = await this.userRoleService.find({ where: { roleId: In(idArr) } });
|
||||
const urs:any = await this.userRoleService.find({ where: { roleId: In(idArr) } });
|
||||
if (urs.length > 0) {
|
||||
throw new Error("该角色已被用户使用,无法删除");
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export class AcmeAccountAccess extends BaseAccess {
|
||||
"\nGoogle:请查看[google获取eab帮助文档](https://certd.docmirror.cn/guide/use/google/),用过一次后会绑定邮箱,后续复用EAB要用同一个邮箱" +
|
||||
"\nSSL.com:[SSL.com账号页面](https://secure.ssl.com/account),然后点击api credentials链接,然后点击编辑按钮,查看Secret key和HMAC key" +
|
||||
"\nlitessl:[litesslEAB页面](https://freessl.cn/automation/eab-manager),然后点击新增EAB",
|
||||
required: false,
|
||||
required: true,
|
||||
encrypt: true,
|
||||
mergeScript: `
|
||||
return {
|
||||
@@ -121,7 +121,7 @@ export class AcmeAccountAccess extends BaseAccess {
|
||||
component: {
|
||||
placeholder: "需要EAB的颁发机构生成账号时填写",
|
||||
},
|
||||
required: false,
|
||||
required: true,
|
||||
encrypt: true,
|
||||
mergeScript: `
|
||||
return {
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from "./plugin-deploy-to-website.js";
|
||||
export * from "./plugin-deploy-to-aawaf.js";
|
||||
export * from "./plugin-deploy-to-website-win.js";
|
||||
export * from "./plugin-delete-expiring-cert.js";
|
||||
export * from "./plugin-deploy-automatch.js";
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
import { HttpClient } from "@certd/basic";
|
||||
import { AbstractTaskPlugin, CertTargetItem, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { createCertDomainGetterInputDefine } from "@certd/plugin-lib";
|
||||
import { BaotaClient } from "../lib/client.js";
|
||||
import { BaotaAccess } from "../access.js";
|
||||
|
||||
/**
|
||||
* 宝塔-全自动部署插件
|
||||
* 根据证书域名自动匹配宝塔站点,全自动部署SSL证书
|
||||
* 参照阿里云DCDN部署插件的"根据证书匹配"模式实现
|
||||
*/
|
||||
@IsTaskPlugin({
|
||||
name: "BaotaAutoDeploySiteCert",
|
||||
title: "宝塔-全自动部署",
|
||||
icon: "svg:icon-bt",
|
||||
group: pluginGroups.panel.key,
|
||||
desc: "根据证书域名自动匹配宝塔站点,全自动部署SSL证书。新增加速域名自动感知,自动新增部署",
|
||||
runStrategy: RunStrategy.AlwaysRun,
|
||||
})
|
||||
export class BaotaAutoDeploySiteCert extends AbstractTaskPlugin {
|
||||
/** 域名证书 */
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "output-selector",
|
||||
from: [...CertApplyPluginNames],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||
certDomains!: string[];
|
||||
|
||||
/** 宝塔授权 */
|
||||
@TaskInput({
|
||||
title: "宝塔授权",
|
||||
helper: "baota的接口密钥",
|
||||
component: {
|
||||
name: "access-selector",
|
||||
type: "baota",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
/** 输出:已部署过的站点列表 */
|
||||
@TaskOutput({
|
||||
title: "已部署过的站点",
|
||||
})
|
||||
deployedList!: string[];
|
||||
|
||||
async onInstance() {}
|
||||
|
||||
async execute(): Promise<any> {
|
||||
this.logger.info("开始宝塔全自动部署证书");
|
||||
const access = await this.getAccess<BaotaAccess>(this.accessId);
|
||||
const http: HttpClient = this.ctx.http;
|
||||
const client = new BaotaClient(access, http);
|
||||
|
||||
// 宝塔并发部署会导致nginx的conf错乱,用锁串行化
|
||||
const lockKey = `baota-lock-${this.accessId}`;
|
||||
|
||||
const { result, deployedList } = await this.autoMatchedDeploy({
|
||||
targetName: "宝塔站点",
|
||||
// 1. 获取证书域名列表
|
||||
getCertDomains: async () => {
|
||||
return this.certDomains;
|
||||
},
|
||||
// 上传证书(宝塔不需要预上传,直接传入key/crt部署)
|
||||
uploadCert: async () => {
|
||||
return { key: this.cert.key, crt: this.cert.crt };
|
||||
},
|
||||
// 4. 部署证书到匹配的站点
|
||||
deployOne: async (req: { target: CertTargetItem; cert: any }) => {
|
||||
await this.ctx.utils.locker.execute(lockKey, async () => {
|
||||
this.logger.info(`为站点: ${req.target.label} 设置证书`);
|
||||
const res = await client.doRequest("/site", "SetSSL", {
|
||||
type: 0,
|
||||
siteName: req.target.value,
|
||||
key: req.cert.key,
|
||||
csr: req.cert.crt,
|
||||
});
|
||||
this.logger.info(res?.msg || `站点 ${req.target.label} 部署证书成功`);
|
||||
});
|
||||
},
|
||||
// 2. 获取待部署证书目标列表
|
||||
getDeployTargetList: async (data: PageSearch) => {
|
||||
return await this.querySiteList(client);
|
||||
},
|
||||
});
|
||||
|
||||
this.deployedList = deployedList;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从宝塔查询站点列表,按证书域名匹配分组
|
||||
*/
|
||||
private async querySiteList(client: BaotaClient): Promise<{ list: CertTargetItem[]; total: number }> {
|
||||
const domains = this.certDomains;
|
||||
const url = "/ssl?action=GetSiteDomain";
|
||||
const data = {
|
||||
cert_list: JSON.stringify(domains),
|
||||
};
|
||||
const res = await client.doRequest(url, null, data, { skipCheckRes: false });
|
||||
this.logger.info(`获取到站点数量: ${res?.total ?? res?.all?.length ?? 0}`);
|
||||
const all: string[] = res.all || [];
|
||||
const options: CertTargetItem[] = all.map((item: string) => ({
|
||||
value: item,
|
||||
label: item,
|
||||
domain: item,
|
||||
}));
|
||||
return {
|
||||
list: options,
|
||||
total: options.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
new BaotaAutoDeploySiteCert();
|
||||
Reference in New Issue
Block a user