mirror of https://github.com/certd/certd
perf: 数据库备份支持oss
parent
50a5fa15bb
commit
308d4600ef
|
@ -6,8 +6,8 @@ import { Challenge } from "@certd/acme-client/types/rfc8555";
|
||||||
import { IContext } from "@certd/pipeline";
|
import { IContext } from "@certd/pipeline";
|
||||||
import { ILogger, utils } from "@certd/basic";
|
import { ILogger, utils } from "@certd/basic";
|
||||||
import { IDnsProvider, IDomainParser } from "../../dns-provider/index.js";
|
import { IDnsProvider, IDomainParser } from "../../dns-provider/index.js";
|
||||||
import { HttpChallengeUploader } from "./uploads/api.js";
|
|
||||||
import punycode from "node:punycode";
|
import punycode from "node:punycode";
|
||||||
|
import { IOssClient } from "@certd/plugin-lib";
|
||||||
export type CnameVerifyPlan = {
|
export type CnameVerifyPlan = {
|
||||||
type?: string;
|
type?: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
|
@ -18,7 +18,7 @@ export type CnameVerifyPlan = {
|
||||||
export type HttpVerifyPlan = {
|
export type HttpVerifyPlan = {
|
||||||
type: string;
|
type: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
httpUploader: HttpChallengeUploader;
|
httpUploader: IOssClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DomainVerifyPlan = {
|
export type DomainVerifyPlan = {
|
||||||
|
@ -35,7 +35,7 @@ export type DomainsVerifyPlan = {
|
||||||
export type Providers = {
|
export type Providers = {
|
||||||
dnsProvider?: IDnsProvider;
|
dnsProvider?: IDnsProvider;
|
||||||
domainsVerifyPlan?: DomainsVerifyPlan;
|
domainsVerifyPlan?: DomainsVerifyPlan;
|
||||||
httpUploader?: HttpChallengeUploader;
|
httpUploader?: IOssClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CertInfo = {
|
export type CertInfo = {
|
||||||
|
@ -184,7 +184,7 @@ export class AcmeService {
|
||||||
return authz.challenges.find((c: any) => c.type === type);
|
return authz.challenges.find((c: any) => c.type === type);
|
||||||
};
|
};
|
||||||
|
|
||||||
const doHttpVerify = async (challenge: any, httpUploader: HttpChallengeUploader) => {
|
const doHttpVerify = async (challenge: any, httpUploader: IOssClient) => {
|
||||||
const keyAuthorization = await keyAuthorizationGetter(challenge);
|
const keyAuthorization = await keyAuthorizationGetter(challenge);
|
||||||
this.logger.info("http校验");
|
this.logger.info("http校验");
|
||||||
const filePath = `.well-known/acme-challenge/${challenge.token}`;
|
const filePath = `.well-known/acme-challenge/${challenge.token}`;
|
||||||
|
@ -287,7 +287,7 @@ export class AcmeService {
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async challengeRemoveFn(authz: any, challenge: any, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider?: IDnsProvider, httpUploader?: HttpChallengeUploader) {
|
async challengeRemoveFn(authz: any, challenge: any, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider?: IDnsProvider, httpUploader?: IOssClient) {
|
||||||
this.logger.info("执行清理");
|
this.logger.info("执行清理");
|
||||||
|
|
||||||
/* http-01 */
|
/* http-01 */
|
||||||
|
|
|
@ -9,8 +9,8 @@ import { CertReader } from "./cert-reader.js";
|
||||||
import { CertApplyBasePlugin } from "./base.js";
|
import { CertApplyBasePlugin } from "./base.js";
|
||||||
import { GoogleClient } from "../../libs/google.js";
|
import { GoogleClient } from "../../libs/google.js";
|
||||||
import { EabAccess } from "../../access";
|
import { EabAccess } from "../../access";
|
||||||
import { httpChallengeUploaderFactory } from "./uploads/factory.js";
|
|
||||||
import { DomainParser } from "../../dns-provider/domain-parser.js";
|
import { DomainParser } from "../../dns-provider/domain-parser.js";
|
||||||
|
import { ossClientFactory } from "@certd/plugin-lib";
|
||||||
export * from "./base.js";
|
export * from "./base.js";
|
||||||
export type { CertInfo };
|
export type { CertInfo };
|
||||||
export * from "./cert-reader.js";
|
export * from "./cert-reader.js";
|
||||||
|
@ -115,6 +115,7 @@ HTTP文件验证:不支持泛域名,需要配置网站文件上传`,
|
||||||
})
|
})
|
||||||
dnsProviderType!: string;
|
dnsProviderType!: string;
|
||||||
|
|
||||||
|
// dns解析授权类型,勿删
|
||||||
dnsProviderAccessType!: string;
|
dnsProviderAccessType!: string;
|
||||||
|
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
|
@ -446,7 +447,7 @@ HTTP文件验证:不支持泛域名,需要配置网站文件上传`,
|
||||||
rootDir = rootDir + "/";
|
rootDir = rootDir + "/";
|
||||||
}
|
}
|
||||||
this.logger.info("上传方式", httpRecord.httpUploaderType);
|
this.logger.info("上传方式", httpRecord.httpUploaderType);
|
||||||
const httpUploader = await httpChallengeUploaderFactory.createUploaderByType(httpRecord.httpUploaderType, {
|
const httpUploader = await ossClientFactory.createOssClientByType(httpRecord.httpUploaderType, {
|
||||||
access,
|
access,
|
||||||
rootDir: rootDir,
|
rootDir: rootDir,
|
||||||
ctx: httpUploaderContext,
|
ctx: httpUploaderContext,
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { IAccessService } from "@certd/pipeline";
|
|
||||||
import { ILogger, utils } from "@certd/basic";
|
|
||||||
|
|
||||||
export type HttpChallengeUploader = {
|
|
||||||
upload: (fileName: string, fileContent: Buffer) => Promise<void>;
|
|
||||||
remove: (fileName: string) => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type HttpChallengeUploadContext = {
|
|
||||||
accessService: IAccessService;
|
|
||||||
logger: ILogger;
|
|
||||||
utils: typeof utils;
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class BaseHttpChallengeUploader<A> implements HttpChallengeUploader {
|
|
||||||
rootDir: string;
|
|
||||||
access: A = null;
|
|
||||||
logger: ILogger;
|
|
||||||
utils: typeof utils;
|
|
||||||
ctx: HttpChallengeUploadContext;
|
|
||||||
protected constructor(opts: { rootDir: string; access: A }) {
|
|
||||||
this.rootDir = opts.rootDir;
|
|
||||||
this.access = opts.access;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setCtx(ctx: any) {
|
|
||||||
// set context
|
|
||||||
this.ctx = ctx;
|
|
||||||
this.logger = ctx.logger;
|
|
||||||
this.utils = ctx.utils;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract remove(fileName: string): Promise<void>;
|
|
||||||
abstract upload(fileName: string, fileContent: Buffer): Promise<void>;
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
import { BaseHttpChallengeUploader } from "../api.js";
|
|
||||||
import { AliossAccess, AliyunAccess } from "@certd/plugin-lib";
|
|
||||||
import { AliossClient } from "@certd/plugin-lib";
|
|
||||||
|
|
||||||
export class AliossHttpChallengeUploader extends BaseHttpChallengeUploader<AliossAccess> {
|
|
||||||
async upload(filePath: string, fileContent: Buffer) {
|
|
||||||
const aliyunAccess = await this.ctx.accessService.getById<AliyunAccess>(this.access.accessId);
|
|
||||||
const client = new AliossClient({
|
|
||||||
access: aliyunAccess,
|
|
||||||
bucket: this.access.bucket,
|
|
||||||
region: this.access.region,
|
|
||||||
});
|
|
||||||
|
|
||||||
const key = this.rootDir + filePath;
|
|
||||||
this.logger.info(`开始上传文件: ${key}`);
|
|
||||||
await client.uploadFile(key, fileContent);
|
|
||||||
|
|
||||||
this.logger.info(`校验文件上传成功: ${filePath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(filePath: string) {
|
|
||||||
const key = this.rootDir + filePath;
|
|
||||||
// remove file from alioss
|
|
||||||
const client = await this.getAliossClient();
|
|
||||||
await client.removeFile(key);
|
|
||||||
this.logger.info(`文件删除成功: ${key}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getAliossClient() {
|
|
||||||
const aliyunAccess = await this.ctx.accessService.getById<AliyunAccess>(this.access.accessId);
|
|
||||||
const client = new AliossClient({
|
|
||||||
access: aliyunAccess,
|
|
||||||
bucket: this.access.bucket,
|
|
||||||
region: this.access.region,
|
|
||||||
});
|
|
||||||
await client.init();
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { BaseHttpChallengeUploader } from "../api.js";
|
|
||||||
import { QiniuOssAccess, QiniuClient, QiniuAccess } from "@certd/plugin-lib";
|
|
||||||
|
|
||||||
export class QiniuOssHttpChallengeUploader extends BaseHttpChallengeUploader<QiniuOssAccess> {
|
|
||||||
async upload(filePath: string, fileContent: Buffer) {
|
|
||||||
const qiniuAccess = await this.ctx.accessService.getById<QiniuAccess>(this.access.accessId);
|
|
||||||
const client = new QiniuClient({
|
|
||||||
access: qiniuAccess,
|
|
||||||
logger: this.logger,
|
|
||||||
http: this.ctx.utils.http,
|
|
||||||
});
|
|
||||||
if (this.rootDir.endsWith("/")) {
|
|
||||||
this.rootDir = this.rootDir.slice(0, -1);
|
|
||||||
}
|
|
||||||
await client.uploadFile(this.access.bucket, this.rootDir + filePath, fileContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(filePath: string) {
|
|
||||||
const qiniuAccess = await this.ctx.accessService.getById<QiniuAccess>(this.access.accessId);
|
|
||||||
const client = new QiniuClient({
|
|
||||||
access: qiniuAccess,
|
|
||||||
logger: this.logger,
|
|
||||||
http: this.ctx.utils.http,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.rootDir.endsWith("/")) {
|
|
||||||
this.rootDir = this.rootDir.slice(0, -1);
|
|
||||||
}
|
|
||||||
await client.removeFile(this.access.bucket, this.rootDir + filePath);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,10 +16,11 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alicloud/pop-core": "^1.7.10",
|
"@alicloud/pop-core": "^1.7.10",
|
||||||
|
"@aws-sdk/client-s3": "^3.787.0",
|
||||||
"@certd/basic": "^1.33.7",
|
"@certd/basic": "^1.33.7",
|
||||||
"@certd/pipeline": "^1.33.7",
|
"@certd/pipeline": "^1.33.7",
|
||||||
"@kubernetes/client-node": "0.21.0",
|
"@kubernetes/client-node": "0.21.0",
|
||||||
"ali-oss": "^6.21.0",
|
"ali-oss": "^6.22.0",
|
||||||
"basic-ftp": "^5.0.5",
|
"basic-ftp": "^5.0.5",
|
||||||
"cos-nodejs-sdk-v5": "^2.14.6",
|
"cos-nodejs-sdk-v5": "^2.14.6",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AliyunAccess } from "../access";
|
import { AliyunAccess } from "../access/index.js";
|
||||||
|
|
||||||
export class AliossClient {
|
export class AliossClient {
|
||||||
access: AliyunAccess;
|
access: AliyunAccess;
|
||||||
|
@ -61,4 +61,23 @@ export class AliossClient {
|
||||||
await this.init();
|
await this.init();
|
||||||
return await this.client.delete(filePath);
|
return await this.client.delete(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async downloadFile(key: string, savePath: string) {
|
||||||
|
await this.init();
|
||||||
|
return await this.client.get(key, savePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async listDir(dirKey: string) {
|
||||||
|
await this.init();
|
||||||
|
const res = await this.client.listV2({
|
||||||
|
prefix: dirKey,
|
||||||
|
// max-keys: 100,
|
||||||
|
// continuation-token: "token",
|
||||||
|
// delimiter: "/",
|
||||||
|
// marker: "marker",
|
||||||
|
// encoding-type: "url",
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.objects;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,4 +44,14 @@ export class FtpClient {
|
||||||
this.logger.info(`开始删除文件${filePath}`);
|
this.logger.info(`开始删除文件${filePath}`);
|
||||||
await this.client.remove(filePath, true);
|
await this.client.remove(filePath, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listDir(dir: string): Promise<any[]> {
|
||||||
|
this.logger.info(`开始列出目录${dir}`);
|
||||||
|
return await this.client.list(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
async download(filePath: string, savePath: string): Promise<void> {
|
||||||
|
this.logger.info(`开始下载文件${filePath} -> ${savePath}`);
|
||||||
|
await this.client.downloadTo(savePath, filePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,3 +5,4 @@ export * from "./ftp/index.js";
|
||||||
export * from "./tencent/index.js";
|
export * from "./tencent/index.js";
|
||||||
export * from "./qiniu/index.js";
|
export * from "./qiniu/index.js";
|
||||||
export * from "./ctyun/index.js";
|
export * from "./ctyun/index.js";
|
||||||
|
export * from "./oss/index.js";
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { AliyunAccess } from "../aliyun";
|
|
||||||
import { HttpClient, ILogger } from "@certd/basic";
|
|
||||||
import { IOssClient, OssClientDeleteReq } from "./api";
|
|
||||||
|
|
||||||
export class AliossClient implements IOssClient {
|
|
||||||
access: AliyunAccess;
|
|
||||||
logger: ILogger;
|
|
||||||
http: HttpClient;
|
|
||||||
|
|
||||||
constructor(opts: { access: AliyunAccess; http: HttpClient; logger: ILogger }) {
|
|
||||||
this.access = opts.access;
|
|
||||||
this.http = opts.http;
|
|
||||||
this.logger = opts.logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
upload(key: string, content: Buffer | string): Promise<void> {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
download(key: string, savePath?: string): Promise<void> {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
delete(opts: OssClientDeleteReq): Promise<void> {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +1,89 @@
|
||||||
export type OssClientDeleteReq = {
|
import { IAccessService } from "@certd/pipeline";
|
||||||
key: string;
|
import { ILogger, utils } from "@certd/basic";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
export type OssClientRemoveByOpts = {
|
||||||
|
dir?: string;
|
||||||
|
//删除多少天前的文件
|
||||||
beforeDays?: number;
|
beforeDays?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IOssClient {
|
export type OssFileItem = {
|
||||||
upload(key: string, content: Buffer | string): Promise<void>;
|
name: string;
|
||||||
|
path: string;
|
||||||
|
size: number;
|
||||||
|
//毫秒时间戳
|
||||||
|
lastModified: number;
|
||||||
|
};
|
||||||
|
|
||||||
download(key: string, savePath?: string): Promise<void>;
|
export type IOssClient = {
|
||||||
|
upload: (fileName: string, fileContent: Buffer) => Promise<void>;
|
||||||
|
remove: (fileName: string) => Promise<void>;
|
||||||
|
|
||||||
delete(opts: OssClientDeleteReq): Promise<void>;
|
download: (fileName: string, savePath: string) => Promise<void>;
|
||||||
|
|
||||||
|
removeBy: (removeByOpts: OssClientRemoveByOpts) => Promise<void>;
|
||||||
|
|
||||||
|
listDir: (dir: string) => Promise<OssFileItem[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OssClientContext = {
|
||||||
|
accessService: IAccessService;
|
||||||
|
logger: ILogger;
|
||||||
|
utils: typeof utils;
|
||||||
|
};
|
||||||
|
|
||||||
|
export abstract class BaseOssClient<A> implements IOssClient {
|
||||||
|
rootDir: string = "";
|
||||||
|
access: A = null;
|
||||||
|
logger: ILogger;
|
||||||
|
utils: typeof utils;
|
||||||
|
ctx: OssClientContext;
|
||||||
|
|
||||||
|
protected constructor(opts: { rootDir?: string; access: A }) {
|
||||||
|
this.rootDir = opts.rootDir || "";
|
||||||
|
this.access = opts.access;
|
||||||
|
}
|
||||||
|
|
||||||
|
join(...strs: string[]) {
|
||||||
|
let res = "";
|
||||||
|
for (const item of strs) {
|
||||||
|
if (item) {
|
||||||
|
if (!res) {
|
||||||
|
res = item;
|
||||||
|
} else {
|
||||||
|
res += "/" + item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = res.replace(/[\\/]+/g, "/");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setCtx(ctx: any) {
|
||||||
|
// set context
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.logger = ctx.logger;
|
||||||
|
this.utils = ctx.utils;
|
||||||
|
await this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract remove(fileName: string): Promise<void>;
|
||||||
|
abstract upload(fileName: string, fileContent: Buffer): Promise<void>;
|
||||||
|
abstract download(fileName: string, savePath: string): Promise<void>;
|
||||||
|
abstract listDir(dir: string): Promise<OssFileItem[]>;
|
||||||
|
|
||||||
|
async removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
|
||||||
|
const list = await this.listDir(removeByOpts.dir);
|
||||||
|
const beforeDate = dayjs().subtract(removeByOpts.beforeDays, "day");
|
||||||
|
for (const item of list) {
|
||||||
|
if (item.lastModified && item.lastModified < beforeDate.valueOf()) {
|
||||||
|
await this.remove(item.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,33 @@
|
||||||
import { HttpChallengeUploadContext } from "./api";
|
import { OssClientContext } from "./api";
|
||||||
|
|
||||||
export class HttpChallengeUploaderFactory {
|
export class OssClientFactory {
|
||||||
async getClassByType(type: string) {
|
async getClassByType(type: string) {
|
||||||
if (type === "alioss") {
|
if (type === "alioss") {
|
||||||
const module = await import("./impls/alioss.js");
|
const module = await import("./impls/alioss.js");
|
||||||
return module.AliossHttpChallengeUploader;
|
return module.default;
|
||||||
} else if (type === "ssh") {
|
} else if (type === "ssh") {
|
||||||
const module = await import("./impls/ssh.js");
|
const module = await import("./impls/ssh.js");
|
||||||
return module.SshHttpChallengeUploader;
|
return module.default;
|
||||||
} else if (type === "sftp") {
|
} else if (type === "sftp") {
|
||||||
const module = await import("./impls/sftp.js");
|
const module = await import("./impls/sftp.js");
|
||||||
return module.SftpHttpChallengeUploader;
|
return module.default;
|
||||||
} else if (type === "ftp") {
|
} else if (type === "ftp") {
|
||||||
const module = await import("./impls/ftp.js");
|
const module = await import("./impls/ftp.js");
|
||||||
return module.FtpHttpChallengeUploader;
|
return module.default;
|
||||||
} else if (type === "tencentcos") {
|
} else if (type === "tencentcos") {
|
||||||
const module = await import("./impls/tencentcos.js");
|
const module = await import("./impls/tencentcos.js");
|
||||||
return module.TencentCosHttpChallengeUploader;
|
return module.default;
|
||||||
} else if (type === "qiniuoss") {
|
} else if (type === "qiniuoss") {
|
||||||
const module = await import("./impls/qiniuoss.js");
|
const module = await import("./impls/qiniuoss.js");
|
||||||
return module.QiniuOssHttpChallengeUploader;
|
return module.default;
|
||||||
|
} else if (type === "s3") {
|
||||||
|
const module = await import("./impls/s3.js");
|
||||||
|
return module.default;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`暂不支持此文件上传方式: ${type}`);
|
throw new Error(`暂不支持此文件上传方式: ${type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async createUploaderByType(type: string, opts: { rootDir: string; access: any; ctx: HttpChallengeUploadContext }) {
|
async createOssClientByType(type: string, opts: { rootDir: string; access: any; ctx: OssClientContext }) {
|
||||||
const cls = await this.getClassByType(type);
|
const cls = await this.getClassByType(type);
|
||||||
if (cls) {
|
if (cls) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -35,4 +38,4 @@ export class HttpChallengeUploaderFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const httpChallengeUploaderFactory = new HttpChallengeUploaderFactory();
|
export const ossClientFactory = new OssClientFactory();
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { BaseOssClient, OssFileItem } from "../api.js";
|
||||||
|
import { AliossAccess, AliossClient, AliyunAccess } from "../../aliyun/index.js";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
export default class AliOssClientImpl extends BaseOssClient<AliossAccess> {
|
||||||
|
client: AliossClient;
|
||||||
|
join(...strs: string[]) {
|
||||||
|
const str = super.join(...strs);
|
||||||
|
if (str.startsWith("/")) {
|
||||||
|
return str.substring(1);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
async init() {
|
||||||
|
const aliyunAccess = await this.ctx.accessService.getById<AliyunAccess>(this.access.accessId);
|
||||||
|
const client = new AliossClient({
|
||||||
|
access: aliyunAccess,
|
||||||
|
bucket: this.access.bucket,
|
||||||
|
region: this.access.region,
|
||||||
|
});
|
||||||
|
await client.init();
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
async download(filePath: string, savePath: string): Promise<void> {
|
||||||
|
const key = this.join(this.rootDir, filePath);
|
||||||
|
await this.client.downloadFile(key, savePath);
|
||||||
|
}
|
||||||
|
async listDir(dir: string): Promise<OssFileItem[]> {
|
||||||
|
const dirKey = this.join(this.rootDir, dir) + "/";
|
||||||
|
const list = await this.client.listDir(dirKey);
|
||||||
|
this.logger.info(`列出目录: ${dirKey},文件数:${list.length}`);
|
||||||
|
return list.map(item => {
|
||||||
|
return {
|
||||||
|
path: item.name,
|
||||||
|
lastModified: dayjs(item.lastModified).valueOf(),
|
||||||
|
size: item.size,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async upload(filePath: string, fileContent: Buffer) {
|
||||||
|
const key = this.join(this.rootDir, filePath);
|
||||||
|
this.logger.info(`开始上传文件: ${key}`);
|
||||||
|
await this.client.uploadFile(key, fileContent);
|
||||||
|
|
||||||
|
this.logger.info(`文件上传成功: ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(filePath: string) {
|
||||||
|
const key = this.join(this.rootDir, filePath);
|
||||||
|
// remove file from alioss
|
||||||
|
await this.client.removeFile(key);
|
||||||
|
this.logger.info(`文件删除成功: ${key}`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,17 @@
|
||||||
import { BaseHttpChallengeUploader } from "../api.js";
|
import { BaseOssClient } from "../api.js";
|
||||||
import { FtpAccess, FtpClient } from "@certd/plugin-lib";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import { FtpAccess, FtpClient } from "../../ftp/index.js";
|
||||||
|
|
||||||
export class FtpHttpChallengeUploader extends BaseHttpChallengeUploader<FtpAccess> {
|
export default class FtpOssClientImpl extends BaseOssClient<FtpAccess> {
|
||||||
|
async download(fileName: string, savePath: string) {}
|
||||||
|
async listDir(dir: string) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
async upload(filePath: string, fileContent: Buffer) {
|
async upload(filePath: string, fileContent: Buffer) {
|
||||||
const client = new FtpClient({
|
const client = this.getFtpClient();
|
||||||
access: this.access,
|
await client.connect(async client => {
|
||||||
logger: this.logger,
|
|
||||||
});
|
|
||||||
await client.connect(async (client) => {
|
|
||||||
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
|
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
|
||||||
const dir = path.dirname(tmpFilePath);
|
const dir = path.dirname(tmpFilePath);
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
|
@ -19,7 +20,7 @@ export class FtpHttpChallengeUploader extends BaseHttpChallengeUploader<FtpAcces
|
||||||
fs.writeFileSync(tmpFilePath, fileContent);
|
fs.writeFileSync(tmpFilePath, fileContent);
|
||||||
try {
|
try {
|
||||||
// Write file to temp path
|
// Write file to temp path
|
||||||
const path = this.rootDir + filePath;
|
const path = this.join(this.rootDir, filePath);
|
||||||
await client.upload(path, tmpFilePath);
|
await client.upload(path, tmpFilePath);
|
||||||
} finally {
|
} finally {
|
||||||
// Remove temp file
|
// Remove temp file
|
||||||
|
@ -28,13 +29,17 @@ export class FtpHttpChallengeUploader extends BaseHttpChallengeUploader<FtpAcces
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(filePath: string) {
|
private getFtpClient() {
|
||||||
const client = new FtpClient({
|
return new FtpClient({
|
||||||
access: this.access,
|
access: this.access,
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
});
|
});
|
||||||
await client.connect(async (client) => {
|
}
|
||||||
const path = this.rootDir + filePath;
|
|
||||||
|
async remove(filePath: string) {
|
||||||
|
const client = this.getFtpClient();
|
||||||
|
await client.connect(async client => {
|
||||||
|
const path = this.join(this.rootDir, filePath);
|
||||||
await client.client.remove(path);
|
await client.client.remove(path);
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { QiniuAccess, QiniuClient, QiniuOssAccess } from "../../qiniu/index.js";
|
||||||
|
import { BaseOssClient, OssClientRemoveByOpts, OssFileItem } from "../api.js";
|
||||||
|
|
||||||
|
export default class QiniuOssClientImpl extends BaseOssClient<QiniuOssAccess> {
|
||||||
|
client: QiniuClient;
|
||||||
|
async init() {
|
||||||
|
const qiniuAccess = await this.ctx.accessService.getById<QiniuAccess>(this.access.accessId);
|
||||||
|
this.client = new QiniuClient({
|
||||||
|
access: qiniuAccess,
|
||||||
|
logger: this.logger,
|
||||||
|
http: this.ctx.utils.http,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
download(fileName: string, savePath: string): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
listDir(dir: string): Promise<OssFileItem[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
async upload(filePath: string, fileContent: Buffer) {
|
||||||
|
const path = this.join(this.rootDir, filePath);
|
||||||
|
await this.client.uploadFile(this.access.bucket, path, fileContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(filePath: string) {
|
||||||
|
const path = this.join(this.rootDir, filePath);
|
||||||
|
await this.client.removeFile(this.access.bucket, path);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { BaseOssClient, OssClientRemoveByOpts, OssFileItem } from "../api.js";
|
||||||
|
import path from "node:path";
|
||||||
|
import { S3Access } from "../../s3/access.js";
|
||||||
|
export default class S3OssClientImpl extends BaseOssClient<S3Access> {
|
||||||
|
client: any;
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
// import { S3Client } from "@aws-sdk/client-s3";
|
||||||
|
const { S3Client } = await import("@aws-sdk/client-s3");
|
||||||
|
this.client = new S3Client({
|
||||||
|
forcePathStyle: true,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: this.access.accessKeyId, // 默认 MinIO 访问密钥
|
||||||
|
secretAccessKey: this.access.secretAccessKey, // 默认 MinIO 秘密密钥
|
||||||
|
},
|
||||||
|
region: "us-east-1",
|
||||||
|
endpoint: this.access.endpoint,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
download(fileName: string, savePath: string): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
listDir(dir: string): Promise<OssFileItem[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
async upload(filePath: string, fileContent: Buffer) {
|
||||||
|
const { PutObjectCommand } = await import("@aws-sdk/client-s3");
|
||||||
|
const key = path.join(this.rootDir, filePath);
|
||||||
|
this.logger.info(`开始上传文件: ${key}`);
|
||||||
|
const params = {
|
||||||
|
Bucket: this.access.bucket, // The name of the bucket. For example, 'sample_bucket_101'.
|
||||||
|
Key: key, // The name of the object. For example, 'sample_upload.txt'.
|
||||||
|
};
|
||||||
|
await this.client.send(new PutObjectCommand({ Body: fileContent, ...params }));
|
||||||
|
|
||||||
|
this.logger.info(`文件上传成功: ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(filePath: string) {
|
||||||
|
const key = path.join(this.rootDir, filePath);
|
||||||
|
const { DeleteObjectCommand } = await import("@aws-sdk/client-s3");
|
||||||
|
await this.client.send(
|
||||||
|
new DeleteObjectCommand({
|
||||||
|
Bucket: this.access.bucket,
|
||||||
|
Key: key,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.logger.info(`文件删除成功: ${key}`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,19 @@
|
||||||
import { BaseHttpChallengeUploader } from "../api.js";
|
import { BaseOssClient, OssClientRemoveByOpts, OssFileItem } from "../api.js";
|
||||||
import { SshAccess, SshClient } from "@certd/plugin-lib";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { SftpAccess } from "@certd/plugin-lib";
|
import { SftpAccess, SshAccess, SshClient } from "../../ssh/index.js";
|
||||||
|
|
||||||
export class SftpHttpChallengeUploader extends BaseHttpChallengeUploader<SftpAccess> {
|
export default class SftpOssClientImpl extends BaseOssClient<SftpAccess> {
|
||||||
|
download(fileName: string, savePath: string): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
listDir(dir: string): Promise<OssFileItem[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
async upload(filePath: string, fileContent: Buffer) {
|
async upload(filePath: string, fileContent: Buffer) {
|
||||||
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
|
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
import { BaseHttpChallengeUploader } from "../api.js";
|
import { BaseOssClient, OssClientRemoveByOpts, OssFileItem } from "../api.js";
|
||||||
import { SshAccess, SshClient } from "@certd/plugin-lib";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import { SshAccess, SshClient } from "../../ssh/index.js";
|
||||||
|
|
||||||
export class SshHttpChallengeUploader extends BaseHttpChallengeUploader<SshAccess> {
|
export default class SshOssClientImpl extends BaseOssClient<SshAccess> {
|
||||||
|
download(fileName: string, savePath: string): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
listDir(dir: string): Promise<OssFileItem[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
async upload(filePath: string, fileContent: Buffer) {
|
async upload(filePath: string, fileContent: Buffer) {
|
||||||
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
|
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
import { BaseHttpChallengeUploader } from "../api.js";
|
import { TencentAccess, TencentCosAccess, TencentCosClient } from "../../tencent/index.js";
|
||||||
import { TencentAccess, TencentCosAccess, TencentCosClient } from "@certd/plugin-lib";
|
import { BaseOssClient, OssClientRemoveByOpts, OssFileItem } from "../api.js";
|
||||||
|
|
||||||
export class TencentCosHttpChallengeUploader extends BaseHttpChallengeUploader<TencentCosAccess> {
|
export default class TencentOssClientImpl extends BaseOssClient<TencentCosAccess> {
|
||||||
|
download(fileName: string, savePath: string): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
listDir(dir: string): Promise<OssFileItem[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
async upload(filePath: string, fileContent: Buffer) {
|
async upload(filePath: string, fileContent: Buffer) {
|
||||||
const access = await this.ctx.accessService.getById<TencentAccess>(this.access.accessId);
|
const access = await this.ctx.accessService.getById<TencentAccess>(this.access.accessId);
|
||||||
const client = new TencentCosClient({
|
const client = new TencentCosClient({
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./factory.js";
|
||||||
|
export * from "./api.js";
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个注解将注册一个授权配置
|
||||||
|
* 在certd的后台管理系统中,用户可以选择添加此类型的授权
|
||||||
|
*/
|
||||||
|
@IsAccess({
|
||||||
|
name: "s3",
|
||||||
|
title: "s3/minio授权",
|
||||||
|
desc: "S3/minio oss授权",
|
||||||
|
icon: "mdi:folder-upload-outline",
|
||||||
|
})
|
||||||
|
export class S3Access extends BaseAccess {
|
||||||
|
@AccessInput({
|
||||||
|
title: "endpoint",
|
||||||
|
component: {
|
||||||
|
placeholder: "http://xxxxxx:9000",
|
||||||
|
name: "a-input",
|
||||||
|
vModel: "value",
|
||||||
|
},
|
||||||
|
helper: "Minio的地址,如果是aws s3 则无需填写",
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
endpoint!: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* const minioClient = new S3Client({
|
||||||
|
* endpoint: "http://localhost:9000",
|
||||||
|
* forcePathStyle: true,
|
||||||
|
* credentials: {
|
||||||
|
* accessKeyId: "minioadmin", // 默认 MinIO 访问密钥
|
||||||
|
* secretAccessKey: "minioadmin", // 默认 MinIO 秘密密钥
|
||||||
|
* },
|
||||||
|
* region: "us-east-1",
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: "accessKeyId",
|
||||||
|
component: {
|
||||||
|
placeholder: "accessKeyId",
|
||||||
|
},
|
||||||
|
helper: "accessKeyId",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
accessKeyId!: string;
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: "secretAccessKey",
|
||||||
|
component: {
|
||||||
|
placeholder: "secretAccessKey",
|
||||||
|
component: {
|
||||||
|
name: "a-input",
|
||||||
|
vModel: "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
helper: "secretAccessKey",
|
||||||
|
encrypt: true,
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
secretAccessKey!: string;
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: "地区",
|
||||||
|
value: "us-east-1",
|
||||||
|
component: {
|
||||||
|
name: "a-input",
|
||||||
|
vModel: "value",
|
||||||
|
},
|
||||||
|
helper: "region",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
region!: string;
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: "存储桶",
|
||||||
|
component: {
|
||||||
|
name: "a-input",
|
||||||
|
vModel: "value",
|
||||||
|
},
|
||||||
|
helper: "bucket 名称",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
bucket!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
new S3Access();
|
|
@ -75,6 +75,7 @@ const uploaderTypeDict = dict({
|
||||||
{ label: "阿里云OSS", value: "alioss" },
|
{ label: "阿里云OSS", value: "alioss" },
|
||||||
{ label: "腾讯云COS", value: "tencentcos" },
|
{ label: "腾讯云COS", value: "tencentcos" },
|
||||||
{ label: "七牛OSS", value: "qiniuoss" },
|
{ label: "七牛OSS", value: "qiniuoss" },
|
||||||
|
{ label: "S3/Minio", value: "s3" },
|
||||||
{ label: "SSH(已废弃,请选择SFTP方式)", value: "ssh", disabled: true },
|
{ label: "SSH(已废弃,请选择SFTP方式)", value: "ssh", disabled: true },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import {IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline';
|
import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||||
import fs from 'fs';
|
import fs from "fs";
|
||||||
import path from 'path';
|
import path from "path";
|
||||||
import dayjs from 'dayjs';
|
import dayjs from "dayjs";
|
||||||
import {AbstractPlusTaskPlugin} from '@certd/plugin-plus';
|
import { AbstractPlusTaskPlugin } from "@certd/plugin-plus";
|
||||||
import JSZip from 'jszip';
|
import JSZip from "jszip";
|
||||||
import * as os from 'node:os';
|
import * as os from "node:os";
|
||||||
import {SshAccess, SshClient} from '@certd/plugin-lib';
|
import { OssClientContext, ossClientFactory, OssClientRemoveByOpts, SshAccess, SshClient } from "@certd/plugin-lib";
|
||||||
|
|
||||||
const defaultBackupDir = 'certd_backup';
|
const defaultBackupDir = 'certd_backup';
|
||||||
const defaultFilePrefix = 'db-backup';
|
const defaultFilePrefix = 'db-backup';
|
||||||
|
@ -63,12 +63,14 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin {
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
title: 'OSS类型',
|
title: 'OSS类型',
|
||||||
component: {
|
component: {
|
||||||
name: 'a-input',
|
name: 'a-select',
|
||||||
options: [
|
options: [
|
||||||
{value: "aliyun", label: "阿里云OSS"},
|
{value: "alioss", label: "阿里云OSS"},
|
||||||
{value: "s3", label: "MinIO/S3"},
|
{value: "s3", label: "MinIO/S3"},
|
||||||
{value: "qiniu", label: "七牛云"},
|
{value: "qiniuoss", label: "七牛云"},
|
||||||
{value: "tencent", label: "腾讯云COS"}
|
{value: "tencentcos", label: "腾讯云COS"},
|
||||||
|
{value: "ftp", label: "Ftp"},
|
||||||
|
{value: "sftp", label: "Sftp"},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
mergeScript: `
|
mergeScript: `
|
||||||
|
@ -90,7 +92,7 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin {
|
||||||
mergeScript: `
|
mergeScript: `
|
||||||
return {
|
return {
|
||||||
show:ctx.compute(({form})=>{
|
show:ctx.compute(({form})=>{
|
||||||
return form.backupMode === 'ssh';
|
return form.backupMode === 'oss';
|
||||||
}),
|
}),
|
||||||
component:{
|
component:{
|
||||||
type: ctx.compute(({form})=>{
|
type: ctx.compute(({form})=>{
|
||||||
|
@ -270,7 +272,38 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ossBackup(dbPath: string, backupDir: string, backupPath: string) {
|
private async ossBackup(dbPath: string, backupDir: string, backupPath: string) {
|
||||||
// TODO
|
if (!this.ossAccessId) {
|
||||||
|
throw new Error('未配置ossAccessId');
|
||||||
|
}
|
||||||
|
const access = await this.getAccess(this.ossAccessId);
|
||||||
|
const ossType = this.ossType
|
||||||
|
|
||||||
|
const ctx: OssClientContext = {
|
||||||
|
logger: this.logger,
|
||||||
|
utils: this.ctx.utils,
|
||||||
|
accessService:this.accessService
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info(`开始备份文件到:${ossType}`);
|
||||||
|
const client = await ossClientFactory.createOssClientByType(ossType, {
|
||||||
|
access,
|
||||||
|
ctx,
|
||||||
|
})
|
||||||
|
|
||||||
|
await client.upload(backupPath, dbPath);
|
||||||
|
|
||||||
|
if (this.retainDays > 0) {
|
||||||
|
// 删除过期备份
|
||||||
|
this.logger.info('开始删除过期备份文件');
|
||||||
|
const removeByOpts: OssClientRemoveByOpts = {
|
||||||
|
dir: backupDir,
|
||||||
|
beforeDays: this.retainDays,
|
||||||
|
};
|
||||||
|
await client.removeBy(removeByOpts);
|
||||||
|
this.logger.info('删除过期备份文件完成');
|
||||||
|
}else{
|
||||||
|
this.logger.info('已禁止删除过期文件');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue