Compare commits

...

49 Commits

Author SHA1 Message Date
xiaojunnuo
844fd4358c v1.26.16 2024-10-30 11:13:20 +08:00
xiaojunnuo
79b41954f9 build: prepare to build 2024-10-30 11:12:00 +08:00
xiaojunnuo
5a4a7814e1 chore: 1 2024-10-30 11:11:16 +08:00
xiaojunnuo
c4630aaf7b chore: 1 2024-10-30 10:35:14 +08:00
xiaojunnuo
d35ad50254 chore: 暂时移除jks 2024-10-30 10:24:53 +08:00
xiaojunnuo
b1cc6f2a9c chore: 2024-10-30 10:18:05 +08:00
xiaojunnuo
0d94329940 chore: 2024-10-30 10:18:04 +08:00
xiaojunnuo
04150e1c0a chore: 2024-10-30 10:18:03 +08:00
xiaojunnuo
385757b54b chore: 证书支持jks格式 2024-10-30 10:18:01 +08:00
xiaojunnuo
ccfe922c30 chore: 2024-10-30 01:42:41 +08:00
xiaojunnuo
b3e0546f78 chore: 1 2024-10-30 01:42:40 +08:00
xiaojunnuo
aaaf8d7db3 fix: 修复lego No help topic for 错误 2024-10-29 23:08:40 +08:00
xiaojunnuo
b1b2cd088b perf: 支持白山云cdn部署 2024-10-29 22:18:45 +08:00
xiaojunnuo
d1ea61debc chore: 2024-10-29 18:15:38 +08:00
xiaojunnuo
12cebea29e chore: 2024-10-29 18:13:24 +08:00
xiaojunnuo
81a3fdbc29 perf: 支持华为云cdn 2024-10-29 13:59:20 +08:00
xiaojunnuo
fea4669d82 chore: 2024-10-29 09:54:42 +08:00
xiaojunnuo
241f9ed383 build: publish 2024-10-28 21:59:45 +08:00
xiaojunnuo
3d06ce444c build: trigger build image 2024-10-28 21:59:31 +08:00
xiaojunnuo
06fed944c9 v1.26.15 2024-10-28 21:58:30 +08:00
xiaojunnuo
5d225c2583 build: prepare to build 2024-10-28 21:56:17 +08:00
xiaojunnuo
e626367a06 chore: 2024-10-28 21:55:37 +08:00
xiaojunnuo
5c992c3214 Merge remote-tracking branch 'origin/v2-dev' into v2-dev 2024-10-28 21:34:14 +08:00
xiaojunnuo
5575c83970 perf: 授权加密支持解密查看 2024-10-28 18:20:10 +08:00
xiaojunnuo
6dabad76ba fix: 顶部菜单变...的bug 2024-10-28 17:27:10 +08:00
xiaojunnuo
1c656f8b90 chore: 2024-10-28 16:44:10 +08:00
xiaojunnuo
51b6fed468 perf: 默认证书更新时间设置为35天,增加腾讯云删除过期证书插件,可以避免腾讯云过期证书邮件 2024-10-28 15:31:45 +08:00
xiaojunnuo
f92d918a1e perf: 重置管理员密码同时启用管理员账户,避免之前禁用了,重置密码还是登录不进去 2024-10-28 10:26:14 +08:00
xiaojunnuo
3e290f057f chore: 2024-10-27 11:03:09 +08:00
xiaojunnuo
13eb0231ac chore: 2024-10-27 03:13:09 +08:00
xiaojunnuo
6089f0aa8e chore: 2024-10-27 03:02:20 +08:00
xiaojunnuo
b0c4050567 chore: 2024-10-27 02:58:26 +08:00
xiaojunnuo
3f9244542d build: publish 2024-10-27 02:56:49 +08:00
xiaojunnuo
70b6098ee5 build: trigger build image 2024-10-27 02:56:36 +08:00
xiaojunnuo
1656e91296 v1.26.14 2024-10-27 02:55:31 +08:00
xiaojunnuo
5b7df9c175 build: prepare to build 2024-10-27 02:53:58 +08:00
xiaojunnuo
8d8600aaa8 chore: 2024-10-27 02:52:45 +08:00
xiaojunnuo
54d136cc6a perf: 顶部菜单自定义 2024-10-27 02:51:56 +08:00
xiaojunnuo
661293c189 perf: 用户管理优化头像上传 2024-10-27 00:52:26 +08:00
xiaojunnuo
d10d42e206 perf: 禁用readonly用户 2024-10-27 00:04:02 +08:00
xiaojunnuo
b780eab5f5 chore: 2024-10-26 23:56:13 +08:00
xiaojunnuo
315e43746b perf: 限制其他用户流水线数量 2024-10-26 23:54:49 +08:00
xiaojunnuo
526c48450b fix: 修复启动时自签证书无法保存的bug 2024-10-26 23:24:26 +08:00
xiaojunnuo
abd2dcf2e8 fix: 修复阿里云部署大杀器报插件_还未注册错误的bug 2024-10-26 23:08:10 +08:00
xiaojunnuo
87defa569c chore: 2024-10-26 22:11:10 +08:00
xiaojunnuo
b4db5518db chore: 2024-10-26 20:33:05 +08:00
xiaojunnuo
a50b635424 chore: 2024-10-26 20:32:09 +08:00
xiaojunnuo
40a794f624 build: publish 2024-10-26 20:31:58 +08:00
xiaojunnuo
6876790374 build: trigger build image 2024-10-26 20:31:45 +08:00
103 changed files with 1381 additions and 301 deletions

View File

@@ -0,0 +1,79 @@
name: build-image-for-test
on:
push:
branches: ['v2-dev']
paths:
- "build-dev.trigger"
# schedule:
# - # 国际时间 19:17 执行北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
# - cron: '17 19 * * *'
permissions:
contents: read
jobs:
build-certd-image:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: v2-dev
- 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
# - name: Use Node.js
# uses: actions/setup-node@v4
# with:
# node-version: 18
# cache: 'npm'
# working-directory: ./packages/ui/certd-client
- run: |
npm install -g pnpm@8.15.7
pnpm install
npm run build
working-directory: ./packages/ui/certd-client
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to aliyun container Registry
uses: docker/login-action@v3
with:
registry: registry.cn-shenzhen.aliyuncs.com
username: ${{ secrets.aliyun_cs_username }}
password: ${{ secrets.aliyun_cs_password }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.dockerhub_username }}
password: ${{ secrets.dockerhub_password }}
- name: Build default platforms
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true
context: ./packages/ui/
tags: |
registry.cn-shenzhen.aliyuncs.com/handsfree/certd-dev:latest
greper/certd-dev:latest

View File

@@ -17,6 +17,8 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: get_certd_version
id: get_certd_version

View File

@@ -2,8 +2,8 @@ name: deploy-demo
on:
push:
branches: ['v2-dev']
# paths:
# - "deploy.trigger"
paths:
- "deploy.trigger"
workflow_run:
workflows: [ "build-image" ]
types:
@@ -21,6 +21,9 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: v2-dev
- name: get_certd_version
id: get_certd_version
uses: actions/github-script@v6
@@ -38,7 +41,6 @@ jobs:
with:
time: '10' # for 60 seconds
- name: deploy-certd-demo
id: request
uses: tyrrrz/action-http-request@master
with:
url: http://flow-openapi.aliyun.com/pipeline/webhook/lzCzlGrLCOHQaTMMt0mG
@@ -53,11 +55,12 @@ jobs:
retry-delay: 5000
- name: deploy-certd-doc
id: request
uses: tyrrrz/action-http-request@master
with:
url: http://flow-openapi.aliyun.com/pipeline/webhook/IiSxLDp9aOhgDUxJPytv
method: POST
body: |
{}
headers: |
Content-Type: application/json
retry-count: 3

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout work repo # 1. 检出当前仓库(certd-sync-work)
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set git user # 2. 给git命令设置用户名和邮箱,↙↙↙ 改成你的name和email

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout work repo # 1. 检出当前仓库(certd-sync-work)
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set git user # 2. 给git命令设置用户名和邮箱,↙↙↙ 改成你的name和email

View File

