This commit is contained in:
xiaojunnuo
2026-01-07 19:06:59 +08:00
parent a79fe1f350
commit b16b9e813d
3 changed files with 8 additions and 250 deletions
+2 -2
View File
@@ -175,11 +175,11 @@ https://certd.handfree.work/
| 功能 | 免费版 | 专业版 |
|---------|---------------------------------------|--------------------------------|
| 免费证书申请 | 免费无限制 | 免费无限制 |
| 域名数量 | 无限制 | 无限制 |
| 证书域名数量 | 无限制 | 无限制 |
| 证书流水线条数 | 无限制 | 无限制 |
| 站点证书监控 | 限制1条 | 无限制 |
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 |
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 |
| 站点监控 | 限制1条 | 无限制 |
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 |
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 |
@@ -1,190 +0,0 @@
import { SynologyAccess } from "./access.js";
import { HttpClient, ILogger } from "@certd/basic";
import qs from "querystring";
export type SynologyAccessToken = {
sid: string;
did?: string;
synotoken: string;
};
export type SynologyRequest = {
method?: string;
apiParams: {
api: string;
version: number;
method: string;
};
params?: any;
data?: any;
form?: any;
headers?: any;
useSynoToken?: boolean;
};
const device_name = "certd";
export class SynologyClient {
access: SynologyAccess;
http: HttpClient;
logger: ILogger;
skipSslVerify: boolean;
token: SynologyAccessToken;
constructor(access: SynologyAccess, http: HttpClient, logger: ILogger, skipSslVerify: boolean) {
this.access = access;
this.http = http;
this.logger = logger;
this.skipSslVerify = skipSslVerify;
}
// 登录 DSM 的函数
async doLogin() {
const access = this.access;
if (access.otp && access.deviceId != null) {
this.logger.info("OTP登录");
return await this.doLoginWithDeviceId(access.deviceId);
}
this.logger.info("使用普通登录");
const loginUrl = this.getLoginUrl();
const res = await this.http.request({
url: loginUrl,
method: "GET",
params: {
api: "SYNO.API.Auth",
version: 6,
method: "login",
account: access.username,
passwd: access.password,
session: "Certd",
format: "sid",
enable_syno_token: "yes",
},
skipSslVerify: this.skipSslVerify ?? true,
timeout: this.access.timeout * 1000 || 120000,
});
if (!res.success) {
throw new Error(`登录失败: `, res.error);
}
this.logger.info("登录成功");
this.token = res.data as SynologyAccessToken;
return this.token;
}
async doLoginWithOTPCode(otpCode: string) {
const loginUrl = this.getLoginUrl();
const access = this.access;
const res = await this.http.request({
url: loginUrl,
method: "GET",
params: {
api: "SYNO.API.Auth",
version: 6,
method: "login",
account: access.username,
passwd: access.password,
otp_code: otpCode,
enable_device_token: "yes",
device_name,
},
timeout: this.access.timeout * 1000 || 30000,
skipSslVerify: this.skipSslVerify ?? true,
});
if (!res.success) {
throw new Error(`登录失败: `, res.error);
}
this.logger.info("登录成功");
this.token = res.data as SynologyAccessToken;
return this.token;
}
private getLoginUrl() {
const access = this.access;
const loginPath = access.version === "6" ? "auth.cgi" : "entry.cgi";
return `${access.baseUrl}/webapi/${loginPath}`;
}
async doLoginWithDeviceId(device_id: string) {
const access = this.access;
const loginUrl = this.getLoginUrl();
const res = await this.http.request({
url: loginUrl,
method: "GET",
params: {
api: "SYNO.API.Auth",
version: 6,
method: "login",
account: access.username,
passwd: access.password,
device_name,
device_id,
session: "Certd",
format: "sid",
enable_syno_token: "yes",
},
timeout: this.access.timeout * 1000 || 30000,
skipSslVerify: this.skipSslVerify ?? true,
});
if (!res.success) {
throw new Error(`登录失败: `, res.error);
}
this.logger.info("登录成功");
this.token = res.data as SynologyAccessToken;
return this.token;
}
async doRequest(req: SynologyRequest) {
const sid = this.token.sid;
const method = req.method || "POST";
const params = {
...req.apiParams,
_sid: sid, // 使用登录后获得的 session ID
...req.params,
SynoToken: this.token.synotoken,
};
const res = await this.http.request({
url: `${this.access.baseUrl}/webapi/entry.cgi?${qs.stringify(params)}`,
method,
data: req.data,
headers: req.headers,
skipSslVerify: this.skipSslVerify ?? true,
timeout: this.access.timeout * 1000 || 30000,
});
if (!res.success) {
throw new Error(`API 调用失败: ${JSON.stringify(res.error)}`);
}
return res.data;
}
async getCertList() {
this.logger.info("获取证书列表");
return await this.doRequest({
method: "GET",
apiParams: {
api: "SYNO.Core.Certificate.CRT",
version: 1,
method: "list",
},
});
}
async getInfo() {
this.logger.info("获取信息");
return await this.doRequest({
method: "GET",
apiParams: {
api: "SYNO.API.Info",
version: 1,
method: "query",
},
});
}
}
@@ -1,11 +1,9 @@
import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertInfo, CertReader } from "@certd/plugin-cert";
import { CertInfo } from "@certd/plugin-lib";
import { AbstractPlusTaskPlugin } from "@certd/plugin-lib";
import { SynologyClient } from "../client.js";
import fs from "fs";
import FormData from "form-data";
import { SynologyClient } from "@certd/plugin-plus";
import { SynologyAccess } from "../access.js";
import { CertApplyPluginNames } from "@certd/plugin-cert";
import { CertApplyPluginNames } from "@certd/plugin-lib";
@IsTaskPlugin({
name: "SynologyDeployToPanel",
title: "群晖-部署证书到群晖面板",
@@ -74,66 +72,16 @@ export class SynologyDeployToPanel extends AbstractPlusTaskPlugin {
throw new Error(`未找到证书: ${this.certName}`);
}
this.logger.info(`找到证书: ${certItem.id}`);
await this.updateCertToPanel(client, certItem);
await client.updateCertToPanel(certItem,this.cert);
} else {
this.logger.info("开始更新全部证书");
for (const item of certListRes.certificates) {
this.logger.info(`更新证书: ${item.id}`);
await this.updateCertToPanel(client, item);
await client.updateCertToPanel(item,this.cert);
}
}
}
async updateCertToPanel(client: SynologyClient, certItem: any) {
/**
* query
* api: SYNO.Core.Certificate
* method: import
* version: 1
* SynoToken: Bvum9p7BNeSc6
*
* key: (二进制)
* cert: (二进制)
* inter_cert: (二进制)
* id: yxTtcC
* desc: certd
* as_default:
*/
this.logger.info(`更新证书:${certItem.id}`);
const certReader = new CertReader(this.cert);
return certReader.readCertFile({
logger: this.logger,
handle: async (ctx) => {
const form = new FormData();
const { tmpCrtPath, tmpKeyPath, tmpIcPath } = ctx;
this.logger.info(`上传证书:${tmpCrtPath},${tmpKeyPath}`);
form.append("key", fs.createReadStream(tmpKeyPath));
form.append("cert", fs.createReadStream(tmpCrtPath));
if (certReader.cert.ic) {
this.logger.info(`包含中间证书:${tmpIcPath}`);
form.append("inter_cert", fs.createReadStream(tmpIcPath));
}
form.append("id", certItem.id);
form.append("desc", certItem.desc);
// form传输必须是stringbool要改成string
// form.append("as_default", certItem.is_default + "");
console.log(JSON.stringify(form.getHeaders()));
return await client.doRequest({
method: "POST",
apiParams: {
api: "SYNO.Core.Certificate",
version: 1,
method: "import",
},
data: form,
headers: {
...form.getHeaders(),
},
});
},
});
}
}
new SynologyDeployToPanel();