mirror of https://github.com/certd/certd
perf: 部署到IIS插件
parent
aedc462135
commit
1534f45236
|
@ -48,4 +48,4 @@ admin/123456
|
||||||
|
|
||||||
## 五、备份恢复
|
## 五、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`覆盖到原来的位置,重启certd即可
|
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置,重启certd即可
|
||||||
|
|
|
@ -81,4 +81,4 @@ services:
|
||||||
|
|
||||||
## 五、备份恢复
|
## 五、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`覆盖到原来的位置,重启certd即可
|
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置,重启certd即可
|
||||||
|
|
|
@ -71,4 +71,4 @@ docker compose up -d
|
||||||
|
|
||||||
## 四、备份恢复
|
## 四、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`覆盖到原来的位置,重启certd即可
|
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置,重启certd即可
|
|
@ -1,6 +1,9 @@
|
||||||
# 源码部署
|
# 源码部署
|
||||||
|
不推荐
|
||||||
## 一、源码安装
|
## 一、源码安装
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
- nodejs 20 及以上
|
||||||
### 源码启动
|
### 源码启动
|
||||||
```shell
|
```shell
|
||||||
# 克隆代码
|
# 克隆代码
|
||||||
|
@ -42,4 +45,4 @@ kill -9 $(lsof -t -i:7001)
|
||||||
|
|
||||||
## 四、备份恢复
|
## 四、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`覆盖到原来的位置,重启certd即可
|
将备份的`db.sqlite`及同目录下的其他文件覆盖到原来的位置,重启certd即可
|
||||||
|
|
|
@ -25,3 +25,15 @@ win+R 弹出运行对话框,输入 services.msc 打开服务管理器
|
||||||
C:\Users\xxxxx>
|
C:\Users\xxxxx>
|
||||||
↑↑↑↑---------这个就是windows ssh的登录用户名
|
↑↑↑↑---------这个就是windows ssh的登录用户名
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 4. 切换默认shell终端
|
||||||
|
安装openssh后,默认终端是cmd,建议切换成powershell
|
||||||
|
```shell
|
||||||
|
# powershell中执行如下命令切换
|
||||||
|
# 设置默认shell为powershell 【推荐】
|
||||||
|
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force
|
||||||
|
|
||||||
|
# 恢复默认shell为cmd 【不推荐】
|
||||||
|
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\cmd.exe" -PropertyType String -Force
|
||||||
|
|
||||||
|
```
|
|
@ -4,7 +4,6 @@ import type { CertInfo } from "./acme.js";
|
||||||
import { CertReader } from "./cert-reader.js";
|
import { CertReader } from "./cert-reader.js";
|
||||||
import JSZip from "jszip";
|
import JSZip from "jszip";
|
||||||
import { CertConverter } from "./convert.js";
|
import { CertConverter } from "./convert.js";
|
||||||
import fs from "fs";
|
|
||||||
import { pick } from "lodash-es";
|
import { pick } from "lodash-es";
|
||||||
|
|
||||||
export { CertReader };
|
export { CertReader };
|
||||||
|
@ -59,6 +58,19 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
||||||
})
|
})
|
||||||
pfxPassword!: string;
|
pfxPassword!: string;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: "PFX证书转换参数",
|
||||||
|
value: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES",
|
||||||
|
component: {
|
||||||
|
name: "a-input",
|
||||||
|
vModel: "value",
|
||||||
|
},
|
||||||
|
required: false,
|
||||||
|
order: 100,
|
||||||
|
helper: "兼容Server 2016,如果导入证书失败,请删除此参数",
|
||||||
|
})
|
||||||
|
pfxArgs = "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES";
|
||||||
|
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
title: "更新天数",
|
title: "更新天数",
|
||||||
value: 35,
|
value: 35,
|
||||||
|
@ -143,23 +155,18 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
||||||
const res = await converter.convert({
|
const res = await converter.convert({
|
||||||
cert,
|
cert,
|
||||||
pfxPassword: this.pfxPassword,
|
pfxPassword: this.pfxPassword,
|
||||||
|
pfxArgs: this.pfxArgs,
|
||||||
});
|
});
|
||||||
if (cert.pfx == null && res.pfxPath) {
|
if (cert.pfx == null && res.pfx) {
|
||||||
const pfxBuffer = fs.readFileSync(res.pfxPath);
|
cert.pfx = res.pfx;
|
||||||
cert.pfx = pfxBuffer.toString("base64");
|
|
||||||
fs.unlinkSync(res.pfxPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cert.der == null && res.derPath) {
|
if (cert.der == null && res.der) {
|
||||||
const derBuffer = fs.readFileSync(res.derPath);
|
cert.der = res.der;
|
||||||
cert.der = derBuffer.toString("base64");
|
|
||||||
fs.unlinkSync(res.derPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cert.jks == null && res.jksPath) {
|
if (cert.jks == null && res.jks) {
|
||||||
const jksBuffer = fs.readFileSync(res.jksPath);
|
cert.jks = res.jks;
|
||||||
cert.jks = jksBuffer.toString("base64");
|
|
||||||
fs.unlinkSync(res.jksPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info("转换证书格式成功");
|
this.logger.info("转换证书格式成功");
|
||||||
|
|
|
@ -14,31 +14,31 @@ export class CertConverter {
|
||||||
constructor(opts: { logger: ILogger }) {
|
constructor(opts: { logger: ILogger }) {
|
||||||
this.logger = opts.logger;
|
this.logger = opts.logger;
|
||||||
}
|
}
|
||||||
async convert(opts: { cert: CertInfo; pfxPassword: string }): Promise<{
|
async convert(opts: { cert: CertInfo; pfxPassword: string; pfxArgs: string }): Promise<{
|
||||||
pfxPath: string;
|
pfx: string;
|
||||||
derPath: string;
|
der: string;
|
||||||
jksPath: string;
|
jks: string;
|
||||||
}> {
|
}> {
|
||||||
const certReader = new CertReader(opts.cert);
|
const certReader = new CertReader(opts.cert);
|
||||||
let pfxPath: string;
|
let pfx: string;
|
||||||
let derPath: string;
|
let der: string;
|
||||||
let jksPath: string;
|
let jks: string;
|
||||||
const handle = async (ctx: CertReaderHandleContext) => {
|
const handle = async (ctx: CertReaderHandleContext) => {
|
||||||
// 调用openssl 转pfx
|
// 调用openssl 转pfx
|
||||||
pfxPath = await this.convertPfx(ctx, opts.pfxPassword);
|
pfx = await this.convertPfx(ctx, opts.pfxPassword, opts.pfxArgs);
|
||||||
|
|
||||||
// 转der
|
// 转der
|
||||||
derPath = await this.convertDer(ctx);
|
der = await this.convertDer(ctx);
|
||||||
|
|
||||||
jksPath = await this.convertJks(ctx, opts.pfxPassword);
|
jks = await this.convertJks(ctx, opts.pfxPassword);
|
||||||
};
|
};
|
||||||
|
|
||||||
await certReader.readCertFile({ logger: this.logger, handle });
|
await certReader.readCertFile({ logger: this.logger, handle });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pfxPath,
|
pfx,
|
||||||
derPath,
|
der,
|
||||||
jksPath,
|
jks,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ export class CertConverter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async convertPfx(opts: CertReaderHandleContext, pfxPassword: string) {
|
private async convertPfx(opts: CertReaderHandleContext, pfxPassword: string, pfxArgs: string) {
|
||||||
const { tmpCrtPath, tmpKeyPath } = opts;
|
const { tmpCrtPath, tmpKeyPath } = opts;
|
||||||
|
|
||||||
const pfxPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + "_cert.pfx");
|
const pfxPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + "_cert.pfx");
|
||||||
|
@ -65,12 +65,14 @@ export class CertConverter {
|
||||||
passwordArg = `-password pass:${pfxPassword}`;
|
passwordArg = `-password pass:${pfxPassword}`;
|
||||||
}
|
}
|
||||||
// 兼容server 2016,旧版本不能用sha256
|
// 兼容server 2016,旧版本不能用sha256
|
||||||
const oldPfxCmd = `openssl pkcs12 -macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -out ${pfxPath} -inkey ${tmpKeyPath} -in ${tmpCrtPath} ${passwordArg}`;
|
const oldPfxCmd = `openssl pkcs12 ${pfxArgs} -export -out ${pfxPath} -inkey ${tmpKeyPath} -in ${tmpCrtPath} ${passwordArg}`;
|
||||||
// const newPfx = `openssl pkcs12 -export -out ${pfxPath} -inkey ${tmpKeyPath} -in ${tmpCrtPath} ${passwordArg}`;
|
// const newPfx = `openssl pkcs12 -export -out ${pfxPath} -inkey ${tmpKeyPath} -in ${tmpCrtPath} ${passwordArg}`;
|
||||||
await this.exec(oldPfxCmd);
|
await this.exec(oldPfxCmd);
|
||||||
return pfxPath;
|
const fileBuffer = fs.readFileSync(pfxPath);
|
||||||
// const fileBuffer = fs.readFileSync(pfxPath);
|
const pfxCert = fileBuffer.toString("base64");
|
||||||
// this.pfxCert = fileBuffer.toString("base64");
|
fs.unlinkSync(pfxPath);
|
||||||
|
return pfxCert;
|
||||||
|
|
||||||
//
|
//
|
||||||
// const applyTime = new Date().getTime();
|
// const applyTime = new Date().getTime();
|
||||||
// const filename = reader.buildCertFileName("pfx", applyTime);
|
// const filename = reader.buildCertFileName("pfx", applyTime);
|
||||||
|
@ -87,15 +89,10 @@ export class CertConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.exec(`openssl x509 -outform der -in ${tmpCrtPath} -out ${derPath}`);
|
await this.exec(`openssl x509 -outform der -in ${tmpCrtPath} -out ${derPath}`);
|
||||||
|
const fileBuffer = fs.readFileSync(derPath);
|
||||||
return derPath;
|
const derCert = fileBuffer.toString("base64");
|
||||||
|
fs.unlinkSync(derPath);
|
||||||
// const fileBuffer = fs.readFileSync(derPath);
|
return derCert;
|
||||||
// this.derCert = fileBuffer.toString("base64");
|
|
||||||
//
|
|
||||||
// const applyTime = new Date().getTime();
|
|
||||||
// const filename = reader.buildCertFileName("der", applyTime);
|
|
||||||
// this.saveFile(filename, fileBuffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async convertJks(opts: CertReaderHandleContext, pfxPassword = "") {
|
async convertJks(opts: CertReaderHandleContext, pfxPassword = "") {
|
||||||
|
@ -120,7 +117,11 @@ export class CertConverter {
|
||||||
`keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${jksPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" `
|
`keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${jksPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" `
|
||||||
);
|
);
|
||||||
fs.unlinkSync(p12Path);
|
fs.unlinkSync(p12Path);
|
||||||
return jksPath;
|
|
||||||
|
const fileBuffer = fs.readFileSync(jksPath);
|
||||||
|
const certBase64 = fileBuffer.toString("base64");
|
||||||
|
fs.unlinkSync(jksPath);
|
||||||
|
return certBase64;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error("转换jks失败", e);
|
this.logger.error("转换jks失败", e);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -25,7 +25,7 @@ export class AsyncSsh2Client {
|
||||||
if (this.encoding) {
|
if (this.encoding) {
|
||||||
return iconv.decode(buffer, this.encoding);
|
return iconv.decode(buffer, this.encoding);
|
||||||
}
|
}
|
||||||
return buffer.toString();
|
return buffer.toString().replaceAll("\r\n", "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
|
@ -95,7 +95,12 @@ export class AsyncSsh2Client {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async exec(script: string) {
|
async exec(
|
||||||
|
script: string,
|
||||||
|
opts: {
|
||||||
|
throwOnStdErr?: boolean;
|
||||||
|
} = {}
|
||||||
|
): Promise<string> {
|
||||||
if (!script) {
|
if (!script) {
|
||||||
this.logger.info("script 为空,取消执行");
|
this.logger.info("script 为空,取消执行");
|
||||||
return;
|
return;
|
||||||
|
@ -114,9 +119,17 @@ export class AsyncSsh2Client {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let data = "";
|
let data = "";
|
||||||
|
let hasErrorLog = false;
|
||||||
stream
|
stream
|
||||||
.on("close", (code: any, signal: any) => {
|
.on("close", (code: any, signal: any) => {
|
||||||
this.logger.info(`[${this.connConf.host}][close]:code:${code}`);
|
this.logger.info(`[${this.connConf.host}][close]:code:${code}`);
|
||||||
|
if (opts.throwOnStdErr == null && this.windows) {
|
||||||
|
opts.throwOnStdErr = true;
|
||||||
|
}
|
||||||
|
if (opts.throwOnStdErr && hasErrorLog) {
|
||||||
|
reject(new Error(data));
|
||||||
|
}
|
||||||
|
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
resolve(data);
|
resolve(data);
|
||||||
} else {
|
} else {
|
||||||
|
@ -135,13 +148,14 @@ export class AsyncSsh2Client {
|
||||||
.stderr.on("data", (ret: Buffer) => {
|
.stderr.on("data", (ret: Buffer) => {
|
||||||
const err = this.convert(iconv, ret);
|
const err = this.convert(iconv, ret);
|
||||||
data += err;
|
data += err;
|
||||||
this.logger.info(`[${this.connConf.host}][error]: ` + err.trimEnd());
|
hasErrorLog = true;
|
||||||
|
this.logger.error(`[${this.connConf.host}][error]: ` + err.trimEnd());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async shell(script: string | string[]): Promise<string[]> {
|
async shell(script: string | string[]): Promise<string> {
|
||||||
return new Promise<any>((resolve, reject) => {
|
return new Promise<any>((resolve, reject) => {
|
||||||
this.logger.info(`执行shell脚本:[${this.connConf.host}][shell]: ` + script);
|
this.logger.info(`执行shell脚本:[${this.connConf.host}][shell]: ` + script);
|
||||||
this.conn.shell((err: Error, stream: any) => {
|
this.conn.shell((err: Error, stream: any) => {
|
||||||
|
@ -149,11 +163,11 @@ export class AsyncSsh2Client {
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const output: string[] = [];
|
let output = "";
|
||||||
function ansiHandle(data: string) {
|
function ansiHandle(data: string) {
|
||||||
data = data.replace(/\[[0-9]+;1H/g, "\n");
|
data = data.replace(/\[[0-9]+;1H/g, "");
|
||||||
data = stripAnsi(data);
|
data = stripAnsi(data);
|
||||||
return data;
|
return data.replaceAll("\r\n", "\n");
|
||||||
}
|
}
|
||||||
stream
|
stream
|
||||||
.on("close", (code: any) => {
|
.on("close", (code: any) => {
|
||||||
|
@ -163,7 +177,7 @@ export class AsyncSsh2Client {
|
||||||
.on("data", (ret: Buffer) => {
|
.on("data", (ret: Buffer) => {
|
||||||
const data = ansiHandle(ret.toString());
|
const data = ansiHandle(ret.toString());
|
||||||
this.logger.info(data);
|
this.logger.info(data);
|
||||||
output.push(data);
|
output += data;
|
||||||
})
|
})
|
||||||
.on("error", (err: any) => {
|
.on("error", (err: any) => {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -171,8 +185,8 @@ export class AsyncSsh2Client {
|
||||||
})
|
})
|
||||||
.stderr.on("data", (ret: Buffer) => {
|
.stderr.on("data", (ret: Buffer) => {
|
||||||
const data = ansiHandle(ret.toString());
|
const data = ansiHandle(ret.toString());
|
||||||
output.push(data);
|
output += data;
|
||||||
this.logger.info(`[${this.connConf.host}][error]: ` + data);
|
this.logger.error(`[${this.connConf.host}][error]: ` + data);
|
||||||
});
|
});
|
||||||
//保证windows下正常退出
|
//保证windows下正常退出
|
||||||
const exit = "\r\nexit\r\n";
|
const exit = "\r\nexit\r\n";
|
||||||
|
@ -269,7 +283,7 @@ export class SshClient {
|
||||||
|
|
||||||
async getIsCmd(options: { connectConf: SshAccess }) {
|
async getIsCmd(options: { connectConf: SshAccess }) {
|
||||||
const { connectConf } = options;
|
const { connectConf } = options;
|
||||||
return await this._call({
|
return await this._call<boolean>({
|
||||||
connectConf,
|
connectConf,
|
||||||
callable: async (conn: AsyncSsh2Client) => {
|
callable: async (conn: AsyncSsh2Client) => {
|
||||||
return await this.isCmd(conn);
|
return await this.isCmd(conn);
|
||||||
|
@ -285,7 +299,7 @@ export class SshClient {
|
||||||
* Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\cmd.exe"
|
* Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\cmd.exe"
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
async exec(options: { connectConf: SshAccess; script: string | Array<string>; env?: any }): Promise<string[]> {
|
async exec(options: { connectConf: SshAccess; script: string | Array<string>; env?: any }): Promise<string> {
|
||||||
let { script } = options;
|
let { script } = options;
|
||||||
const { connectConf } = options;
|
const { connectConf } = options;
|
||||||
|
|
||||||
|
@ -337,7 +351,7 @@ export class SshClient {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async shell(options: { connectConf: SshAccess; script: string | Array<string> }): Promise<string[]> {
|
async shell(options: { connectConf: SshAccess; script: string | Array<string> }): Promise<string> {
|
||||||
let { script } = options;
|
let { script } = options;
|
||||||
const { connectConf } = options;
|
const { connectConf } = options;
|
||||||
if (_.isArray(script)) {
|
if (_.isArray(script)) {
|
||||||
|
@ -361,7 +375,7 @@ export class SshClient {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _call(options: { connectConf: SshAccess; callable: any }): Promise<string[]> {
|
async _call<T = any>(options: { connectConf: SshAccess; callable: (conn: AsyncSsh2Client) => Promise<T> }): Promise<T> {
|
||||||
const { connectConf, callable } = options;
|
const { connectConf, callable } = options;
|
||||||
const conn = new AsyncSsh2Client(connectConf, this.logger);
|
const conn = new AsyncSsh2Client(connectConf, this.logger);
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<pi-status-show :status="item.node.status?.result" type="icon"></pi-status-show>
|
<pi-status-show :status="item.node.status?.result" type="icon"></pi-status-show>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="pi-task-view-logs" :class="item.node.id" style="overflow: auto">
|
<div class="pi-task-view-logs" :class="'id-' + item.node.id" style="overflow: auto">
|
||||||
<template v-for="(logItem, index) of item.logs" :key="index">
|
<template v-for="(logItem, index) of item.logs" :key="index">
|
||||||
<span :class="logItem.color"> {{ logItem.time }}</span> <span>{{ logItem.content }}</span>
|
<span :class="logItem.color"> {{ logItem.time }}</span> <span>{{ logItem.content }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -84,11 +84,14 @@ export default {
|
||||||
return node.logs.value.length;
|
return node.logs.value.length;
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
let el = document.querySelector(`.pi-task-view-logs.${node.node.id}`);
|
let el = document.querySelector(`.pi-task-view-logs.id-${node.node.id}`);
|
||||||
|
if (!el) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
//判断当前是否在底部
|
//判断当前是否在底部
|
||||||
const isBottom = el ? el.scrollHeight - el.scrollTop === el.clientHeight : true;
|
const isBottom = el ? el.scrollHeight - el.scrollTop === el.clientHeight : true;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
el = document.querySelector(`.pi-task-view-logs.${node.node.id}`);
|
el = document.querySelector(`.pi-task-view-logs.id-${node.node.id}`);
|
||||||
//如果在底部则滚动到底部
|
//如果在底部则滚动到底部
|
||||||
if (isBottom && el) {
|
if (isBottom && el) {
|
||||||
el?.scrollTo({
|
el?.scrollTo({
|
||||||
|
|
|
@ -79,6 +79,18 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin {
|
||||||
})
|
})
|
||||||
filePrefix: string = defaultFilePrefix;
|
filePrefix: string = defaultFilePrefix;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: '附加上传文件',
|
||||||
|
value: true,
|
||||||
|
component: {
|
||||||
|
name: 'a-switch',
|
||||||
|
vModel: 'checked',
|
||||||
|
placeholder: `是否备份上传的头像等文件`,
|
||||||
|
},
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
withUpload = true;
|
||||||
|
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
title: '删除过期备份',
|
title: '删除过期备份',
|
||||||
component: {
|
component: {
|
||||||
|
@ -101,7 +113,6 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin {
|
||||||
this.logger.error('数据库文件不存在:', dbPath);
|
this.logger.error('数据库文件不存在:', dbPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbTmpFilename = `${this.filePrefix}.${dayjs().format('YYYYMMDD.HHmmss')}.sqlite`;
|
const dbTmpFilename = `${this.filePrefix}.${dayjs().format('YYYYMMDD.HHmmss')}.sqlite`;
|
||||||
const dbZipFilename = `${dbTmpFilename}.zip`;
|
const dbZipFilename = `${dbTmpFilename}.zip`;
|
||||||
const tempDir = path.resolve(os.tmpdir(), 'certd_backup');
|
const tempDir = path.resolve(os.tmpdir(), 'certd_backup');
|
||||||
|
@ -118,6 +129,12 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin {
|
||||||
const stream = fs.createReadStream(dbTmpPath);
|
const stream = fs.createReadStream(dbTmpPath);
|
||||||
// 使用流的方式添加文件内容
|
// 使用流的方式添加文件内容
|
||||||
zip.file(dbTmpFilename, stream, { binary: true, compression: 'DEFLATE' });
|
zip.file(dbTmpFilename, stream, { binary: true, compression: 'DEFLATE' });
|
||||||
|
|
||||||
|
const uploadDir = path.resolve('data', 'upload');
|
||||||
|
if (this.withUpload && fs.existsSync(uploadDir)) {
|
||||||
|
zip.folder(uploadDir);
|
||||||
|
}
|
||||||
|
|
||||||
const content = await zip.generateAsync({ type: 'nodebuffer' });
|
const content = await zip.generateAsync({ type: 'nodebuffer' });
|
||||||
|
|
||||||
await fs.promises.writeFile(dbZipPath, content);
|
await fs.promises.writeFile(dbZipPath, content);
|
||||||
|
|
Loading…
Reference in New Issue