mirror of
https://github.com/certd/certd.git
synced 2026-04-24 04:17:25 +08:00
Merge branch 'v2-dev' into v2
This commit is contained in:
@@ -9,6 +9,6 @@ typeorm:
|
||||
port: 3309
|
||||
username: root
|
||||
password: root
|
||||
database: certd2
|
||||
database: certd
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,23 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复用户最大流水线数量校验的问题 ([919f70a](https://github.com/certd/certd/commit/919f70a5fd2842ca69f96f1659bb5a7ba3f73776))
|
||||
* 修复中文域名使用cname方式校验无法通过的问题 ([f7d5baa](https://github.com/certd/certd/commit/f7d5baa6d04cb83c572b06e62f885890cfa0143a))
|
||||
* 修复cv4pve sdk (proxmox插件连接失败时无法正常结束任务的bug) ([49f26b4](https://github.com/certd/certd/commit/49f26b4049a0549b0270395157e96e8f04a68bc4))
|
||||
* 修复flexcdn部署证书的顶级CA名称显示 ([6467edb](https://github.com/certd/certd/commit/6467edb84324d7c80a85212675dbacedc459df83))
|
||||
* 修复flexcdn证书commonNames错误的问题 ([ace363f](https://github.com/certd/certd/commit/ace363fa355436e769b27f71cc487d30d6441780))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 分组选择支持清空选项 ([03e2e99](https://github.com/certd/certd/commit/03e2e9949837b34eb3ea56d14a9e8a5dabc96063))
|
||||
* 优化cname检查,当有冲突的cname记录时,给出提示 ([e639a8f](https://github.com/certd/certd/commit/e639a8f9f12640ffcca69f1a6a0324459924afbd))
|
||||
* 站点监控支持批量导入域名和ip ([2d7729d](https://github.com/certd/certd/commit/2d7729dbe98f29088f5f317db2b52cc1ede223a6))
|
||||
* 支持设置用户有效期 ([6ac3bc5](https://github.com/certd/certd/commit/6ac3bc564f407dad2cd0b0b0744e887387aa5da3))
|
||||
|
||||
## [1.34.10](https://github.com/certd/certd/compare/v1.34.9...v1.34.10) (2025-06-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
ALTER TABLE cd_cname_record ADD COLUMN `error` varchar(4096);
|
||||
ALTER TABLE sys_user ADD COLUMN `valid_time` bigint;
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
ALTER TABLE cd_cname_record ADD COLUMN "error" varchar(4096);
|
||||
ALTER TABLE sys_user ADD COLUMN "valid_time" bigint;
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
ALTER TABLE cd_cname_record ADD COLUMN "error" varchar(4096);
|
||||
ALTER TABLE sys_user ADD COLUMN "valid_time" integer;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.34.10",
|
||||
"version": "1.34.11",
|
||||
"description": "fast-server base midway",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -42,20 +42,20 @@
|
||||
"@aws-sdk/client-iam": "^3.699.0",
|
||||
"@aws-sdk/client-cloudfront": "^3.699.0",
|
||||
"@aws-sdk/client-s3": "^3.705.0",
|
||||
"@certd/acme-client": "^1.34.10",
|
||||
"@certd/basic": "^1.34.10",
|
||||
"@certd/commercial-core": "^1.34.10",
|
||||
"@certd/jdcloud": "^1.34.10",
|
||||
"@certd/lib-huawei": "^1.34.10",
|
||||
"@certd/lib-k8s": "^1.34.10",
|
||||
"@certd/lib-server": "^1.34.10",
|
||||
"@certd/midway-flyway-js": "^1.34.10",
|
||||
"@certd/pipeline": "^1.34.10",
|
||||
"@certd/plugin-cert": "^1.34.10",
|
||||
"@certd/plugin-lib": "^1.34.10",
|
||||
"@certd/plugin-plus": "^1.34.10",
|
||||
"@certd/plus-core": "^1.34.10",
|
||||
"@corsinvest/cv4pve-api-javascript": "^8.3.0",
|
||||
"@certd/acme-client": "^1.34.11",
|
||||
"@certd/basic": "^1.34.11",
|
||||
"@certd/commercial-core": "^1.34.11",
|
||||
"@certd/cv4pve-api-javascript": "^8.4.1",
|
||||
"@certd/jdcloud": "^1.34.11",
|
||||
"@certd/lib-huawei": "^1.34.11",
|
||||
"@certd/lib-k8s": "^1.34.11",
|
||||
"@certd/lib-server": "^1.34.11",
|
||||
"@certd/midway-flyway-js": "^1.34.11",
|
||||
"@certd/pipeline": "^1.34.11",
|
||||
"@certd/plugin-cert": "^1.34.11",
|
||||
"@certd/plugin-lib": "^1.34.11",
|
||||
"@certd/plugin-plus": "^1.34.11",
|
||||
"@certd/plus-core": "^1.34.11",
|
||||
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
|
||||
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
|
||||
"@koa/cors": "^5.0.0",
|
||||
@@ -105,6 +105,7 @@
|
||||
"otplib": "^12.0.1",
|
||||
"pg": "^8.12.0",
|
||||
"psl": "^1.9.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"qiniu": "^7.12.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"qs": "^6.13.1",
|
||||
|
||||
@@ -105,6 +105,17 @@ export class SiteInfoController extends CrudController<SiteInfoService> {
|
||||
await this.service.checkAllByUsers(userId);
|
||||
return this.ok();
|
||||
}
|
||||
|
||||
@Post('/import', { summary: Constants.per.authOnly })
|
||||
async doImport(@Body(ALL) body: any) {
|
||||
const userId = this.getUserId();
|
||||
await this.service.doImport({
|
||||
text:body.text,
|
||||
userId
|
||||
})
|
||||
return this.ok();
|
||||
}
|
||||
|
||||
@Post('/ipCheckChange', { summary: Constants.per.authOnly })
|
||||
async ipCheckChange(@Body(ALL) bean: any) {
|
||||
const userId = this.getUserId();
|
||||
|
||||
@@ -111,5 +111,16 @@ export class SiteInfoController extends CrudController<SiteIpService> {
|
||||
return this.ok();
|
||||
}
|
||||
|
||||
@Post('/import', { summary: Constants.per.authOnly })
|
||||
async doImport(@Body(ALL) body: any) {
|
||||
const userId = this.getUserId();
|
||||
await this.service.doImport({
|
||||
text:body.text,
|
||||
userId,
|
||||
siteId:body.siteId
|
||||
})
|
||||
return this.ok();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ export class CnameRecordEntity {
|
||||
@Column({ comment: '验证状态', length: 20 })
|
||||
status: string;
|
||||
|
||||
@Column({ comment: '错误信息' })
|
||||
error: string
|
||||
|
||||
@Column({
|
||||
comment: '创建时间',
|
||||
name: 'create_time',
|
||||
|
||||
@@ -6,13 +6,13 @@ import {CnameRecordEntity, CnameRecordStatusType} from '../entity/cname-record.j
|
||||
import {createDnsProvider, IDnsProvider} from '@certd/plugin-cert';
|
||||
import {CnameProvider, CnameRecord} from '@certd/pipeline';
|
||||
import {cache, http, isDev, logger, utils} from '@certd/basic';
|
||||
import {walkTxtRecord} from '@certd/acme-client';
|
||||
import {getAuthoritativeDnsResolver, walkTxtRecord} from '@certd/acme-client';
|
||||
import {CnameProviderService} from './cname-provider-service.js';
|
||||
import {CnameProviderEntity} from '../entity/cname-provider.js';
|
||||
import {CommonDnsProvider} from './common-provider.js';
|
||||
import {SubDomainService, SubDomainsGetter} from "../../pipeline/service/sub-domain-service.js";
|
||||
import {DomainParser} from "@certd/plugin-cert/dist/dns-provider/domain-parser.js";
|
||||
|
||||
import punycode from 'punycode.js'
|
||||
type CnameCheckCacheValue = {
|
||||
validating: boolean;
|
||||
pass: boolean;
|
||||
@@ -22,11 +22,12 @@ type CnameCheckCacheValue = {
|
||||
intervalId?: NodeJS.Timeout;
|
||||
dnsProvider?: IDnsProvider;
|
||||
};
|
||||
|
||||
/**
|
||||
* 授权
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||
export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
@InjectEntityModel(CnameRecordEntity)
|
||||
repository: Repository<CnameRecordEntity>;
|
||||
@@ -47,6 +48,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
* @param param 数据
|
||||
@@ -62,7 +64,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
param.domain = param.domain.substring(2);
|
||||
}
|
||||
param.domain = param.domain.trim()
|
||||
const info = await this.getRepository().findOne({ where: { domain: param.domain,userId: param.userId } });
|
||||
const info = await this.getRepository().findOne({where: {domain: param.domain, userId: param.userId}});
|
||||
if (info) {
|
||||
return info;
|
||||
}
|
||||
@@ -77,17 +79,17 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
} else {
|
||||
cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
||||
}
|
||||
await this.cnameProviderChanged(param.userId,param, cnameProvider);
|
||||
await this.cnameProviderChanged(param.userId, param, cnameProvider);
|
||||
|
||||
param.status = 'cname';
|
||||
const { id } = await super.add(param);
|
||||
const {id} = await super.add(param);
|
||||
return await this.info(id);
|
||||
}
|
||||
|
||||
private async cnameProviderChanged(userId:number,param: any, cnameProvider: CnameProviderEntity) {
|
||||
private async cnameProviderChanged(userId: number, param: any, cnameProvider: CnameProviderEntity) {
|
||||
param.cnameProviderId = cnameProvider.id;
|
||||
|
||||
const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService)
|
||||
const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService)
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
|
||||
const realDomain = await domainParser.parse(param.domain);
|
||||
@@ -117,7 +119,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
}
|
||||
if (old.cnameProviderId !== param.cnameProviderId) {
|
||||
const cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
||||
await this.cnameProviderChanged(old.userId,param, cnameProvider);
|
||||
await this.cnameProviderChanged(old.userId, param, cnameProvider);
|
||||
param.status = 'cname';
|
||||
}
|
||||
return await super.update(param);
|
||||
@@ -157,10 +159,10 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
if (userId == null) {
|
||||
throw new ValidateException('userId不能为空');
|
||||
}
|
||||
let record = await this.getRepository().findOne({ where: { domain, userId } });
|
||||
let record = await this.getRepository().findOne({where: {domain, userId}});
|
||||
if (record == null) {
|
||||
if (createOnNotFound) {
|
||||
record = await this.add({ domain, userId });
|
||||
record = await this.add({domain, userId});
|
||||
} else {
|
||||
throw new ValidateException(`找不到${domain}的CNAME记录`);
|
||||
}
|
||||
@@ -191,7 +193,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
return true;
|
||||
}
|
||||
|
||||
const subDomainGetter = new SubDomainsGetter(bean.userId, this.subDomainService)
|
||||
const subDomainGetter = new SubDomainsGetter(bean.userId, this.subDomainService)
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
|
||||
const cacheKey = `cname.record.verify.${bean.id}`;
|
||||
@@ -208,7 +210,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
if (isDev()) {
|
||||
ttl = 30 * 1000;
|
||||
}
|
||||
const testRecordValue = 'certd-cname-verify';
|
||||
const testRecordValue = `certd-cname-verify-${bean.id}`;
|
||||
|
||||
const buildDnsProvider = async () => {
|
||||
const cnameProvider = await this.cnameProviderService.info(bean.cnameProviderId);
|
||||
@@ -228,7 +230,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
}
|
||||
|
||||
const access = await this.accessService.getById(cnameProvider.accessId, cnameProvider.userId);
|
||||
const context = { access, logger, http, utils,domainParser };
|
||||
const context = {access, logger, http, utils, domainParser};
|
||||
const dnsProvider: IDnsProvider = await createDnsProvider({
|
||||
dnsProviderType: cnameProvider.dnsProviderType,
|
||||
context,
|
||||
@@ -239,7 +241,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
const clearVerifyRecord = async () => {
|
||||
cache.delete(cacheKey);
|
||||
try {
|
||||
let dnsProvider =value.dnsProvider
|
||||
let dnsProvider = value.dnsProvider
|
||||
if (!dnsProvider) {
|
||||
dnsProvider = await buildDnsProvider();
|
||||
}
|
||||
@@ -271,6 +273,9 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
|
||||
logger.info(`检查CNAME配置 ${fullDomain} ${testRecordValue}`);
|
||||
|
||||
//检查是否有重复的acme配置
|
||||
await this.checkRepeatAcmeChallengeRecords(fullDomain,bean.recordValue)
|
||||
|
||||
// const txtRecords = await dns.promises.resolveTxt(fullDomain);
|
||||
// if (txtRecords.length) {
|
||||
// records = [].concat(...txtRecords);
|
||||
@@ -286,7 +291,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
if (success) {
|
||||
clearInterval(value.intervalId);
|
||||
logger.info(`检测到CNAME配置,修改状态 ${fullDomain} ${testRecordValue}`);
|
||||
await this.updateStatus(bean.id, 'valid');
|
||||
await this.updateStatus(bean.id, 'valid', "");
|
||||
value.pass = true;
|
||||
await clearVerifyRecord()
|
||||
return success;
|
||||
@@ -312,24 +317,102 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
type: 'TXT',
|
||||
value: testRecordValue,
|
||||
};
|
||||
|
||||
const dnsProvider = await buildDnsProvider();
|
||||
if(dnsProvider.usePunyCode()){
|
||||
//是否需要中文转英文
|
||||
req.domain = dnsProvider.punyCodeEncode(req.domain)
|
||||
req.fullRecord = dnsProvider.punyCodeEncode(req.fullRecord)
|
||||
req.hostRecord = dnsProvider.punyCodeEncode(req.hostRecord)
|
||||
req.value = dnsProvider.punyCodeEncode(req.value)
|
||||
}
|
||||
const recordRes = await dnsProvider.createRecord(req);
|
||||
value.dnsProvider = dnsProvider;
|
||||
value.validating = true;
|
||||
value.recordReq = req;
|
||||
value.recordRes = recordRes;
|
||||
await this.updateStatus(bean.id, 'validating');
|
||||
await this.updateStatus(bean.id, 'validating', "");
|
||||
|
||||
value.intervalId = setInterval(async () => {
|
||||
try {
|
||||
await checkRecordValue();
|
||||
} catch (e) {
|
||||
logger.error('检查cname出错:', e);
|
||||
await this.updateError(bean.id, e.message);
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
async updateStatus(id: number, status: CnameRecordStatusType) {
|
||||
await this.getRepository().update(id, { status });
|
||||
async updateStatus(id: number, status: CnameRecordStatusType, error?: string) {
|
||||
const updated: any = {status}
|
||||
if (error != null) {
|
||||
updated.error = error
|
||||
}
|
||||
await this.getRepository().update(id, updated);
|
||||
}
|
||||
|
||||
async updateError(id: number, error: string) {
|
||||
await this.getRepository().update(id, {error});
|
||||
}
|
||||
|
||||
async checkRepeatAcmeChallengeRecords(acmeRecordDomain: string,targetCnameDomain:string) {
|
||||
|
||||
let dnsResolver = null
|
||||
try{
|
||||
dnsResolver = await getAuthoritativeDnsResolver(acmeRecordDomain)
|
||||
}catch (e) {
|
||||
logger.error(`获取${acmeRecordDomain}的权威DNS服务器失败,${e.message}`)
|
||||
return
|
||||
}
|
||||
let cnameRecords = []
|
||||
try{
|
||||
cnameRecords = await dnsResolver.resolveCname(acmeRecordDomain);
|
||||
}catch (e) {
|
||||
logger.error(`查询CNAME记录失败:${e.message}`)
|
||||
return
|
||||
}
|
||||
targetCnameDomain = targetCnameDomain.toLowerCase()
|
||||
targetCnameDomain = punycode.toASCII(targetCnameDomain)
|
||||
if (cnameRecords.length > 0) {
|
||||
for (const cnameRecord of cnameRecords) {
|
||||
if(cnameRecord.toLowerCase() !== targetCnameDomain){
|
||||
//确保只有一个cname记录
|
||||
throw new Error(`${acmeRecordDomain}存在多个CNAME记录,请删除多余的CNAME记录:${cnameRecord}`)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 确保权威服务器里面没有纯粹的TXT记录
|
||||
let txtRecords = []
|
||||
try{
|
||||
const txtRecordRes = await dnsResolver.resolveTxt(acmeRecordDomain);
|
||||
|
||||
if (txtRecordRes && txtRecordRes.length > 0) {
|
||||
logger.info(`找到 ${txtRecordRes.length} 条 TXT记录( ${acmeRecordDomain})`);
|
||||
logger.info(`TXT records: ${JSON.stringify(txtRecords)}`);
|
||||
txtRecords = txtRecords.concat(...txtRecordRes);
|
||||
}
|
||||
}catch (e) {
|
||||
logger.error(`查询Txt记录失败:${e.message}`)
|
||||
}
|
||||
|
||||
if (txtRecords.length === 0) {
|
||||
//如果权威服务器中查不到txt,无需继续检查
|
||||
return
|
||||
}
|
||||
if (cnameRecords.length > 0) {
|
||||
// 从cname记录中获取txt记录
|
||||
// 对比是否存在,如果不存在于cname中获取的txt中,说明本体有创建多余的txt记录
|
||||
const res = await walkTxtRecord(cnameRecords[0]);
|
||||
if (res.length > 0) {
|
||||
for (const txtRecord of txtRecords) {
|
||||
if (!res.includes(txtRecord)) {
|
||||
throw new Error(`${acmeRecordDomain}存在多个TXT记录,请删除多余的TXT记录:${txtRecord}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {CreateRecordOptions, DnsProviderContext, IDnsProvider, RemoveRecordOptions} from '@certd/plugin-cert';
|
||||
import {PlusService} from '@certd/lib-server';
|
||||
|
||||
import punycode from 'punycode.js'
|
||||
export type CommonCnameProvider = {
|
||||
id: number;
|
||||
domain: string;
|
||||
@@ -24,6 +24,23 @@ export class CommonDnsProvider implements IDnsProvider {
|
||||
this.plusService = opts.plusService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 中文转英文
|
||||
* @param domain
|
||||
*/
|
||||
punyCodeEncode(domain: string) {
|
||||
return punycode.encode(domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转中文域名
|
||||
* @param domain
|
||||
*/
|
||||
punyCodeDecode(domain: string) {
|
||||
return punycode.decode(domain);
|
||||
}
|
||||
|
||||
|
||||
usePunyCode(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { BaseService, NeedSuiteException, NeedVIPException, SysSettingsService } from '@certd/lib-server';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SiteInfoEntity } from '../entity/site-info.js';
|
||||
import { siteTester } from './site-tester.js';
|
||||
import dayjs from 'dayjs';
|
||||
import { logger, utils } from '@certd/basic';
|
||||
import { PeerCertificate } from 'tls';
|
||||
import { NotificationService } from '../../pipeline/service/notification-service.js';
|
||||
import { isComm, isPlus } from '@certd/plus-core';
|
||||
import { UserSuiteService } from '@certd/commercial-core';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { BaseService, NeedSuiteException, NeedVIPException, SysSettingsService } from "@certd/lib-server";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { SiteInfoEntity } from "../entity/site-info.js";
|
||||
import { siteTester } from "./site-tester.js";
|
||||
import dayjs from "dayjs";
|
||||
import { logger, utils } from "@certd/basic";
|
||||
import { PeerCertificate } from "tls";
|
||||
import { NotificationService } from "../../pipeline/service/notification-service.js";
|
||||
import { isComm, isPlus } from "@certd/plus-core";
|
||||
import { UserSuiteService } from "@certd/commercial-core";
|
||||
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
|
||||
import { UserSiteMonitorSetting } from "../../mine/service/models.js";
|
||||
import {SiteIpService} from "./site-ip-service.js";
|
||||
import {SiteIpEntity} from "../entity/site-ip.js";
|
||||
import { UserSiteMonitorSetting } from "../../mine/service/models.js";
|
||||
import { SiteIpService } from "./site-ip-service.js";
|
||||
import { SiteIpEntity } from "../entity/site-ip.js";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -43,7 +43,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
|
||||
async add(data: SiteInfoEntity) {
|
||||
if (!data.userId) {
|
||||
throw new Error('userId is required');
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
|
||||
if (isComm()) {
|
||||
@@ -51,25 +51,34 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
if (suiteSetting.enabled) {
|
||||
const userSuite = await this.userSuiteService.getMySuiteDetail(data.userId);
|
||||
if (userSuite.monitorCount.max != -1 && userSuite.monitorCount.max <= userSuite.monitorCount.used) {
|
||||
throw new NeedSuiteException('站点监控数量已达上限,请购买或升级套餐');
|
||||
throw new NeedSuiteException("站点监控数量已达上限,请购买或升级套餐");
|
||||
}
|
||||
}
|
||||
}else if (!isPlus()) {
|
||||
const count = await this.getUserMonitorCount(data.userId);
|
||||
if (count >= 1) {
|
||||
throw new NeedVIPException('站点监控数量已达上限,请升级专业版');
|
||||
}
|
||||
} else if (!isPlus()) {
|
||||
const count = await this.getUserMonitorCount(data.userId);
|
||||
if (count >= 1) {
|
||||
throw new NeedVIPException("站点监控数量已达上限,请升级专业版");
|
||||
}
|
||||
}
|
||||
data.disabled = false;
|
||||
|
||||
const found = await this.repository.findOne({
|
||||
where: {
|
||||
domain: data.domain,
|
||||
userId: data.userId,
|
||||
httpsPort: data.httpsPort || 443
|
||||
}
|
||||
});
|
||||
if (found) {
|
||||
return { id: found.id };
|
||||
}
|
||||
|
||||
|
||||
|
||||
data.disabled = false;
|
||||
return await super.add(data);
|
||||
}
|
||||
|
||||
async update(data: any) {
|
||||
if (!data.id) {
|
||||
throw new Error('id is required');
|
||||
throw new Error("id is required");
|
||||
}
|
||||
delete data.userId;
|
||||
await super.update(data);
|
||||
@@ -77,10 +86,10 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
|
||||
async getUserMonitorCount(userId: number) {
|
||||
if (!userId) {
|
||||
throw new Error('userId is required');
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
return await this.repository.count({
|
||||
where: { userId },
|
||||
where: { userId }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,26 +101,26 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
*/
|
||||
async doCheck(site: SiteInfoEntity, notify = true, retryTimes = 3) {
|
||||
if (!site?.domain) {
|
||||
throw new Error('站点域名不能为空');
|
||||
throw new Error("站点域名不能为空");
|
||||
}
|
||||
try {
|
||||
await this.update({
|
||||
id: site.id,
|
||||
checkStatus: 'checking',
|
||||
lastCheckTime: dayjs().valueOf(),
|
||||
checkStatus: "checking",
|
||||
lastCheckTime: dayjs().valueOf()
|
||||
});
|
||||
const res = await siteTester.test({
|
||||
host: site.domain,
|
||||
port: site.httpsPort,
|
||||
retryTimes,
|
||||
retryTimes
|
||||
});
|
||||
|
||||
const certi: PeerCertificate = res.certificate;
|
||||
if (!certi) {
|
||||
throw new Error('没有发现证书');
|
||||
throw new Error("没有发现证书");
|
||||
}
|
||||
const expires = certi.valid_to;
|
||||
const allDomains = certi.subjectaltname?.replaceAll('DNS:', '').split(',') ||[];
|
||||
const allDomains = certi.subjectaltname?.replaceAll("DNS:", "").split(",") || [];
|
||||
const mainDomain = certi.subject?.CN;
|
||||
let domains = allDomains;
|
||||
if (!allDomains.includes(mainDomain)) {
|
||||
@@ -119,23 +128,26 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
}
|
||||
const issuer = `${certi.issuer.O}<${certi.issuer.CN}>`;
|
||||
const isExpired = dayjs().valueOf() > dayjs(expires).valueOf();
|
||||
const status = isExpired ? 'expired' : 'ok';
|
||||
const status = isExpired ? "expired" : "ok";
|
||||
const updateData = {
|
||||
id: site.id,
|
||||
certDomains: domains.join(','),
|
||||
certDomains: domains.join(","),
|
||||
certStatus: status,
|
||||
certProvider: issuer,
|
||||
certExpiresTime: dayjs(expires).valueOf(),
|
||||
lastCheckTime: dayjs().valueOf(),
|
||||
error: null,
|
||||
checkStatus: 'ok',
|
||||
checkStatus: "ok"
|
||||
};
|
||||
|
||||
if (site.ipCheck) {
|
||||
delete updateData.checkStatus
|
||||
}
|
||||
await this.update(updateData);
|
||||
|
||||
|
||||
//检查ip
|
||||
await this.checkAllIp(site)
|
||||
await this.checkAllIp(site);
|
||||
|
||||
if (!notify) {
|
||||
return;
|
||||
@@ -143,15 +155,15 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
try {
|
||||
await this.sendExpiresNotify(site);
|
||||
} catch (e) {
|
||||
logger.error('send notify error', e);
|
||||
logger.error("send notify error", e);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('check site error', e);
|
||||
logger.error("check site error", e);
|
||||
await this.update({
|
||||
id: site.id,
|
||||
checkStatus: 'error',
|
||||
checkStatus: "error",
|
||||
lastCheckTime: dayjs().valueOf(),
|
||||
error: e.message,
|
||||
error: e.message
|
||||
});
|
||||
if (!notify) {
|
||||
return;
|
||||
@@ -159,49 +171,56 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
try {
|
||||
await this.sendCheckErrorNotify(site);
|
||||
} catch (e) {
|
||||
logger.error('send notify error', e);
|
||||
logger.error("send notify error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async checkAllIp(site:SiteInfoEntity){
|
||||
if( !site.ipCheck){
|
||||
async checkAllIp(site: SiteInfoEntity) {
|
||||
if (!site.ipCheck) {
|
||||
return;
|
||||
}
|
||||
const certExpiresTime = site.certExpiresTime;
|
||||
const onFinished = async (list:SiteIpEntity[])=>{
|
||||
let errorCount = 0
|
||||
let errorMessage = ""
|
||||
const onFinished = async (list: SiteIpEntity[]) => {
|
||||
let errorCount = 0;
|
||||
let errorMessage = "";
|
||||
for (const item of list) {
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
errorCount++
|
||||
if(item.error){
|
||||
errorMessage += `${item.ipAddress}:${item.error}; \n`
|
||||
}else if(item.certExpiresTime!==certExpiresTime){
|
||||
errorMessage += `${item.ipAddress}:与主站证书过期时间不一致; \n`
|
||||
}else{
|
||||
errorCount--
|
||||
errorCount++;
|
||||
if (item.error) {
|
||||
errorMessage += `${item.ipAddress}:${item.error}; \n`;
|
||||
} else if (item.certExpiresTime !== certExpiresTime) {
|
||||
errorMessage += `${item.ipAddress}:与主站证书过期时间不一致; \n`;
|
||||
} else {
|
||||
errorCount--;
|
||||
}
|
||||
}
|
||||
if (errorCount<=0){
|
||||
return
|
||||
if (errorCount <= 0) {
|
||||
//检查正常
|
||||
await this.update({
|
||||
id: site.id,
|
||||
checkStatus: "ok",
|
||||
error: "",
|
||||
ipErrorCount: 0
|
||||
});
|
||||
return;
|
||||
}
|
||||
await this.update({
|
||||
id: site.id,
|
||||
checkStatus: 'error',
|
||||
checkStatus: "error",
|
||||
error: errorMessage,
|
||||
ipErrorCount: errorCount,
|
||||
})
|
||||
ipErrorCount: errorCount
|
||||
});
|
||||
try {
|
||||
site = await this.info(site.id)
|
||||
await this.sendCheckErrorNotify(site,true);
|
||||
site = await this.info(site.id);
|
||||
await this.sendCheckErrorNotify(site, true);
|
||||
} catch (e) {
|
||||
logger.error('send notify error', e);
|
||||
logger.error("send notify error", e);
|
||||
}
|
||||
}
|
||||
await this.siteIpService.checkAll(site,onFinished)
|
||||
};
|
||||
await this.siteIpService.checkAll(site, onFinished);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,13 +232,13 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
async check(id: number, notify = false, retryTimes = 3) {
|
||||
const site = await this.info(id);
|
||||
if (!site) {
|
||||
throw new Error('站点不存在');
|
||||
throw new Error("站点不存在");
|
||||
}
|
||||
return await this.doCheck(site, notify, retryTimes);
|
||||
}
|
||||
|
||||
async sendCheckErrorNotify(site: SiteInfoEntity,fromIpCheck=false) {
|
||||
const url = await this.notificationService.getBindUrl('#/certd/monitor/site');
|
||||
async sendCheckErrorNotify(site: SiteInfoEntity, fromIpCheck = false) {
|
||||
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
|
||||
// 发邮件
|
||||
await this.notificationService.send(
|
||||
{
|
||||
@@ -227,22 +246,23 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
logger: logger,
|
||||
body: {
|
||||
url,
|
||||
title: `站点证书${fromIpCheck?"(IP)":""}检查出错<${site.name}>`,
|
||||
title: `站点证书${fromIpCheck ? "(IP)" : ""}检查出错<${site.name}>`,
|
||||
content: `站点名称: ${site.name} \n站点域名: ${site.domain} \n错误信息:${site.error}`,
|
||||
errorMessage: site.error,
|
||||
},
|
||||
errorMessage: site.error
|
||||
}
|
||||
},
|
||||
site.userId
|
||||
);
|
||||
}
|
||||
|
||||
async sendExpiresNotify(site: SiteInfoEntity) {
|
||||
|
||||
const tipDays = 10
|
||||
const tipDays = 10;
|
||||
|
||||
const expires = site.certExpiresTime;
|
||||
const validDays = dayjs(expires).diff(dayjs(), 'day');
|
||||
const url = await this.notificationService.getBindUrl('#/certd/monitor/site');
|
||||
const content = `站点名称: ${site.name} \n站点域名: ${site.domain} \n证书域名: ${site.certDomains} \n颁发机构: ${site.certProvider} \n过期时间: ${dayjs(site.certExpiresTime).format('YYYY-MM-DD')} \n`;
|
||||
const validDays = dayjs(expires).diff(dayjs(), "day");
|
||||
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
|
||||
const content = `站点名称: ${site.name} \n站点域名: ${site.domain} \n证书域名: ${site.certDomains} \n颁发机构: ${site.certProvider} \n过期时间: ${dayjs(site.certExpiresTime).format("YYYY-MM-DD")} \n`;
|
||||
if (validDays >= 0 && validDays < tipDays) {
|
||||
// 发通知
|
||||
await this.notificationService.send(
|
||||
@@ -252,8 +272,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
body: {
|
||||
title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`,
|
||||
content,
|
||||
url,
|
||||
},
|
||||
url
|
||||
}
|
||||
},
|
||||
site.userId
|
||||
);
|
||||
@@ -268,7 +288,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
content,
|
||||
url,
|
||||
errorMessage: "站点证书已过期"
|
||||
},
|
||||
}
|
||||
},
|
||||
site.userId
|
||||
);
|
||||
@@ -277,10 +297,10 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
|
||||
async checkAllByUsers(userId: any) {
|
||||
if (!userId) {
|
||||
throw new Error('userId is required');
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
const sites = await this.repository.find({
|
||||
where: { userId },
|
||||
where: { userId }
|
||||
});
|
||||
this.checkList(sites);
|
||||
}
|
||||
@@ -294,7 +314,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
async getSetting(userId: number){
|
||||
async getSetting(userId: number) {
|
||||
return await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, UserSiteMonitorSetting);
|
||||
}
|
||||
|
||||
@@ -302,26 +322,78 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
await this.userSettingsService.saveSetting(userId, bean);
|
||||
}
|
||||
|
||||
async ipCheckChange(req: {id: any; ipCheck: any}) {
|
||||
async ipCheckChange(req: { id: any; ipCheck: any }) {
|
||||
|
||||
await this.update({
|
||||
id: req.id,
|
||||
ipCheck: req.ipCheck,
|
||||
ipCheck: req.ipCheck
|
||||
});
|
||||
if(req.ipCheck){
|
||||
if (req.ipCheck) {
|
||||
const site = await this.info(req.id);
|
||||
await this.siteIpService.sync(site)
|
||||
await this.siteIpService.sync(site);
|
||||
}
|
||||
}
|
||||
|
||||
async disabledChange(req: { disabled: any; id: any }) {
|
||||
await this.update({
|
||||
id: req.id,
|
||||
disabled: req.disabled,
|
||||
disabled: req.disabled
|
||||
});
|
||||
if(!req.disabled){
|
||||
if (!req.disabled) {
|
||||
const site = await this.info(req.id);
|
||||
await this.doCheck(site)
|
||||
await this.doCheck(site);
|
||||
}
|
||||
}
|
||||
|
||||
async doImport(req: { text: string; userId: number }) {
|
||||
if (!req.text) {
|
||||
throw new Error("text is required");
|
||||
}
|
||||
if (!req.userId) {
|
||||
throw new Error("userId is required");
|
||||
}
|
||||
|
||||
const rows = req.text.split("\n");
|
||||
|
||||
const list = [];
|
||||
for (const item of rows) {
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
const arr = item.trim().split(":");
|
||||
if (arr.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const domain = arr[0];
|
||||
let port = 443;
|
||||
let name = domain;
|
||||
if (arr.length > 1) {
|
||||
try {
|
||||
port = parseInt(arr[1] || "443");
|
||||
} catch (e) {
|
||||
throw new Error(`${item}格式错误`);
|
||||
}
|
||||
|
||||
}
|
||||
if (arr.length > 2) {
|
||||
name = arr[2] || domain;
|
||||
}
|
||||
|
||||
list.push({
|
||||
domain,
|
||||
name,
|
||||
httpsPort: port,
|
||||
userId: req.userId
|
||||
});
|
||||
}
|
||||
|
||||
const batchAdd = async (list: any[]) => {
|
||||
for (const item of list) {
|
||||
await this.add(item);
|
||||
}
|
||||
|
||||
// await this.checkAllByUsers(req.userId);
|
||||
};
|
||||
await batchAdd(list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
const finished = res.filter(item=>{
|
||||
return item!=null
|
||||
})
|
||||
if (finished.length > 0) {
|
||||
if (onFinish) {
|
||||
onFinish && onFinish(finished)
|
||||
}
|
||||
})
|
||||
@@ -232,4 +232,50 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
ipCount:count
|
||||
})
|
||||
}
|
||||
|
||||
async doImport(req: { text: string; userId:number, siteId:number }) {
|
||||
if (!req.text) {
|
||||
throw new Error("text is required");
|
||||
}
|
||||
if (!req.siteId) {
|
||||
throw new Error("siteId is required");
|
||||
}
|
||||
|
||||
const siteEntity = await this.siteInfoRepository.findOne({
|
||||
where: {
|
||||
id: req.siteId,
|
||||
userId:req.userId
|
||||
}
|
||||
});
|
||||
if (!siteEntity) {
|
||||
throw new Error(`站点${req.siteId}不存在`);
|
||||
}
|
||||
|
||||
const userId = siteEntity.userId;
|
||||
|
||||
const rows = req.text.split("\n");
|
||||
|
||||
const list = [];
|
||||
for (const item of rows) {
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
list.push({
|
||||
ipAddress:item,
|
||||
userId: userId,
|
||||
siteId: req.siteId,
|
||||
from: "import",
|
||||
disabled:false,
|
||||
});
|
||||
}
|
||||
|
||||
const batchAdd = async (list: any[]) => {
|
||||
for (const item of list) {
|
||||
await this.add(item);
|
||||
}
|
||||
|
||||
// await this.checkAllByUsers(req.userId);
|
||||
};
|
||||
await batchAdd(list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,17 +233,18 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const userId = bean.userId;
|
||||
const userIsAdmin = await this.userService.isAdmin(userId);
|
||||
if (!userIsAdmin) {
|
||||
//非管理员用户,限制pipeline数量
|
||||
const count = await this.repository.count({ where: { userId } });
|
||||
const sysPublic = await this.sysSettingsService.getSetting<SysPublicSettings>(SysPublicSettings);
|
||||
const limitUserPipelineCount = sysPublic.limitUserPipelineCount;
|
||||
if (limitUserPipelineCount && limitUserPipelineCount > 0 && count >= limitUserPipelineCount) {
|
||||
throw new NeedVIPException(`普通用户最多只能创建${limitUserPipelineCount}条流水线`);
|
||||
}else{
|
||||
//非商业版校验用户最大流水线数量
|
||||
const userId = bean.userId;
|
||||
const userIsAdmin = await this.userService.isAdmin(userId);
|
||||
if (!userIsAdmin) {
|
||||
//非管理员用户,限制pipeline数量
|
||||
const count = await this.repository.count({ where: { userId } });
|
||||
const sysPublic = await this.sysSettingsService.getSetting<SysPublicSettings>(SysPublicSettings);
|
||||
const limitUserPipelineCount = sysPublic.limitUserPipelineCount;
|
||||
if (limitUserPipelineCount && limitUserPipelineCount > 0 && count >= limitUserPipelineCount) {
|
||||
throw new NeedVIPException(`普通用户最多只能创建${limitUserPipelineCount}条流水线`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,6 +330,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
if (isComm()) {
|
||||
await this.checkHasDeployCount(id, entity.userId);
|
||||
}
|
||||
await this.checkUserStatus(entity.userId)
|
||||
this.cron.register({
|
||||
name: `pipeline.${id}.trigger.once`,
|
||||
cron: null,
|
||||
@@ -446,6 +448,13 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
if (isComm()) {
|
||||
suite = await this.checkHasDeployCount(id, entity.userId);
|
||||
}
|
||||
try{
|
||||
await this.checkUserStatus(entity.userId)
|
||||
}catch (e) {
|
||||
logger.info(e.message)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const pipeline = JSON.parse(entity.content);
|
||||
if (!pipeline.id) {
|
||||
@@ -745,5 +754,25 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async checkUserStatus(userId: number) {
|
||||
const userEntity = await this.userService.info(userId);
|
||||
if(userEntity == null){
|
||||
throw new Error('用户不存在');
|
||||
}
|
||||
if(userEntity.status === 0){
|
||||
const message = `账户${userId}已被禁用,禁止运行流水线`
|
||||
throw new Error(message)
|
||||
}
|
||||
const sysPublic = await this.sysSettingsService.getPublicSettings()
|
||||
if(isPlus() && sysPublic.userValidTimeEnabled === true){
|
||||
//校验用户有效期是否设置
|
||||
if(userEntity.validTime!= null && userEntity.validTime > 0){
|
||||
if(userEntity.validTime < new Date().getTime()){
|
||||
//用户已过期
|
||||
const message = `账户${userId}已过有效期,禁止运行流水线`
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ export class UserEntity {
|
||||
|
||||
@Column({ comment: '状态 0:禁用 1:启用', default: 1 })
|
||||
status: number;
|
||||
|
||||
@Column({ name: 'valid_time', comment: '有效期', nullable: true })
|
||||
validTime: number;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
comment: '创建时间',
|
||||
|
||||
@@ -7,7 +7,7 @@ import { CertApplyPluginNames} from '@certd/plugin-cert';
|
||||
title: '阿里云-部署证书至OSS',
|
||||
icon: 'svg:icon-aliyun',
|
||||
group: pluginGroups.aliyun.key,
|
||||
desc: '自动部署域名证书至阿里云OSS',
|
||||
desc: '部署域名证书至阿里云OSS自定义域名,不是上传到阿里云oss',
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
|
||||
@@ -26,6 +26,11 @@ export type CloudflareRecord = {
|
||||
})
|
||||
export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord> {
|
||||
access!: CloudflareAccess;
|
||||
usePunyCode(): boolean {
|
||||
//是否使用punycode来添加解析记录
|
||||
//默认都使用原始中文域名来添加
|
||||
return true;
|
||||
}
|
||||
async onInstance() {
|
||||
//一些初始化的操作
|
||||
// 也可以通过ctx成员变量传递context
|
||||
|
||||
@@ -91,8 +91,9 @@ export class FlexCDNRefreshCert extends AbstractTaskPlugin {
|
||||
* timeEndAt: Math.floor((new Date(currentInfo.validTo)).getTime() / 1000),
|
||||
*
|
||||
*/
|
||||
const commonNames =[ certReader.getMainDomain()]
|
||||
const dnsNames = certReader.getAltNames()
|
||||
const topCrt = CertReader.readCertDetail(certReader.cert.ic)
|
||||
const commonNames =[ topCrt.detail.issuer.commonName]
|
||||
const dnsNames = certReader.getAllDomains()
|
||||
const timeBeginAt = Math.floor(certReader.detail.notBefore.getTime() / 1000);
|
||||
const timeEndAt = Math.floor(certReader.detail.notAfter.getTime() / 1000);
|
||||
const body = {
|
||||
|
||||
@@ -77,7 +77,13 @@ export class GithubCheckRelease extends AbstractTaskPlugin {
|
||||
this.logger.info(`有更新,${lastVersion??"0"}->${res.tag_name}`)
|
||||
this.lastVersion = res.tag_name;
|
||||
|
||||
const body = res.body.replaceAll("* ","- ")
|
||||
// const body = res.body.replaceAll("* ","- ")
|
||||
//仅每行开头的* 替换成 -, *号前面可以有空格
|
||||
const body = res.body.replace(/^(\s*)\* /gm, "$1- ")
|
||||
|
||||
if (this.notificationIds == null){
|
||||
this.notificationIds = [0]
|
||||
}
|
||||
//发送通知
|
||||
for (const notificationId of this.notificationIds) {
|
||||
await this.ctx.notificationService.send({
|
||||
|
||||
@@ -23,6 +23,11 @@ export class NamesiloDnsProvider extends AbstractDnsProvider<NamesiloRecord> {
|
||||
// 也可以通过ctx成员变量传递context
|
||||
this.access = this.ctx.access as NamesiloAccess;
|
||||
}
|
||||
usePunyCode(): boolean {
|
||||
//是否使用punycode来添加解析记录
|
||||
//默认都使用原始中文域名来添加
|
||||
return true;
|
||||
}
|
||||
|
||||
private async doRequest(url: string, params: any = null) {
|
||||
params = merge(
|
||||
|
||||
@@ -93,7 +93,7 @@ export class ProxmoxUploadCert extends AbstractPlusTaskPlugin {
|
||||
|
||||
async getClient() {
|
||||
const access: ProxmoxAccess = await this.getAccess<ProxmoxAccess>(this.accessId);
|
||||
const pve = await import('@corsinvest/cv4pve-api-javascript');
|
||||
const pve = await import('@certd/cv4pve-api-javascript');
|
||||
const client = new pve.PveClient(access.host, access.port);
|
||||
const login = await client.login(access.username, access.password, access.realm || 'pam');
|
||||
if (!login) {
|
||||
|
||||
Reference in New Issue
Block a user