2024-11-04 15:14:56 +08:00
import { IsTaskPlugin , pluginGroups , RunStrategy , 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 JSZip from "jszip" ;
2024-07-18 21:10:13 +08:00
export { CertReader } ;
export type { CertInfo } ;
2025-04-05 17:01:41 +08:00
export type PrivateKeyType = "rsa2048" | "rsa3072" | "rsa4096" | "rsa8192" | "ec256" | "ec384" ;
2024-07-18 21:10:13 +08:00
@IsTaskPlugin ( {
name : "CertApplyLego" ,
2024-09-19 17:38:51 +08:00
icon : "ph:certificate" ,
2024-07-18 21:10:13 +08:00
title : "证书申请( Lego) " ,
2024-07-21 02:26:03 +08:00
group : pluginGroups.cert.key ,
desc : "支持海量DNS解析提供商, 推荐使用, 一样的免费通配符域名证书申请, 支持多个域名打到同一个证书上" ,
2024-07-18 21:10:13 +08:00
default : {
input : {
2024-10-28 15:31:45 +08:00
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" ,
2024-07-21 02:26:03 +08:00
placeholder : "alidns" ,
2024-07-18 21:10:13 +08:00
} ,
2024-07-21 02:26:03 +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" ,
2024-07-21 02:26:03 +08:00
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 : {
2024-10-07 03:21:16 +08:00
name : "access-selector" ,
2024-07-18 21:10:13 +08:00
type : "eab" ,
} ,
2024-07-21 02:26:03 +08:00
maybeNeed : true ,
2024-07-18 21:10:13 +08:00
helper : "如果需要提供EAB授权" ,
} )
2024-07-21 02:26:03 +08:00
legoEabAccessId ! : number ;
2024-07-18 21:10:13 +08:00
@TaskInput ( {
2025-06-04 16:31:25 +08:00
title : "自定义LEGO全局参数" ,
2024-07-18 21:10:13 +08:00
component : {
name : "a-input" ,
vModel : "value" ,
2024-07-21 02:26:03 +08:00
placeholder : "--dns-timeout 30" ,
2024-07-18 21:10:13 +08:00
} ,
2025-06-04 16:31:25 +08:00
helper : "额外的lego全局命令行参数, 参考文档: https://go-acme.github.io/lego/usage/cli/options/" ,
2024-07-21 02:26:03 +08:00
maybeNeed : true ,
2024-07-18 21:10:13 +08:00
} )
customArgs = "" ;
2025-06-04 16:31:25 +08:00
@TaskInput ( {
title : "自定义LEGO签名参数" ,
component : {
name : "a-input" ,
vModel : "value" ,
placeholder : "--no-bundle" ,
} ,
helper : "额外的lego签名命令行参数, 参考文档: https://go-acme.github.io/lego/usage/cli/options/" ,
maybeNeed : true ,
} )
customCommandOptions = "" ;
2025-04-05 17:01:41 +08:00
@TaskInput ( {
title : "加密算法" ,
value : "ec256" ,
component : {
name : "a-select" ,
vModel : "value" ,
options : [
{ value : "rsa2048" , label : "RSA 2048" } ,
{ value : "rsa3072" , label : "RSA 3072" } ,
{ value : "rsa4096" , label : "RSA 4096" } ,
{ value : "rsa8192" , label : "RSA 8192" } ,
{ value : "ec256" , label : "EC 256" } ,
{ value : "ec384" , label : "EC 384" } ,
// { value: "ec_521", label: "EC 521" },
] ,
} ,
helper : "如无特殊需求,默认即可" ,
required : true ,
} )
privateKeyType ! : PrivateKeyType ;
2024-07-18 21:10:13 +08:00
eab? : EabAccess ;
2025-05-25 22:58:30 +08:00
getCheckChangeInputKeys() {
return [ "domains" , "privateKeyType" , "dnsType" ] ;
}
2024-07-18 21:10:13 +08:00
async onInstance() {
this . accessService = this . ctx . accessService ;
this . logger = this . ctx . logger ;
this . userContext = this . ctx . userContext ;
this . lastStatus = this . ctx . lastStatus as Step ;
2024-07-21 02:26:03 +08:00
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 ) {
2024-10-29 23:08:40 +08:00
eabArgs = ` --eab --kid " ${ this . eab . kid } " --hmac " ${ this . eab . hmacKey } " ` ;
2024-07-18 21:10:13 +08:00
}
2025-04-05 17:01:41 +08:00
const keyType = ` -k ${ this . privateKeyType } ` ;
2024-07-18 21:10:13 +08:00
const saveDir = ` ./data/.lego/pipeline_ ${ this . pipeline . id } / ` ;
const savePathArgs = ` --path " ${ saveDir } " ` ;
const os_type = process . platform === "win32" ? "windows" : "linux" ;
2024-11-04 15:14:56 +08:00
const legoDir = "./tools/lego" ;
2024-11-04 16:39:02 +08:00
const legoPath = path . resolve ( legoDir , os_type === "windows" ? "lego.exe" : "lego" ) ;
2024-10-30 16:12:08 +08:00
if ( ! fs . existsSync ( legoPath ) ) {
//解压缩
2024-11-04 15:14:56 +08:00
const arch = process . arch ;
let platform = "amd64" ;
if ( arch === "arm64" || arch === "arm" ) {
platform = "arm64" ;
}
const LEGO_VERSION = process . env . LEGO_VERSION ;
2024-11-04 16:39:02 +08:00
let legoZipFileName = ` lego_v ${ LEGO_VERSION } _windows_ ${ platform } .zip ` ;
2024-10-30 16:12:08 +08:00
if ( os_type === "linux" ) {
2024-11-04 16:39:02 +08:00
legoZipFileName = ` lego_v ${ LEGO_VERSION } _linux_ ${ platform } .tar.gz ` ;
2024-11-04 15:14:56 +08:00
}
2024-11-04 16:39:02 +08:00
const legoZipFilePath = ` ${ legoDir } / ${ legoZipFileName } ` ;
if ( ! fs . existsSync ( legoZipFilePath ) ) {
this . logger . info ( ` lego文件不存在: ${ legoZipFilePath } ,准备下载 ` ) ;
const downloadUrl = ` https://github.com/go-acme/lego/releases/download/v ${ LEGO_VERSION } / ${ legoZipFileName } ` ;
await this . ctx . download (
2024-11-04 15:14:56 +08:00
{
url : downloadUrl ,
method : "GET" ,
2024-11-04 16:39:02 +08:00
logRes : false ,
2024-11-04 15:14:56 +08:00
} ,
2024-11-04 16:39:02 +08:00
legoZipFilePath
2024-11-04 15:14:56 +08:00
) ;
this . logger . info ( "下载lego成功" ) ;
}
if ( os_type === "linux" ) {
//tar是否存在
await this . ctx . utils . sp . spawn ( {
2024-11-04 16:39:02 +08:00
cmd : ` tar -zxvf ${ legoZipFilePath } -C ${ legoDir } / ` ,
2024-10-30 16:12:08 +08:00
} ) ;
2024-11-04 15:14:56 +08:00
await this . ctx . utils . sp . spawn ( {
cmd : ` chmod +x ${ legoDir } /* ` ,
2024-11-02 23:37:25 +08:00
} ) ;
2024-10-30 16:12:08 +08:00
this . logger . info ( "解压lego成功" ) ;
} else {
const zip = new JSZip ( ) ;
2024-11-04 16:39:02 +08:00
const data = fs . readFileSync ( legoZipFilePath ) ;
2024-10-30 16:12:08 +08:00
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" ) ;
2024-11-04 16:39:02 +08:00
fs . writeFileSync ( ` ${ legoDir } / ${ file } ` , content ) ;
2024-10-30 16:12:08 +08:00
}
this . logger . info ( "解压lego成功" ) ;
}
}
2024-07-24 02:11:38 +08:00
let serverArgs = "" ;
if ( this . acmeServer ) {
serverArgs = ` --server ${ this . acmeServer } ` ;
}
2025-06-04 16:31:25 +08:00
const cmds = [ ` ${ legoPath } -a --email " ${ this . email } " --dns ${ this . dnsType } ${ keyType } ${ domainArgs } ${ serverArgs } ${ eabArgs } ${ savePathArgs } ${ this . customArgs || "" } run ${ this . customCommandOptions || "" } ` ] ;
2024-07-18 21:10:13 +08:00
2024-11-04 15:14:56 +08:00
await this . ctx . utils . sp . spawn ( {
2024-07-18 21:10:13 +08:00
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 ( ) ;