@@ -3,6 +3,43 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
### Bug Fixes
* 修复lego No help topic for 错误 ([aaaf8d7](https://github.com/certd/certd/commit/aaaf8d7db34896cf8f2ff8f12eec1ab0cae58f0f))
### Performance Improvements
* 支持白山云cdn部署 ([b1b2cd0](https://github.com/certd/certd/commit/b1b2cd088b684eda764962abd61754c26a204d1c))
* 支持华为云cdn ([81a3fdb](https://github.com/certd/certd/commit/81a3fdbc29b71f380762008cc151493ec97458f9))
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
### Bug Fixes
* 顶部菜单变...的bug ([6dabad7](https://github.com/certd/certd/commit/6dabad76baba96be0f8af36a3fbfb9f5182aecf1))
### Performance Improvements
* 默认证书更新时间设置为35天增加腾讯云删除过期证书插件可以避免腾讯云过期证书邮件 ([51b6fed](https://github.com/certd/certd/commit/51b6fed468eaa6f28ce4497ce303ace1a52abb96))
* 授权加密支持解密查看 ([5575c83](https://github.com/certd/certd/commit/5575c839705f6987ad2bdcd33256b0962c6a9c6a))
* 重置管理员密码同时启用管理员账户,避免之前禁用了,重置密码还是登录不进去 ([f92d918](https://github.com/certd/certd/commit/f92d918a1e28e29b794ad4754661ea760c18af46))
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
### Bug Fixes
* 修复阿里云部署大杀器报插件_还未注册错误的bug ([abd2dcf](https://github.com/certd/certd/commit/abd2dcf2e85a545321bae6451406d081f773b132))
* 修复启动时自签证书无法保存的bug ([526c484](https://github.com/certd/certd/commit/526c48450bcd37b3ccded9b448f17de8140bdc6e))
### Performance Improvements
* 顶部菜单自定义 ([54d136c](https://github.com/certd/certd/commit/54d136cc6ae122f7c891b7a5c7232fe5de8e5cb5))
* 禁用readonly用户 ([d10d42e](https://github.com/certd/certd/commit/d10d42e20619bb55a50d636b8867ff33db4e3b4b))
* 限制其他用户流水线数量 ([315e437](https://github.com/certd/certd/commit/315e43746baf01682737f82e41579237a48409af))
* 用户管理优化头像上传 ([661293c](https://github.com/certd/certd/commit/661293c189a3abf3cdc953b5225192372f57930d))
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
### Bug Fixes

View File

@@ -13,7 +13,7 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
* 全自动部署更新证书目前支持部署到主机、部署到阿里云、腾讯云等目前已支持30+部署插件)
* 支持通配符域名/泛域名,支持多个域名打到一个证书上
* 邮件通知
* 私有化部署,保障数据安全
* 私有化部署,数据保存本地镜像由Github Actions构建过程公开透明
* 支持sqlitepostgresql数据库

1
build-dev.trigger Normal file
View File

@@ -0,0 +1 @@
21:59

View File

@@ -1 +1 @@
00:01
21:59

View File

@@ -28,18 +28,14 @@ services:
# 设置环境变量即可自定义certd配置
# 配置项见: packages/ui/certd-server/src/config/config.default.ts
# 配置规则: certd_ + 配置项, 点号用_代替
# ↓↓↓↓ ------------------------------------ 这里可以设置http代理
#- HTTPS_PROXY=http://xxxxxx:xx
#- HTTP_PROXY=http://xxxxxx:xx
# ↓↓↓↓ ----------------------------- 如果忘记管理员密码可以设置为true重启之后管理员密码将改成123456然后请及时修改回false
- certd_system_resetAdminPasswd=false
# ↓↓↓↓ -------------------------- 如果设置为true启动后所有配置了cron的流水线任务都将被立即触发一次
- certd_cron_immediateTriggerOnce=false
# ↓↓↓↓ -------------------------------- 配置证书和key则表示https方式启动使用https协议访问https://your.domain:7001
#- certd_koa_key=./data/ssl/cert.key
#- certd_koa_cert=./data/ssl/cert.crt
# ↓↓↓↓ -------------------------------- 默认同时启动httpshttps访问地址https://your.domain:7002
#- certd_https_key=./data/ssl/cert.key
#- certd_https_cert=./data/ssl/cert.crt
#- certd_https_enabled=true
#- certd_https_port=7002
-
# ↓↓↓↓ ------------------------------- 使用postgresql数据库
# - certd_flyway_scriptDir=./db/migration-pg # 升级脚本目录
# - certd_typeorm_dataSource_default_type=postgres # 数据库类型

View File

@@ -5,7 +5,7 @@ import lightbox from "vitepress-plugin-lightbox";
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "Certd",
description: "Certd帮助文档,Certd是一款开源免费的全自动SSL证书管理工具自动证书申请、更新、续期通配符证书泛域名证书申请证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔。",
description: "Certd帮助文档,Certd是一款开源免费的全自动SSL证书管理工具证书自动化申请部署流水线;自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔。",
markdown: {
config: (md) => {
// Use lightbox plugin
@@ -23,7 +23,7 @@ export default defineConfig({
// ],
["meta", {
name: "keywords",
content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化https证书pfx证书der证书TLS证书nginx证书自动续签自动部署,SSL平台证书管理平台"
content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化https证书pfx证书der证书TLS证书nginx证书自动续签自动部署,SSL平台证书管理平台,证书流水线"
}],
["meta", { name: "google-site-verification",content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"}],
//<meta name="baidu-site-verification" content="codeva-MiWN8Y07Ua" />

View File

@@ -28,3 +28,7 @@ Certd 是一款开源、免费、全自动申请和部署更新SSL证书的工
* 免费证书过期时间90天以后可能还会缩短所以自动化部署必不可少
* 设置每天自动运行当证书过期前20天会自动重新申请证书并部署
## 三、证书颁发机构对比
* Let's Encrypt申请最简单。
* Google: 大厂光环兼容性好需要翻墙获取EAB。
* ZeroSSL 有数量限制获取EAB无需翻墙。

View File

@@ -4,7 +4,10 @@
## windows开启OpenSSH Server
### 1. 安装OpenSSH Server
请前往Microsoft官方文档查看如何开启openSSH
* 下载安装包安装: https://github.com/PowerShell/Win32-OpenSSH/releases OpenSSH-Win64-vxx.xx.x.msi
* 前往Microsoft官方文档查看如何开启openSSH以及其他设置
https://learn.microsoft.com/zh-cn/windows-server/administration/openssh/openssh_install_firstuse?tabs=gui#install-openssh-for-windows
### 2. 启动OpenSSH Server服务

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -6,3 +6,16 @@
打开 https://console.cloud.tencent.com/cam/capi
然后按如下方式获取腾讯云的API密钥
![](./tencent-access.png)
## 如何避免收到腾讯云证书过期邮件
腾讯云在证书有效期还剩28天时会发送过期通知邮件
您可以通过配置“腾讯云过期证书删除”任务来避免收到此类邮件。
![](./images/delete.png)
注意点:
> 1. 选择腾讯云授权,需授权`服务角色SSL_QCSLinkedRoleInReplaceLoadCertificate`权限
> 2. `1.26.14`版本之前Certd创建的证书流水线默认是到期前20天才更新证书需要将之前创建的证书申请任务的更新天数修改为35天保证删除之前就已经替换掉即将过期证书
![](./images/delete2.png)

View File

@@ -28,7 +28,7 @@ features:
- title: 多域名、泛域名打到一个证书上
details: 支持通配符域名/泛域名,支持多个域名打到一个证书上
- title: 多证书格式支持
details: 支持pem、pfx、der等多种证书格式支持Google、Letsencrypt、ZeroSSL证书颁发机构
details: 支持pem、pfx、der、jks等多种证书格式支持Google、Letsencrypt、ZeroSSL证书颁发机构
- title: 支持私有化部署
details: 保障数据安全
- title: 多数据库支持

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.26.13"
"version": "1.26.16"
}

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/publishlab/node-acme-client/compare/v1.26.15...v1.26.16) (2024-10-30)
**Note:** Version bump only for package @certd/acme-client
## [1.26.15](https://github.com/publishlab/node-acme-client/compare/v1.26.14...v1.26.15) (2024-10-28)
**Note:** Version bump only for package @certd/acme-client
## [1.26.14](https://github.com/publishlab/node-acme-client/compare/v1.26.13...v1.26.14) (2024-10-26)
### Bug Fixes
* 修复启动时自签证书无法保存的bug ([526c484](https://github.com/publishlab/node-acme-client/commit/526c48450bcd37b3ccded9b448f17de8140bdc6e))
## [1.26.13](https://github.com/publishlab/node-acme-client/compare/v1.26.12...v1.26.13) (2024-10-26)
**Note:** Version bump only for package @certd/acme-client

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.26.13",
"version": "1.26.16",
"main": "src/index.js",
"types": "types/index.d.ts",
"license": "MIT",
@@ -60,5 +60,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "11d0daa59ae409c229037189066414f29b787de0"
"gitHead": "06fed944c96fc6c5d4911bb7d3f45b51948f9d4b"
}

View File

@@ -8,7 +8,7 @@ function createAgent(opts = {}) {
let httpAgent;
let
httpsAgent;
const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy;
const httpProxy = opts.httpProxy || process.env.HTTP_PROXY || process.env.http_proxy;
if (httpProxy) {
log(`acme use httpProxy:${httpProxy}`);
httpAgent = new HttpProxyAgent(httpProxy, opts);
@@ -16,7 +16,7 @@ function createAgent(opts = {}) {
else {
httpAgent = new nodeHttp.Agent(opts);
}
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
const httpsProxy = opts.httpsProxy || process.env.HTTPS_PROXY || process.env.https_proxy;
if (httpsProxy) {
log(`acme use httpsProxy:${httpsProxy}`);
httpsAgent = new HttpsProxyAgent(httpsProxy, opts);
@@ -38,14 +38,7 @@ function getGlobalAgents() {
function setGlobalProxy(opts) {
log('acme setGlobalProxy:', opts);
if (opts.httpProxy) {
process.env.HTTP_PROXY = opts.httpProxy;
}
if (opts.httpsProxy) {
process.env.HTTPS_PROXY = opts.httpsProxy;
}
defaultAgents = createAgent();
defaultAgents = createAgent(opts);
}
class HttpError extends Error {

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
### Performance Improvements
* 支持华为云cdn ([81a3fdb](https://github.com/certd/certd/commit/81a3fdbc29b71f380762008cc151493ec97458f9))
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
**Note:** Version bump only for package @certd/basic
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
### Bug Fixes
* 修复启动时自签证书无法保存的bug ([526c484](https://github.com/certd/certd/commit/526c48450bcd37b3ccded9b448f17de8140bdc6e))
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
**Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
20:27
11:12

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.26.13",
"version": "1.26.16",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -64,5 +64,5 @@
"vite": "^4.3.8",
"vue-tsc": "^1.6.5"
},
"gitHead": "11d0daa59ae409c229037189066414f29b787de0"
"gitHead": "06fed944c96fc6c5d4911bb7d3f45b51948f9d4b"
}

View File

@@ -1,4 +1,4 @@
import log4js, { LoggingEvent, Logger } from "log4js";
import log4js, { LoggingEvent, Logger } from 'log4js';
const OutputAppender = {
configure: (config: any, layouts: any, findAppender: any, levels: any) => {
@@ -18,18 +18,22 @@ const OutputAppender = {
},
};
// @ts-ignore
log4js.configure({
appenders: { std: { type: "stdout" }, output: { type: OutputAppender } },
categories: { default: { appenders: ["std"], level: "info" }, pipeline: { appenders: ["std", "output"], level: "info" } },
});
export const logger = log4js.getLogger("default");
export function resetLogConfigure() {
// @ts-ignore
log4js.configure({
appenders: { std: { type: 'stdout' }, output: { type: OutputAppender } },
categories: { default: { appenders: ['std'], level: 'info' }, pipeline: { appenders: ['std', 'output'], level: 'info' } },
});
}
resetLogConfigure();
export const logger = log4js.getLogger('default');
export function buildLogger(write: (text: string) => void) {
const logger = log4js.getLogger("pipeline");
logger.addContext("outputHandler", {
const logger = log4js.getLogger('pipeline');
logger.addContext('outputHandler', {
write,
});
return logger;
}
export type ILogger = Logger;

View File

@@ -61,14 +61,7 @@ let defaultAgents = createAgent();
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
logger.info('setGlobalProxy:', opts);
if (opts.httpProxy) {
process.env.HTTP_PROXY = opts.httpProxy;
}
if (opts.httpsProxy) {
process.env.HTTPS_PROXY = opts.httpsProxy;
}
defaultAgents = createAgent();
defaultAgents = createAgent(opts);
}
export function getGlobalAgents() {
@@ -192,9 +185,13 @@ export type HttpClient = {
request<D = any, R = any>(config: HttpRequestConfig<D>): Promise<HttpClientResponse<R>>;
};
export function createAgent(opts: nodeHttp.AgentOptions = {}) {
export type CreateAgentOptions = {
httpProxy?: string;
httpsProxy?: string;
} & nodeHttp.AgentOptions;
export function createAgent(opts: CreateAgentOptions = {}) {
let httpAgent, httpsAgent;
const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy;
const httpProxy = opts.httpProxy || process.env.HTTP_PROXY || process.env.http_proxy;
if (httpProxy) {
logger.info('use httpProxy:', httpProxy);
httpAgent = new HttpProxyAgent(httpProxy, opts as any);
@@ -202,7 +199,7 @@ export function createAgent(opts: nodeHttp.AgentOptions = {}) {
} else {
httpAgent = new nodeHttp.Agent(opts);
}
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
const httpsProxy = opts.httpsProxy || process.env.HTTPS_PROXY || process.env.https_proxy;
if (httpsProxy) {
logger.info('use httpsProxy:', httpsProxy);
httpsAgent = new HttpsProxyAgent(httpsProxy, opts as any);

View File

@@ -1,8 +1,8 @@
//转换为import
import childProcess from "child_process";
import { safePromise } from "./util.promise.js";
import { ILogger, logger } from "./util.log.js";
import childProcess from 'child_process';
import { safePromise } from './util.promise.js';
import { ILogger, logger } from './util.log.js';
import iconv from 'iconv-lite';
export type ExecOption = {
cmd: string | string[];
env: any;
@@ -11,12 +11,12 @@ export type ExecOption = {
};
async function exec(opts: ExecOption): Promise<string> {
let cmd = "";
let cmd = '';
const log = opts.logger || logger;
if (opts.cmd instanceof Array) {
for (const item of opts.cmd) {
if (cmd) {
cmd += " && " + item;
cmd += ' && ' + item;
} else {
cmd = item;
}
@@ -38,7 +38,7 @@ async function exec(opts: ExecOption): Promise<string> {
log.error(`exec error: ${error}`);
reject(error);
} else {
const res = stdout.toString("utf-8");
const res = stdout.toString('utf-8');
log.info(`stdout: ${res}`);
resolve(res);
}
@@ -55,13 +55,31 @@ export type SpawnOption = {
logger?: ILogger;
options?: any;
};
function isWindows() {
return process.platform === 'win32';
}
function convert(buffer: any) {
if (isWindows()) {
const decoded = iconv.decode(buffer, 'GBK');
// 检查是否有有效字符
return decoded && decoded.trim().length > 0 ? decoded : buffer.toString();
} else {
return buffer;
}
}
// function convert(buffer: any) {
// return buffer;
// }
async function spawn(opts: SpawnOption): Promise<string> {
let cmd = "";
let cmd = '';
const log = opts.logger || logger;
if (opts.cmd instanceof Array) {
for (const item of opts.cmd) {
if (cmd) {
cmd += " && " + item;
cmd += ' && ' + item;
} else {
cmd = item;
}
@@ -70,8 +88,8 @@ async function spawn(opts: SpawnOption): Promise<string> {
cmd = opts.cmd;
}
log.info(`执行命令: ${cmd}`);
let stdout = "";
let stderr = "";
let stdout = '';
let stderr = '';
return safePromise((resolve, reject) => {
const ls = childProcess.spawn(cmd, {
shell: true,
@@ -81,21 +99,23 @@ async function spawn(opts: SpawnOption): Promise<string> {
},
...opts.options,
});
ls.stdout.on("data", (data) => {
ls.stdout.on('data', data => {
data = convert(data);
log.info(`stdout: ${data}`);
stdout += data;
});
ls.stderr.on("data", (data) => {
log.error(`stderr: ${data}`);
ls.stderr.on('data', data => {
data = convert(data);
log.warn(`stderr: ${data}`);
stderr += data;
});
ls.on("error", (error) => {
ls.on('error', error => {
log.error(`child process error: ${error}`);
reject(error);
});
ls.on("close", (code: number) => {
ls.on('close', (code: number) => {
if (code !== 0) {
log.error(`child process exited with code ${code}`);
reject(new Error(stderr));

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
### Performance Improvements
* 支持白山云cdn部署 ([b1b2cd0](https://github.com/certd/certd/commit/b1b2cd088b684eda764962abd61754c26a204d1c))
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
### Performance Improvements
* 默认证书更新时间设置为35天增加腾讯云删除过期证书插件可以避免腾讯云过期证书邮件 ([51b6fed](https://github.com/certd/certd/commit/51b6fed468eaa6f28ce4497ce303ace1a52abb96))
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
**Note:** Version bump only for package @certd/pipeline
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
**Note:** Version bump only for package @certd/pipeline

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.26.13",
"version": "1.26.16",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -15,8 +15,8 @@
"test": "mocha --loader=ts-node/esm"
},
"dependencies": {
"@certd/basic": "^1.26.13",
"@certd/plus-core": "^1.26.13",
"@certd/basic": "^1.26.16",
"@certd/plus-core": "^1.26.16",
"axios": "^1.7.2",
"dayjs": "^1.11.7",
"fix-path": "^4.0.0",
@@ -66,5 +66,5 @@
"vite": "^4.3.8",
"vue-tsc": "^1.6.5"
},
"gitHead": "11d0daa59ae409c229037189066414f29b787de0"
"gitHead": "06fed944c96fc6c5d4911bb7d3f45b51948f9d4b"
}

View File

@@ -111,13 +111,19 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
return this._result.files;
}
checkSignal() {
if (this.ctx.signal && this.ctx.signal.aborted) {
throw new Error("用户取消");
}
}
setCtx(ctx: TaskInstanceContext) {
this.ctx = ctx;
this.logger = ctx.logger;
this.accessService = ctx.accessService;
}
async getAccess(accessId: string) {
async getAccess<T = any>(accessId: string) {
if (accessId == null) {
throw new Error("您还没有配置授权");
}
@@ -125,7 +131,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
if (res == null) {
throw new Error("授权不存在,可能已被删除,请前往任务配置里面重新选择授权");
}
return res;
return res as T;
}
randomFileId() {

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
**Note:** Version bump only for package @certd/lib-huawei
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
**Note:** Version bump only for package @certd/lib-huawei
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
**Note:** Version bump only for package @certd/lib-huawei
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.26.13",
"version": "1.26.16",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -17,5 +17,5 @@
"rimraf": "^5.0.5",
"rollup": "^3.7.4"
},
"gitHead": "11d0daa59ae409c229037189066414f29b787de0"
"gitHead": "06fed944c96fc6c5d4911bb7d3f45b51948f9d4b"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
**Note:** Version bump only for package @certd/lib-iframe
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
**Note:** Version bump only for package @certd/lib-iframe
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
**Note:** Version bump only for package @certd/lib-iframe
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.26.13",
"version": "1.26.16",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -39,5 +39,5 @@
"tslib": "^2.5.2",
"typescript": "^5.4.2"
},
"gitHead": "11d0daa59ae409c229037189066414f29b787de0"
"gitHead": "06fed944c96fc6c5d4911bb7d3f45b51948f9d4b"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
**Note:** Version bump only for package @certd/lib-jdcloud
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
**Note:** Version bump only for package @certd/lib-jdcloud
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
**Note:** Version bump only for package @certd/lib-jdcloud
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
**Note:** Version bump only for package @certd/lib-jdcloud

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-jdcloud",
"private": false,
"version": "1.26.13",
"version": "1.26.16",
"main": "./dist/bundle.mjs",
"module": "./dist/bundle.mjs",
"types": "./dist/d/index.d.ts",
@@ -27,5 +27,5 @@
"rimraf": "^5.0.5",
"rollup": "^3.7.4"
},
"gitHead": "11d0daa59ae409c229037189066414f29b787de0"
"gitHead": "06fed944c96fc6c5d4911bb7d3f45b51948f9d4b"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
**Note:** Version bump only for package @certd/lib-k8s
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
**Note:** Version bump only for package @certd/lib-k8s
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
**Note:** Version bump only for package @certd/lib-k8s
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
**Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.26.13",
"version": "1.26.16",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -18,7 +18,7 @@
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
"@certd/pipeline": "^1.26.13",
"@certd/pipeline": "^1.26.16",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
@@ -40,5 +40,5 @@
"tslib": "^2.5.2",
"typescript": "^5.4.2"
},
"gitHead": "11d0daa59ae409c229037189066414f29b787de0"
"gitHead": "06fed944c96fc6c5d4911bb7d3f45b51948f9d4b"
}

View File

@@ -3,6 +3,21 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
**Note:** Version bump only for package @certd/lib-server
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
**Note:** Version bump only for package @certd/lib-server
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
### Performance Improvements
* 限制其他用户流水线数量 ([315e437](https://github.com/certd/certd/commit/315e43746baf01682737f82e41579237a48409af))
* 用户管理优化头像上传 ([661293c](https://github.com/certd/certd/commit/661293c189a3abf3cdc953b5225192372f57930d))
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
**Note:** Version bump only for package @certd/lib-server

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.26.13",
"version": "1.26.16",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -26,9 +26,9 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.26.13",
"@certd/basic": "^1.26.13",
"@certd/pipeline": "^1.26.13",
"@certd/acme-client": "^1.26.16",
"@certd/basic": "^1.26.16",
"@certd/pipeline": "^1.26.16",
"@midwayjs/cache": "~3.14.0",
"@midwayjs/core": "~3.17.1",
"@midwayjs/i18n": "~3.17.3",
@@ -69,5 +69,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "11d0daa59ae409c229037189066414f29b787de0"
"gitHead": "06fed944c96fc6c5d4911bb7d3f45b51948f9d4b"
}

View File

@@ -1,4 +1,4 @@
import { Provide } from '@midwayjs/core';
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import dayjs from 'dayjs';
import path from 'path';
import fs from 'fs';
@@ -14,6 +14,7 @@ export const uploadTmpFileCacheKey = 'tmpfile_key_';
/**
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class FileService {
async saveFile(userId: number, tmpCacheKey: any, permission: 'public' | 'private') {
if (tmpCacheKey.startsWith(`/${permission}`)) {

View File

@@ -15,6 +15,7 @@ export class SysPublicSettings extends BaseSettings {
static __title__ = '系统公共设置';
static __access__ = 'public';
registerEnabled = false;
limitUserPipelineCount = 0;
managerOtherUserPipeline = false;
icpNo?: string;
// triggerOnStartup = false;

View File

@@ -127,14 +127,12 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
async reloadPrivateSettings() {
const bean = await this.getPrivateSettings();
if (bean.httpProxy || bean.httpsProxy) {
const opts = {
httpProxy: bean.httpProxy,
httpsProxy: bean.httpsProxy,
};
setGlobalProxy(opts);
agents.setGlobalProxy(opts);
}
const opts = {
httpProxy: bean.httpProxy,
httpsProxy: bean.httpsProxy,
};
setGlobalProxy(opts);
agents.setGlobalProxy(opts);
}
async updateByKey(key: string, setting: any) {

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.26.13",
"version": "1.26.16",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -56,5 +56,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "11d0daa59ae409c229037189066414f29b787de0"
"gitHead": "06fed944c96fc6c5d4911bb7d3f45b51948f9d4b"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
### Bug Fixes
* 修复lego No help topic for 错误 ([aaaf8d7](https://github.com/certd/certd/commit/aaaf8d7db34896cf8f2ff8f12eec1ab0cae58f0f))
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
### Performance Improvements
* 默认证书更新时间设置为35天增加腾讯云删除过期证书插件可以避免腾讯云过期证书邮件 ([51b6fed](https://github.com/certd/certd/commit/51b6fed468eaa6f28ce4497ce303ace1a52abb96))
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
**Note:** Version bump only for package @certd/plugin-cert
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
### Bug Fixes

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.26.13",
"version": "1.26.16",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -15,9 +15,9 @@
"preview": "vite preview"
},
"dependencies": {
"@certd/acme-client": "^1.26.13",
"@certd/basic": "^1.26.13",
"@certd/pipeline": "^1.26.13",
"@certd/acme-client": "^1.26.16",
"@certd/basic": "^1.26.16",
"@certd/pipeline": "^1.26.16",
"@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",
@@ -57,5 +57,5 @@
"vite": "^3.1.0",
"vue-tsc": "^0.38.9"
},
"gitHead": "11d0daa59ae409c229037189066414f29b787de0"
"gitHead": "06fed944c96fc6c5d4911bb7d3f45b51948f9d4b"
}

View File

@@ -30,6 +30,7 @@ export type CertInfo = {
ic?: string;
pfx?: string;
der?: string;
jks?: string;
};
export type SSLProvider = "letsencrypt" | "google" | "zerossl";
export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521";

View File

@@ -55,13 +55,14 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
},
required: false,
order: 100,
helper: "PFX格式证书是否需要加密",
// helper: "PFX、jks格式证书是否加密jks必须设置密码不传则默认123456",
helper: "PFX证书是否加密",
})
pfxPassword!: string;
@TaskInput({
title: "更新天数",
value: 20,
value: 35,
component: {
name: "a-input-number",
vModel: "value",
@@ -157,14 +158,28 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
cert,
pfxPassword: this.pfxPassword,
});
const pfxBuffer = fs.readFileSync(res.pfxPath);
cert.pfx = pfxBuffer.toString("base64");
if (res.pfxPath) {
const pfxBuffer = fs.readFileSync(res.pfxPath);
cert.pfx = pfxBuffer.toString("base64");
fs.unlinkSync(res.pfxPath);
isNew = true;
}
const derBuffer = fs.readFileSync(res.derPath);
cert.der = derBuffer.toString("base64");
if (res.derPath) {
const derBuffer = fs.readFileSync(res.derPath);
cert.der = derBuffer.toString("base64");
fs.unlinkSync(res.derPath);
isNew = true;
}
if (res.jksPath) {
const jksBuffer = fs.readFileSync(res.jksPath);
cert.jks = jksBuffer.toString("base64");
fs.unlinkSync(res.jksPath);
isNew = true;
}
this.logger.info("转换证书格式成功");
isNew = true;
} catch (e) {
this.logger.error("转换证书格式失败", e);
}
@@ -186,12 +201,16 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
zip.file("cert.crt", cert.crt);
zip.file("cert.key", cert.key);
zip.file("intermediate.crt", cert.ic);
if (cert.pfx) {
zip.file("cert.pfx", Buffer.from(cert.pfx, "base64"));
}
if (cert.der) {
zip.file("cert.der", Buffer.from(cert.der, "base64"));
}
if (cert.jks) {
zip.file("cert.jks", Buffer.from(cert.jks, "base64"));
}
const content = await zip.generateAsync({ type: "nodebuffer" });
this.saveFile(filename, content);
this.logger.info(`已保存文件:${filename}`);
@@ -212,7 +231,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
this.logger.info("input hash 有变更,检查是否需要重新申请证书");
//判断域名有没有变更
/**
* "renewDays": 20,
* "renewDays": 35,
* "certApplyPlugin": "CertApply",
* "sslProvider": "letsencrypt",
* "privateKeyType": "rsa_2048_pkcs1",

View File

@@ -13,6 +13,7 @@ export type CertReaderHandleContext = {
tmpPfxPath?: string;
tmpDerPath?: string;
tmpIcPath?: string;
tmpJksPath?: string;
};
export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise<void>;
export type HandleOpts = { logger: ILogger; handle: CertReaderHandle };
@@ -72,14 +73,14 @@ export class CertReader {
return domains;
}
saveToFile(type: "crt" | "key" | "pfx" | "der" | "ic", filepath?: string) {
saveToFile(type: "crt" | "key" | "pfx" | "der" | "ic" | "jks", filepath?: string) {
if (!this.cert[type]) {
return;
}
if (filepath == null) {
//写入临时目录
filepath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + "", `cert.${type}`);
filepath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + `_cert.${type}`);
}
const dir = path.dirname(filepath);
@@ -103,6 +104,7 @@ export class CertReader {
const tmpIcPath = this.saveToFile("ic");
logger.info("本地文件写入成功");
const tmpDerPath = this.saveToFile("der");
const tmpJksPath = this.saveToFile("jks");
try {
return await opts.handle({
reader: this,
@@ -111,6 +113,7 @@ export class CertReader {
tmpPfxPath: tmpPfxPath,
tmpDerPath: tmpDerPath,
tmpIcPath: tmpIcPath,
tmpJksPath: tmpJksPath,
});
} catch (err) {
throw err;
@@ -127,6 +130,7 @@ export class CertReader {
removeFile(tmpPfxPath);
removeFile(tmpDerPath);
removeFile(tmpIcPath);
removeFile(tmpJksPath);
}
}

View File

@@ -17,16 +17,20 @@ export class CertConverter {
async convert(opts: { cert: CertInfo; pfxPassword: string }): Promise<{
pfxPath: string;
derPath: string;
jksPath: string;
}> {
const certReader = new CertReader(opts.cert);
let pfxPath: string;
let derPath: string;
let jksPath: string;
const handle = async (ctx: CertReaderHandleContext) => {
// 调用openssl 转pfx
pfxPath = await this.convertPfx(ctx, opts.pfxPassword);
// 转der
derPath = await this.convertDer(ctx);
//jksPath = await this.convertJks(ctx, pfxPath, opts.pfxPassword);
};
await certReader.readCertFile({ logger: this.logger, handle });
@@ -34,10 +38,12 @@ export class CertConverter {
return {
pfxPath,
derPath,
jksPath,
};
}
async exec(cmd: string) {
process.env.LANG = "zh_CN.GBK";
await sp.spawn({
cmd: cmd,
logger: this.logger,
@@ -47,7 +53,7 @@ export class CertConverter {
private async convertPfx(opts: CertReaderHandleContext, pfxPassword: string) {
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");
const dir = path.dirname(pfxPath);
if (!fs.existsSync(dir)) {
@@ -70,7 +76,7 @@ export class CertConverter {
private async convertDer(opts: CertReaderHandleContext) {
const { tmpCrtPath } = opts;
const derPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + "", `cert.der`);
const derPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + `_cert.der`);
const dir = path.dirname(derPath);
if (!fs.existsSync(dir)) {
@@ -88,4 +94,32 @@ export class CertConverter {
// const filename = reader.buildCertFileName("der", applyTime);
// this.saveFile(filename, fileBuffer);
}
async convertJks(opts: CertReaderHandleContext, pfxPath: string, pfxPassword = "") {
const jksPassword = pfxPassword || "123456";
try {
const randomStr = Math.floor(Math.random() * 1000000) + "";
// const p12Path = path.join(os.tmpdir(), "/certd/tmp/", randomStr + `_cert.p12`);
// const { tmpCrtPath, tmpKeyPath } = opts;
// let passwordArg = "-passout pass:";
// if (pfxPassword) {
// passwordArg = `-password pass:${pfxPassword}`;
// }
// await this.exec(`openssl pkcs12 -export -in ${tmpCrtPath} -inkey ${tmpKeyPath} -out ${p12Path} -name certd ${passwordArg}`);
const jksPath = path.join(os.tmpdir(), "/certd/tmp/", randomStr + `_cert.jks`);
const dir = path.dirname(jksPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
await this.exec(
`keytool -importkeystore -srckeystore ${pfxPath} -srcstoretype PKCS12 -srcstorepass "${pfxPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" `
);
return jksPath;
} catch (e) {
this.logger.error("转换jks失败", e);
return;
}
}
}

View File

@@ -33,7 +33,7 @@ export type DomainsVerifyPlanInput = {
desc: "免费通配符域名证书申请,支持多个域名打到同一个证书上",
default: {
input: {
renewDays: 20,
renewDays: 35,
forceUpdate: false,
},
strategy: {
@@ -55,12 +55,12 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
},
required: true,
helper:
"DNS直接验证适合域名是在阿里云、腾讯云、华为云、Cloudflare、西数注册的需要提供Access授权信息。\nCNAME代理验证支持任何注册商注册的域名并且不需要提供Access授权信息但第一次需要手动添加CNAME记录",
"DNS直接验证域名是在阿里云、腾讯云、华为云、Cloudflare、西数注册的选它。\nCNAME代理验证支持任何注册商注册的域名但第一次需要手动添加CNAME记录",
})
challengeType!: string;
@TaskInput({
title: "DNS提供商",
title: "DNS解析服务商",
component: {
name: "dns-provider-selector",
},
@@ -72,7 +72,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
}
`,
required: true,
helper: "请选择dns解析提供商您的域名是在哪里注册或者域名的dns解析服务器属于哪个平台\n如果这里没有您需要的dns解析提供商请选择CNAME代理验证校验方式",
helper: "您的域名注册或者域名的dns服务器属于哪个平台\n如果这里没有请选择CNAME代理验证校验方式",
})
dnsProviderType!: string;
@@ -82,7 +82,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
name: "access-selector",
},
required: true,
helper: "请选择dns解析提供商授权",
helper: "请选择dns解析服务商授权",
mergeScript: `return {
component:{
type: ctx.compute(({form})=>{
@@ -134,7 +134,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
{ value: "zerossl", label: "ZeroSSL" },
],
},
helper: "Let's Encrypt最简单如果使用ZeroSSL、Google证书需要提供EAB授权",
helper: "Let's Encrypt:申请最简单\nGoogle大厂光环兼容性好需要翻墙获取EAB授权\nZeroSSL有数量限制获取EAB授权无需翻墙",
required: true,
})
sslProvider!: SSLProvider;

View File

@@ -17,7 +17,7 @@ export type { CertInfo };
desc: "支持海量DNS解析提供商推荐使用一样的免费通配符域名证书申请支持多个域名打到同一个证书上",
default: {
input: {
renewDays: 20,
renewDays: 35,
forceUpdate: false,
},
strategy: {
@@ -118,7 +118,7 @@ export class CertApplyLegoPlugin extends CertApplyBasePlugin {
this.logger.info(`环境变量:${JSON.stringify(env)}`);
let eabArgs = "";
if (this.eab) {
eabArgs = ` --eab "${this.eab.kid}" --kid "${this.eab.kid}" --hmac "${this.eab.hmacKey}"`;
eabArgs = ` --eab --kid "${this.eab.kid}" --hmac "${this.eab.hmacKey}"`;
}
const keyType = "-k rsa2048";

View File

@@ -13,6 +13,7 @@ RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker
FROM node:18-alpine
RUN apk add --no-cache openssl
RUN #apk add --no-cache openjdk11-jdk
WORKDIR /app/
COPY --from=builder /workspace/certd-server/ /app/
RUN chmod +x /app/tools/linux/*

View File

@@ -3,6 +3,35 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
**Note:** Version bump only for package @certd/ui-client
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
### Bug Fixes
* 顶部菜单变...的bug ([6dabad7](https://github.com/certd/certd/commit/6dabad76baba96be0f8af36a3fbfb9f5182aecf1))
### Performance Improvements
* 默认证书更新时间设置为35天增加腾讯云删除过期证书插件可以避免腾讯云过期证书邮件 ([51b6fed](https://github.com/certd/certd/commit/51b6fed468eaa6f28ce4497ce303ace1a52abb96))
* 授权加密支持解密查看 ([5575c83](https://github.com/certd/certd/commit/5575c839705f6987ad2bdcd33256b0962c6a9c6a))
* 重置管理员密码同时启用管理员账户,避免之前禁用了,重置密码还是登录不进去 ([f92d918](https://github.com/certd/certd/commit/f92d918a1e28e29b794ad4754661ea760c18af46))
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
### Bug Fixes
* 修复启动时自签证书无法保存的bug ([526c484](https://github.com/certd/certd/commit/526c48450bcd37b3ccded9b448f17de8140bdc6e))
### Performance Improvements
* 顶部菜单自定义 ([54d136c](https://github.com/certd/certd/commit/54d136cc6ae122f7c891b7a5c7232fe5de8e5cb5))
* 禁用readonly用户 ([d10d42e](https://github.com/certd/certd/commit/d10d42e20619bb55a50d636b8867ff33db4e3b4b))
* 限制其他用户流水线数量 ([315e437](https://github.com/certd/certd/commit/315e43746baf01682737f82e41579237a48409af))
* 用户管理优化头像上传 ([661293c](https://github.com/certd/certd/commit/661293c189a3abf3cdc953b5225192372f57930d))
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.26.13",
"version": "1.26.16",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -63,8 +63,8 @@
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@certd/lib-iframe": "^1.26.13",
"@certd/pipeline": "^1.26.13",
"@certd/lib-iframe": "^1.26.16",
"@certd/pipeline": "^1.26.16",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -2,7 +2,7 @@ import { request } from "/@/api/service";
export type ComponentPropsType = {
type: string;
typeName: string;
action: string;
action?: string;
form: any;
value?: any;
};

View File

@@ -1,7 +1,7 @@
<template>
<div>
<contextHolder />
<a-input :value="value" :allow-clear="true" v-bind="attrs" @update:value="emit('update:value', $event)">
<a-input v-bind="attrs" :value="value" :allow-clear="true" @update:value="emit('update:value', $event)">
<template #suffix>
<a-tag class="cursor-pointer" @click="getDeviceId">获取设备ID</a-tag>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<a-dropdown class="fs-locale-picker">
<div>
<div style="display: block">
<fs-iconify icon="ion-globe-outline" @click.prevent></fs-iconify>
</div>

View File

@@ -89,11 +89,12 @@ export default defineComponent({
}
}
const title: any = () => {
if (sub?.meta?.icon) {
const icon = sub.icon || sub?.meta?.icon;
if (icon) {
// @ts-ignore
return (
<div class={"menu-item-title"}>
<fsIcon class={"anticon"} icon={sub.meta.icon} />
<fsIcon class={"anticon"} icon={icon} />
<span>{sub.title}</span>
</div>
);

View File

@@ -2,8 +2,21 @@
<div class="fs-multiple-page-control-group">
<div class="fs-multiple-page-control-content">
<div class="fs-multiple-page-control-content-inner">
<a-tabs class="fs-multiple-page-control fs-multiple-page-sort" :active-key="page.getCurrent" type="editable-card" hide-add @tab-click="handleClick" @edit="handleTabEdit">
<a-tab-pane v-for="item in page.getOpened" :key="item.fullPath" :tab="item.meta?.title || '未命名'" :name="item.fullPath" :closable="isTabClosable(item)" />
<a-tabs
class="fs-multiple-page-control fs-multiple-page-sort"
:active-key="page.getCurrent"
type="editable-card"
hide-add
@tab-click="handleClick"
@edit="handleTabEdit"
>
<a-tab-pane
v-for="item in page.getOpened"
:key="item.fullPath"
:tab="item.meta?.title || '未命名'"
:name="item.fullPath"
:closable="isTabClosable(item)"
/>
</a-tabs>
<!-- <fs-contextmenu v-model:open="contextmenuFlag" :x="contentmenuX" :y="contentmenuY">-->
<!-- <fs-contextmenu-list-->

View File

@@ -41,6 +41,8 @@
<!-- >-->
<!-- Button-->
<!-- </button>-->
<fs-menu class="header-menu" mode="horizontal" :expand-selected="false" :selectable="false" :menus="settingStore.getHeaderMenus" />
<fs-menu
v-if="!settingStore?.isAgent && !settingStore.isComm"
class="header-menu"
@@ -250,8 +252,6 @@ const { token } = useToken();
justify-content: flex-end;
align-items: center;
display: flex;
}
.header-menu {
flex: 1;
}
}

View File

@@ -185,7 +185,8 @@ function install(app: App, options: any = {}) {
defaultType: "form",
form: {
keepName: true,
action: "http://www.docmirror.cn:7070/api/upload/form/upload",
type: "form",
action: "/basic/file/upload",
name: "file",
withCredentials: false,
uploadRequest: async ({ action, file, onProgress }: any) => {
@@ -205,12 +206,8 @@ function install(app: App, options: any = {}) {
}
});
},
successHandle(ret: any) {
// 上传完成后的结果处理, 此处应返回格式为{url:xxx}
return {
url: "http://www.docmirror.cn:7070" + ret,
key: ret.replace("/api/upload/form/download?key=", "")
};
successHandle(res: any) {
return res;
}
}
});

View File

@@ -4,7 +4,7 @@ export const headerResource = [
path: "https://certd.docmirror.cn",
meta: {
icon: "ion:document-text-outline"
},
}
},
{
title: "源码",

View File

@@ -68,20 +68,20 @@ export const sysResources = [
permission: "sys:settings:view"
}
},
// {
// title: "顶部菜单设置",
// name: "HeaderMenus",
// path: "/sys/settings/header-menus",
// component: "/sys/settings/header-menus/index.vue",
// meta: {
// show: () => {
// const settingStore = useSettingStore();
// return settingStore.isComm;
// },
// icon: "ion:document-text-outline",
// permission: "sys:settings:view"
// }
// },
{
title: "顶部菜单设置",
name: "HeaderMenus",
path: "/sys/settings/header-menus",
component: "/sys/settings/header-menus/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:document-text-outline",
permission: "sys:settings:view"
}
},
{
title: "系统级授权",
name: "SysAccess",

View File

@@ -1,6 +1,6 @@
import { defineStore } from "pinia";
import { Modal, notification, theme } from "ant-design-vue";
import _ from "lodash-es";
import _, { cloneDeep } from "lodash-es";
// @ts-ignore
import { LocalStorage } from "/src/utils/util.storage";
@@ -10,6 +10,7 @@ import { useUserStore } from "/@/store/modules/user";
import { mitter } from "/@/utils/util.mitt";
import { env } from "/@/utils/util.env";
import { toRef } from "vue";
import { util } from "/@/utils";
export type ThemeToken = {
token: {
@@ -120,6 +121,9 @@ export const useSettingStore = defineStore({
comm: "商业版"
};
return vipLabelMap[this.plusInfo?.vipType || "free"];
},
getHeaderMenus() {
return this.headerMenus?.menus || [];
}
},
actions: {
@@ -137,6 +141,7 @@ export const useSettingStore = defineStore({
_.merge(this.installInfo, allSettings.installInfo || {});
_.merge(this.siteEnv, allSettings.siteEnv || {});
_.merge(this.plusInfo, allSettings.plusInfo || {});
_.merge(this.headerMenus, allSettings.headerMenus || {});
//@ts-ignore
this.initSiteInfo(allSettings.siteInfo || {});
},

View File

@@ -62,4 +62,9 @@
justify-content: space-between;
}
}
}
}
.settings-form {
width: 800px;
margin: 20px;
}

View File

@@ -178,6 +178,14 @@ h1, h2, h3, h4, h5, h6 {
color: #1890ff;
}
.color-red {
color: red;
}
.color-green {
color: green;
}
.iconify{
//font-size: 16px;
}
@@ -235,7 +243,7 @@ h1, h2, h3, h4, h5, h6 {
}
}
.settings-form {
width: 800px;
margin: 20px;
.fs-16{
font-size: 16px;
}

View File

@@ -4,11 +4,13 @@ import * as storages from "./util.storage";
import commons from "./util.common";
import * as mitt from "./util.mitt";
import router from "/util.router";
import { treeUtils } from "./util.tree";
export const util = {
...envs,
...sites,
...storages,
...commons,
...mitt,
...router
...router,
tree: treeUtils
};

View File

@@ -0,0 +1,12 @@
export function eachTree(tree: any[], callback: (item: any) => void) {
tree.forEach((item) => {
callback(item);
if (item.children) {
eachTree(item.children, callback);
}
});
}
export const treeUtils = {
eachTree
};

View File

@@ -0,0 +1,32 @@
<template>
<a-button class="cd-secret-plain-getter ml-5">
<fs-icon class="pointer" :icon="computedIcon" @click="showPlain" />
</a-button>
</template>
<script lang="ts" setup>
import { computed, inject, ref } from "vue";
defineOptions({
name: "SecretPlainGetter"
});
const props = defineProps<{
modelValue?: string;
accessId?: number;
inputKey: string;
}>();
const emit = defineEmits(["update:modelValue"]);
const showRef = ref(false);
const computedIcon = computed(() => {
return showRef.value ? "ion:eye-outline" : "ion:eye-off-outline";
});
const accessApi: any = inject("accessApi");
async function showPlain() {
showRef.value = true;
if (props.accessId) {
const plain = await accessApi.GetSecretPlain(props.accessId, props.inputKey);
emit("update:modelValue", plain);
}
}
</script>
<style lang="less"></style>

View File

@@ -43,6 +43,14 @@ export function createAccessApi(from = "user") {
});
},
async GetSecretPlain(id: number, key: string) {
return await request({
url: apiPrefix + "/getSecretPlain",
method: "post",
data: { id, key }
});
},
async GetProviderDefine(type: string) {
return await request({
url: apiPrefix + "/define",

View File

@@ -1,9 +1,11 @@
import { ColumnCompositionProps, dict } from "@fast-crud/fast-crud";
import { computed, ref, toRef } from "vue";
import { computed, provide, ref, toRef } from "vue";
import { useReference } from "/@/use/use-refrence";
import { forEach, get, merge, set } from "lodash-es";
import SecretPlainGetter from "/@/views/certd/access/access-selector/access/secret-plain-getter.vue";
export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
provide("accessApi", api);
const AccessTypeDictRef = dict({
url: "/pi/access/accessTypeDict"
});
@@ -14,7 +16,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
}
};
function buildDefineFields(define: any, form: any) {
function buildDefineFields(define: any, form: any, mode: string) {
const formWrapperRef = crudExpose.getFormWrapperRef();
const columnsRef = toRef(formWrapperRef.formOptions, "columns");
@@ -32,6 +34,13 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
};
const column = merge({ title: key }, defaultPluginConfig, field);
if (value.encrypt === true && mode != "add") {
column.suffixRender = (scope: { form: any; key: string }) => {
const { form, key } = scope;
const inputKey = scope.key.replace("access.", "");
return <SecretPlainGetter accessId={form.id} inputKey={inputKey} v-model={form[key]} />;
};
}
//eval
useReference(column);
@@ -83,7 +92,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
if (!immediate) {
form.access = {};
}
buildDefineFields(define, form);
buildDefineFields(define, form, mode);
}
},
helper: computed(() => {

View File

@@ -124,7 +124,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
{
title: "申请证书",
input: {
renewDays: 20,
renewDays: 35,
...form
},
strategy: {

View File

@@ -16,8 +16,6 @@ export class PluginManager {
}
this.map = map;
}
}
export const pluginManager = new PluginManager();

View File

@@ -16,7 +16,7 @@ export async function GetTree() {
}
export async function AddObj(obj: any) {
return await request({
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj

View File

@@ -1,5 +1,6 @@
import * as api from "./api";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { useUserStore } from "/@/store/modules/user";
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
@@ -17,6 +18,8 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
return await api.AddObj(form);
};
const userStore = useUserStore();
return {
crudOptions: {
request: {
@@ -114,8 +117,52 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
style: {
height: "30px",
width: "auto"
},
buildUrl(key: string) {
return `/api/basic/file/download?&key=` + key;
}
}
},
form: {
component: {
vModel: "modelValue",
valueType: "key",
cropper: {
aspectRatio: 1,
autoCropArea: 1,
viewMode: 0
},
onReady: null,
uploader: {
type: "form",
action: "/basic/file/upload",
name: "file",
headers: {
Authorization: "Bearer " + userStore.getToken
},
successHandle(res: any) {
return res;
}
},
buildUrl(key: string) {
return `/api/basic/file/download?&key=` + key;
}
}
}
},
status: {
title: "状态",
type: "dict-switch",
dict: dict({
data: [
{ label: "启用", value: 1, color: "green" },
{ label: "禁用", value: 0, color: "red" }
]
}),
column: {
align: "center",
sorter: true,
width: 100
}
},
remark: {

View File

@@ -5,6 +5,7 @@ export type SysSettings = { public: SysPublicSetting; private: SysPrivateSetting
export type SysPublicSetting = {
registerEnabled?: boolean;
limitUserPipelineCount?: number;
managerOtherUserPipeline?: boolean;
icpNo?: string;
};

View File

@@ -1,99 +1,124 @@
import { useI18n } from "vue-i18n";
import { Ref, ref } from "vue";
import { useRouter } from "vue-router";
import { compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from "@fast-crud/fast-crud";
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { useSettingStore } from "/@/store/modules/settings";
import { cloneDeep } from "lodash-es";
import { cloneDeep, find, merge, remove } from "lodash-es";
import { nanoid } from "nanoid";
import { SettingsSave } from "../api";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { crudBinding } = crudExpose;
const router = useRouter();
const { t } = useI18n();
const settingStore = useSettingStore();
const menusRef = ref(cloneDeep(settingStore.headerMenus?.menus || []));
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
async function saveMenus() {
const menus = settingStore.headerMenus;
await SettingsSave("sys.header.menus", menus);
}
function eachTree(tree: any[], callback: (item: any) => void) {
tree.forEach((item) => {
callback(item);
if (item.children) {
eachTree(item.children, callback);
}
});
}
const expandedRowKeys = ref<string[]>([]);
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
const records = cloneDeep(settingStore.headerMenus?.menus || []);
expandedRowKeys.value = [];
eachTree(records, (item) => {
if (item.children && item.children.length > 0) {
expandedRowKeys.value.push(item.id);
}
});
return {
records: records,
total: records.length,
limit: 9999999,
offset: 0
};
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
let found: any = undefined;
eachTree(settingStore.headerMenus?.menus || [], (item) => {
if (item.id === row.id) {
merge(item, form);
found = item;
}
});
await saveMenus();
return found;
};
const delRequest = async ({ row }: DelReq) => {
eachTree([{ children: settingStore.headerMenus?.menus }], (item) => {
if (item.children) {
remove(item.children, (child) => child.id === row.id);
}
});
await saveMenus();
};
const addRequest = async ({ form }: AddReq) => {
form.id = nanoid();
if (form.parentId) {
eachTree(settingStore.headerMenus?.menus || [], (item) => {
if (item.id === form.parentId) {
if (!item.children) {
item.children = [];
}
item.children.push(form);
}
});
} else {
settingStore.headerMenus?.menus.push(form);
}
parent.value = null;
await saveMenus();
return form;
};
return {
crudOptions: {
settings: {
plugins: {
//这里使用行选择插件生成行选择crudOptions配置最终会与crudOptions合并
rowSelection: {
enabled: true,
order: -2,
before: true,
// handle: (pluginProps,useCrudProps)=>CrudOptions,
props: {
multiple: true,
crossPage: true,
selectedRowKeys
}
}
}
},
actionbar: {
buttons: {
add: {
show: false
},
addRow: {
show: true,
click: () => {
crudBinding.value.data.push({ id: nanoid() });
}
},
save: {
text: "保存菜单",
type: "primary",
click: async () => {
await settingStore.saveHeaderMenus({ menus: menusRef.value });
}
}
}
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
search: {
show: false
},
toolbar: {
buttons: {
refresh: {
show: false
}
}
},
mode: {
name: "local",
isMergeWhenUpdate: true,
isAppendWhenAdd: true
},
table: {
defaultExpandAllRows: true,
expandRowByClick: true,
editable: {
enabled: true,
mode: "row",
activeDefault: true,
showAction: true,
rowKey: "id"
defaultExpandAllRows: true,
expandedRowKeys: expandedRowKeys,
"onUpdate:expandedRowKeys": (val: string[]) => {
expandedRowKeys.value = val;
}
},
pagination: { show: false, pageSize: 9999999 },
rowHandle: {
width: 300,
fixed: "right",
group: {
editRow: {
addChild: {
text: "添加子菜单",
click: ({ row }) => {
if (row.children == null) {
row.children = [];
buttons: {
addChild: {
title: "添加子菜单",
text: null,
type: "link",
icon: "ion:add-circle-outline",
click: ({ row }) => {
crudExpose.openAdd({
row: {
parentId: row.id
}
row.children.push({ id: nanoid() });
}
});
}
}
}
@@ -104,7 +129,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
key: "id",
type: "text",
column: {
width: 200
width: 200,
show: false
},
form: {
show: false
@@ -115,20 +141,60 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
type: "text",
column: {
width: 300
},
form: {
rules: [
{
required: true,
message: "请输入标题"
}
]
}
},
icon: {
title: "图标",
type: "text",
column: {
width: 300
width: 300,
cellRender: ({ row }) => {
return <fs-icon class={"fs-16"} icon={row.icon}></fs-icon>;
}
},
form: {
component: {
placeholder: "ion:add-circle"
},
helper: {
render: () => {
return (
<span>
<a href="https://icon-sets.iconify.design/" target="_blank">
</a>
<span>--------</span>
</span>
);
}
}
}
},
link: {
path: {
title: "链接",
type: "text",
type: "link",
column: {
width: 300
},
form: {
rules: [
{
required: true,
message: "请输入链接"
},
{
type: "url",
message: "请输入正确的链接"
}
]
}
}
}

View File

@@ -12,7 +12,7 @@ import { onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useSettingStore } from "/@/store/modules/settings";
import { cloneDeep } from "lodash-es";
defineOptions({
name: "SettingsHeaderMenus"
});
@@ -21,7 +21,7 @@ const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions
const settingStore = useSettingStore();
// 页面打开后获取列表数据
onMounted(() => {
crudBinding.value.data = cloneDeep(settingStore.headerMenus.menus || []);
crudExpose.doRefresh();
});
</script>
<style lang="less"></style>

View File

@@ -16,6 +16,10 @@
<a-form-item label="开启自助注册" :name="['public', 'registerEnabled']">
<a-switch v-model:checked="formState.public.registerEnabled" />
</a-form-item>
<a-form-item label="限制用户流水线数量" :name="['public', 'limitUserPipelineCount']">
<a-input-number v-model:value="formState.public.limitUserPipelineCount" />
<div class="helper">0为不限制</div>
</a-form-item>
<a-form-item label="管理其他用户流水线" :name="['public', 'managerOtherUserPipeline']">
<a-switch v-model:checked="formState.public.managerOtherUserPipeline" />
</a-form-item>
@@ -25,12 +29,13 @@
<a-form-item label="HTTP代理" :name="['private', 'httpProxy']" :rules="urlRules">
<a-input v-model:value="formState.private.httpProxy" placeholder="http://192.168.1.2:18010/" />
<div class="helper">当某些网站被墙时可以配置</div>
</a-form-item>
<a-form-item label="HTTPS代理" :name="['private', 'httpsProxy']" :rules="urlRules">
<div class="flex">
<a-input v-model:value="formState.private.httpsProxy" placeholder="http://192.168.1.2:18010/" />
<a-button class="ml-5" type="primary" title="保存后,再点击测试" @click="testProxy">测试</a-button>
<a-button class="ml-5" type="primary" :loading="testProxyLoading" title="保存后,再点击测试" @click="testProxy">测试</a-button>
</div>
<div class="helper">一般这两个代理填一样的</div>
</a-form-item>
@@ -57,6 +62,7 @@ defineOptions({
const formState = reactive<Partial<SysSettings>>({
public: {
registerEnabled: false,
limitUserPipelineCount: 0,
managerOtherUserPipeline: false,
icpNo: ""
},
@@ -100,13 +106,19 @@ async function stopOtherUserTimer() {
});
}
const testProxyLoading = ref(false);
async function testProxy() {
const res = await api.TestProxy();
const content = `测试google:${res.google === true ? "成功" : "失败" + res.google},测试百度:${res.baidu === true ? "成功" : "失败:" + res.baidu}`;
notification.success({
message: "测试完成",
description: content
});
testProxyLoading.value = true;
try {
const res = await api.TestProxy();
const content = `测试google:${res.google === true ? "成功" : "失败" + res.google},测试百度:${res.baidu === true ? "成功" : "失败:" + res.baidu}`;
notification.success({
message: "测试完成",
description: content
});
} finally {
testProxyLoading.value = false;
}
}
</script>

View File

@@ -64,6 +64,7 @@ import * as api from "./api";
import { notification } from "ant-design-vue";
import { useSettingStore } from "/src/store/modules/settings";
import { useUserStore } from "/@/store/modules/user";
import { merge } from "lodash-es";
defineOptions({
name: "SiteSetting"
@@ -85,7 +86,7 @@ async function loadSysSiteSettings() {
if (data == null) {
return;
}
Object.assign(formState, data);
merge(formState, data);
}
const saveLoading = ref(false);
loadSysSiteSettings();

View File

@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.16](https://github.com/certd/certd/compare/v1.26.15...v1.26.16) (2024-10-30)
### Performance Improvements
* 支持华为云cdn ([81a3fdb](https://github.com/certd/certd/commit/81a3fdbc29b71f380762008cc151493ec97458f9))
## [1.26.15](https://github.com/certd/certd/compare/v1.26.14...v1.26.15) (2024-10-28)
### Performance Improvements
* 默认证书更新时间设置为35天增加腾讯云删除过期证书插件可以避免腾讯云过期证书邮件 ([51b6fed](https://github.com/certd/certd/commit/51b6fed468eaa6f28ce4497ce303ace1a52abb96))
* 授权加密支持解密查看 ([5575c83](https://github.com/certd/certd/commit/5575c839705f6987ad2bdcd33256b0962c6a9c6a))
* 重置管理员密码同时启用管理员账户,避免之前禁用了,重置密码还是登录不进去 ([f92d918](https://github.com/certd/certd/commit/f92d918a1e28e29b794ad4754661ea760c18af46))
## [1.26.14](https://github.com/certd/certd/compare/v1.26.13...v1.26.14) (2024-10-26)
### Bug Fixes
* 修复阿里云部署大杀器报插件_还未注册错误的bug ([abd2dcf](https://github.com/certd/certd/commit/abd2dcf2e85a545321bae6451406d081f773b132))
* 修复启动时自签证书无法保存的bug ([526c484](https://github.com/certd/certd/commit/526c48450bcd37b3ccded9b448f17de8140bdc6e))
### Performance Improvements
* 禁用readonly用户 ([d10d42e](https://github.com/certd/certd/commit/d10d42e20619bb55a50d636b8867ff33db4e3b4b))
* 限制其他用户流水线数量 ([315e437](https://github.com/certd/certd/commit/315e43746baf01682737f82e41579237a48409af))
* 用户管理优化头像上传 ([661293c](https://github.com/certd/certd/commit/661293c189a3abf3cdc953b5225192372f57930d))
## [1.26.13](https://github.com/certd/certd/compare/v1.26.12...v1.26.13) (2024-10-26)
### Performance Improvements

View File

@@ -0,0 +1,3 @@
update sys_user set status = 0 where id = 2;

View File

@@ -0,0 +1,3 @@
update sys_user set status = 0 where id = 2;

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.26.13",
"version": "1.26.16",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -27,17 +27,19 @@
},
"dependencies": {
"@alicloud/pop-core": "^1.7.10",
"@certd/acme-client": "^1.26.13",
"@certd/commercial-core": "^1.26.13",
"@certd/lib-huawei": "^1.26.13",
"@certd/lib-jdcloud": "^1.26.13",
"@certd/lib-k8s": "^1.26.13",
"@certd/lib-server": "^1.26.13",
"@certd/midway-flyway-js": "^1.26.13",
"@certd/pipeline": "^1.26.13",
"@certd/plugin-cert": "^1.26.13",
"@certd/plugin-plus": "^1.26.13",
"@certd/plus-core": "^1.26.13",
"@certd/acme-client": "^1.26.16",
"@certd/commercial-core": "^1.26.16",
"@certd/lib-huawei": "^1.26.16",
"@certd/lib-jdcloud": "^1.26.16",
"@certd/lib-k8s": "^1.26.16",
"@certd/lib-server": "^1.26.16",
"@certd/midway-flyway-js": "^1.26.16",
"@certd/pipeline": "^1.26.16",
"@certd/plugin-cert": "^1.26.16",
"@certd/plugin-plus": "^1.26.16",
"@certd/plus-core": "^1.26.16",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
"@koa/cors": "^5.0.0",
"@midwayjs/bootstrap": "~3.17.1",
"@midwayjs/cache": "~3.14.0",

View File

@@ -67,6 +67,12 @@ export class AccessController extends CrudController<AccessService> {
return this.ok(access);
}
@Post('/getSecretPlain', { summary: Constants.per.authOnly })
async getSecretPlain(@Body(ALL) body: { id: number; key: string }) {
const value = await this.service.getById(body.id, this.getUserId());
return this.ok(value[body.key]);
}
@Post('/accessTypeDict', { summary: Constants.per.authOnly })
async getAccessTypeDict() {
const list = this.service.getDefineList();

View File

@@ -111,7 +111,9 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
await http.request({
url: google,
method: 'GET',
timeout: 4000,
timeout: 5000,
logRes: false,
logParams: false,
});
googleRes = true;
} catch (e) {
@@ -123,7 +125,9 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
await http.request({
url: baidu,
method: 'GET',
timeout: 4000,
timeout: 5000,
logRes: false,
logParams: false,
});
baiduRes = true;
} catch (e) {

View File

@@ -30,6 +30,7 @@ export class ResetPasswdMiddleware implements IWebMiddleware {
logger.info('开始重置1号管理员用户的密码');
const newPasswd = '123456';
await this.userService.resetPassword(1, newPasswd);
await this.userService.updateStatus(1, 1);
logger.info(`重置1号管理员用户的密码完成新密码为${newPasswd}`);
}
}

View File

@@ -2,6 +2,7 @@ import { logger } from '@certd/pipeline';
import fs from 'fs';
// @ts-ignore
import forge from 'node-forge';
import path from 'path';
export function createSelfCertificate(opts: { crtPath: string; keyPath: string }) {
// 生成密钥对
@@ -32,6 +33,14 @@ export function createSelfCertificate(opts: { crtPath: string; keyPath: string }
logger.info('生成自签名证书成功');
logger.info(`自签证书保存路径: ${opts.crtPath}`);
logger.info(`自签私钥保存路径: ${opts.keyPath}`);
const crtDir = path.dirname(opts.crtPath);
if (!fs.existsSync(crtDir)) {
fs.mkdirSync(crtDir, { recursive: true });
}
const keyDir = path.dirname(opts.keyPath);
if (!fs.existsSync(keyDir)) {
fs.mkdirSync(keyDir, { recursive: true });
}
fs.writeFileSync(opts.crtPath, pemCert);
fs.writeFileSync(opts.keyPath, pemKey);

View File

@@ -35,6 +35,9 @@ export class LoginService {
if (!right) {
throw new CommonException('用户名或密码错误');
}
if (info.status === 0) {
throw new CommonException('用户已被禁用');
}
const roleIds = await this.roleService.getRoleIdsByUserId(info.id);
return this.generateToken(info, roleIds);

View File

@@ -1,7 +1,7 @@
import { Config, Inject, Provide, Scope, ScopeEnum, sleep } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { In, Repository } from 'typeorm';
import { BaseService, NeedVIPException, PageReq } from '@certd/lib-server';
import { BaseService, NeedVIPException, PageReq, SysPublicSettings, SysSettingsService } from '@certd/lib-server';
import { PipelineEntity } from '../entity/pipeline.js';
import { PipelineDetail } from '../entity/vo/pipeline-detail.js';
import { Executor, isPlus, logger, Pipeline, ResultType, RunHistory, UserInfo } from '@certd/pipeline';
@@ -47,6 +47,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
@Inject()
pluginConfigGetter: PluginConfigGetter;
@Inject()
sysSettingsService: SysSettingsService;
@Inject()
userService: UserService;
@@ -121,16 +124,27 @@ export class PipelineService extends BaseService<PipelineEntity> {
old = await this.info(bean.id);
}
const isUpdate = bean.id > 0 && old != null;
if (!isPlus()) {
let count = await this.repository.count();
if (!isUpdate) {
//如果是添加要加1
count += 1;
if (!isUpdate) {
//如果是添加,校验数量
if (!isPlus()) {
const count = await this.repository.count();
if (count >= freeCount) {
throw new NeedVIPException(`基础版最多只能创建${freeCount}条流水线`);
}
}
if (count > freeCount) {
throw new NeedVIPException('基础版最多只能创建10个pipeline');
const userId = bean.userId;
const userIsAdmin = await this.userService.isAdmin(userId);
if (!userIsAdmin) {
//非管理员用户限制pipeline数量
const count = await this.repository.count({ where: { userId } });
const sysPublic = await this.sysSettingsService.getSetting<SysPublicSettings>(SysPublicSettings);
const limitUserPipelineCount = sysPublic.limitUserPipelineCount;
if (limitUserPipelineCount && limitUserPipelineCount > 0 && count >= limitUserPipelineCount) {
throw new NeedVIPException(`您最多只能创建${limitUserPipelineCount}条流水线`);
}
}
}
if (!isUpdate) {
//如果是添加先保存一下获取到id更新pipeline.id
await this.addOrUpdate(bean);

View File

@@ -4,7 +4,7 @@ import { Repository } from 'typeorm';
import { UserEntity } from '../entity/user.js';
import * as _ from 'lodash-es';
import md5 from 'md5';
import { CommonException } from '@certd/lib-server';
import { CommonException, FileService } from '@certd/lib-server';
import { BaseService } from '@certd/lib-server';
import { RoleService } from './role-service.js';
import { PermissionService } from './permission-service.js';
@@ -34,6 +34,9 @@ export class UserService extends BaseService<UserEntity> {
@Inject()
sysSettingsService: SysSettingsService;
@Inject()
fileService: FileService;
//@ts-ignore
getRepository() {
return this.repository;
@@ -68,6 +71,11 @@ export class UserService extends BaseService<UserEntity> {
const plainPassword = param.password ?? RandomUtil.randomStr(6);
param.passwordVersion = 2;
param.password = await this.genPassword(plainPassword, param.passwordVersion); // 默认密码 建议未改密码不能登陆
if (param.avatar) {
param.avatar = await this.fileService.saveFile(0, param.avatar, 'public');
}
await super.add(param);
//添加角色
if (param.roles && param.roles.length > 0) {
@@ -98,6 +106,10 @@ export class UserService extends BaseService<UserEntity> {
} else {
delete param.password;
}
if (param.avatar) {
param.avatar = await this.fileService.saveFile(userInfo.id, param.avatar, 'public');
}
await super.update(param);
await this.roleService.updateRoles(param.id, param.roles);
}
@@ -186,6 +198,9 @@ export class UserService extends BaseService<UserEntity> {
}
async resetPassword(userId: any, newPasswd: string) {
if (!userId) {
throw new CommonException('userId不能为空');
}
const param = {
id: userId,
password: newPasswd,
@@ -198,15 +213,19 @@ export class UserService extends BaseService<UserEntity> {
ids = ids.split(',');
ids = ids.map(id => parseInt(id));
}
if (ids instanceof Array) {
if (ids.includes(1)) {
throw new CommonException('不能删除管理员');
}
if (ids.length === 0) {
return;
}
if (ids.includes(1)) {
throw new CommonException('不能删除管理员');
}
await super.delete(ids);
}
async isAdmin(userId: any) {
if (!userId) {
throw new CommonException('userId不能为空');
}
const userRoles = await this.userRoleService.find({
where: {
userId,
@@ -217,4 +236,13 @@ export class UserService extends BaseService<UserEntity> {
return true;
}
}
async updateStatus(id: number, status: number) {
if (!id) {
throw new CommonException('userId不能为空');
}
await this.repository.update(id, {
status,
});
}
}

View File

@@ -19,18 +19,18 @@ import path from 'path';
export class CopyCertToLocalPlugin extends AbstractTaskPlugin {
@TaskInput({
title: '证书保存路径',
helper: '全链证书,路径要包含文件名' + '\n推荐使用相对路径将写入与数据库同级目录无需映射例如./tmp/cert.pem',
helper: '全链证书,路径要包含文件名' + '\n推荐使用相对路径将写入与数据库同级目录无需映射例如tmp/cert.pem',
component: {
placeholder: './tmp/full_chain.pem',
placeholder: 'tmp/full_chain.pem',
},
rules: [{ type: 'filepath' }],
})
crtPath!: string;
@TaskInput({
title: '私钥保存路径',
helper: '路径要包含文件名\n推荐使用相对路径将写入与数据库同级目录无需映射例如./tmp/cert.key',
helper: '路径要包含文件名\n推荐使用相对路径将写入与数据库同级目录无需映射例如tmp/cert.key',
component: {
placeholder: './tmp/cert.key',
placeholder: 'tmp/cert.key',
},
rules: [{ type: 'filepath' }],
})
@@ -48,9 +48,9 @@ export class CopyCertToLocalPlugin extends AbstractTaskPlugin {
@TaskInput({
title: 'PFX证书保存路径',
helper: '用于IIS证书部署路径要包含文件名\n推荐使用相对路径将写入与数据库同级目录无需映射例如./tmp/cert.pfx',
helper: '用于IIS证书部署路径要包含文件名\n推荐使用相对路径将写入与数据库同级目录无需映射例如tmp/cert.pfx',
component: {
placeholder: './tmp/cert.pfx',
placeholder: 'tmp/cert.pfx',
},
rules: [{ type: 'filepath' }],
})
@@ -59,14 +59,24 @@ export class CopyCertToLocalPlugin extends AbstractTaskPlugin {
@TaskInput({
title: 'DER证书保存路径',
helper:
'用于Apache证书部署路径要包含文件名\n推荐使用相对路径将写入与数据库同级目录无需映射例如./tmp/cert.der\n.der和.cer是相同的东西改个后缀名即可',
'用于Apache证书部署路径要包含文件名\n推荐使用相对路径将写入与数据库同级目录无需映射例如tmp/cert.der\n.der和.cer是相同的东西改个后缀名即可',
component: {
placeholder: './tmp/cert.der 或 ./tmp/cert.cer',
placeholder: 'tmp/cert.der 或 tmp/cert.cer',
},
rules: [{ type: 'filepath' }],
})
derPath!: string;
// @TaskInput({
// title: 'jks证书保存路径',
// helper: '用于java路径要包含文件名例如tmp/cert.jks',
// component: {
// placeholder: 'tmp/cert.jks',
// },
// rules: [{ type: 'filepath' }],
// })
jksPath!: string;
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
@@ -108,6 +118,12 @@ export class CopyCertToLocalPlugin extends AbstractTaskPlugin {
})
hostDerPath!: string;
@TaskOutput({
title: 'jks保存路径',
type: 'HostJksPath',
})
hostJksPath!: string;
async onInstance() {}
copyFile(srcFile: string, destFile: string) {
@@ -123,10 +139,10 @@ export class CopyCertToLocalPlugin extends AbstractTaskPlugin {
throw new Error('只有管理员才能运行此任务');
}
let { crtPath, keyPath, icPath, pfxPath, derPath } = this;
let { crtPath, keyPath, icPath, pfxPath, derPath, jksPath } = this;
const certReader = new CertReader(this.cert);
const handle = async ({ reader, tmpCrtPath, tmpKeyPath, tmpDerPath, tmpPfxPath, tmpIcPath }) => {
const handle = async ({ reader, tmpCrtPath, tmpKeyPath, tmpDerPath, tmpPfxPath, tmpIcPath, tmpJksPath }) => {
this.logger.info('复制到目标路径');
if (crtPath) {
crtPath = crtPath.startsWith('/') ? crtPath : path.join(Constants.dataDir, crtPath);
@@ -153,6 +169,11 @@ export class CopyCertToLocalPlugin extends AbstractTaskPlugin {
this.copyFile(tmpDerPath, derPath);
this.hostDerPath = derPath;
}
if (jksPath) {
jksPath = jksPath.startsWith('/') ? jksPath : path.join(Constants.dataDir, jksPath);
this.copyFile(tmpJksPath, jksPath);
this.hostJksPath = jksPath;
}
this.logger.info('请注意,如果使用的是相对路径,那么文件就在你的数据库同级目录下,默认是/data/certd/下面');
this.logger.info(
'请注意如果使用的是绝对路径文件在容器内的目录下你需要给容器做目录映射才能复制到宿主机需要在docker-compose.yaml中配置主机目录映射 volumes: /你宿主机的路径:/任务配置的证书路径'

View File

@@ -7,10 +7,10 @@ import dayjs from 'dayjs';
@IsTaskPlugin({
name: 'uploadCertToHost',
title: '上传证书到主机',
title: '部署证书到主机',
icon: 'line-md:uploading-loop',
group: pluginGroups.host.key,
desc: '支持上传完成后执行脚本命令',
desc: '上传证书到主机,然后执行部署脚本命令',
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
@@ -67,6 +67,16 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
})
derPath!: string;
// @TaskInput({
// title: 'jks证书保存路径',
// helper: '需要有写入权限,路径要包含证书文件名,例如:/tmp/cert.jks',
// component: {
// placeholder: '/root/deploy/nginx/cert.jks',
// },
// rules: [{ type: 'filepath' }],
// })
jksPath!: string;
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
@@ -147,6 +157,10 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
title: 'DER保存路径',
})
hostDerPath!: string;
@TaskOutput({
title: 'jks保存路径',
})
hostJksPath!: string;
async onInstance() {}
@@ -167,7 +181,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
const certReader = new CertReader(cert);
const handle = async (opts: CertReaderHandleContext) => {
const { tmpCrtPath, tmpKeyPath, tmpDerPath, tmpPfxPath, tmpIcPath } = opts;
const { tmpCrtPath, tmpKeyPath, tmpDerPath, tmpJksPath, tmpPfxPath, tmpIcPath } = opts;
// if (this.copyToThisHost) {
// this.logger.info('复制到目标路径');
// this.copyFile(tmpCrtPath, crtPath);
@@ -227,6 +241,13 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
});
this.logger.info(`上传DER证书到主机${this.derPath}`);
}
if (this.jksPath) {
transports.push({
localPath: tmpJksPath,
remotePath: this.jksPath,
});
this.logger.info(`上传jks证书到主机${this.jksPath}`);
}
this.logger.info('开始上传文件到服务器');
await sshClient.uploadFiles({
connectConf,
@@ -240,6 +261,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
this.hostIcPath = this.icPath;
this.hostPfxPath = this.pfxPath;
this.hostDerPath = this.derPath;
this.hostJksPath = this.jksPath;
};
await certReader.readCertFile({

View File

@@ -1,2 +1,3 @@
export * from './access/index.js';
export * from './dns-provider/index.js';
export * from './plugins/deploy-to-cdn/index.js';

View File

@@ -0,0 +1,116 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, resetLogConfigure, RunStrategy, TaskInput } from '@certd/pipeline';
import { HuaweiAccess } from '../../access/index.js';
import { CertInfo } from '@certd/plugin-cert';
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-plus';
@IsTaskPlugin({
name: 'HauweiDeployCertToCDN',
title: '部署证书至华为云CDN',
icon: 'ant-design:huawei-outlined',
group: pluginGroups.huawei.key,
desc: '',
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class HauweiDeployCertToCDN extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: ['CertApply', 'CertApplyLego'],
},
required: true,
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: 'Access授权',
helper: '华为云授权AccessKeyId、AccessKeySecret',
component: {
name: 'access-selector',
type: 'huawei',
},
required: true,
})
accessId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: 'CDN域名',
helper: '请选择域名或输入域名',
typeName: 'HauweiDeployCertToCDN',
action: HauweiDeployCertToCDN.prototype.onGetDomainList.name,
})
)
domains!: string[];
async execute(): Promise<void> {
this.logger.info('开始部署证书到华为云cdn');
const { cdn, client } = await this.getCdnClient();
const httpsConfig = new cdn.HttpPutBody()
.withHttpsStatus('on')
.withCertificateType('server')
.withCertificateName(this.appendTimeSuffix('certd'))
.withCertificateValue(this.cert.crt)
.withPrivateKey(this.cert.key);
const config = new cdn.Configs().withHttps(httpsConfig);
const body = new cdn.ModifyDomainConfigRequestBody().withConfigs(config);
if (!this.domains || this.domains.length === 0) {
throw new Error('您还未配置CDN域名');
}
this.logger.info('部署域名:', JSON.stringify(this.domains));
for (const domain of this.domains) {
this.logger.info('部署到域名:', domain);
const req = new cdn.UpdateDomainFullConfigRequest().withDomainName(domain).withBody(body);
await client.updateDomainFullConfig(req);
this.logger.info(`部署到域名${domain}完成:`);
}
this.logger.info('部署证书到华为云cdn完成');
}
async getCdnClient() {
const access = await this.accessService.getById<HuaweiAccess>(this.accessId);
const { BasicCredentials } = await import('@huaweicloud/huaweicloud-sdk-core');
const cdn = await import('@huaweicloud/huaweicloud-sdk-cdn/v2/public-api.js');
//恢复华为云把log4j的config改了的问题
resetLogConfigure();
const credentials = new BasicCredentials().withAk(access.accessKeyId).withSk(access.accessKeySecret);
const client = cdn.CdnClient.newBuilder().withCredential(credentials).withEndpoint('https://cdn.myhuaweicloud.com').build();
return {
client,
cdn,
};
}
async onGetDomainList(data: any) {
const { client, cdn } = await this.getCdnClient();
const request = new cdn.ListDomainsRequest();
request.pageNumber = 1;
request.pageSize = 1000;
const result: any = await client.listDomains(request);
if (!result || !result.domains || result.domains.length === 0) {
throw new Error('未找到CDN域名您可以手动输入');
}
const domains = result.domains.map(domain => {
return {
value: domain.domain_name,
label: domain.domain_name,
domain: domain.domain_name,
};
});
return this.ctx.utils.options.buildGroupOptions(domains, this.certDomains);
}
}
new HauweiDeployCertToCDN();

View File

@@ -55,4 +55,21 @@ export class TencentSslClient {
this.checkRet(res);
return res;
}
async DescribeCertificates(params: any) {
const client = await this.getSslClient();
const res = await client.DescribeCertificates(params);
this.checkRet(res);
return res;
}
async doRequest(action: string, params: any) {
const client = await this.getSslClient();
if (!client[action]) {
throw new Error(`action ${action} not found`);
}
const res = await client[action](params);
this.checkRet(res);
return res;
}
}

View File

@@ -0,0 +1,202 @@
import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { AbstractPlusTaskPlugin, TencentAccess } from '@certd/plugin-plus';
import { TencentSslClient } from '../../lib/index.js';
import dayjs from 'dayjs';
import { remove } from 'lodash-es';
@IsTaskPlugin({
name: 'TencentDeleteExpiringCert',
title: '删除腾讯云即将过期证书',
icon: 'svg:icon-tencentcloud',
group: pluginGroups.tencent.key,
desc: '仅删除未使用的证书',
default: {
strategy: {
runStrategy: RunStrategy.AlwaysRun,
},
},
needPlus: true,
})
export class TencentDeleteExpiringCert extends AbstractPlusTaskPlugin {
@TaskInput({
title: 'Access提供者',
helper: 'access 授权',
component: {
name: 'access-selector',
type: 'tencent',
},
required: true,
})
accessId!: string;
@TaskInput({
title: '关键字筛选',
helper: '仅匹配ID、备注名称、域名包含关键字的证书可以不填',
required: false,
component: {
name: 'a-input',
},
})
searchKey!: string;
@TaskInput({
title: '最大删除数量',
helper: '单次运行最大删除数量',
value: 100,
component: {
name: 'a-input-number',
vModel: 'value',
},
required: true,
})
maxCount!: number;
@TaskInput({
title: '即将过期天数',
helper:
'仅删除有效期小于此天数的证书,\n<span class="color-red">注意:`1.26.14`版本之前Certd创建的证书流水线默认是到期前20天才更新证书需要将之前创建的证书申请任务的更新天数改为35天保证删除之前就已经替换掉即将过期证书</span>',
value: 30,
component: {
name: 'a-input-number',
vModel: 'value',
},
required: true,
})
expiringDays!: number;
@TaskInput({
title: '检查超时时间',
helper: '检查删除任务结果超时时间,单位分钟',
value: 10,
component: {
name: 'a-input-number',
vModel: 'value',
},
required: true,
})
checkTimeout!: number;
async onInstance() {}
async execute(): Promise<void> {
const access = await this.accessService.getById<TencentAccess>(this.accessId);
const sslClient = new TencentSslClient({
access,
logger: this.logger,
});
const params = {
Limit: this.maxCount ?? 100,
SearchKey: this.searchKey,
ExpirationSort: 'ASC',
FilterSource: 'upload',
// FilterExpiring: 1,
};
const res = await sslClient.DescribeCertificates(params);
let certificates = res?.Certificates;
if (!certificates && !certificates.length) {
this.logger.info('没有找到证书');
return;
}
certificates = certificates.filter((item: any) => {
const endTime = item.CertEndTime;
return dayjs(endTime).add(this.expiringDays, 'day').isBefore(dayjs());
});
for (const certificate of certificates) {
this.logger.info(`证书ID:${certificate.CertificateId}, 过期时间:${certificate.CertEndTime}Alias:${certificate.Alias},证书域名:${certificate.Domain}`);
}
this.logger.info(`即将过期的证书数量:${certificates.length}`);
if (certificates.length === 0) {
this.logger.info('没有即将过期的证书, 无需删除');
return;
}
const certIds = certificates.map((cert: any) => cert.CertificateId);
const deleteRes = await sslClient.doRequest('DeleteCertificates', {
CertificateIds: certIds,
IsSync: true,
});
this.logger.info('删除任务已提交: ', JSON.stringify(deleteRes));
const ids = deleteRes?.CertTaskIds;
if (!ids && !ids.length) {
this.logger.error('没有找到任务ID');
return;
}
const taskIds = ids.map((id: any) => id.TaskId);
const startTime = Date.now();
const results = {};
const statusCount = {
success: 0,
failed: 0,
unauthorized: 0,
unbind: 0,
timeout: 0,
};
const total = taskIds.length;
while (Date.now() < startTime + this.checkTimeout * 60 * 1000) {
this.checkSignal();
const taskResultRes = await sslClient.doRequest('DescribeDeleteCertificatesTaskResult', {
TaskIds: taskIds,
});
const result = taskResultRes.DeleteTaskResult;
if (!result || result.length === 0) {
this.logger.info('暂未获取到有效的任务结果');
continue;
}
for (const item of result) {
//遍历结果
const status = item.Status;
if (status !== 0) {
remove(taskIds, id => id === item.TaskId);
}
// Status : 0表示任务进行中、 1表示任务成功、 2表示任务失败、3表示未授权服务角色导致任务失败、4表示有未解绑的云资源导致任务失败、5表示查询关联云资源超时导致任务失败
if (status === 0) {
this.logger.info(`任务${item.TaskId}<${item.CertId}>: 进行中`);
} else if (status === 1) {
this.logger.info(`任务${item.TaskId}<${item.CertId}>: 成功`);
results[item.TaskId] = '成功';
statusCount.success++;
} else if (status === 2) {
this.logger.error(`任务${item.TaskId}<${item.CertId}>: 失败`);
results[item.TaskId] = '失败';
statusCount.failed++;
} else if (status === 3) {
this.logger.error(`任务${item.TaskId}<${item.CertId}>: 未授权服务角色导致任务失败`);
results[item.TaskId] = '未授权服务角色导致任务失败';
statusCount.unauthorized++;
} else if (status === 4) {
this.logger.error(`任务${item.TaskId}<${item.CertId}>: 有未解绑的云资源导致任务失败`);
results[item.TaskId] = '有未解绑的云资源导致任务失败';
statusCount.unbind++;
} else if (status === 5) {
this.logger.error(`任务${item.TaskId}<${item.CertId}>: 查询关联云资源超时导致任务失败`);
results[item.TaskId] = '查询关联云资源超时导致任务失败';
statusCount.timeout++;
} else {
this.logger.info(`任务${item.TaskId}<${item.CertId}>: 未知状态:${status}`);
statusCount.failed++;
}
}
this.logger.info(
// eslint-disable-next-line max-len
`任务总数:${total}, 进行中:${taskIds.length} 成功:${statusCount.success}, 未授权服务角色导致失败:${statusCount.unauthorized}, 未解绑关联资源失败:${statusCount.unbind}, 查询关联资源超时:${statusCount.timeout},未知原因失败:${statusCount.failed}`
);
if (taskIds.length === 0) {
this.logger.info('任务已全部完成');
if (statusCount.unauthorized > 0) {
throw new Error('有未授权服务角色导致任务失败需给Access授权服务角色SSL_QCSLinkedRoleInReplaceLoadCertificate');
}
return;
}
await this.ctx.utils.sleep(10000);
}
this.logger.error('检查任务结果超时', JSON.stringify(results));
}
}
new TencentDeleteExpiringCert();

Some files were not shown because too many files have changed in this diff Show More