Compare commits

...
14 Commits
39 changed files with 934 additions and 93 deletions
+58
View File
@@ -0,0 +1,58 @@
name: publish-atomgit
on:
push:
branches: ['v2-dev']
paths:
- "trigger/publish.trigger"
# workflow_run:
# workflows: [ "deploy-demo" ]
# types:
# - completed
# schedule:
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
# - cron: '17 19 * * *'
permissions:
contents: read
packages: write
jobs:
publish-atomgit:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
lfs: true
- name: get_certd_version
id: get_certd_version
uses: actions/github-script@v6
with:
result-encoding: string
script: |
const fs = require('fs');
const path = require('path');
const pnpmWorkspace = "./pnpm-workspace.yaml";
fs.unlinkSync(pnpmWorkspace)
const jsonFilePath = "./packages/ui/certd-server/package.json";
const jsonContent = fs.readFileSync(jsonFilePath, 'utf-8');
const pkg = JSON.parse(jsonContent)
console.log("certd_version:",pkg.version);
return pkg.version
- run: |
npm install -g pnpm
pnpm install
npm run build
working-directory: ./packages/ui/certd-client
- name: publish_to_atomgit
id: publish_to_atomgit
run: |
rm -rf ./packages/ui/certd-client/dist/**/*.gz
zip -r ui.zip ./packages/ui/certd-client/dist
export ATOMGIT_TOKEN=${{ secrets.ATOMGIT_TOKEN }}
pnpm install
npm run publish_to_atomgit
working-directory: ./
+10 -1
View File
@@ -117,7 +117,6 @@ jobs:
# registry.cn-shenzhen.aliyuncs.com/handsfree/certd-agent:${{steps.get_certd_version.outputs.result}}
# greper/certd-agent:latest
# greper/certd-agent:${{steps.get_certd_version.outputs.result}}
- name: deploy-certd-doc
uses: tyrrrz/action-http-request@master
with:
@@ -131,3 +130,13 @@ jobs:
Content-Type: application/json
retry-count: 3
retry-delay: 5000
- name: publish_to_atomgit
id: publish_to_atomgit
run: |
rm -rf ./packages/ui/certd-client/dist/**/*.gz
zip -r ui.zip ./packages/ui/certd-client/dist
export ATOMGIT_TOKEN=${{ secrets.ATOMGIT_TOKEN }}
pnpm install
npm run publish_to_atomgit
working-directory: ./
+1 -1
View File
@@ -3,4 +3,4 @@ prefer-workspace-packages=true
better_sqlite3_binary_host=https://registry.npmmirror.com/-/binary/better-sqlite3
better_sqlite3_binary_host_mirror=https://registry.npmmirror.com/-/binary/better-sqlite3
better-sqlite3_binary_host=https://registry.npmmirror.com/-/binary/better-sqlite3
better-sqlite3_binary_host_mirror=https://registry.npmmirror.com/-/binary/better-sqlite3
better-sqlite3_binary_host_mirror=https://registry.npmmirror.com/-/binary/better-sqlite3
+6 -4
View File
@@ -8,6 +8,7 @@
"@lerna-lite/publish": "^3.9.3",
"@lerna-lite/run": "^3.9.3",
"@lerna-lite/version": "^3.9.3",
"axios": "^1.9.0",
"medium-zoom": "^1.1.0",
"vitepress": "^2.0.0-alpha.4",
"vitepress-plugin-lightbox": "^1.0.2"
@@ -27,8 +28,8 @@
"prepublishOnly1": "npm run check && lerna run build ",
"prepublishOnly2": "npm run check && npm run before-build && lerna run build ",
"before-build": "npm run transform-sql && cd ./packages/core/basic && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"",
"deploy1": "node --experimental-json-modules deploy.js ",
"check": "node --experimental-json-modules publish-check.js",
"deploy1": "node --experimental-json-modules ./scripts/deploy.js ",
"check": "node --experimental-json-modules ./scripts/publish-check.js",
"init": "lerna run build",
"init:dev": "lerna run build",
"docs:dev": "vitepress dev docs",
@@ -36,11 +37,12 @@
"docs:preview": "vitepress preview docs",
"pub": "echo 1",
"dev": "pnpm run -r --parallel compile ",
"release": "time /t >trigger/release.trigger && git add trigger/release.trigger && git commit -m \"build: release\" && git push"
"release": "time /t >trigger/release.trigger && git add trigger/release.trigger && git commit -m \"build: release\" && git push",
"publish_to_atomgit": "node --experimental-json-modules ./scripts/publish-atomgit.js",
"get_version": "node --experimental-json-modules ./scripts/version.js"
},
"license": "AGPL-3.0",
"dependencies": {
"axios": "^1.7.7",
"copyfiles": "^2.4.1",
"lodash-es": "^4.17.21",
"typescript": "^5.4.2"
+1 -1
View File
@@ -21,7 +21,7 @@
"@certd/basic": "^1.37.16",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
"axios": "^1.9.0",
"debug": "^4.3.5",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.5",
+1 -1
View File
@@ -18,7 +18,7 @@
},
"dependencies": {
"async-lock": "^1.4.1",
"axios": "^1.7.2",
"axios": "^1.9.0",
"dayjs": "^1.11.7",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.5",
+1 -1
View File
@@ -14,7 +14,7 @@
"pub": "npm publish"
},
"dependencies": {
"axios": "^1.7.2",
"axios": "^1.9.0",
"rimraf": "^5.0.5",
"rollup": "^3.7.4"
},
+16 -1
View File
@@ -1,4 +1,13 @@
FROM node:22-alpine AS builder
RUN apk add build-base
RUN wget -O - https://github.com/jemalloc/jemalloc/releases/download/5.3.0/jemalloc-5.3.0.tar.bz2 | tar -xj && \
cd jemalloc-5.3.0 && \
./configure && \
make && \
make install
WORKDIR /workspace/
COPY . /workspace/
# armv7 目前只能用node18 pnpm9不支持node18,所以pnpm只能用8.15.7版本
@@ -14,6 +23,12 @@ RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker
FROM node:22-alpine
EXPOSE 7001
EXPOSE 7002
# 安装jemalloc内存分配器,优化内存占用
COPY --from=builder /usr/local/lib/libjemalloc.so.2 /usr/local/lib/
ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so.2
RUN apk add --no-cache openssl
RUN apk add --no-cache openjdk8
WORKDIR /app/
@@ -21,7 +36,7 @@ COPY --from=builder /workspace/certd-server/ /app/
COPY ./patch/ssh2/*.js /app/node_modules/.pnpm/node_modules/ssh2/lib/protocol/
ENV LEGO_VERSION=4.22.2
ENV LEGO_VERSION=4.30.1
ENV LEGO_DOWNLOAD_DIR=/app/tools/lego
RUN mkdir -p $LEGO_DOWNLOAD_DIR
+1 -1
View File
@@ -50,7 +50,7 @@
"@vueuse/core": "^10.11.0",
"ant-design-vue": "^4.2.6",
"async-validator": "^4.2.5",
"axios": "^1.7.2",
"axios": "^1.9.0",
"axios-mock-adapter": "^1.22.0",
"base64-js": "^1.5.1",
"better-scroll": "^2.5.1",
@@ -138,6 +138,7 @@ const getOptions = async () => {
onError(err: any) {
hasError.value = true;
message.value = `获取选项出错:${err.message}`;
optionsRef.value = [];
},
showErrorNotify: false,
}
@@ -159,6 +159,7 @@ export default {
selectedCount: "Selected {count} items",
batchDelete: "Batch Delete",
batchForceRerun: "Force Rerun",
batchRerun: "Rerun",
applyCertificate: "Apply for Certificate",
pipelineExecutionRecords: "Pipeline Execution Records",
confirm: "Confirm",
@@ -165,6 +165,7 @@ export default {
selectedCount: "已选择 {count} 项",
batchDelete: "批量删除",
batchForceRerun: "强制重新运行",
batchRerun: "重新运行",
applyCertificate: "申请证书",
pipelineExecutionRecords: "流水线执行记录",
confirm: "确认",
@@ -59,7 +59,7 @@ export default {
vip_group_priority: "可加VIP群,您的需求将优先实现",
unlimited_site_certificate_monitoring: "站点证书监控无限制",
more_notification_methods: "更多通知方式",
plugins_fully_open: "插件全开放,群等更多插件",
plugins_fully_open: "插件全开放,群等更多插件",
click_to_get_7_day_trial: "点击获取7天试用",
years: "年",
afdian_support_vip: "新用户开通永久专业版立享50优惠券",
@@ -117,11 +117,11 @@ export async function BatchDelete(pipelineIds: number[]): Promise<void> {
data: { ids: pipelineIds },
});
}
export async function BatchRerun(pipelineIds: number[]): Promise<void> {
export async function BatchRerun(pipelineIds: number[], force: boolean): Promise<void> {
return await request({
url: apiPrefix + "/batchRerun",
method: "post",
data: { ids: pipelineIds },
data: { ids: pipelineIds, force },
});
}
@@ -0,0 +1,86 @@
<template>
<fs-button icon="icon-park-outline:replay-music" class="need-plus" type="link" :text="t('certd.batchRerun')" @click="openFormDialog"></fs-button>
</template>
<script setup lang="ts">
import { compute, dict, useFormWrapper } from "@fast-crud/fast-crud";
import * as api from "../api";
import { useSettingStore } from "/@/store/settings";
import { useI18n } from "/src/locales";
import { computed } from "vue";
const { t } = useI18n();
const props = defineProps<{
selectedRowKeys: any[];
}>();
const emit = defineEmits<{
change: any;
}>();
async function batchUpdateRequest(form: any) {
await api.BatchRerun(props.selectedRowKeys, form.force ?? false);
emit("change");
}
const { openCrudFormDialog } = useFormWrapper();
const settingStore = useSettingStore();
async function openFormDialog() {
settingStore.checkPlus();
const crudOptions: any = {
columns: {
force: {
title: "运行模式",
form: {
value: false,
required: true,
helper: "强制重新运行:清除流水线所有状态,全部重新执行\n普通运行:成功过的任务会跳过",
component: {
name: "fs-dict-radio",
vModel: "value",
style: {
marginTop: "5px",
},
dict: dict({
data: [
{
label: "普通运行",
value: false,
},
{
label: "强制重新运行",
value: true,
},
],
}),
},
},
},
},
form: {
mode: "edit",
initialForm: {
clear: false,
},
//@ts-ignore
async doSubmit({ form }) {
await batchUpdateRequest(form);
},
col: {
span: 22,
},
labelCol: {
style: {
width: "100px",
},
},
wrapper: {
title: t("certd.batchRerun"),
width: 600,
},
},
} as any;
await openCrudFormDialog({ crudOptions });
}
</script>
@@ -8,7 +8,7 @@
<div class="batch-actions-inner">
<span>{{ t("certd.selectedCount", { count: selectedRowKeys.length }) }}</span>
<fs-button icon="ion:trash-outline" class="color-red" type="link" :text="t('certd.batchDelete')" @click="batchDelete"></fs-button>
<fs-button icon="icon-park-outline:replay-music" class="need-plus" type="link" :text="t('certd.batchForceRerun')" @click="batchRerun"></fs-button>
<batch-rerun :selected-row-keys="selectedRowKeys" @change="batchFinished"></batch-rerun>
<change-group :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-group>
<change-notification :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-notification>
<change-trigger :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-trigger>
@@ -28,6 +28,7 @@ import { dict, useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import ChangeGroup from "./components/change-group.vue";
import ChangeTrigger from "./components/change-trigger.vue";
import BatchRerun from "./components/batch-rerun.vue";
import { Modal, notification } from "ant-design-vue";
import * as api from "./api";
import { useI18n } from "/src/locales";
@@ -75,20 +76,6 @@ function batchDelete() {
},
});
}
function batchRerun() {
settingStore.checkPlus();
Modal.confirm({
title: "确认强制重新运行吗",
content: "确定要强制重新运行选中流水线吗?(20条一批执行)",
async onOk() {
await api.BatchRerun(selectedRowKeys.value);
notification.success({ message: "任务已提交" });
await crudExpose.doRefresh();
selectedRowKeys.value = [];
},
});
}
</script>
<style lang="less">
.batch-actions {
+1 -1
View File
@@ -1 +1 @@
LEGO_VERSION=4.22.2
LEGO_VERSION=4.30.1
+1 -1
View File
@@ -78,7 +78,7 @@
"@ucloud-sdks/ucloud-sdk-js": "^0.2.4",
"@volcengine/openapi": "^1.28.1",
"ali-oss": "^6.21.0",
"axios": "^1.7.2",
"axios": "^1.9.0",
"basic-ftp": "^5.0.5",
"bcryptjs": "^2.4.3",
"better-sqlite3": "^11.1.2",
@@ -192,8 +192,8 @@ export class PipelineController extends CrudController<PipelineService> {
}
@Post('/batchRerun', { summary: Constants.per.authOnly })
async batchRerun(@Body('ids') ids: number[]) {
await this.service.batchRerun(ids, this.getUserId());
async batchRerun(@Body('ids') ids: number[], @Body('force') force: boolean) {
await this.service.batchRerun(ids, this.getUserId(), force);
return this.ok({});
}
}
@@ -49,7 +49,7 @@ export class AutoZPrint {
}
setInterval(() => {
const mu = process.memoryUsage();
logger.info(`rss:${format(mu.rss)},heapUsed: ${format(mu.heapUsed)},heapTotal: ${format(mu.heapTotal)},external: ${format(mu.external)}`);
logger.info(`rss:${format(mu.rss)},heapUsed: ${format(mu.heapUsed)},heapTotal: ${format(mu.heapTotal)},external: ${format(mu.external)},arrayBuffers: ${format(mu.arrayBuffers)}`);
}, 20000);
}
@@ -210,7 +210,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
if (item.error) {
errorMessage += `${item.ipAddress}${item.error} \n`;
} else if (item.certExpiresTime !== certExpiresTime) {
errorMessage += `${item.ipAddress}:与主站证书过期时间不一致; \n`;
errorMessage += `${item.ipAddress}:与主站证书过期时间不一致(主站:${dayjs(certExpiresTime).format("YYYY-MM-DD")}IP${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}) \n`;
} else {
errorCount--;
}
@@ -175,7 +175,11 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
await this.update(updateData);
logger.info(`测试站点ip成功: id=${updateData.id},ip=${entity.ipAddress},expiresTime=${updateData.certExpiresTime}`)
return updateData
return {
...updateData,
ipAddress: entity.ipAddress,
}
} catch (e) {
logger.error("check site ip error", e);
@@ -898,7 +898,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
}
async batchRerun(ids: number[], userId: any) {
async batchRerun(ids: number[], userId: any, force: boolean) {
if (!isPlus()) {
throw new NeedVIPException("此功能需要升级专业版");
}
@@ -919,18 +919,20 @@ export class PipelineService extends BaseService<PipelineEntity> {
ids = list.map(item => item.id);
//异步执行
this.startBatchRerun(ids);
this.startBatchRerun(userId,ids, force);
}
async startBatchRerun(ids: number[]) {
//20条一批
const batchSize = 20;
for (let i = 0; i < ids.length; i += batchSize) {
const batchIds = ids.slice(i, i + batchSize);
const batchPromises = batchIds.map(async (id) => {
await this.run(id, null, "ALL");
startBatchRerun(userId:number, ids: number[], force: boolean) {
for (const id of ids) {
executorQueue.addTask(userId,{
task: async () => {
if (force) {
await this.run(id, null, "ALL");
} else {
await this.run(id, null);
}
}
});
await Promise.all(batchPromises);
}
}
@@ -41,4 +41,5 @@ export * from './plugin-xinnetconnet/index.js'
export * from './plugin-oauth/index.js'
export * from './plugin-cmcc/index.js'
export * from './plugin-template/index.js'
export * from './plugin-ucloud/index.js'
export * from './plugin-ucloud/index.js'
export * from './plugin-goedge/index.js'
@@ -162,9 +162,6 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
this.logger.error("清理站点[${siteId}]证书失败",e)
}
}
}
}
@@ -0,0 +1,264 @@
import {AccessInput, BaseAccess, IsAccess} from "@certd/pipeline";
import {HttpRequestConfig} from "@certd/basic";
import { CertInfo, CertReader } from "@certd/plugin-cert";
import dayjs from "dayjs";
/**
*/
@IsAccess({
name: "goedge",
title: "GoEdge授权",
icon: "fa-solid:leaf:#6C6BF6",
order: 100
})
export class GoEdgeAccess extends BaseAccess {
@AccessInput({
title: "系统地址",
component: {
name: "a-input",
vModel: "value"
},
helper:"例如:http://yourdomain.com:8002 需要在API节点配置中开启HTTP访问地址",
encrypt: false,
required: true
})
endpoint!: string;
@AccessInput({
title: "用户类型",
component: {
name: "a-select",
vModel: "value",
options: [
{
label: "用户",
value: "user"
},
{
label: "管理员",
value: "admin"
}
]
},
encrypt: false,
required: true
})
userType!: string;
@AccessInput({
title: "accessKeyId",
helper:`用户AccessKey: 在”平台用户-用户-详情-AccessKey” 或 商业版的“访问控制” 中创建。
AccessKey---AccessKey `,
component: {
name: "a-input",
vModel: "value"
},
encrypt: false,
required: true
})
accessKeyId!: string;
@AccessInput({
title: "accessKey",
component: {
name: "a-input",
vModel: "value"
},
encrypt: true,
required: true
})
accessKey!: string;
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "点击测试接口是否正常"
})
testRequest = true;
accessToken: {expiresAt:number,token:string}
async onTestRequest() {
await this.getCertList({pageSize:1});
return "ok"
}
/**
*
* @param req "id": 600,
"isOn": true,
"name": "124.220.225.222",
"description": "",
"certData": null,
"keyData": null,
"serverName": "",
"isCA": false,
"isACME": false,
"timeBeginAt": 1763856000,
"timeEndAt": 1771718399,
"dnsNames": [
"124.220.225.222" //domain
],
"commonNames": [
"ZeroSSL ECC Domain Secure Site CA",
"USERTrust ECC Certification Authority"
],
"ocsp": null,
"ocspExpiresAt": 0,
"ocspError": ""
* @returns
*/
async getCertList(req:{pageNo?:number,pageSize?:number,query?:string,onlyUser?:boolean,userId?:number}){
const pageNo = req.pageNo ?? 1;
const pageSize = req.pageSize ?? 20;
const body:any = {
keyword: req.query??"",
offset: (pageNo-1)*pageSize,
size: pageSize,
}
if (req.onlyUser){
body["onlyUser"] = true;
}
if (req.userId){
body["userId"] = req.userId;
}
const countRes = await this.doRequest({
url: `/SSLCertService/countSSLCerts`,
method: "POST",
data:body
});
const total = countRes.count || 9999;
const res = await this.doRequest({
url: `/SSLCertService/listSSLCerts`,
method: "POST",
data:body
});
// this.ctx.logger.info("getCertList",JSON.stringify(res));
const sslCertsJSON = this.ctx.utils.hash.base64Decode(res.sslCertsJSON) || "[]";
const sslCerts = JSON.parse(sslCertsJSON) as CertInfo[];
return {
total: total,
list: sslCerts || [],
pageNo: pageNo,
pageSize: pageSize
}
}
async doCertReplace(req:{certId:number,cert:CertInfo}){
const res = await this.doRequest({
url: `/SSLCertService/findEnabledSSLCertConfig`,
method: "POST",
data: {
sslCertId: req.certId,
}
});
const sslCertJSON = this.ctx.utils.hash.base64Decode(res.sslCertJSON) || "{}";
const sslCert = JSON.parse(sslCertJSON) ;
const certReader = new CertReader(req.cert);
const dnsNames = certReader.getAllDomains()
// /product/sslcenter/{id}
return await this.doRequest({
url: `/SSLCertService/updateSSLCert`,
method: "POST",
data: {
sslCertId: req.certId,
certData: this.ctx.utils.hash.base64(req.cert.crt),
keyData: this.ctx.utils.hash.base64(req.cert.key),
isOn: sslCert.isOn,
name: sslCert.name || certReader.buildCertName(),
description: sslCert.description || "upload by certd",
serverName: sslCert.serverName,
timeBeginAt: certReader.detail.notBefore.getTime()/1000,
timeEndAt: certReader.detail.notAfter.getTime()/1000,
dnsNames: dnsNames,
/**
* // 是否启用
bool isOn;
// 名称
string name;
// 描述(备注)
string description;
string serverName;
bool isCA;
bytes certData;
bytes keyData;
int64 timeBeginAt;
int64 timeEndAt;
[]string dnsNames;
[]string commonNames;
*/
}
});
}
async getToken(){
// /APIAccessTokenService/getAPIAccessToken
if (this.accessToken && this.accessToken.expiresAt >dayjs().unix()){
return this.accessToken;
}
const res = await this.doRequest({
url: "/APIAccessTokenService/getAPIAccessToken",
method: "POST",
data: {
type: this.userType,
"accessKeyId": this.accessKeyId,
"accessKey": this.accessKey,
}
});
this.accessToken = res;
return res;
}
async doRequest(req:HttpRequestConfig){
const headers: Record<string,string> = {}
if(!req.url.endsWith("/getAPIAccessToken")){
if (!this.accessToken || this.accessToken.expiresAt < dayjs().unix()){
await this.getToken();
}
headers["X-Edge-Access-Token"] = this.accessToken.token;
}
let endpoint = this.endpoint;
if (endpoint.endsWith("/")){
endpoint = endpoint.slice(0,-1);
}
const res = await this.ctx.http.request({
url: req.url,
baseURL: endpoint,
method: req.method|| "POST",
data: req.data,
params: req.params,
headers:{
...headers,
...req.headers
},
// httpProxy: this.httpProxy||undefined,
});
if (res.code === 200) {
return res.data;
}
throw new Error(res.message || res);
}
}
new GoEdgeAccess();
@@ -0,0 +1,2 @@
export * from "./plugins/index.js";
export * from "./access.js";
@@ -0,0 +1 @@
export * from './plugin-refresh-cert.js'
@@ -0,0 +1,131 @@
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { GoEdgeAccess } from "../access.js";
@IsTaskPlugin({
//命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名
name: "GoEdgeRefreshCert",
title: "GoEdge-更新证书",
desc: "GoEdge",
icon: "fa-solid:leaf:#6C6BF6",
//插件分组
group: pluginGroups.cdn.key,
needPlus: false,
default: {
//默认值配置照抄即可
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
})
//类名规范,跟上面插件名称(name)一致
export class GoEdgeRefreshCert extends AbstractTaskPlugin {
//证书选择,此项必须要有
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
}
// required: true, // 必填
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
//授权选择框
@TaskInput({
title: "GoEdge授权",
component: {
name: "access-selector",
type: "goedge" //固定授权类型
},
required: true //必填
})
accessId!: string;
//
@TaskInput({
title: "用户id",
component: {
name: "a-input-number",
vModel: "value"
},
helper:"用于查询用户证书,点击用户详情->浏览器地址中userId值,如:/users/user?userId=1\n如果为空,则查询管理员证书",
required: false //必填
})
userId!: number;
@TaskInput(
createRemoteSelectInputDefine({
title: "证书Id",
helper: "要更新的GoEdge证书id",
pager:true,
search:true,
action: GoEdgeRefreshCert.prototype.onGetCertList.name
})
)
certList!: number[];
//插件实例化时执行的方法
async onInstance() {
}
//插件执行方法
async execute(): Promise<void> {
const access = await this.getAccess<GoEdgeAccess>(this.accessId);
for (const item of this.certList) {
this.logger.info(`----------- 开始更新证书:${item}`);
await access.doCertReplace({
certId: item,
cert: this.cert
});
this.logger.info(`----------- 更新证书${item}成功`);
}
this.logger.info("部署完成");
}
async onGetCertList(req: PageSearch = {}) {
const access = await this.getAccess<GoEdgeAccess>(this.accessId);
const pageNo = req.pageNo ?? 1;
const pageSize = req.pageSize ?? 100;
const res = await access.getCertList({
pageNo,
pageSize,
query: req.searchKey,
userId: this.userId,
onlyUser: this.userId !== undefined
});
const total = res.total;
const list = res.list;
if (!list || list.length === 0) {
throw new Error("没有找到证书,请先在控制台上传一次证书且关联站点");
}
const options = list.map((item: any) => {
return {
label: `${item.name}<${item.id}>`,
value: item.id,
domain: item.dnsNames || [],
title: item.dnsNames?.join(",") || ""
};
});
return {
list: this.ctx.utils.options.buildGroupOptions(options, this.certDomains),
total: total,
pageNo: pageNo,
pageSize: pageSize
};
}
}
//实例化一下,注册插件
new GoEdgeRefreshCert();
@@ -56,7 +56,7 @@ export class ServerChan3Notification extends BaseNotification {
data: {
text: body.title,
desp: body.content + '\n\n[查看详情](' + body.url + ')',
tags: this.tags.join('|'),
tags: this.tags?.join('|') || undefined,
short: this.short,
},
skipSslVerify: this.skipSslVerify,
@@ -19,10 +19,8 @@ export class RainyunAccess extends BaseAccess {
title: "ApiKey",
component: {
placeholder: "api-key",
component: {
name: "a-input",
vModel: "value"
}
name: "a-input",
vModel: "value"
},
helper:"https://app.rainyun.com/account/settings/api-key",
encrypt: true,
@@ -21,6 +21,7 @@ export class CommonEmailTemplateProvider extends BaseEmailTemplateProvider imple
{label:"URL",value:"url"}
]
},
helper:"html格式下${content}建议使用pre标签包裹:例如:&lt;pre&gt;${content}&lt;/pre&gt;",
col: { span: 24 },
})
paramIntro = "";
+109 -28
View File
@@ -8,9 +8,6 @@ importers:
.:
dependencies:
axios:
specifier: ^1.7.7
version: 1.9.0(debug@4.4.1)
copyfiles:
specifier: ^2.4.1
version: 2.4.1
@@ -33,6 +30,9 @@ importers:
'@lerna-lite/version':
specifier: ^3.9.3
version: 3.12.3(@lerna-lite/publish@3.12.3)(@lerna-lite/run@3.12.3)(@types/node@22.15.18)(typescript@5.8.3)
axios:
specifier: ^1.9.0
version: 1.9.0(debug@4.4.1)
medium-zoom:
specifier: ^1.1.0
version: 1.1.0
@@ -55,7 +55,7 @@ importers:
specifier: ^3.0.5
version: 3.0.6
axios:
specifier: ^1.7.2
specifier: ^1.9.0
version: 1.9.0(debug@4.4.1)
debug:
specifier: ^4.3.5
@@ -128,7 +128,7 @@ importers:
specifier: ^1.4.1
version: 1.4.1
axios:
specifier: ^1.7.2
specifier: ^1.9.0
version: 1.9.0(debug@4.4.1)
dayjs:
specifier: ^1.11.7
@@ -214,7 +214,7 @@ importers:
version: link:../basic
'@certd/plus-core':
specifier: ^1.37.16
version: link:../../pro/plus-core
version: 1.37.16
dayjs:
specifier: ^1.11.7
version: 1.11.13
@@ -286,7 +286,7 @@ importers:
packages/libs/lib-huawei:
dependencies:
axios:
specifier: ^1.7.2
specifier: ^1.9.0
version: 1.9.0(debug@4.4.1)
rimraf:
specifier: ^5.0.5
@@ -462,7 +462,7 @@ importers:
version: link:../../plugins/plugin-lib
'@certd/plus-core':
specifier: ^1.37.16
version: link:../../pro/plus-core
version: 1.37.16
'@midwayjs/cache':
specifier: 3.14.0
version: 3.14.0
@@ -783,19 +783,19 @@ importers:
packages/pro/commercial-core:
dependencies:
'@certd/basic':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../../core/basic
'@certd/lib-server':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../../libs/lib-server
'@certd/pipeline':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../../core/pipeline
'@certd/plugin-plus':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../plugin-plus
'@certd/plus-core':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../plus-core
'@midwayjs/core':
specifier: 3.20.11
@@ -880,22 +880,22 @@ importers:
specifier: ^1.0.2
version: 1.0.3
'@certd/basic':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../../core/basic
'@certd/lib-k8s':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../../libs/lib-k8s
'@certd/pipeline':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../../core/pipeline
'@certd/plugin-cert':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../../plugins/plugin-cert
'@certd/plugin-lib':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../../plugins/plugin-lib
'@certd/plus-core':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../plus-core
ali-oss:
specifier: ^6.21.0
@@ -998,7 +998,7 @@ importers:
packages/pro/plus-core:
dependencies:
'@certd/basic':
specifier: ^1.37.16
specifier: ^1.37.15
version: link:../../core/basic
dayjs:
specifier: ^1.11.7
@@ -1131,7 +1131,7 @@ importers:
specifier: ^4.2.5
version: 4.2.5
axios:
specifier: ^1.7.2
specifier: ^1.9.0
version: 1.9.0(debug@4.4.1)
axios-mock-adapter:
specifier: ^1.22.0
@@ -1490,7 +1490,7 @@ importers:
version: link:../../core/basic
'@certd/commercial-core':
specifier: ^1.37.16
version: link:../../pro/commercial-core
version: 1.37.16(better-sqlite3@11.10.0)(encoding@0.1.13)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3))
'@certd/cv4pve-api-javascript':
specifier: ^8.4.2
version: 8.4.2
@@ -1520,10 +1520,10 @@ importers:
version: link:../../plugins/plugin-lib
'@certd/plugin-plus':
specifier: ^1.37.16
version: link:../../pro/plugin-plus
version: 1.37.16(encoding@0.1.13)
'@certd/plus-core':
specifier: ^1.37.16
version: link:../../pro/plus-core
version: 1.37.16
'@huaweicloud/huaweicloud-sdk-cdn':
specifier: ^3.1.120
version: 3.1.149
@@ -1579,7 +1579,7 @@ importers:
specifier: ^6.21.0
version: 6.23.0
axios:
specifier: ^1.7.2
specifier: ^1.9.0
version: 1.9.0(debug@4.4.1)
basic-ftp:
specifier: ^5.0.5
@@ -2879,9 +2879,18 @@ packages:
'@better-scroll/zoom@2.5.1':
resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==}
'@certd/commercial-core@1.37.16':
resolution: {integrity: sha512-JH6wlx88ljh2m2QiTJ1dvI/Up3jjOTEQqD/x3cfVmXHYfT//QFIL/O6cRIFKLWxN5Q+0k4YHmCDw/DI+WWdwlQ==}
'@certd/cv4pve-api-javascript@8.4.2':
resolution: {integrity: sha512-udGce7ewrVl4DmZvX+17PjsnqsdDIHEDatr8QP0AVrY2p+8JkaSPW4mXCKiLGf82C9K2+GXgT+qNIqgW7tfF9Q==}
'@certd/plugin-plus@1.37.16':
resolution: {integrity: sha512-lzxyHyq9K+sDDFlPICs/0/W1nODvxTwk7N+qI1gskTbNpPt5CY1b47sPhwLb6oRTeW2/8iVdmt283GECFEAXXg==}
'@certd/plus-core@1.37.16':
resolution: {integrity: sha512-PAyDMlLy/r5kx03A6pH4ICUAdPR9WZroGOx/ivJJo0Auk07uHU/kyXMfSCB6LytUHH0tOy36K+zuTCTdGr2NOA==}
'@certd/vue-js-cron-core@6.0.3':
resolution: {integrity: sha512-kqzoAMhYz9j6FGNWEODRYtt4NpUEUwjpkU89z5WVg2tCtOcI5VhwyUGOd8AxiBCRfd6PtXvzuqw85PaOps9wrQ==}
@@ -15437,12 +15446,84 @@ snapshots:
dependencies:
'@better-scroll/core': 2.5.1
'@certd/commercial-core@1.37.16(better-sqlite3@11.10.0)(encoding@0.1.13)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3))':
dependencies:
'@certd/basic': link:packages/core/basic
'@certd/lib-server': link:packages/libs/lib-server
'@certd/pipeline': link:packages/core/pipeline
'@certd/plugin-plus': 1.37.16(encoding@0.1.13)
'@certd/plus-core': 1.37.16
'@midwayjs/core': 3.20.11
'@midwayjs/koa': 3.20.13
'@midwayjs/logger': 3.4.2
'@midwayjs/typeorm': 3.20.11
alipay-sdk: 4.14.0
dayjs: 1.11.13
typeorm: 0.3.24(better-sqlite3@11.10.0)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.8.3))
wechatpay-node-v3: 2.2.1
transitivePeerDependencies:
- '@google-cloud/spanner'
- '@sap/hana-client'
- babel-plugin-macros
- better-sqlite3
- encoding
- hdb-pool
- ioredis
- mongodb
- mssql
- mysql2
- oracledb
- pg
- pg-native
- pg-query-stream
- proxy-agent
- redis
- reflect-metadata
- sql.js
- sqlite3
- supports-color
- ts-node
- typeorm-aurora-data-api-driver
'@certd/cv4pve-api-javascript@8.4.2':
dependencies:
debug: 4.4.1(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
'@certd/plugin-plus@1.37.16(encoding@0.1.13)':
dependencies:
'@alicloud/pop-core': 1.8.0
'@baiducloud/sdk': 1.0.3
'@certd/basic': link:packages/core/basic
'@certd/lib-k8s': link:packages/libs/lib-k8s
'@certd/pipeline': link:packages/core/pipeline
'@certd/plugin-cert': link:packages/plugins/plugin-cert
'@certd/plugin-lib': link:packages/plugins/plugin-lib
'@certd/plus-core': 1.37.16
ali-oss: 6.23.0
baidu-aip-sdk: 4.16.16
basic-ftp: 5.0.5
cos-nodejs-sdk-v5: 2.14.7
crypto-js: 4.2.0
dayjs: 1.11.13
form-data: 4.0.2
https-proxy-agent: 7.0.6
js-yaml: 4.1.0
jsencrypt: 3.3.2
jsrsasign: 11.1.0
qiniu: 7.14.0
tencentcloud-sdk-nodejs: 4.1.112(encoding@0.1.13)
transitivePeerDependencies:
- encoding
- proxy-agent
- supports-color
'@certd/plus-core@1.37.16':
dependencies:
'@certd/basic': link:packages/core/basic
dayjs: 1.11.13
'@certd/vue-js-cron-core@6.0.3':
dependencies:
mustache: 4.2.0
@@ -21295,13 +21376,13 @@ snapshots:
resolve: 1.22.10
semver: 6.3.1
eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8):
eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8):
dependencies:
eslint: 7.32.0
prettier: 2.8.8
prettier-linter-helpers: 1.0.0
optionalDependencies:
eslint-config-prettier: 8.10.0(eslint@7.32.0)
eslint-config-prettier: 8.10.0(eslint@8.57.0)
eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8):
dependencies:
@@ -23715,7 +23796,7 @@ snapshots:
eslint: 7.32.0
eslint-config-prettier: 8.10.0(eslint@7.32.0)
eslint-plugin-node: 11.1.0(eslint@7.32.0)
eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8)
eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8)
execa: 5.1.1
inquirer: 7.3.3
json5: 2.2.3
+1 -1
View File
@@ -1,7 +1,7 @@
import http from 'axios'
import fs from 'fs'
//读取 packages/core/pipline/package.json的版本号
import {default as packageJson} from './packages/core/pipeline/package.json' assert { type: "json" };
import {default as packageJson} from '../packages/core/pipeline/package.json' assert { type: "json" };
const certdVersion = packageJson.version
console.log("certdVersion", certdVersion)
+181
View File
@@ -0,0 +1,181 @@
import fs from 'fs'
import axios from 'axios'
const AtomgitAccessToken = process.env.ATOMGIT_TOKEN
// CHANGELOG.md
const changelog = fs.readFileSync('./CHANGELOG.md', 'utf8')
// 解析CHANGELOG.md
let lines = changelog.split('\n')
const versionLineIndex = lines.findIndex(line => line.startsWith('## '))
const versionLine = lines[versionLineIndex]
// ## [1.37.16](https://github.com/certd/certd/compare/v1.37.15...v1.37.16) (2025-12-15)
const versionTitle = versionLine.match(/\[(.*?)\]/)[1]
const contentStart = versionLineIndex + 1
lines = lines.slice(contentStart)
const contentEnd = lines.findIndex(line => {
return line.startsWith('## ')
})
const content = lines.slice(0, contentEnd).join('\n')
console.log("-------title------/n")
console.log(versionTitle)
console.log("-------content------/n")
console.log(content)
/**
* 创建仓库Release
POST
https://api.atomgit.com/api/v5/repos/:owner/:repo/releases
Request
Path Parameters
owner
string
required
仓库所属空间地址企业组织或个人的地址path
repo
string
required
仓库路径
Query Parameters
access_token
string
required
用户授权码
application/json
Body
tag_name
string
required
tag名称
name
string
required
release名称
body
string
required
release描述
target_commitish
string
分支名称或者commit SHA如果tag不存在需要新建tag则传入该参数如果不传入该参数则为默认分支的最新提交
*/
// 创建release
async function createRelease() {
const response = await axios.request({
method: 'POST',
url: `https://api.atomgit.com/api/v5/repos/certd/certd/releases`,
headers: {
"Content-Type": "application/json"
},
params: {
access_token: AtomgitAccessToken
},
data: {
tag_name: `v${versionTitle}`,
name: `v${versionTitle}`,
body: content,
target_commitish: 'v2'
},
}
)
console.log("createRelease success")
return response.data
}
/**
* 获取Release附件上传地址
GET
https://api.atomgit.com/api/v5/repos/:owner/:repo/releases/:tag/upload_url
Request
Path Parameters
owner
string
required
仓库所属空间地址企业组织或个人的地址path
repo
string
required
仓库路径
tag
string
required
tag名称
Query Parameters
access_token
string
required
用户授权码
file_name
string
required
要上传的文件名称
Responses
200
Response Headers
application/json
Schema
Example (auto)
Schema
url
string
required
上传的地址使用put请求
headers
object
*/
async function getUploadUrl() {
const response = await axios.request({
method: 'GET',
url: `https://api.atomgit.com/api/v5/repos/certd/certd/releases/v${versionTitle}/upload_url`,
headers: {
"Content-Type": "application/json"
},
params: {
access_token: AtomgitAccessToken,
file_name: `ui-${versionTitle}.zip`
},
}
)
console.log("getUploadUrl success:",response.data?.url)
return response.data // {url: string, headers: any}
}
async function uploadFile(url, headers, data) {
const response = await axios.request({
method: 'PUT',
url,
headers,
data,
}
)
return response.data
}
async function publishToAtomgit() {
const release = await createRelease()
const uploadUrl = await getUploadUrl()
const fileName = `ui.zip`
const fileData = fs.createReadStream(fileName)
const contentLength = fs.statSync(fileName).size
uploadUrl.headers['Content-Length'] = contentLength
const response = await uploadFile(uploadUrl.url, uploadUrl.headers, fileData)
console.log("uploadFile success:")
console.log("publishToAtomgit success")
}
publishToAtomgit()
+3
View File
@@ -0,0 +1,3 @@
import fs from 'fs'
const pkg = JSON.parse(fs.readFileSync('./packages/ui/certd-server/package.json'))
console.log(pkg.version)
+23 -9
View File
@@ -18,10 +18,10 @@ fi
# fi
# find ./packages -mindepth 1 -maxdepth 1 -type d ! -name 'ui' -exec rm -rf {} +
# echo "删除成功"
echo "修改 pnpm-workspace.yaml"
cat > pnpm-workspace.yaml << EOF
packages:
- 'packages/ui/**'
- 'packages/ui/certd-server'
EOF
@@ -43,17 +43,31 @@ $SUDO_CMD npm install -g pnpm --registry https://registry.npmmirror.com
echo "安装依赖"
$SUDO_CMD pnpm install --registry https://registry.npmmirror.com
echo "开始构建"
echo "构建certd-client"
export NODE_OPTIONS=--max-old-space-size=32768
cd packages/ui/certd-client
$SUDO_CMD_E pnpm run build
cp -r dist/* ../certd-server/public
# 获取版本号
version=$(node --experimental-json-modules ./scripts/version.js)
echo "当前版本号为: $version"
echo "开始构建"
cd packages/ui/certd-server
echo "构建certd-server"
cd ../certd-server
$SUDO_CMD_E pnpm run build
echo "构建完成"
echo "下载前端ui"
# 如果zip有了就不下载
if [ -f ui-$version.zip ]; then
echo "ui-$version.zip 已经存在,不需要下载"
else
# 下载之前清理一下
rm -rf ui-*.zip
# https://atomgit.com/certd/certd/releases/download/v1.37.16/ui-1.37.16.zip
# 判断是否下载失败
wget https://atomgit.com/certd/certd/releases/download/v$version/ui-$version.zip
# 覆盖解压缩
unzip -o ui-$version.zip -d ./public
echo "启动服务"
# 前台运行
+1
View File
@@ -0,0 +1 @@
6