Compare commits

...

25 Commits

Author SHA1 Message Date
xiaojunnuo 0d97ad67c5 chore: 1 2026-06-17 00:39:04 +08:00
xiaojunnuo c94a5537a3 chore: 1 2026-06-17 00:26:50 +08:00
xiaojunnuo d88dfc197e chore: 1 2026-06-17 00:04:24 +08:00
xiaojunnuo f709c05c0d build(ui): 重构Dockerfile支持多架构 2026-06-16 23:41:40 +08:00
xiaojunnuo 2c609da7a1 build: release 2026-06-16 23:15:03 +08:00
xiaojunnuo d770c7bd08 chore: 1 2026-06-16 23:09:33 +08:00
xiaojunnuo b9ccd4a8a0 chore: 1 2026-06-16 22:30:21 +08:00
xiaojunnuo 8875e8059f chore: ncurses-base 退格键 ^H的问题 2026-06-16 00:27:32 +08:00
xiaojunnuo 6490366c68 chore: 1 2026-06-16 00:21:24 +08:00
xiaojunnuo c278946771 chore(nettest): 修复跨平台端口测试匹配逻辑 2026-06-16 00:15:42 +08:00
xiaojunnuo 1562d9de36 chore: 1 2026-06-15 23:39:47 +08:00
xiaojunnuo 5bb0990abb chore: 1 2026-06-15 23:32:32 +08:00
xiaojunnuo bfd3cacc68 perf: 优化ACME账号字段的选择提示 2026-06-15 23:30:13 +08:00
xiaojunnuo c7e1163d59 chore: 改回dnsResultOrder不使用默认ipv4 2026-06-15 23:26:20 +08:00
xiaojunnuo fba7aeb71b Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-06-15 23:24:46 +08:00
xiaojunnuo c66a2bd77a perf: 基础镜像改成node:22-trixie-slim,对网络兼容性更好 2026-06-15 23:24:06 +08:00
xiaojunnuo ed58ae3c53 perf: 优化阿里云API网关增加翻页查询 2026-06-15 10:02:02 +08:00
xiaojunnuo 194463bea9 perf: dns默认ipv4first 2026-06-14 23:18:37 +08:00
xiaojunnuo 260f5ae777 fix: 修复jdk证书格式的问题 2026-06-14 23:18:17 +08:00
xiaojunnuo e85d824337 chore:1 2026-06-14 22:21:07 +08:00
xiaojunnuo e17fc39709 chore: 1 2026-06-14 22:14:59 +08:00
xiaojunnuo da9b297b12 chore: 1 2026-06-14 21:46:06 +08:00
xiaojunnuo 807dfcd57a chore: 尝试使用 node:22.22-trixie-slim 2026-06-14 21:43:18 +08:00
xiaojunnuo 0a410db52a build: publish 2026-06-14 21:30:16 +08:00
xiaojunnuo 4501095106 build: trigger build image 2026-06-14 21:30:05 +08:00
27 changed files with 128 additions and 61 deletions
+1 -1
View File
@@ -181,7 +181,7 @@ https://certd.handfree.work/
> [50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product) https://app.handfree.work/subject/#/app/certd/product > [50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product) https://app.handfree.work/subject/#/app/certd/product
> handfree.work是Certd官方激活码购买平台 > app.handfree.work是Certd官方激活码购买平台
专业版、商业版特权对比 专业版、商业版特权对比
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
### Bug Fixes
* 修复设置里面不显示tab页签,导致某些页面需要点击查询按钮才有数据出来的bug ([c1b5a35](https://github.com/certd/certd/commit/c1b5a35f90a7d4b41397717b5c27905bc68e1bfb))
### Performance Improvements
* **plugin:** 增加 Dynadot DNS and access 插件 ([a3a215b](https://github.com/certd/certd/commit/a3a215b7ae2b90efcde91270ce4165bbfe77dc64))
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11) ## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
### Bug Fixes ### Bug Fixes
+1 -1
View File
@@ -6,7 +6,7 @@ Certd 是一款开源、免费、全自动申请和部署更新SSL证书的工
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具 关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具
| 官方开源地址: | | |  |官方开源地址: |
| ---- | ---- | | ---- | ---- |
| [Github](https://github.com/certd/certd)| ![](https://img.shields.io/github/stars/certd/certd?logo=github) | | [Github](https://github.com/certd/certd)| ![](https://img.shields.io/github/stars/certd/certd?logo=github) |
| [Gitee](https://gitee.com/certd/certd) | ![](https://gitee.com/certd/certd/badge/star.svg?theme=dark) | | [Gitee](https://gitee.com/certd/certd) | ![](https://gitee.com/certd/certd/badge/star.svg?theme=dark) |
+1 -2
View File
@@ -15,8 +15,7 @@
"vitepress-plugin-lightbox": "^1.0.2" "vitepress-plugin-lightbox": "^1.0.2"
}, },
"scripts": { "scripts": {
"start": "lerna bootstrap --hoist", "start": "cd ./packages/ui/certd-server && pnpm start",
"start:server": "cd ./packages/ui/certd-server && pnpm start",
"devb": "lerna run dev-build", "devb": "lerna run dev-build",
"i-all": "lerna link && lerna exec npm install ", "i-all": "lerna link && lerna exec npm install ",
"publish": "pnpm run prepublishOnly2 && lerna publish --force-publish=pro/plus-core --conventional-commits && pnpm run afterpublishOnly ", "publish": "pnpm run prepublishOnly2 && lerna publish --force-publish=pro/plus-core --conventional-commits && pnpm run afterpublishOnly ",
+1 -1
View File
@@ -76,5 +76,5 @@
"bugs": { "bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues" "url": "https://github.com/publishlab/node-acme-client/issues"
}, },
"gitHead": "6cbd62977731a3b72c42b5f88c49500631da0a46" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+1 -1
View File
@@ -52,5 +52,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "6cbd62977731a3b72c42b5f88c49500631da0a46" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+1 -1
View File
@@ -49,5 +49,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "6cbd62977731a3b72c42b5f88c49500631da0a46" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+1 -1
View File
@@ -28,5 +28,5 @@
"prettier": "^2.8.8", "prettier": "^2.8.8",
"tslib": "^2.8.1" "tslib": "^2.8.1"
}, },
"gitHead": "6cbd62977731a3b72c42b5f88c49500631da0a46" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+1 -1
View File
@@ -35,5 +35,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "6cbd62977731a3b72c42b5f88c49500631da0a46" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+1 -1
View File
@@ -60,5 +60,5 @@
"fetch" "fetch"
] ]
}, },
"gitHead": "6cbd62977731a3b72c42b5f88c49500631da0a46" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+1 -1
View File
@@ -36,5 +36,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "6cbd62977731a3b72c42b5f88c49500631da0a46" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+1 -1
View File
@@ -69,5 +69,5 @@
"typeorm": "^0.3.11", "typeorm": "^0.3.11",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "6cbd62977731a3b72c42b5f88c49500631da0a46" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
@@ -169,11 +169,10 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
}; };
setGlobalProxy(opts); setGlobalProxy(opts);
setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders)); setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders));
if (privateSetting.dnsResultOrder) { if (privateSetting.dnsResultOrder) {
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any); dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
} }
if (privateSetting.pipelineMaxRunningCount) { if (privateSetting.pipelineMaxRunningCount) {
executorQueue.setMaxRunningCount(privateSetting.pipelineMaxRunningCount); executorQueue.setMaxRunningCount(privateSetting.pipelineMaxRunningCount);
} }
+1 -1
View File
@@ -50,5 +50,5 @@
"typeorm": "^0.3.11", "typeorm": "^0.3.11",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "6cbd62977731a3b72c42b5f88c49500631da0a46" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+1 -1
View File
@@ -41,5 +41,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "6cbd62977731a3b72c42b5f88c49500631da0a46" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+1 -1
View File
@@ -61,5 +61,5 @@
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "6cbd62977731a3b72c42b5f88c49500631da0a46" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
@@ -132,7 +132,7 @@ export class CertConverter {
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
} }
await this.exec(`keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${jksPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" `); await this.exec(`keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${jksPassword}" -destkeystore ${jksPath} -deststoretype JKS -deststorepass "${jksPassword}" `);
fs.unlinkSync(p12Path); fs.unlinkSync(p12Path);
const fileBuffer = fs.readFileSync(jksPath); const fileBuffer = fs.readFileSync(jksPath);
+37 -30
View File
@@ -1,12 +1,9 @@
FROM node:22-alpine3.21 AS builder # 根据目标平台选择基础镜像:amd64/arm64 用 trixie-slimarm/v7 没有 trixie-slim 发布,回退到 alpine
FROM --platform=linux/amd64 node:22-trixie-slim AS base-amd64
# RUN apk add build-base FROM --platform=linux/arm64 node:22-trixie-slim AS base-arm64
# RUN wget -O - https://github.com/jemalloc/jemalloc/releases/download/5.3.0/jemalloc-5.3.0.tar.bz2 | tar -xj && \ FROM --platform=linux/arm/v7 node:22-alpine AS base-arm-v7
# cd jemalloc-5.3.0 && \
# ./configure && \
# make && \
# make install
FROM base-${TARGETARCH}${TARGETVARIANT:+-}${TARGETVARIANT} AS builder
WORKDIR /workspace/ WORKDIR /workspace/
COPY . /workspace/ COPY . /workspace/
@@ -14,34 +11,44 @@ COPY . /workspace/
# https://pnpm.io/zh/migration # https://pnpm.io/zh/migration
RUN npm install -g pnpm@10.33.4 RUN npm install -g pnpm@10.33.4
#RUN cd /workspace/certd-client && pnpm install && npm run build
RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf
RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker
# RUN cd /workspace/certd-server && \
# pnpm install --ignore-scripts && \
# yes | pnpm approve-builds && \
# pnpm rebuild && \
# npm run build-on-docker
FROM base-${TARGETARCH}${TARGETVARIANT:+-}${TARGETVARIANT}
FROM node:22-alpine3.21
EXPOSE 7001 EXPOSE 7001
EXPOSE 7002 EXPOSE 7002
# 安装jemalloc内存分配器,优化内存占用 -- 基本没用,反而更高了 # 根据基础镜像发行版选择包管理器
# COPY --from=builder /usr/local/lib/libjemalloc.so.2 /usr/local/lib/ # trixie-slim -> apt-get, alpine -> apk
# ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 RUN if [ -f /etc/debian_version ]; then \
apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
gnupg \
wget \
openssl \
netcat-openbsd \
iputils-ping \
dnsutils \
iproute2 \
&& wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor | tee /usr/share/keyrings/adoptium.gpg > /dev/null \
&& echo "deb [signed-by=/usr/share/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb bookworm main" | tee /etc/apt/sources.list.d/adoptium.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends temurin-8-jre \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*; \
elif [ -f /etc/alpine-release ]; then \
apk add --no-cache \
openssl \
openjdk8-jre; \
else \
echo "Unsupported base image"; exit 1; \
fi
RUN apk add --no-cache openssl
RUN apk add --no-cache openjdk8
RUN apk add --no-cache gcompat
WORKDIR /app/ WORKDIR /app/
COPY --from=builder /workspace/certd-server/ /app/
COPY ./patch/ssh2/*.js /app/node_modules/.pnpm/node_modules/ssh2/lib/protocol/
ENV TERM=xterm
ENV LEGO_VERSION=4.30.1 ENV LEGO_VERSION=4.30.1
ENV LEGO_DOWNLOAD_DIR=/app/tools/lego ENV LEGO_DOWNLOAD_DIR=/app/tools/lego
@@ -57,14 +64,14 @@ RUN ARCH=$(uname -m) && \
elif [ "$ARCH" = "aarch64" ]; then \ elif [ "$ARCH" = "aarch64" ]; then \
wget -O $LEGO_DOWNLOAD_DIR/lego_v${LEGO_VERSION}_linux_arm64.tar.gz https://github.com/go-acme/lego/releases/download/v${LEGO_VERSION}/lego_v${LEGO_VERSION}_linux_arm64.tar.gz; \ wget -O $LEGO_DOWNLOAD_DIR/lego_v${LEGO_VERSION}_linux_arm64.tar.gz https://github.com/go-acme/lego/releases/download/v${LEGO_VERSION}/lego_v${LEGO_VERSION}_linux_arm64.tar.gz; \
else \ else \
# armv7 不支持lego 不要再尝试了
echo "Unsupported architecture: $ARCH"; \ echo "Unsupported architecture: $ARCH"; \
fi fi
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
ENV NODE_ENV=production ENV NODE_ENV=production
ENV MIDWAY_SERVER_ENV=production ENV MIDWAY_SERVER_ENV=production
COPY --from=builder /workspace/certd-server/ /app/
COPY ./patch/ssh2/*.js /app/node_modules/.pnpm/node_modules/ssh2/lib/protocol/
CMD ["node", "--optimize-for-size", "./bootstrap.js"] CMD ["node", "--optimize-for-size", "./bootstrap.js"]
+2 -2
View File
@@ -119,8 +119,6 @@
"log4js": "^6.7.1", "log4js": "^6.7.1",
"lru-cache": "^11.0.1", "lru-cache": "^11.0.1",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"mwts": "^1.3.0",
"mwtsc": "^1.15.1",
"mysql2": "^3.14.0", "mysql2": "^3.14.0",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
@@ -149,6 +147,8 @@
"xml2js": "^0.6.2" "xml2js": "^0.6.2"
}, },
"devDependencies": { "devDependencies": {
"mwts": "^1.3.0",
"mwtsc": "^1.15.1",
"@midwayjs/mock": "3.20.11", "@midwayjs/mock": "3.20.11",
"@types/ali-oss": "^6.16.11", "@types/ali-oss": "^6.16.11",
"@types/cache-manager": "^4.0.6", "@types/cache-manager": "^4.0.6",
@@ -0,0 +1,29 @@
import assert from "assert";
import esmock from "esmock";
describe("NetTestService.telnet", () => {
it("treats nc succeeded output as a successful port connection", async () => {
const { NetTestService } = await esmock("./nettest-service.js", {
"@certd/basic": {
http: {},
logger: {
error() {},
},
utils: {
sp: {
async spawn() {
return "Connection to baidu.com (110.242.74.102) 443 port [tcp/*] succeeded!";
},
},
},
},
});
const service = new NetTestService();
(service as any).isWindows = () => false;
const result = await service.telnet("baidu.com", 443);
assert.equal(result.success, true);
assert.equal(result.message, "端口连接测试成功");
});
});
@@ -40,7 +40,11 @@ export class NetTestService {
}); });
// 判断测试是否成功 // 判断测试是否成功
const success = this.isWindows() ? output.includes("端口连接成功") : output.includes(" open"); const normalizedOutput = output.toLowerCase();
const success = this.isWindows()
? normalizedOutput.includes("端口连接成功")
: normalizedOutput.includes("succeeded!") || normalizedOutput.includes("connected to") || normalizedOutput.includes(" open");
// 处理结果 // 处理结果
return { return {
@@ -227,7 +227,11 @@ export class DeployCertToAliyunApig extends AbstractTaskPlugin {
domain: item.name, domain: item.name,
}; };
}); });
return optionsUtils.buildGroupOptions(options, this.certDomains); const records = optionsUtils.buildGroupOptions(options, this.certDomains);
return {
list: records,
total: res?.data?.totalSize || 0,
}
} }
async onGetRegionList(data: any) { async onGetRegionList(data: any) {
@@ -1,4 +1,4 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { AbstractTaskPlugin, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { optionsUtils } from "@certd/basic"; import { optionsUtils } from "@certd/basic";
@@ -70,6 +70,8 @@ export class DeployCertToAliyunApiGateway extends AbstractTaskPlugin {
watches: ["regionEndpoint", "accessId"], watches: ["regionEndpoint", "accessId"],
required: true, required: true,
single: true, single: true,
pager: true,
search: true,
}) })
) )
groupId!: string; groupId!: string;
@@ -122,7 +124,7 @@ export class DeployCertToAliyunApiGateway extends AbstractTaskPlugin {
this.logger.info(`设置${domainName}证书成功:`, ret.RequestId); this.logger.info(`设置${domainName}证书成功:`, ret.RequestId);
} }
async onGetGroupList(data: any) { async onGetGroupList(data: PageSearch) {
if (!this.accessId) { if (!this.accessId) {
throw new Error("请选择Access授权"); throw new Error("请选择Access授权");
} }
@@ -131,23 +133,36 @@ export class DeployCertToAliyunApiGateway extends AbstractTaskPlugin {
} }
const access = await this.getAccess<AliyunAccess>(this.accessId); const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = access.getClient(this.regionEndpoint); const client = access.getClient(this.regionEndpoint);
const pager = new Pager(data)
const res = await client.doRequest({ const res = await client.doRequest({
// 接口名称 // 接口名称
action: "DescribeApiGroups", action: "DescribeApiGroups",
// 接口版本 // 接口版本
version: "2016-07-14", version: "2016-07-14",
data: {}, data: {
query: {
GroupName: data.searchKey,
PageNumber: pager.pageNo,
PageSize: pager.pageSize,
},
},
}); });
const list = res?.ApiGroupAttributes?.ApiGroupAttribute; const list = res?.ApiGroupAttributes?.ApiGroupAttribute;
if (!list || list.length === 0) { if (!list || list.length === 0) {
throw new Error("没有数据,您可以手动输入API网关ID"); throw new Error("没有数据,您可以手动输入API网关ID");
} }
return list.map((item: any) => { const records = list.map((item: any) => {
return { return {
value: item.GroupId, value: item.GroupId,
label: `${item.GroupName}<${item.GroupId}>`, label: `${item.GroupName}<${item.GroupId}>`,
}; };
}); });
return {
list: records,
total: res?.TotalCount || 0,
}
} }
async onGetDomainList(data: any) { async onGetDomainList(data: any) {
@@ -138,7 +138,7 @@ export class AcmeAccountAccess extends BaseAccess {
eabHmacKey = ""; eabHmacKey = "";
@AccessInput({ @AccessInput({
title: "ACME账号信息", title: "生成ACME账号",
component: { component: {
name: "refresh-input", name: "refresh-input",
action: "GenerateAccount", action: "GenerateAccount",
@@ -149,7 +149,7 @@ export class AcmeAccountAccess extends BaseAccess {
}, },
col: { span: 24 }, col: { span: 24 },
required: true, required: true,
helper: "请生成ACME账号,账号一旦生成不允许修改", helper: "请点击右边按钮生成ACME账号,账号一旦生成不允许修改",
encrypt: true, encrypt: true,
mergeScript: ` mergeScript: `
return { return {
@@ -377,7 +377,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
type: "acmeAccount", type: "acmeAccount",
}, },
required: false, required: false,
helper: "请选择颁发机构对应的ACME账号", helper: "直接本地生成,无需外部注册\n点击选择按钮->添加->填写邮箱->生成账号即可",
mergeScript: ` mergeScript: `
return { return {
show: ctx.compute(({form})=>{ show: ctx.compute(({form})=>{
+1 -1
View File
@@ -1 +1 @@
23:57 21:30
+1 -1
View File
@@ -1 +1 @@
00:17 23:15