Files
certd/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts

195 lines
5.9 KiB
TypeScript
Raw Normal View History

import { IsTaskPlugin, pluginGroups, RunStrategy, sp, Step, TaskInput } from "@certd/pipeline";
2024-07-24 02:11:38 +08:00
import type { CertInfo } from "../acme.js";
import { CertReader } from "../cert-reader.js";
import { CertApplyBasePlugin } from "../base.js";
2024-07-18 21:10:13 +08:00
import fs from "fs";
2024-07-24 02:11:38 +08:00
import { EabAccess } from "../../../access/index.js";
2024-07-18 21:10:13 +08:00
import path from "path";
2024-10-30 16:12:08 +08:00
import { utils } from "@certd/basic";
import JSZip from "jszip";
2024-07-18 21:10:13 +08:00
export { CertReader };
export type { CertInfo };
@IsTaskPlugin({
name: "CertApplyLego",
2024-09-19 17:38:51 +08:00
icon: "ph:certificate",
2024-07-18 21:10:13 +08:00
title: "证书申请Lego",
group: pluginGroups.cert.key,
desc: "支持海量DNS解析提供商推荐使用一样的免费通配符域名证书申请支持多个域名打到同一个证书上",
2024-07-18 21:10:13 +08:00
default: {
input: {
renewDays: 35,
2024-07-18 21:10:13 +08:00
forceUpdate: false,
},
strategy: {
runStrategy: RunStrategy.AlwaysRun,
},
},
})
export class CertApplyLegoPlugin extends CertApplyBasePlugin {
2024-07-24 02:11:38 +08:00
// @TaskInput({
// title: "ACME服务端点",
// default: "https://acme-v02.api.letsencrypt.org/directory",
// component: {
// name: "a-select",
// vModel: "value",
// options: [
// { value: "https://acme-v02.api.letsencrypt.org/directory", label: "Let's Encrypt" },
// { value: "https://letsencrypt.proxy.handsfree.work/directory", label: "Let's Encrypt代理letsencrypt.org无法访问时使用" },
// ],
// },
// required: true,
// })
acmeServer!: string;
2024-07-18 21:10:13 +08:00
@TaskInput({
title: "DNS类型",
component: {
name: "a-input",
vModel: "value",
placeholder: "alidns",
2024-07-18 21:10:13 +08:00
},
helper: "你的域名是通过哪家提供商进行解析的具体应该配置什么请参考lego文档https://go-acme.github.io/lego/dns/",
2024-07-18 21:10:13 +08:00
required: true,
})
dnsType!: string;
@TaskInput({
title: "环境变量",
component: {
name: "a-textarea",
vModel: "value",
rows: 4,
placeholder: "ALICLOUD_ACCESS_KEY=abcdefghijklmnopqrstuvwx\nALICLOUD_SECRET_KEY=your-secret-key",
2024-07-18 21:10:13 +08:00
},
required: true,
2024-07-20 10:09:18 +08:00
helper: "一行一条,例如 appKeyId=xxxxx具体配置请参考lego文档https://go-acme.github.io/lego/dns/",
2024-07-18 21:10:13 +08:00
})
environment!: string;
@TaskInput({
title: "EAB授权",
component: {
name: "access-selector",
2024-07-18 21:10:13 +08:00
type: "eab",
},
maybeNeed: true,
2024-07-18 21:10:13 +08:00
helper: "如果需要提供EAB授权",
})
legoEabAccessId!: number;
2024-07-18 21:10:13 +08:00
@TaskInput({
title: "自定义LEGO参数",
component: {
name: "a-input",
vModel: "value",
placeholder: "--dns-timeout 30",
2024-07-18 21:10:13 +08:00
},
helper: "额外的lego命令行参数参考文档https://go-acme.github.io/lego/usage/cli/options/",
maybeNeed: true,
2024-07-18 21:10:13 +08:00
})
customArgs = "";
eab?: EabAccess;
async onInstance() {
this.accessService = this.ctx.accessService;
this.logger = this.ctx.logger;
this.userContext = this.ctx.userContext;
this.http = this.ctx.http;
this.lastStatus = this.ctx.lastStatus as Step;
if (this.legoEabAccessId) {
2024-08-14 15:10:55 +08:00
this.eab = await this.accessService.getById(this.legoEabAccessId);
2024-07-18 21:10:13 +08:00
}
}
async onInit(): Promise<void> {}
async doCertApply() {
const env: any = {};
const env_lines = this.environment.split("\n");
for (const line of env_lines) {
const [key, value] = line.trim().split("=");
env[key] = value.trim();
}
let domainArgs = "";
for (const domain of this.domains) {
domainArgs += ` -d "${domain}"`;
}
this.logger.info(`环境变量:${JSON.stringify(env)}`);
let eabArgs = "";
if (this.eab) {
eabArgs = ` --eab --kid "${this.eab.kid}" --hmac "${this.eab.hmacKey}"`;
2024-07-18 21:10:13 +08:00
}
const keyType = "-k rsa2048";
const saveDir = `./data/.lego/pipeline_${this.pipeline.id}/`;
const savePathArgs = `--path "${saveDir}"`;
const os_type = process.platform === "win32" ? "windows" : "linux";
const legoPath = path.resolve("./tools", os_type, "lego");
2024-10-30 16:12:08 +08:00
if (!fs.existsSync(legoPath)) {
//解压缩
if (os_type === "linux") {
2024-11-01 02:59:36 +08:00
//判断当前是arm64 还是amd64
const arch = process.arch;
let platform = "amd64";
if (arch === "arm64" || arch === "arm") {
platform = "arm64";
}
2024-10-30 16:12:08 +08:00
await utils.sp.spawn({
2024-11-01 02:59:36 +08:00
cmd: `tar -zxvf ./tools/linux/lego_linux_${platform}.tar.gz -C ./tools/linux/`,
2024-10-30 16:12:08 +08:00
});
this.logger.info("解压lego成功");
} else {
const zip = new JSZip();
const data = fs.readFileSync("./tools/windows/lego_windows_amd64.zip");
const zipData = await zip.loadAsync(data);
const files = Object.keys(zipData.files);
for (const file of files) {
const content = await zipData.files[file].async("nodebuffer");
fs.writeFileSync(`./tools/windows/${file}`, content);
}
this.logger.info("解压lego成功");
}
}
2024-07-24 02:11:38 +08:00
let serverArgs = "";
if (this.acmeServer) {
serverArgs = ` --server ${this.acmeServer}`;
}
2024-07-18 21:10:13 +08:00
const cmds = [
2024-07-24 02:11:38 +08:00
`${legoPath} -a --email "${this.email}" --dns ${this.dnsType} ${keyType} ${domainArgs} ${serverArgs} ${eabArgs} ${savePathArgs} ${
this.customArgs || ""
} run`,
2024-07-18 21:10:13 +08:00
];
await sp.spawn({
cmd: cmds,
logger: this.logger,
env,
});
//读取证书文件
// example.com.crt
// example.com.issuer.crt
// example.com.json
// example.com.key
let domain1 = this.domains[0];
domain1 = domain1.replaceAll("*", "_");
const crtPath = path.resolve(saveDir, "certificates", `${domain1}.crt`);
if (fs.existsSync(crtPath) === false) {
throw new Error(`证书文件不存在,证书申请失败:${crtPath}`);
}
const crt = fs.readFileSync(crtPath, "utf8");
const keyPath = path.resolve(saveDir, "certificates", `${domain1}.key`);
const key = fs.readFileSync(keyPath, "utf8");
const csr = "";
const cert = { crt, key, csr };
const certInfo = this.formatCerts(cert);
return new CertReader(certInfo);
}
}
new CertApplyLegoPlugin();