Compare commits

...

103 Commits

Author SHA1 Message Date
耗子 367348e688 feat: bump v2.2.2 2024-05-23 23:14:59 +08:00
耗子 07dfcd778c feat: bump v2.2.1 2024-05-23 22:27:43 +08:00
耗子 789d5e7ddd fix: test 2024-05-23 22:04:13 +08:00
耗子 1102aba9a3 fix: test 2024-05-23 21:04:20 +08:00
耗子 fddb076136 fix: #93 2024-05-23 20:33:16 +08:00
耗子 b1ff8fff3d feat(文件): 优化chmod使用系统命令 2024-05-23 20:27:09 +08:00
耗子 5cd68fcdc3 fix(文件): 统一copy的字段 2024-05-21 21:32:54 +08:00
耗子 3a5ff9179a fix: 获取公网IP 2024-05-21 02:01:14 +08:00
耗子 fa7c54ada5 feat: 优化打包大小 2024-05-20 02:16:14 +08:00
耗子 94047174d1 feat: 优化打包大小 2024-05-20 02:04:04 +08:00
耗子 305777f65a feat: bump version v2.2.0 2024-05-20 01:26:45 +08:00
耗子 d56d3ec1dd feat: 优化路径正则 2024-05-20 01:13:33 +08:00
耗子 8ac8da7914 feat: 优化路径正则 2024-05-20 00:56:39 +08:00
耗子 5d56663b69 fix: 脚本文件权限 2024-05-19 20:31:33 +08:00
耗子 7ff574cb92 fix: OpenResty pcre2 版本号 2024-05-19 16:09:34 +08:00
耗子 00b4153a28 feat: 添加软链接返回 2024-05-19 04:27:17 +08:00
耗子 9412b31279 feat: 助手函数更新 2024-05-19 04:21:57 +08:00
耗子 bbf04c91cf Merge remote-tracking branch 'origin/main' 2024-05-19 04:18:51 +08:00
耗子 252456a1cd feat: 文件管理分页 2024-05-19 04:18:42 +08:00
机器人 8de68b317d Merge branch 'renovate/github.com-imroc-req-v3-3.x' into 'main'
chore(deps): Update module github.com/imroc/req/v3 to v3.43.5

See merge request opensource/panel!71
2024-05-16 16:19:01 +00:00
机器人 8bf273e1fd Merge branch 'renovate/github.com-docker-docker-26.x' into 'main'
chore(deps): Update module github.com/docker/docker to v26.1.3+incompatible

See merge request opensource/panel!70
2024-05-16 16:17:48 +00:00
机器人 32111ecd7a chore(deps): Update module github.com/imroc/req/v3 to v3.43.5 2024-05-16 16:08:19 +00:00
机器人 82b8a32133 chore(deps): Update module github.com/docker/docker to v26.1.3+incompatible 2024-05-16 16:07:37 +00:00
耗子 b82c644d81 feat: MySQL 8.4 支持 2024-05-16 00:18:01 +08:00
耗子 06bf1b0604 feat: 部分软件使用 7z 格式 2024-05-15 22:48:17 +08:00
耗子 7b29e4546a feat: 替换新的下载节点 2024-05-15 21:53:04 +08:00
耗子 ae6b354a78 feat: MySQL 安装脚本支持 8.4 2024-05-15 02:56:39 +08:00
耗子 d96c616c61 feat: start rsyslog after install 2024-05-10 22:41:43 +08:00
耗子 213698dd2a feat: install rsyslog in el 9 2024-05-10 22:38:35 +08:00
耗子 39b3c74684 Merge remote-tracking branch 'refs/remotes/origin/renovate/golangci-golangci-lint-action-6.x' 2024-05-10 22:37:58 +08:00
机器人 b3a1af1001 Merge branch 'renovate/github.com-docker-docker-26.x' into 'main'
chore(deps): Update module github.com/docker/docker to v26.1.2+incompatible

See merge request opensource/panel!69
2024-05-09 16:18:56 +00:00
机器人 91b244276a chore(deps): Update module github.com/docker/docker to v26.1.2+incompatible 2024-05-09 16:09:17 +00:00
机器人 b1eefd062d Merge branch 'renovate/github.com-imroc-req-v3-3.x' into 'main'
chore(deps): Update module github.com/imroc/req/v3 to v3.43.4

See merge request opensource/panel!68
2024-05-08 10:22:09 +00:00
机器人 2edf6748e7 chore(deps): Update module github.com/imroc/req/v3 to v3.43.4 2024-05-08 10:07:04 +00:00
机器人 eac69b8dab chore(deps): Update golangci/golangci-lint-action action to v6 2024-05-07 04:07:04 +00:00
机器人 bb7b641193 Merge branch 'renovate/golang.org-x-net-0.x' into 'main'
chore(deps): Update module golang.org/x/net to v0.25.0

See merge request opensource/panel!66
2024-05-06 22:27:43 +00:00
机器人 3b0aee1967 Merge branch 'renovate/github.com-mholt-acmez-v2-2.x' into 'main'
chore(deps): Update module github.com/mholt/acmez/v2 to v2.0.1

See merge request opensource/panel!65
2024-05-06 22:24:19 +00:00
机器人 2ae7f3e458 chore(deps): Update module golang.org/x/net to v0.25.0 2024-05-06 22:10:22 +00:00
机器人 ee0442d159 chore(deps): Update module github.com/mholt/acmez/v2 to v2.0.1 2024-05-06 22:09:55 +00:00
机器人 363b7a55be Merge branch 'renovate/golang.org-x-crypto-0.x' into 'main'
chore(deps): Update module golang.org/x/crypto to v0.23.0

See merge request opensource/panel!64
2024-05-06 16:19:58 +00:00
机器人 3437bb2b1f chore(deps): Update module golang.org/x/crypto to v0.23.0 2024-05-06 16:08:16 +00:00
耗子 7e000cddc8 feat: optimize script 2024-05-06 03:27:34 +08:00
机器人 a18652f134 Merge branch 'renovate/github.com-docker-docker-26.x' into 'main'
chore(deps): Update module github.com/docker/docker to v26.1.1+incompatible

See merge request opensource/panel!63
2024-04-30 22:21:04 +00:00
机器人 dcd8bb3dd9 chore(deps): Update module github.com/docker/docker to v26.1.1+incompatible 2024-04-30 22:10:15 +00:00
耗子 d62a280fe9 docs: update README 2024-05-01 02:53:36 +08:00
耗子 c3a7710e6f chore: update dependences 2024-05-01 02:51:32 +08:00
耗子 7f1491f739 Merge remote-tracking branch 'origin/renovate/github.com-mholt-acmez-2.x' 2024-04-30 12:54:56 +08:00
耗子 c96b88548e Merge remote-tracking branch 'refs/remotes/origin/renovate/golangci-golangci-lint-action-5.x' 2024-04-30 12:54:04 +08:00
krishan kumar f62b112e1b Merge pull request #91 from TheTNB/kkumar-gcc/#45
feat: support localization
2024-04-28 18:45:37 +05:30
kkumar-gcc e55e3402e9 finish translation 2024-04-27 16:13:28 +05:30
kkumar-gcc 48b539b31a finish translation for panel.writeSite 2024-04-26 19:05:50 +05:30
kkumar-gcc d35245aa11 finish translation for panel.cutoff 2024-04-26 18:46:41 +05:30
kkumar-gcc 7f46dea052 finish translation for panel.writeMysqlPassword 2024-04-26 18:19:55 +05:30
kkumar-gcc 48ed174dc6 finish translation for panel.deletePlugin 2024-04-26 18:16:26 +05:30
kkumar-gcc a5743f94b2 finish translation for panel.writePlugin 2024-04-26 18:13:13 +05:30
kkumar-gcc 227c5344e3 finish translation for panel.deleteEntrance 2024-04-26 18:08:50 +05:30
kkumar-gcc 4c8a88ea03 finish translation for panel.getInfo 2024-04-26 17:57:51 +05:30
kkumar-gcc 3503aac2f6 finish translation for panel.update 2024-04-26 13:36:11 +05:30
耗子 6beda2ea12 feat: 优化 bbr 检查 2024-04-26 15:44:40 +08:00
机器人 4579bf8fa9 chore(deps): Update golangci/golangci-lint-action action to v5 2024-04-25 04:04:38 +00:00
机器人 002db34b4d Merge branch 'renovate/github.com-bytedance-sonic-1.x' into 'main'
chore(deps): Update module github.com/bytedance/sonic to v1.11.6

See merge request opensource/panel!61
2024-04-24 16:17:16 +00:00
机器人 e276c4f747 chore(deps): Update module github.com/bytedance/sonic to v1.11.6 2024-04-24 16:08:10 +00:00
机器人 f5e6219ca4 chore(deps): Update module github.com/mholt/acmez to v2 2024-04-23 22:08:34 +00:00
机器人 fd92b1d360 Merge branch 'renovate/github.com-docker-docker-26.x' into 'main'
chore(deps): Update module github.com/docker/docker to v26.1.0+incompatible

See merge request opensource/panel!59
2024-04-22 22:18:32 +00:00
机器人 5b8289c0b0 chore(deps): Update module github.com/docker/docker to v26.1.0+incompatible 2024-04-22 22:10:41 +00:00
kkumar-gcc d8fe050607 add translation for panel init command 2024-04-22 22:10:38 +05:30
kkumar-gcc c0310d916f add commands prefix for command translation 2024-04-22 20:01:17 +05:30
kkumar-gcc 77ed2d77e1 setup basic translations 2024-04-22 13:51:53 +05:30
耗子 c331143e9e docs: 更新赞助说明 2024-04-21 20:00:59 +08:00
机器人 71e75312ff Merge branch 'renovate/github.com-docker-docker-26.x' into 'main'
chore(deps): Update module github.com/docker/docker to v26.0.2+incompatible

See merge request opensource/panel!58
2024-04-18 22:16:30 +00:00
机器人 0e5bee4852 chore(deps): Update module github.com/docker/docker to v26.0.2+incompatible 2024-04-18 22:08:42 +00:00
机器人 036ad9222a Merge branch 'renovate/github.com-bytedance-sonic-1.x' into 'main'
chore(deps): Update module github.com/bytedance/sonic to v1.11.5

See merge request opensource/panel!57
2024-04-18 10:20:49 +00:00
机器人 39cd5545c5 chore(deps): Update module github.com/bytedance/sonic to v1.11.5 2024-04-18 10:08:40 +00:00
机器人 4568e60133 Merge branch 'renovate/github.com-bytedance-sonic-1.x' into 'main'
chore(deps): Update module github.com/bytedance/sonic to v1.11.4

See merge request opensource/panel!56
2024-04-17 10:15:52 +00:00
机器人 d2f224eaa6 chore(deps): Update module github.com/bytedance/sonic to v1.11.4 2024-04-17 10:08:10 +00:00
耗子 c77713b3d9 fix: lint 2024-04-17 14:58:46 +08:00
耗子 f1e2ba5c35 feat: 降级 sonic 以减少打包大小 2024-04-17 14:49:48 +08:00
耗子 243a36f41c Merge remote-tracking branch 'github/renovate/github.com-docker-docker-26.x' 2024-04-17 14:45:03 +08:00
机器人 74f772b705 Merge branch 'renovate/github.com-imroc-req-v3-3.x' into 'main'
chore(deps): Update module github.com/imroc/req/v3 to v3.43.3

See merge request opensource/panel!55
2024-04-16 16:23:08 +00:00
机器人 e8f1ed5dec chore(deps): Update module github.com/imroc/req/v3 to v3.43.3 2024-04-16 16:09:32 +00:00
机器人 634d8c643b chore(deps): Update module github.com/docker/docker to v26 2024-04-11 16:07:23 +00:00
机器人 1829b46466 Merge branch 'renovate/github.com-imroc-req-v3-3.x' into 'main'
chore(deps): Update module github.com/imroc/req/v3 to v3.43.2

See merge request opensource/panel!54
2024-04-08 04:18:51 +00:00
机器人 478efa5016 chore(deps): Update module github.com/imroc/req/v3 to v3.43.2 2024-04-08 04:06:37 +00:00
耗子 841e0e351f feat: 在面板定时任务出错时将面板设置为错误状态 2024-04-05 21:12:14 +08:00
耗子 0e9761f975 feat: 优化更新中的输出 2024-04-05 21:08:44 +08:00
机器人 5c067e28b7 Merge branch 'renovate/golang.org-x-net-0.x' into 'main'
chore(deps): Update module golang.org/x/net to v0.24.0

See merge request opensource/panel!53
2024-04-05 04:15:08 +00:00
机器人 0a0e0bcac1 chore(deps): Update module golang.org/x/net to v0.24.0 2024-04-05 04:09:02 +00:00
机器人 c3732b904d Merge branch 'renovate/golang.org-x-crypto-0.x' into 'main'
chore(deps): Update module golang.org/x/crypto to v0.22.0

See merge request opensource/panel!52
2024-04-04 22:20:53 +00:00
机器人 161370ac5a chore(deps): Update module golang.org/x/crypto to v0.22.0 2024-04-04 22:11:01 +00:00
机器人 0cfe585ad5 Merge branch 'renovate/golang.org-x-net-0.x' into 'main'
chore(deps): Update module golang.org/x/net to v0.23.0

See merge request opensource/panel!51
2024-04-03 22:23:25 +00:00
机器人 283d9dbe26 chore(deps): Update module golang.org/x/net to v0.23.0 2024-04-03 22:10:16 +00:00
耗子 45ed3f2ff9 feat: 发布 v2.1.44 2024-03-25 00:38:04 +08:00
耗子 5703e3354f feat: CLI 添加插件和网站的命令 2024-03-24 22:38:22 +08:00
耗子 da2e66b7c6 docs: update README 2024-03-24 17:29:35 +08:00
耗子 8dca4c1cbf fix: 系统工具箱版本号错误 2024-03-24 17:12:08 +08:00
耗子 a295eb9a65 ci: 固定 Go 1.22 版本 2024-03-24 16:28:09 +08:00
耗子 868ee73d51 Merge remote-tracking branch 'origin/main' 2024-03-24 15:55:21 +08:00
耗子 ef7d38f2af feat(证书): 优化签发及部署 2024-03-24 15:55:10 +08:00
机器人 801ff28854 Merge branch 'renovate/go.uber.org-zap-1.x' into 'main'
chore(deps): Update module go.uber.org/zap to v1.27.0

See merge request opensource/panel!50
2024-03-22 22:18:27 +00:00
机器人 e0a401f688 chore(deps): Update module go.uber.org/zap to v1.27.0 2024-03-22 22:10:00 +00:00
耗子 f50f2f7f2b fix: lint 2024-03-23 01:11:36 +08:00
耗子 3df1388326 fix: lint 2024-03-23 01:05:56 +08:00
耗子 5390ef3140 refactor: 移除 lego 2024-03-23 00:50:59 +08:00
89 changed files with 2361 additions and 2123 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# These are supported funding model platforms
open_collective: haozi-panel
open_collective: tnb
custom: ['https://afdian.net/a/TheTNB']
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
- uses: actions/setup-go@v5
with:
cache: true
go-version: 'stable'
go-version: '1.22'
- name: Install dependencies
run: go mod tidy
- name: Build ${{ matrix.goarch }}
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 'stable'
go-version: '1.22'
- name: Install dependencies
run: go mod tidy
- name: Run tests with coverage
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 'stable'
go-version: '1.22'
- name: Fetch Frontend
run: |
sudo apt-get install -y curl jq unzip zip
+2 -4
View File
@@ -14,13 +14,11 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 'stable'
go-version: '1.22'
cache: false
- name: Lint
uses: golangci/golangci-lint-action@v4
uses: golangci/golangci-lint-action@v6
with:
skip-cache: true
skip-pkg-cache: true
skip-build-cache: true
version: latest
args: --timeout=30m ./...
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 'stable'
go-version: '1.22'
- name: Install dependencies
run: sudo apt-get install -y curl jq
- name: Set up environment
+4 -2
View File
@@ -1,4 +1,4 @@
image: golang:bookworm
image: golang:1.22-bookworm
# 在每个任务执行前运行
before_script:
@@ -78,7 +78,7 @@ release:
- build
- fetch
image:
name: goreleaser/goreleaser:nightly
name: goreleaser/goreleaser
entrypoint: [ '' ]
only:
- tags
@@ -87,4 +87,6 @@ release:
# generate a changelog.
GIT_DEPTH: 0
script:
- sed -i 's/dl-cdn.alpinelinux.org/mirror.sjtu.edu.cn/g' /etc/apk/repositories
- apk add --no-cache upx
- goreleaser release --clean
+18 -8
View File
@@ -1,7 +1,7 @@
project_name: panel
builds:
-
id: panel
- id: panel
binary: panel
env:
- CGO_ENABLED=0
@@ -15,36 +15,46 @@ builds:
- v2
ldflags:
- -s -w --extldflags "-static"
upx:
- enabled: true
# Filter by build ID.
ids: [ panel ]
# Compress argument.
# Valid options are from '1' (faster) to '9' (better), and 'best'.
compress: best
# Whether to try LZMA (slower).
lzma: true
# Whether to try all methods and filters (slow).
brute: true
archives:
-
id: panel-archive
- id: panel-archive
builds:
- panel
format: zip
wrap_in_directory: false
strip_parent_binary_folder: true
strip_binary_directory: true
files:
- LICENSE
- docs/*
- public/*
- storage/*
- database/*
- lang/*
- scripts/*
- panel-example.conf
gitlab_urls:
api: https://git.haozi.net/api/v4/
download: https://git.haozi.net
# set to true if you use a self-signed certificate
skip_tls_verify: false
# set to true if you want to upload to the Package Registry rather than attachments
# Only works with GitLab 13.5+
#
# Since: v1.3
use_package_registry: true
# Set this if you set GITLAB_TOKEN to the value of CI_JOB_TOKEN.
#
# Since: v1.11
+16 -23
View File
@@ -57,7 +57,7 @@ CentOS Stream 可使用迁移脚本迁移至支持的系统: [CentOS 8/9 迁移
如果你决定继续,请以`root`用户登录服务器,执行以下命令安装面板:
```shell
HAOZI_DL_URL="https://git.haozi.net/opensource/download/-/raw/main/panel"; curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh.checksum.txt && sha256sum -c install_panel.sh.checksum.txt && bash install_panel.sh || echo "Checksum 验证失败,文件可能被篡改,已终止操作"
HAOZI_DL_URL="https://dl.cdn.haozi.net/panel"; curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh.checksum.txt && sha256sum -c install_panel.sh.checksum.txt && bash install_panel.sh || echo "Checksum 验证失败,文件可能被篡改,已终止操作"
```
## 卸载面板
@@ -67,7 +67,7 @@ HAOZI_DL_URL="https://git.haozi.net/opensource/download/-/raw/main/panel"; curl
如果你无法重装系统,请以`root`用户登录服务器,执行以下命令卸载面板:
```shell
HAOZI_DL_URL="https://git.haozi.net/opensource/download/-/raw/main/panel"; curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh.checksum.txt && sha256sum -c uninstall_panel.sh.checksum.txt && bash uninstall_panel.sh || echo "Checksum 验证失败,文件可能被篡改,已终止操作"
HAOZI_DL_URL="https://dl.cdn.haozi.net/panel"; curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh.checksum.txt && sha256sum -c uninstall_panel.sh.checksum.txt && bash uninstall_panel.sh || echo "Checksum 验证失败,文件可能被篡改,已终止操作"
```
卸载面板前请务必备份好所有数据,提前卸载面板全部插件。卸载后数据将**无法恢复**!
@@ -84,26 +84,17 @@ panel
## 问题反馈
使用类问题,可在 [鼠片社区](https://forum.haozi.net) 提问或QQ群`@坤坤`寻求 AI 帮助,亦可在群里寻求付费支持。
使用类问题,可在 [Moe Tom](https://tom.moe) 提问或寻求 AI 帮助,亦可在群里寻求付费支持。
面板自身问题,可在 GitHub 的`Issues`页面提交问题反馈,注意[提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)。
## 贡献代码
### 寻找/创建 Issue
您可以在 [Issue 列表](https://github.com/TheTNB/panel/issues) 中寻找或创建一个 Issue,留言表达想要处理该 Issue 的意愿,得到维护者的确认后,即可开始处理。
### 创建 PR
- 在开发过程中,如果遇到问题可以随时在 Issue 中详尽描述该问题,以进一步沟通,但在此之前请确保自己已通过 Google 等方式尽可能的尝试解决问题;
- PR 须提交至我们的极狐 GitLab 仓库[https://git.haozi.net/opensource/panel](https://git.haozi.net/opensource/panel),勿在 GitHub 上提交;
- 当 PR 开发完毕后,请为其添加 `🚀 Review Ready` 标签,维护者将及时进行评审;
- 我们非常欢迎您的贡献,将在下次发版时将您添加到首页贡献者中;❤️
## 赞助商
### 资金
如果耗子 Linux 面板对你有帮助,欢迎[赞助我们](https://afdian.net/a/TheTNB),感谢以下支持者/赞助商的支持:
**同时接受云资源赞助,可通过QQ群咨询联系**
### 服务器
- [微晓朵](https://www.weixiaoduo.com/)
@@ -112,17 +103,19 @@ panel
- [无畏云加速](https://su.sctes.com/register?code=8st689ujpmm2p)
- [盾云CDN](http://cdn.ddunyun.com/)
### DevOps
- [JetBrains](https://www.jetbrains.com/)
**接受云资源和资金赞助,可通过QQ群咨询联系**
<p align="center">
<a target="_blank" href="https://afdian.net/a/TheTNB">
<img alt="sponsors" src="https://github.com/TheTNB/sponsor/blob/main/sponsors.svg?raw=true"/>
</a>
</p>
## 贡献者
这个项目的存在要归功于所有做出贡献的人,参与贡献请先查看贡献代码部分。
<a href="https://github.com/DevHaoZi" target="_blank"><img src="https://avatars.githubusercontent.com/u/115467771?v=4" width="48" height="48"></a>
<a href="https://github.com/TheTNB/panel/graphs/contributors">
<img alt="contributors" src="https://contrib.rocks/image?repo=TheTNB/panel"/>
</a>
## Star 历史
+16 -23
View File
@@ -57,7 +57,7 @@ As system versions are constantly updated, we may also terminate support for som
If you decide to continue, please log in to the server as `root` user and execute the following command to install the panel:
```shell
HAOZI_DL_URL="https://git.haozi.net/opensource/download/-/raw/main/panel"; curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh.checksum.txt && sha256sum -c install_panel.sh.checksum.txt && bash install_panel.sh || echo "Checksum Verification Failed, File May Have Been Tampered With, Operation Terminated"
HAOZI_DL_URL="https://dl.cdn.haozi.net/panel"; curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/install_panel.sh.checksum.txt && sha256sum -c install_panel.sh.checksum.txt && bash install_panel.sh || echo "Checksum Verification Failed, File May Have Been Tampered With, Operation Terminated"
```
## Uninstall Panel
@@ -67,7 +67,7 @@ Recommended to back up data and reinstall the system first, so that the system c
If you are unable to reinstall the system, log in to the server as the `root` user and execute the following command to uninstall the panel:
```shell
HAOZI_DL_URL="https://git.haozi.net/opensource/download/-/raw/main/panel"; curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh.checksum.txt && sha256sum -c uninstall_panel.sh.checksum.txt && bash uninstall_panel.sh || echo "Checksum Verification Failed, File May Have Been Tampered With, Operation Terminated"
HAOZI_DL_URL="https://dl.cdn.haozi.net/panel"; curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh && curl -sSL -O ${HAOZI_DL_URL}/uninstall_panel.sh.checksum.txt && sha256sum -c uninstall_panel.sh.checksum.txt && bash uninstall_panel.sh || echo "Checksum Verification Failed, File May Have Been Tampered With, Operation Terminated"
```
Before uninstalling the panel, please be sure to back up all data and uninstall all panel plugins in advance. The data will **not be recoverable** after uninstallation!
@@ -84,26 +84,17 @@ See more usage methods and tips in [Wiki](https://github.com/TheTNB/panel/wiki).
## Feedback
For usage issues, you can ask questions in the [HaoZi Community Forum](https://forum.haozi.net) or seek AI help in the QQ group `@坤坤`. You can also seek paid support in the group.
For usage issues, you can ask questions in the [Moe Tom](https://tom.moe) or seek AI help. You can also seek paid support in the group.
For issues with the panel itself, you can submit feedback on the `Issues` page of GitHub. Please note [the wisdom of asking questions](http://www.catb.org/~esr/faqs/smart-questions.html).
## Contribute code
### Find/Create Issue
You can find or create an Issue in [Issue List](https://github.com/TheTNB/panel/issues), leave a message to express your willingness to deal with the Issue, and get confirmation from the maintainer. Start processing.
### Create PR
- During the development process, if you encounter a problem, you can describe the problem in detail in the Issue at any time for further communication, but before doing so, please make sure that you have tried your best to solve the problem through Google and other methods;
- PRs must be submitted to our JiHu GitLab repository [https://git.haozi.net/opensource/panel](https://git.haozi.net/opensource/panel). Do not submit on GitHub;
- When the PR is completed, please add the `🚀 Review Ready` tag to it, and the maintainer will review it in time;
- We very much welcome your contributions and will add you to the homepage contributors in the next release; ❤️
## Sponsor
### Funding
If the HaoZi Linux Panel is helpful to you, welcome to [sponsor us](https://opencollective.com/tnb), thanks to the following supporters/sponsors:
**Accept cloud resources and financial sponsorship, you can contact us through QQ group**
### Server
- [微晓朵](https://www.weixiaoduo.com/)
@@ -112,17 +103,19 @@ You can find or create an Issue in [Issue List](https://github.com/TheTNB/panel/
- [无畏云加速](https://su.sctes.com/register?code=8st689ujpmm2p)
- [盾云CDN](http://cdn.ddunyun.com/)
### DevOps
- [JetBrains](https://www.jetbrains.com/)
**Accept cloud resources and financial sponsorship, you can contact us through QQ group**
<p align="center">
<a target="_blank" href="https://afdian.net/a/TheTNB">
<img alt="sponsors" src="https://github.com/TheTNB/sponsor/blob/main/sponsors.svg?raw=true"/>
</a>
</p>
## Contributor
This project owes its existence to all those who have contributed. To contribute, please check the contributed code section first.
<a href="https://github.com/DevHaoZi" target="_blank"><img src="https://avatars.githubusercontent.com/u/115467771?v=4" width="48" height="48"></a>
<a href="https://github.com/TheTNB/panel/graphs/contributors">
<img alt="contributors" src="https://contrib.rocks/image?repo=TheTNB/panel"/>
</a>
## Star History
+4 -2
View File
@@ -1,6 +1,7 @@
package commands
import (
"context"
"crypto/x509"
"encoding/pem"
@@ -25,7 +26,8 @@ func (receiver *CertRenew) Signature() string {
// Description The console command description.
func (receiver *CertRenew) Description() string {
return "[面板] 证书续签"
ctx := context.Background()
return facades.Lang(ctx).Get("commands.panel:cert-renew.description")
}
// Extend The console command extend.
@@ -36,7 +38,7 @@ func (receiver *CertRenew) Extend() command.Extend {
}
// Handle Execute the console command.
func (receiver *CertRenew) Handle(ctx console.Context) error {
func (receiver *CertRenew) Handle(console.Context) error {
if internal.Status != internal.StatusNormal {
return nil
}
+7 -3
View File
@@ -1,6 +1,7 @@
package commands
import (
"context"
"strconv"
"github.com/gookit/color"
@@ -27,7 +28,8 @@ func (receiver *Monitoring) Signature() string {
// Description The console command description.
func (receiver *Monitoring) Description() string {
return "[面板] 系统监控"
ctx := context.Background()
return facades.Lang(ctx).Get("commands.panel:monitoring.description")
}
// Extend The console command extend.
@@ -38,7 +40,7 @@ func (receiver *Monitoring) Extend() command.Extend {
}
// Handle Execute the console command.
func (receiver *Monitoring) Handle(ctx console.Context) error {
func (receiver *Monitoring) Handle(console.Context) error {
if internal.Status != internal.StatusNormal {
return nil
}
@@ -51,6 +53,8 @@ func (receiver *Monitoring) Handle(ctx console.Context) error {
info := tools.GetMonitoringInfo()
translate := facades.Lang(context.Background())
// 去除部分数据以减少数据库存储
info.Disk = nil
for _, cpu := range info.Cpus {
@@ -70,7 +74,7 @@ func (receiver *Monitoring) Handle(ctx console.Context) error {
})
if err != nil {
facades.Log().Infof("[面板] 系统监控保存失败: %s", err.Error())
color.Redf("[面板] 系统监控保存失败: %s", err.Error())
color.Redf(translate.Get("commands.panel:monitoring.fail")+": %s", err.Error())
return nil
}
+254 -123
View File
@@ -1,6 +1,7 @@
package commands
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -31,7 +32,8 @@ func (receiver *Panel) Signature() string {
// Description The console command description.
func (receiver *Panel) Description() string {
return "[面板] 命令行"
ctx := context.Background()
return facades.Lang(ctx).Get("commands.panel.description")
}
// Extend The console command extend.
@@ -48,75 +50,78 @@ func (receiver *Panel) Handle(ctx console.Context) error {
arg2 := ctx.Argument(2)
arg3 := ctx.Argument(3)
arg4 := ctx.Argument(4)
arg5 := ctx.Argument(5)
translate := facades.Lang(context.Background())
switch action {
case "init":
var check models.User
err := facades.Orm().Query().FirstOrFail(&check)
if err == nil {
color.Redln("面板已初始化")
color.Redln(translate.Get("commands.panel.init.exist"))
return nil
}
settings := []models.Setting{{Key: models.SettingKeyName, Value: "耗子 Linux 面板"}, {Key: models.SettingKeyMonitor, Value: "1"}, {Key: models.SettingKeyMonitorDays, Value: "30"}, {Key: models.SettingKeyBackupPath, Value: "/www/backup"}, {Key: models.SettingKeyWebsitePath, Value: "/www/wwwroot"}, {Key: models.SettingKeyVersion, Value: facades.Config().GetString("panel.version")}}
err = facades.Orm().Query().Create(&settings)
if err != nil {
color.Redln("初始化失败")
color.Redln(translate.Get("commands.panel.init.fail"))
return nil
}
hash, err := facades.Hash().Make(tools.RandomString(32))
if err != nil {
color.Redln("初始化失败")
color.Redln(translate.Get("commands.panel.init.fail"))
return nil
}
user := services.NewUserImpl()
_, err = user.Create("admin", hash)
if err != nil {
color.Redln("创建管理员失败")
color.Redln(translate.Get("commands.panel.init.adminFail"))
return nil
}
color.Greenln("初始化成功")
color.Greenln(translate.Get("commands.panel.init.success"))
case "update":
var task models.Task
err := facades.Orm().Query().Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).FirstOrFail(&task)
if err == nil {
color.Redln("当前有任务正在执行,禁止更新")
color.Redln(translate.Get("commands.panel.update.taskCheck"))
return nil
}
panel, err := tools.GetLatestPanelVersion()
if err != nil {
color.Redln("获取最新版本失败")
color.Redln(translate.Get("commands.panel.update.versionFail"))
return err
}
internal.Status = internal.StatusUpgrade
if err = tools.UpdatePanel(panel); err != nil {
internal.Status = internal.StatusFailed
color.Redln("更新失败: " + err.Error())
color.Redln(translate.Get("commands.panel.update.fail") + ": " + err.Error())
return nil
}
internal.Status = internal.StatusNormal
color.Greenln("更新成功")
color.Greenln(translate.Get("commands.panel.update.success"))
tools.RestartPanel()
case "getInfo":
var user models.User
err := facades.Orm().Query().Where("id", 1).FirstOrFail(&user)
if err != nil {
color.Redln("获取管理员信息失败")
color.Redln(translate.Get("commands.panel.getInfo.adminGetFail"))
return nil
}
password := tools.RandomString(16)
hash, err := facades.Hash().Make(password)
if err != nil {
color.Redln("生成密码失败")
color.Redln(translate.Get("commands.panel.getInfo.passwordGenerationFail"))
return nil
}
user.Username = tools.RandomString(8)
@@ -127,13 +132,13 @@ func (receiver *Panel) Handle(ctx console.Context) error {
err = facades.Orm().Query().Save(&user)
if err != nil {
color.Redln("保存管理员信息失败")
color.Redln(translate.Get("commands.panel.getInfo.adminSaveFail"))
return nil
}
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
color.Redln("获取面板端口失败")
color.Redln(translate.Get("commands.panel.portFail"))
return nil
}
ip, err := tools.GetPublicIP()
@@ -145,42 +150,42 @@ func (receiver *Panel) Handle(ctx console.Context) error {
protocol = "https"
}
color.Greenln("用户名: " + user.Username)
color.Greenln("密码: " + password)
color.Greenln("面板端口: " + port)
color.Greenln("面板入口: " + facades.Config().GetString("http.entrance"))
color.Greenln("面板地址: " + protocol + "://" + ip + ":" + port + facades.Config().GetString("http.entrance"))
color.Greenln(translate.Get("commands.panel.getInfo.username") + ": " + user.Username)
color.Greenln(translate.Get("commands.panel.getInfo.password") + ": " + password)
color.Greenln(translate.Get("commands.panel.port") + ": " + port)
color.Greenln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("http.entrance"))
color.Greenln(translate.Get("commands.panel.getInfo.address") + ": " + protocol + "://" + ip + ":" + port + facades.Config().GetString("http.entrance"))
case "getPort":
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
color.Redln("获取面板端口失败")
color.Redln(translate.Get("commands.panel.portFail"))
return nil
}
color.Greenln("面板端口: " + port)
color.Greenln(translate.Get("commands.panel.port") + ": " + port)
case "getEntrance":
color.Greenln("面板入口: " + facades.Config().GetString("http.entrance"))
color.Greenln(translate.Get("commands.panel.entrance") + ": " + facades.Config().GetString("http.entrance"))
case "deleteEntrance":
oldEntrance, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
color.Redln("获取面板入口失败")
color.Redln(translate.Get("commands.panel.deleteEntrance.fail"))
return nil
}
if _, err = tools.Exec("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=/!g' /www/panel/panel.conf"); err != nil {
color.Redln("删除面板入口失败")
color.Redln(translate.Get("commands.panel.deleteEntrance.fail"))
return nil
}
color.Greenln("删除面板入口成功")
color.Greenln(translate.Get("commands.panel.deleteEntrance.success"))
case "writePlugin":
slug := arg1
version := arg2
if len(slug) == 0 || len(version) == 0 {
color.Redln("参数错误")
color.Redln(translate.Get("commands.panel.writePlugin.paramFail"))
return nil
}
@@ -192,31 +197,31 @@ func (receiver *Panel) Handle(ctx console.Context) error {
})
if err != nil {
color.Redln("写入插件安装状态失败")
color.Redln(translate.Get("commands.panel.writePlugin.fail"))
return nil
}
color.Greenln("写入插件安装状态成功")
color.Greenln(translate.Get("commands.panel.writePlugin.success"))
case "deletePlugin":
slug := arg1
if len(slug) == 0 {
color.Redln("参数错误")
color.Redln(translate.Get("commands.panel.deletePlugin.paramFail"))
return nil
}
_, err := facades.Orm().Query().Where("slug", slug).Delete(&models.Plugin{})
if err != nil {
color.Redln("移除插件安装状态失败")
color.Redln(translate.Get("commands.panel.deletePlugin.fail"))
return nil
}
color.Greenln("移除插件安装状态成功")
color.Greenln(translate.Get("commands.panel.deletePlugin.success"))
case "writeMysqlPassword":
password := arg1
if len(password) == 0 {
color.Redln("参数错误")
color.Redln(translate.Get("commands.panel.writeMysqlPassword.paramFail"))
return nil
}
@@ -228,20 +233,20 @@ func (receiver *Panel) Handle(ctx console.Context) error {
})
if err != nil {
color.Redln("写入MySQL root密码失败")
color.Redln(translate.Get("commands.panel.writeMysqlPassword.fail"))
return nil
}
color.Greenln("写入MySQL root密码成功")
color.Greenln(translate.Get("commands.panel.writeMysqlPassword.success"))
case "cleanTask":
_, err := facades.Orm().Query().Model(&models.Task{}).Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).Update("status", models.TaskStatusFailed)
if err != nil {
color.Redln("清理任务失败")
color.Redln(translate.Get("commands.panel.cleanTask.fail"))
return nil
}
color.Greenln("清理任务成功")
color.Greenln(translate.Get("commands.panel.cleanTask.success"))
case "backup":
backupType := arg1
@@ -250,37 +255,37 @@ func (receiver *Panel) Handle(ctx console.Context) error {
save := arg4
hr := `+----------------------------------------------------`
if len(backupType) == 0 || len(name) == 0 || len(path) == 0 || len(save) == 0 {
color.Redln("参数错误")
color.Redln(translate.Get("commands.panel.backup.paramFail"))
return nil
}
color.Greenln(hr)
color.Greenln("★ 开始备份 [" + carbon.Now().ToDateTimeString() + "]")
color.Greenln("★ " + translate.Get("commands.panel.backup.start") + " [" + carbon.Now().ToDateTimeString() + "]")
color.Greenln(hr)
if !tools.Exists(path) {
if err := tools.Mkdir(path, 0644); err != nil {
color.Redln("|-创建备份目录失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.backupDirFail") + ": " + err.Error())
return nil
}
}
switch backupType {
case "website":
color.Yellowln("|-目标网站: " + name)
color.Yellowln("|-" + translate.Get("commands.panel.backup.targetSite") + ": " + name)
var website models.Website
if err := facades.Orm().Query().Where("name", name).FirstOrFail(&website); err != nil {
color.Redln("|-网站不存在")
color.Redln("|-" + translate.Get("commands.panel.backup.siteNotExist"))
color.Greenln(hr)
return nil
}
backupFile := path + "/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".zip"
if _, err := tools.Exec(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`); err != nil {
color.Redln("|-备份失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.backupFail") + ": " + err.Error())
return nil
}
color.Greenln("|-备份成功")
color.Greenln("|-" + translate.Get("commands.panel.backup.backupSuccess"))
case "mysql":
rootPassword := services.NewSettingImpl().Get(models.SettingKeyMysqlRootPassword)
@@ -288,81 +293,81 @@ func (receiver *Panel) Handle(ctx console.Context) error {
err := os.Setenv("MYSQL_PWD", rootPassword)
if err != nil {
color.Redln("|-备份MySQL数据库失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.mysqlBackupFail") + ": " + err.Error())
color.Greenln(hr)
return nil
}
color.Greenln("|-目标MySQL数据库: " + name)
color.Greenln("|-开始导出")
color.Greenln("|-" + translate.Get("commands.panel.backup.targetMysql") + ": " + name)
color.Greenln("|-" + translate.Get("commands.panel.backup.startExport"))
if _, err = tools.Exec(`mysqldump -uroot ` + name + ` > /tmp/` + backupFile + ` 2>&1`); err != nil {
color.Redln("|-导出失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.exportFail") + ": " + err.Error())
return nil
}
color.Greenln("|-导出成功")
color.Greenln("|-开始压缩")
color.Greenln("|-" + translate.Get("commands.panel.backup.exportSuccess"))
color.Greenln("|-" + translate.Get("commands.panel.backup.startCompress"))
if _, err = tools.Exec("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
color.Redln("|-压缩失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.compressFail") + ": " + err.Error())
return nil
}
if err := tools.Remove("/tmp/" + backupFile); err != nil {
color.Redln("|-删除失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.deleteFail") + ": " + err.Error())
return nil
}
color.Greenln("|-压缩成功")
color.Greenln("|-开始移动")
color.Greenln("|-" + translate.Get("commands.panel.backup.compressSuccess"))
color.Greenln("|-" + translate.Get("commands.panel.backup.startMove"))
if err := tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
color.Redln("|-移动失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.moveFail") + ": " + err.Error())
return nil
}
color.Greenln("|-移动成功")
color.Greenln("|-" + translate.Get("commands.panel.backup.moveSuccess"))
_ = os.Unsetenv("MYSQL_PWD")
color.Greenln("|-备份成功")
color.Greenln("|-" + translate.Get("commands.panel.backup.success"))
case "postgresql":
backupFile := name + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
check, err := tools.Exec(`su - postgres -c "psql -l" 2>&1`)
if err != nil {
color.Redln("|-获取数据库失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.databaseGetFail") + ": " + err.Error())
color.Greenln(hr)
return nil
}
if !strings.Contains(check, name) {
color.Redln("|-数据库不存在")
color.Redln("|-" + translate.Get("commands.panel.backup.databaseNotExist"))
color.Greenln(hr)
return nil
}
color.Greenln("|-目标PostgreSQL数据库: " + name)
color.Greenln("|-开始导出")
color.Greenln("|-" + translate.Get("commands.panel.backup.targetPostgres") + ": " + name)
color.Greenln("|-" + translate.Get("commands.panel.backup.startExport"))
if _, err = tools.Exec(`su - postgres -c "pg_dump '` + name + `'" > /tmp/` + backupFile + ` 2>&1`); err != nil {
color.Redln("|-导出失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.exportFail") + ": " + err.Error())
return nil
}
color.Greenln("|-导出成功")
color.Greenln("|-开始压缩")
color.Greenln("|-" + translate.Get("commands.panel.backup.exportSuccess"))
color.Greenln("|-" + translate.Get("commands.panel.backup.startCompress"))
if _, err = tools.Exec("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
color.Redln("|-压缩失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.compressFail") + ": " + err.Error())
return nil
}
if err := tools.Remove("/tmp/" + backupFile); err != nil {
color.Redln("|-删除失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.deleteFail") + ": " + err.Error())
return nil
}
color.Greenln("|-压缩成功")
color.Greenln("|-开始移动")
color.Greenln("|-" + translate.Get("commands.panel.backup.compressSuccess"))
color.Greenln("|-" + translate.Get("commands.panel.backup.startMove"))
if err := tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
color.Redln("|-移动失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.moveFail") + ": " + err.Error())
return nil
}
color.Greenln("|-移动成功")
color.Greenln("|-备份成功")
color.Greenln("|-" + translate.Get("commands.panel.backup.moveSuccess"))
color.Greenln("|-" + translate.Get("commands.panel.backup.success"))
}
color.Greenln(hr)
files, err := os.ReadDir(path)
if err != nil {
color.Redln("|-清理失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.cleanupFail") + ": " + err.Error())
return nil
}
var filteredFiles []os.FileInfo
@@ -380,15 +385,15 @@ func (receiver *Panel) Handle(ctx console.Context) error {
})
for i := cast.ToInt(save); i < len(filteredFiles); i++ {
fileToDelete := filepath.Join(path, filteredFiles[i].Name())
color.Yellowln("|-清理备份: " + fileToDelete)
color.Yellowln("|-" + translate.Get("commands.panel.backup.cleanBackup") + ": " + fileToDelete)
if err := tools.Remove(fileToDelete); err != nil {
color.Redln("|-清理失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.backup.cleanupFail") + ": " + err.Error())
return nil
}
}
color.Greenln("|-清理完成")
color.Greenln("|-" + translate.Get("commands.panel.backup.cleanupSuccess"))
color.Greenln(hr)
color.Greenln("☆ 备份完成 [" + carbon.Now().ToDateTimeString() + "]")
color.Greenln("☆ " + translate.Get("commands.panel.backup.success") + " [" + carbon.Now().ToDateTimeString() + "]")
color.Greenln(hr)
case "cutoff":
@@ -396,44 +401,44 @@ func (receiver *Panel) Handle(ctx console.Context) error {
save := arg2
hr := `+----------------------------------------------------`
if len(name) == 0 || len(save) == 0 {
color.Redln("参数错误")
color.Redln(translate.Get("commands.panel.cutoff.paramFail"))
return nil
}
color.Greenln(hr)
color.Greenln("★ 开始切割 [" + carbon.Now().ToDateTimeString() + "]")
color.Greenln("★ " + translate.Get("commands.panel.cutoff.start") + " [" + carbon.Now().ToDateTimeString() + "]")
color.Greenln(hr)
color.Yellowln("|-目标网站: " + name)
color.Yellowln("|-" + translate.Get("commands.panel.cutoff.targetSite") + ": " + name)
var website models.Website
if err := facades.Orm().Query().Where("name", name).FirstOrFail(&website); err != nil {
color.Redln("|-网站不存在")
color.Redln("|-" + translate.Get("commands.panel.cutoff.siteNotExist"))
color.Greenln(hr)
return nil
}
logPath := "/www/wwwlogs/" + website.Name + ".log"
if !tools.Exists(logPath) {
color.Redln("|-日志文件不存在")
color.Redln("|-" + translate.Get("commands.panel.cutoff.logNotExist"))
color.Greenln(hr)
return nil
}
backupPath := "/www/wwwlogs/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".log.zip"
if _, err := tools.Exec(`cd /www/wwwlogs && zip -r ` + backupPath + ` ` + website.Name + ".log"); err != nil {
color.Redln("|-备份失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.cutoff.backupFail") + ": " + err.Error())
return nil
}
if _, err := tools.Exec(`echo "" > ` + logPath); err != nil {
color.Redln("|-清空失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.cutoff.clearFail") + ": " + err.Error())
return nil
}
color.Greenln("|-切割成功")
color.Greenln("|-" + translate.Get("commands.panel.cutoff.cutSuccess"))
color.Greenln(hr)
files, err := os.ReadDir("/www/wwwlogs")
if err != nil {
color.Redln("|-清理失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.cutoff.cleanupFail") + ": " + err.Error())
return nil
}
var filteredFiles []os.FileInfo
@@ -451,15 +456,15 @@ func (receiver *Panel) Handle(ctx console.Context) error {
})
for i := cast.ToInt(save); i < len(filteredFiles); i++ {
fileToDelete := filepath.Join("/www/wwwlogs", filteredFiles[i].Name())
color.Yellowln("|-清理日志: " + fileToDelete)
color.Yellowln("|-" + translate.Get("commands.panel.cutoff.clearLog") + ": " + fileToDelete)
if err := tools.Remove(fileToDelete); err != nil {
color.Redln("|-清理失败: " + err.Error())
color.Redln("|-" + translate.Get("commands.panel.cutoff.cleanupFail") + ": " + err.Error())
return nil
}
}
color.Greenln("|-清理完成")
color.Greenln("|-" + translate.Get("commands.panel.cutoff.cleanupSuccess"))
color.Greenln(hr)
color.Greenln("☆ 切割完成 [" + carbon.Now().ToDateTimeString() + "]")
color.Greenln("☆ " + translate.Get("commands.panel.cutoff.end") + " [" + carbon.Now().ToDateTimeString() + "]")
color.Greenln(hr)
case "writeSite":
@@ -469,19 +474,19 @@ func (receiver *Panel) Handle(ctx console.Context) error {
php := cast.ToInt(arg4)
ssl := cast.ToBool(ctx.Argument(5))
if len(name) == 0 || len(path) == 0 {
color.Redln("参数错误")
color.Redln(translate.Get("commands.panel.writeSite.paramFail"))
return nil
}
var website models.Website
if err := facades.Orm().Query().Where("name", name).FirstOrFail(&website); err == nil {
color.Redln("网站已存在")
color.Redln(translate.Get("commands.panel.writeSite.siteExist"))
return nil
}
_, err := os.Stat(path)
if os.IsNotExist(err) {
color.Redln("网站目录不存在")
color.Redln(translate.Get("commands.panel.writeSite.pathNotExist"))
return nil
}
@@ -493,32 +498,32 @@ func (receiver *Panel) Handle(ctx console.Context) error {
Ssl: ssl,
})
if err != nil {
color.Redln("写入网站失败")
color.Redln(translate.Get("commands.panel.writeSite.fail"))
return nil
}
color.Greenln("写入网站成功")
color.Greenln(translate.Get("commands.panel.writeSite.success"))
case "deleteSite":
name := arg1
if len(name) == 0 {
color.Redln("参数错误")
color.Redln(translate.Get("commands.panel.deleteSite.paramFail"))
return nil
}
_, err := facades.Orm().Query().Where("name", name).Delete(&models.Website{})
if err != nil {
color.Redln("删除网站失败")
color.Redln(translate.Get("commands.panel.deleteSite.fail"))
return nil
}
color.Greenln("删除网站成功")
color.Greenln(translate.Get("commands.panel.deleteSite.success"))
case "writeSetting":
key := arg1
value := arg2
if len(key) == 0 || len(value) == 0 {
color.Redln("参数错误")
color.Redln(translate.Get("commands.panel.writeSetting.paramFail"))
return nil
}
@@ -529,16 +534,16 @@ func (receiver *Panel) Handle(ctx console.Context) error {
Value: value,
})
if err != nil {
color.Redln("写入设置失败")
color.Redln(translate.Get("commands.panel.writeSetting.fail"))
return nil
}
color.Greenln("写入设置成功")
color.Greenln(translate.Get("commands.panel.writeSetting.success"))
case "getSetting":
key := arg1
if len(key) == 0 {
color.Redln("参数错误")
color.Redln(translate.Get("commands.panel.getSetting.paramFail"))
return nil
}
@@ -552,39 +557,165 @@ func (receiver *Panel) Handle(ctx console.Context) error {
case "deleteSetting":
key := arg1
if len(key) == 0 {
color.Redln("参数错误")
color.Redln(translate.Get("commands.panel.deleteSetting.paramFail"))
return nil
}
_, err := facades.Orm().Query().Where("key", key).Delete(&models.Setting{})
if err != nil {
color.Redln("删除设置失败")
color.Redln(translate.Get("commands.panel.deleteSetting.fail"))
return nil
}
color.Greenln("删除设置成功")
color.Greenln(translate.Get("commands.panel.deleteSetting.success"))
case "addSite":
name := arg1
domain := arg2
port := arg3
path := arg4
php := arg5
if len(name) == 0 || len(domain) == 0 || len(port) == 0 || len(path) == 0 {
color.Redln(translate.Get("commands.panel.addSite.paramFail"))
return nil
}
domains := strings.Split(domain, ",")
ports := strings.Split(port, ",")
if len(domains) == 0 || len(ports) == 0 {
color.Redln(translate.Get("commands.panel.addSite.paramFail"))
return nil
}
var uintPorts []uint
for _, p := range ports {
uintPorts = append(uintPorts, cast.ToUint(p))
}
website := services.NewWebsiteImpl()
id, err := website.GetIDByName(name)
if err != nil {
color.Redln(err.Error())
return nil
}
if id != 0 {
color.Redln(translate.Get("commands.panel.addSite.siteExist"))
return nil
}
_, err = website.Add(internal.PanelWebsite{
Name: name,
Status: true,
Domains: domains,
Ports: uintPorts,
Path: path,
Php: php,
Ssl: false,
Db: false,
})
if err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln(translate.Get("commands.panel.addSite.success"))
case "removeSite":
name := arg1
if len(name) == 0 {
color.Redln(translate.Get("commands.panel.removeSite.paramFail"))
return nil
}
website := services.NewWebsiteImpl()
id, err := website.GetIDByName(name)
if err != nil {
color.Redln(err.Error())
return nil
}
if id == 0 {
color.Redln(translate.Get("commands.panel.removeSite.siteNotExist"))
return nil
}
if err = website.Delete(id); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln(translate.Get("commands.panel.removeSite.success"))
case "installPlugin":
slug := arg1
if len(slug) == 0 {
color.Redln(translate.Get("commands.panel.installPlugin.paramFail"))
return nil
}
plugin := services.NewPluginImpl()
if err := plugin.Install(slug); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln(translate.Get("commands.panel.installPlugin.success"))
case "uninstallPlugin":
slug := arg1
if len(slug) == 0 {
color.Redln(translate.Get("commands.panel.uninstallPlugin.paramFail"))
return nil
}
plugin := services.NewPluginImpl()
if err := plugin.Uninstall(slug); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln(translate.Get("commands.panel.uninstallPlugin.success"))
case "updatePlugin":
slug := arg1
if len(slug) == 0 {
color.Redln(translate.Get("commands.panel.updatePlugin.paramFail"))
return nil
}
plugin := services.NewPluginImpl()
if err := plugin.Update(slug); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln(translate.Get("commands.panel.updatePlugin.success"))
default:
color.Yellowln(facades.Config().GetString("panel.name") + "命令行工具 - " + facades.Config().GetString("panel.version"))
color.Greenln("请使用以下命令")
color.Greenln("panel update 更新 / 修复面板到最新版本")
color.Greenln("panel getInfo 重新初始化面板账号信息")
color.Greenln("panel getPort 获取面板访问端口")
color.Greenln("panel getEntrance 获取面板访问入口")
color.Greenln("panel deleteEntrance 删除面板访问入口")
color.Greenln("panel cleanTask 清理面板运行中和等待中的任务[任务卡住时使用]")
color.Greenln("panel backup {website/mysql/postgresql} {name} {path} {save_copies} 备份网站 / MySQL数据库 / PostgreSQL数据库到指定目录并保留指定数量")
color.Greenln("panel cutoff {website_name} {save_copies} 切割网站日志并保留指定数量")
color.Redln("以下命令请在开发者指导下使用:")
color.Yellowln("panel init 初始化面板")
color.Yellowln("panel writePlugin {slug} {version} 写入插件安装状态")
color.Yellowln("panel deletePlugin {slug} 移除插件安装状态")
color.Yellowln("panel writeMysqlPassword {password} 写入MySQL root密码")
color.Yellowln("panel writeSite {name} {status} {path} {php} {ssl} 写入网站数据到面板")
color.Yellowln("panel deleteSite {name} 删除面板网站数据")
color.Yellowln("panel getSetting {name} 获取面板设置数据")
color.Yellowln("panel writeSetting {name} {value} 写入 / 更新面板设置数据")
color.Yellowln("panel deleteSetting {name} 删除面板设置数据")
color.Yellowln(facades.Config().GetString("panel.name") + " - " + translate.Get("commands.panel.tool") + " - " + facades.Config().GetString("panel.version"))
color.Greenln(translate.Get("commands.panel.use") + "")
color.Greenln("panel update " + translate.Get("commands.panel.update.description"))
color.Greenln("panel getInfo " + translate.Get("commands.panel.getInfo.description"))
color.Greenln("panel getPort " + translate.Get("commands.panel.getPort.description"))
color.Greenln("panel getEntrance " + translate.Get("commands.panel.getEntrance.description"))
color.Greenln("panel deleteEntrance " + translate.Get("commands.panel.deleteEntrance.description"))
color.Greenln("panel cleanTask " + translate.Get("commands.panel.cleanTask.description"))
color.Greenln("panel backup {website/mysql/postgresql} {name} {path} {save_copies} " + translate.Get("commands.panel.backup.description"))
color.Greenln("panel cutoff {website_name} {save_copies} " + translate.Get("commands.panel.cutoff.description"))
color.Greenln("panel installPlugin {slug} " + translate.Get("commands.panel.installPlugin.description"))
color.Greenln("panel uninstallPlugin {slug} " + translate.Get("commands.panel.uninstallPlugin.description"))
color.Greenln("panel updatePlugin {slug} " + translate.Get("commands.panel.updatePlugin.description"))
color.Greenln("panel addSite {name} {domain} {port} {path} {php} " + translate.Get("commands.panel.addSite.description"))
color.Greenln("panel removeSite {name} " + translate.Get("commands.panel.removeSite.description"))
color.Redln(translate.Get("commands.panel.forDeveloper") + ":")
color.Yellowln("panel init " + translate.Get("commands.panel.init.description"))
color.Yellowln("panel writePlugin {slug} {version} " + translate.Get("commands.panel.writePlugin.description"))
color.Yellowln("panel deletePlugin {slug} " + translate.Get("commands.panel.deletePlugin.description"))
color.Yellowln("panel writeMysqlPassword {password} " + translate.Get("commands.panel.writeMysqlPassword.description"))
color.Yellowln("panel writeSite {name} {status} {path} {php} {ssl} " + translate.Get("commands.panel.writeSite.description"))
color.Yellowln("panel deleteSite {name} " + translate.Get("commands.panel.deleteSite.description"))
color.Yellowln("panel getSetting {name} " + translate.Get("commands.panel.getSetting.description"))
color.Yellowln("panel writeSetting {name} {value} " + translate.Get("commands.panel.writeSetting.description"))
color.Yellowln("panel deleteSetting {name} " + translate.Get("commands.panel.deleteSetting.description"))
}
return nil
+7 -2
View File
@@ -1,6 +1,8 @@
package commands
import (
"context"
"github.com/goravel/framework/contracts/console"
"github.com/goravel/framework/contracts/console/command"
"github.com/goravel/framework/facades"
@@ -21,7 +23,7 @@ func (receiver *PanelTask) Signature() string {
// Description The console command description.
func (receiver *PanelTask) Description() string {
return "[面板] 每日任务"
return facades.Lang(context.Background()).Get("commands.panel:task.description")
}
// Extend The console command extend.
@@ -32,11 +34,12 @@ func (receiver *PanelTask) Extend() command.Extend {
}
// Handle Execute the console command.
func (receiver *PanelTask) Handle(ctx console.Context) error {
func (receiver *PanelTask) Handle(console.Context) error {
internal.Status = internal.StatusMaintain
// 优化数据库
if _, err := facades.Orm().Query().Exec("VACUUM"); err != nil {
internal.Status = internal.StatusFailed
facades.Log().Tags("面板", "每日任务").
With(map[string]any{
"error": err.Error(),
@@ -46,6 +49,7 @@ func (receiver *PanelTask) Handle(ctx console.Context) error {
// 备份面板
if err := tools.Archive([]string{"/www/panel"}, "/www/backup/panel/panel-"+carbon.Now().ToShortDateTimeString()+".zip"); err != nil {
internal.Status = internal.StatusFailed
facades.Log().Tags("面板", "每日任务").
With(map[string]any{
"error": err.Error(),
@@ -55,6 +59,7 @@ func (receiver *PanelTask) Handle(ctx console.Context) error {
// 清理 7 天前的备份
if _, err := tools.Exec(`find /www/backup/panel -mtime +7 -name "*.zip" -exec rm -rf {} \;`); err != nil {
internal.Status = internal.StatusFailed
facades.Log().Tags("面板", "每日任务").
With(map[string]any{
"error": err.Error(),
+31 -1
View File
@@ -650,7 +650,7 @@ func (r *CertController) Renew(ctx http.Context) http.Response {
// @Produce json
// @Security BearerToken
// @Param data body requests.Obtain true "request"
// @Success 200 {object} SuccessResponse{data=map[string]acme.Resolve}
// @Success 200 {object} SuccessResponse{data=[]acme.DNSRecord}
// @Router /panel/cert/manualDNS [post]
func (r *CertController) ManualDNS(ctx http.Context) http.Response {
var obtainRequest requests.Obtain
@@ -669,3 +669,33 @@ func (r *CertController) ManualDNS(ctx http.Context) http.Response {
return Success(ctx, resolves)
}
// Deploy
//
// @Summary 部署证书
// @Description 部署面板证书管理的证书
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.CertDeploy true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/deploy [post]
func (r *CertController) Deploy(ctx http.Context) http.Response {
var deployRequest requests.CertDeploy
sanitize := Sanitize(ctx, &deployRequest)
if sanitize != nil {
return sanitize
}
err := r.cert.Deploy(deployRequest.ID, deployRequest.WebsiteID)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"certID": deployRequest.ID,
"error": err.Error(),
}).Info("部署证书失败")
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
+3 -2
View File
@@ -2,6 +2,7 @@ package controllers
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
)
// SuccessResponse 通用成功响应
@@ -26,14 +27,14 @@ func Success(ctx http.Context, data any) http.Response {
// Error 响应错误
func Error(ctx http.Context, code int, message string) http.Response {
return ctx.Response().Json(code, &ErrorResponse{
Message: "错误: " + message,
Message: facades.Lang(ctx).Get("messages.mistake") + ": " + message,
})
}
// ErrorSystem 响应系统错误
func ErrorSystem(ctx http.Context) http.Response {
return ctx.Response().Json(http.StatusInternalServerError, &ErrorResponse{
Message: "系统内部错误",
Message: facades.Lang(ctx).Get("errors.internal"),
})
}
+47 -11
View File
@@ -6,9 +6,11 @@ import (
"os"
"path/filepath"
"strings"
"syscall"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/carbon"
commonrequests "panel/app/http/requests/common"
requests "panel/app/http/requests/file"
"panel/pkg/tools"
@@ -80,7 +82,7 @@ func (r *FileController) Content(ctx http.Context) http.Response {
return Error(ctx, http.StatusInternalServerError, "目标路径不是文件")
}
if fileInfo.Size() > 10*1024*1024 {
return Error(ctx, http.StatusInternalServerError, "文件大小超过 10M")
return Error(ctx, http.StatusInternalServerError, "文件大小超过 10 M,不支持在线编辑")
}
content, err := tools.Read(request.Path)
@@ -207,15 +209,15 @@ func (r *FileController) Move(ctx http.Context) http.Response {
return sanitize
}
if tools.Exists(request.New) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径已存在,是否覆盖?")
if tools.Exists(request.Target) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径"+request.Target+"已存在")
}
if err := tools.Mv(request.Old, request.New); err != nil {
if err := tools.Mv(request.Source, request.Target); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.New, 0755, "www", "www")
r.setPermission(request.Target, 0755, "www", "www")
return Success(ctx, nil)
}
@@ -237,15 +239,15 @@ func (r *FileController) Copy(ctx http.Context) http.Response {
return sanitize
}
if tools.Exists(request.New) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径已存在,是否覆盖?")
if tools.Exists(request.Target) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径"+request.Target+"已存在")
}
if err := tools.Cp(request.Old, request.New); err != nil {
if err := tools.Cp(request.Source, request.Target); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.New, 0755, "www", "www")
r.setPermission(request.Source, 0755, "www", "www")
return Success(ctx, nil)
}
@@ -454,7 +456,8 @@ func (r *FileController) Search(ctx http.Context) http.Response {
// @Tags 文件管理
// @Accept json
// @Produce json
// @Param data query requests.Exist true "request"
// @Param data query requests.Exist true "request"
// @Param data query commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/list [get]
func (r *FileController) List(ctx http.Context) http.Response {
@@ -464,6 +467,12 @@ func (r *FileController) List(ctx http.Context) http.Response {
return sanitize
}
var paginate commonrequests.Paginate
paginateSanitize := Sanitize(ctx, &paginate)
if paginateSanitize != nil {
return paginateSanitize
}
fileInfoList, err := os.ReadDir(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
@@ -472,17 +481,44 @@ func (r *FileController) List(ctx http.Context) http.Response {
var paths []any
for _, fileInfo := range fileInfoList {
info, _ := fileInfo.Info()
stat := info.Sys().(*syscall.Stat_t)
paths = append(paths, map[string]any{
"name": info.Name(),
"full": filepath.Join(request.Path, info.Name()),
"size": tools.FormatBytes(float64(info.Size())),
"mode_str": info.Mode().String(),
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
"owner": tools.GetUser(stat.Uid),
"group": tools.GetGroup(stat.Gid),
"uid": stat.Uid,
"gid": stat.Gid,
"hidden": tools.IsHidden(info.Name()),
"symlink": tools.IsSymlink(info.Mode()),
"link": tools.GetSymlink(filepath.Join(request.Path, info.Name())),
"dir": info.IsDir(),
"modify": carbon.FromStdTime(info.ModTime()).ToDateTimeString(),
})
}
return Success(ctx, paths)
start := paginate.Limit * (paginate.Page - 1)
end := paginate.Limit * paginate.Page
if start > len(paths) {
start = len(paths)
}
if end > len(paths) {
end = len(paths)
}
paged := paths[start:end]
if paged == nil {
paged = []any{}
}
return Success(ctx, http.Json{
"total": len(paths),
"items": paged,
})
}
// setPermission
+6 -135
View File
@@ -92,162 +92,33 @@ func (r *PluginController) List(ctx http.Context) http.Response {
// Install 安装插件
func (r *PluginController) Install(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
plugin := r.plugin.GetBySlug(slug)
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
if err := r.plugin.Install(slug); err != nil {
return ErrorSystem(ctx)
}
if installedPlugin.ID != 0 {
return Error(ctx, http.StatusUnprocessableEntity, "插件已安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
}
}
var task models.Task
task.Name = "安装插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Install + ` >> '/tmp/` + plugin.Slug + `.log' 2>&1`
task.Log = "/tmp/" + plugin.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
r.task.Process(task.ID)
return Success(ctx, "任务已提交")
}
// Uninstall 卸载插件
func (r *PluginController) Uninstall(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
plugin := r.plugin.GetBySlug(slug)
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
if err := r.plugin.Uninstall(slug); err != nil {
return ErrorSystem(ctx)
}
if installedPlugin.ID == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
}
}
var task models.Task
task.Name = "卸载插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Uninstall + " >> /tmp/" + plugin.Slug + ".log 2>&1"
task.Log = "/tmp/" + plugin.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
r.task.Process(task.ID)
return Success(ctx, "任务已提交")
}
// Update 更新插件
func (r *PluginController) Update(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
plugin := r.plugin.GetBySlug(slug)
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
if err := r.plugin.Update(slug); err != nil {
return ErrorSystem(ctx)
}
if installedPlugin.ID == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
}
}
var task models.Task
task.Name = "更新插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Update + " >> /tmp/" + plugin.Slug + ".log 2>&1"
task.Log = "/tmp/" + plugin.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
r.task.Process(task.ID)
return Success(ctx, "任务已提交")
}
@@ -1,667 +0,0 @@
package plugins
import (
"database/sql"
"fmt"
"regexp"
"github.com/goravel/framework/contracts/http"
"github.com/spf13/cast"
"panel/app/http/controllers"
"panel/app/models"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type Mysql57Controller struct {
setting internal.Setting
backup internal.Backup
}
func NewMysql57Controller() *Mysql57Controller {
return &Mysql57Controller{
setting: services.NewSettingImpl(),
backup: services.NewBackupImpl(),
}
}
// Status 获取运行状态
func (r *Mysql57Controller) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
return controllers.Success(ctx, status)
}
// Reload 重载配置
func (r *Mysql57Controller) Reload(ctx http.Context) http.Response {
if err := tools.ServiceReload("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载MySQL失败")
}
return controllers.Success(ctx, nil)
}
// Restart 重启服务
func (r *Mysql57Controller) Restart(ctx http.Context) http.Response {
if err := tools.ServiceRestart("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启MySQL服务失败")
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *Mysql57Controller) Start(ctx http.Context) http.Response {
if err := tools.ServiceStart("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动MySQL服务失败")
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *Mysql57Controller) Stop(ctx http.Context) http.Response {
if err := tools.ServiceStop("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止MySQL服务失败")
}
return controllers.Success(ctx, nil)
}
// GetConfig 获取配置
func (r *Mysql57Controller) GetConfig(ctx http.Context) http.Response {
config, err := tools.Read("/www/server/mysql/conf/my.cnf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
}
return controllers.Success(ctx, config)
}
// SaveConfig 保存配置
func (r *Mysql57Controller) SaveConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if err := tools.Write("/www/server/mysql/conf/my.cnf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入MySQL配置失败")
}
return r.Restart(ctx)
}
// Load 获取负载
func (r *Mysql57Controller) Load(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
}
status, err := tools.ServiceStatus("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行")
}
raw, err := tools.Exec("/www/server/mysql/bin/mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL负载失败")
}
var data []map[string]string
expressions := []struct {
regex string
name string
}{
{`Uptime\s+\|\s+(\d+)\s+\|`, "运行时间"},
{`Queries\s+\|\s+(\d+)\s+\|`, "总查询次数"},
{`Connections\s+\|\s+(\d+)\s+\|`, "总连接次数"},
{`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒事务"},
{`Com_rollback\s+\|\s+(\d+)\s+\|`, "每秒回滚"},
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, "发送"},
{`Bytes_received\s+\|\s+(\d+)\s+\|`, "接收"},
{`Threads_connected\s+\|\s+(\d+)\s+\|`, "活动连接数"},
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, "峰值连接数"},
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, "索引命中率"},
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"},
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "创建临时表到磁盘"},
{`Open_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"},
{`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"},
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"},
{`Select_range_check\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"},
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"},
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, "锁表次数"},
}
for _, expression := range expressions {
re := regexp.MustCompile(expression.regex)
matches := re.FindStringSubmatch(raw)
if len(matches) > 1 {
d := map[string]string{"name": expression.name, "value": matches[1]}
if expression.name == "发送" || expression.name == "接收" {
d["value"] = tools.FormatBytes(cast.ToFloat64(matches[1]))
}
data = append(data, d)
}
}
// 索引命中率
readRequests := cast.ToFloat64(data[9]["value"])
reads := cast.ToFloat64(data[10]["value"])
data[9]["value"] = fmt.Sprintf("%.2f%%", readRequests/(reads+readRequests)*100)
// Innodb 索引命中率
bufferPoolReads := cast.ToFloat64(data[11]["value"])
bufferPoolReadRequests := cast.ToFloat64(data[12]["value"])
data[10]["value"] = fmt.Sprintf("%.2f%%", bufferPoolReadRequests/(bufferPoolReads+bufferPoolReadRequests)*100)
return controllers.Success(ctx, data)
}
// ErrorLog 获取错误日志
func (r *Mysql57Controller) ErrorLog(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-error.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
return controllers.Success(ctx, log)
}
// ClearErrorLog 清空错误日志
func (r *Mysql57Controller) ClearErrorLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-error.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// SlowLog 获取慢查询日志
func (r *Mysql57Controller) SlowLog(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-slow.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
return controllers.Success(ctx, log)
}
// ClearSlowLog 清空慢查询日志
func (r *Mysql57Controller) ClearSlowLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-slow.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// GetRootPassword 获取root密码
func (r *Mysql57Controller) GetRootPassword(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
}
return controllers.Success(ctx, rootPassword)
}
// SetRootPassword 设置root密码
func (r *Mysql57Controller) SetRootPassword(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行")
}
rootPassword := ctx.Request().Input("password")
if len(rootPassword) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码不能为空")
}
oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if oldRootPassword != rootPassword {
if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
}
if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
}
err := r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword)
if err != nil {
if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\""); err != nil {
return nil
}
if _, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return nil
}
return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
}
}
return controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (r *Mysql57Controller) DatabaseList(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
type database struct {
Name string `json:"name"`
}
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer db.Close()
rows, err := db.Query("SHOW DATABASES")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer rows.Close()
var databases []database
for rows.Next() {
var d database
err := rows.Scan(&d.Name)
if err != nil {
continue
}
databases = append(databases, d)
}
if err := rows.Err(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(databases) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
}
if endIndex > len(databases) {
endIndex = len(databases)
}
pagedDatabases := databases[startIndex:endIndex]
return controllers.Success(ctx, http.Json{
"total": len(databases),
"items": pagedDatabases,
})
}
// AddDatabase 添加数据库
func (r *Mysql57Controller) AddDatabase(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + database + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// DeleteDatabase 删除数据库
func (r *Mysql57Controller) DeleteDatabase(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// BackupList 获取备份列表
func (r *Mysql57Controller) BackupList(ctx http.Context) http.Response {
backupList, err := r.backup.MysqlList()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(backupList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []internal.BackupFile{},
})
}
if endIndex > len(backupList) {
endIndex = len(backupList)
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []internal.BackupFile{}
}
return controllers.Success(ctx, http.Json{
"total": len(backupList),
"items": pagedBackupList,
})
}
// UploadBackup 上传备份
func (r *Mysql57Controller) UploadBackup(ctx http.Context) http.Response {
file, err := ctx.Request().File("file")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
if !tools.Exists(backupPath) {
if err = tools.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
name := file.GetClientOriginalName()
_, err = file.StoreAs(backupPath, name)
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
}
return controllers.Success(ctx, nil)
}
// CreateBackup 创建备份
func (r *Mysql57Controller) CreateBackup(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}
database := ctx.Request().Input("database")
err = r.backup.MysqlBackup(database)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// DeleteBackup 删除备份
func (r *Mysql57Controller) DeleteBackup(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|min_len:1|max_len:255",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
fileName := ctx.Request().Input("name")
if err := tools.Remove(backupPath + "/" + fileName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// RestoreBackup 还原备份
func (r *Mysql57Controller) RestoreBackup(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"backup": "required|min_len:1|max_len:255",
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}
err = r.backup.MysqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup"))
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// UserList 用户列表
func (r *Mysql57Controller) UserList(ctx http.Context) http.Response {
type user struct {
User string `json:"user"`
Host string `json:"host"`
Grants []string `json:"grants"`
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer db.Close()
rows, err := db.Query("SELECT user, host FROM mysql.user")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer rows.Close()
var userGrants []user
for rows.Next() {
var u user
err := rows.Scan(&u.User, &u.Host)
if err != nil {
continue
}
// 查询用户权限
grantsRows, err := db.Query(fmt.Sprintf("SHOW GRANTS FOR '%s'@'%s'", u.User, u.Host))
if err != nil {
continue
}
defer grantsRows.Close()
for grantsRows.Next() {
var grant string
err := grantsRows.Scan(&grant)
if err != nil {
continue
}
u.Grants = append(u.Grants, grant)
}
if err := grantsRows.Err(); err != nil {
continue
}
userGrants = append(userGrants, u)
}
if err := rows.Err(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取用户列表失败")
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(userGrants) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []user{},
})
}
if endIndex > len(userGrants) {
endIndex = len(userGrants)
}
pagedUserGrants := userGrants[startIndex:endIndex]
return controllers.Success(ctx, http.Json{
"total": len(userGrants),
"items": pagedUserGrants,
})
}
// AddUser 添加用户
func (r *Mysql57Controller) AddUser(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + ";'\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// DeleteUser 删除用户
func (r *Mysql57Controller) DeleteUser(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// SetUserPassword 设置用户密码
func (r *Mysql57Controller) SetUserPassword(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// SetUserPrivileges 设置用户权限
func (r *Mysql57Controller) SetUserPrivileges(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"database": "required|min_len:1|max_len:255",
})
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return controllers.Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
database := ctx.Request().Input("database")
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
@@ -15,20 +15,20 @@ import (
"panel/pkg/tools"
)
type Mysql80Controller struct {
type MySQLController struct {
setting internal.Setting
backup internal.Backup
}
func NewMysql80Controller() *Mysql80Controller {
return &Mysql80Controller{
func NewMySQLController() *MySQLController {
return &MySQLController{
setting: services.NewSettingImpl(),
backup: services.NewBackupImpl(),
}
}
// Status 获取运行状态
func (r *Mysql80Controller) Status(ctx http.Context) http.Response {
func (r *MySQLController) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
@@ -38,7 +38,7 @@ func (r *Mysql80Controller) Status(ctx http.Context) http.Response {
}
// Reload 重载配置
func (r *Mysql80Controller) Reload(ctx http.Context) http.Response {
func (r *MySQLController) Reload(ctx http.Context) http.Response {
if err := tools.ServiceReload("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载MySQL配置失败")
}
@@ -47,7 +47,7 @@ func (r *Mysql80Controller) Reload(ctx http.Context) http.Response {
}
// Restart 重启服务
func (r *Mysql80Controller) Restart(ctx http.Context) http.Response {
func (r *MySQLController) Restart(ctx http.Context) http.Response {
if err := tools.ServiceRestart("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启MySQL服务失败")
}
@@ -56,7 +56,7 @@ func (r *Mysql80Controller) Restart(ctx http.Context) http.Response {
}
// Start 启动服务
func (r *Mysql80Controller) Start(ctx http.Context) http.Response {
func (r *MySQLController) Start(ctx http.Context) http.Response {
if err := tools.ServiceStart("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动MySQL服务失败")
}
@@ -65,7 +65,7 @@ func (r *Mysql80Controller) Start(ctx http.Context) http.Response {
}
// Stop 停止服务
func (r *Mysql80Controller) Stop(ctx http.Context) http.Response {
func (r *MySQLController) Stop(ctx http.Context) http.Response {
if err := tools.ServiceStop("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止MySQL服务失败")
}
@@ -74,7 +74,7 @@ func (r *Mysql80Controller) Stop(ctx http.Context) http.Response {
}
// GetConfig 获取配置
func (r *Mysql80Controller) GetConfig(ctx http.Context) http.Response {
func (r *MySQLController) GetConfig(ctx http.Context) http.Response {
config, err := tools.Read("/www/server/mysql/conf/my.cnf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
@@ -84,7 +84,7 @@ func (r *Mysql80Controller) GetConfig(ctx http.Context) http.Response {
}
// SaveConfig 保存配置
func (r *Mysql80Controller) SaveConfig(ctx http.Context) http.Response {
func (r *MySQLController) SaveConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
@@ -98,7 +98,7 @@ func (r *Mysql80Controller) SaveConfig(ctx http.Context) http.Response {
}
// Load 获取负载
func (r *Mysql80Controller) Load(ctx http.Context) http.Response {
func (r *MySQLController) Load(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
@@ -168,7 +168,7 @@ func (r *Mysql80Controller) Load(ctx http.Context) http.Response {
}
// ErrorLog 获取错误日志
func (r *Mysql80Controller) ErrorLog(ctx http.Context) http.Response {
func (r *MySQLController) ErrorLog(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-error.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
@@ -178,7 +178,7 @@ func (r *Mysql80Controller) ErrorLog(ctx http.Context) http.Response {
}
// ClearErrorLog 清空错误日志
func (r *Mysql80Controller) ClearErrorLog(ctx http.Context) http.Response {
func (r *MySQLController) ClearErrorLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-error.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -187,7 +187,7 @@ func (r *Mysql80Controller) ClearErrorLog(ctx http.Context) http.Response {
}
// SlowLog 获取慢查询日志
func (r *Mysql80Controller) SlowLog(ctx http.Context) http.Response {
func (r *MySQLController) SlowLog(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-slow.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
@@ -197,7 +197,7 @@ func (r *Mysql80Controller) SlowLog(ctx http.Context) http.Response {
}
// ClearSlowLog 清空慢查询日志
func (r *Mysql80Controller) ClearSlowLog(ctx http.Context) http.Response {
func (r *MySQLController) ClearSlowLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-slow.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -205,7 +205,7 @@ func (r *Mysql80Controller) ClearSlowLog(ctx http.Context) http.Response {
}
// GetRootPassword 获取root密码
func (r *Mysql80Controller) GetRootPassword(ctx http.Context) http.Response {
func (r *MySQLController) GetRootPassword(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
@@ -215,7 +215,7 @@ func (r *Mysql80Controller) GetRootPassword(ctx http.Context) http.Response {
}
// SetRootPassword 设置root密码
func (r *Mysql80Controller) SetRootPassword(ctx http.Context) http.Response {
func (r *MySQLController) SetRootPassword(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
@@ -253,7 +253,7 @@ func (r *Mysql80Controller) SetRootPassword(ctx http.Context) http.Response {
}
// DatabaseList 获取数据库列表
func (r *Mysql80Controller) DatabaseList(ctx http.Context) http.Response {
func (r *MySQLController) DatabaseList(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
type database struct {
Name string `json:"name"`
@@ -308,7 +308,7 @@ func (r *Mysql80Controller) DatabaseList(ctx http.Context) http.Response {
}
// AddDatabase 添加数据库
func (r *Mysql80Controller) AddDatabase(ctx http.Context) http.Response {
func (r *MySQLController) AddDatabase(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
@@ -343,7 +343,7 @@ func (r *Mysql80Controller) AddDatabase(ctx http.Context) http.Response {
}
// DeleteDatabase 删除数据库
func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) http.Response {
func (r *MySQLController) DeleteDatabase(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
})
@@ -364,7 +364,7 @@ func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) http.Response {
}
// BackupList 获取备份列表
func (r *Mysql80Controller) BackupList(ctx http.Context) http.Response {
func (r *MySQLController) BackupList(ctx http.Context) http.Response {
backupList, err := r.backup.MysqlList()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
@@ -395,7 +395,7 @@ func (r *Mysql80Controller) BackupList(ctx http.Context) http.Response {
}
// UploadBackup 上传备份
func (r *Mysql80Controller) UploadBackup(ctx http.Context) http.Response {
func (r *MySQLController) UploadBackup(ctx http.Context) http.Response {
file, err := ctx.Request().File("file")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
@@ -418,7 +418,7 @@ func (r *Mysql80Controller) UploadBackup(ctx http.Context) http.Response {
}
// CreateBackup 创建备份
func (r *Mysql80Controller) CreateBackup(ctx http.Context) http.Response {
func (r *MySQLController) CreateBackup(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
})
@@ -439,7 +439,7 @@ func (r *Mysql80Controller) CreateBackup(ctx http.Context) http.Response {
}
// DeleteBackup 删除备份
func (r *Mysql80Controller) DeleteBackup(ctx http.Context) http.Response {
func (r *MySQLController) DeleteBackup(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|min_len:1|max_len:255",
})
@@ -460,7 +460,7 @@ func (r *Mysql80Controller) DeleteBackup(ctx http.Context) http.Response {
}
// RestoreBackup 还原备份
func (r *Mysql80Controller) RestoreBackup(ctx http.Context) http.Response {
func (r *MySQLController) RestoreBackup(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"backup": "required|min_len:1|max_len:255",
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
@@ -481,7 +481,7 @@ func (r *Mysql80Controller) RestoreBackup(ctx http.Context) http.Response {
}
// UserList 用户列表
func (r *Mysql80Controller) UserList(ctx http.Context) http.Response {
func (r *MySQLController) UserList(ctx http.Context) http.Response {
type user struct {
User string `json:"user"`
Host string `json:"host"`
@@ -560,7 +560,7 @@ func (r *Mysql80Controller) UserList(ctx http.Context) http.Response {
}
// AddUser 添加用户
func (r *Mysql80Controller) AddUser(ctx http.Context) http.Response {
func (r *MySQLController) AddUser(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
@@ -591,7 +591,7 @@ func (r *Mysql80Controller) AddUser(ctx http.Context) http.Response {
}
// DeleteUser 删除用户
func (r *Mysql80Controller) DeleteUser(ctx http.Context) http.Response {
func (r *MySQLController) DeleteUser(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
})
@@ -612,7 +612,7 @@ func (r *Mysql80Controller) DeleteUser(ctx http.Context) http.Response {
}
// SetUserPassword 设置用户密码
func (r *Mysql80Controller) SetUserPassword(ctx http.Context) http.Response {
func (r *MySQLController) SetUserPassword(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:255",
@@ -638,7 +638,7 @@ func (r *Mysql80Controller) SetUserPassword(ctx http.Context) http.Response {
}
// SetUserPrivileges 设置用户权限
func (r *Mysql80Controller) SetUserPrivileges(ctx http.Context) http.Response {
func (r *MySQLController) SetUserPrivileges(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
"database": "required|min_len:1|max_len:255",
@@ -4,8 +4,8 @@ import (
"regexp"
"time"
"github.com/go-resty/resty/v2"
"github.com/goravel/framework/contracts/http"
"github.com/imroc/req/v3"
"github.com/spf13/cast"
"panel/app/http/controllers"
@@ -117,9 +117,9 @@ func (r *OpenRestyController) ClearErrorLog(ctx http.Context) http.Response {
// Load 获取负载
func (r *OpenRestyController) Load(ctx http.Context) http.Response {
client := req.C().SetTimeout(10 * time.Second)
client := resty.New().SetTimeout(10 * time.Second)
resp, err := client.R().Get("http://127.0.0.1/nginx_status")
if err != nil || !resp.IsSuccessState() {
if err != nil || !resp.IsSuccess() {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty负载失败")
}
+4 -3
View File
@@ -11,10 +11,11 @@ import (
// Jwt 确保通过 JWT 鉴权
func Jwt() http.Middleware {
return func(ctx http.Context) {
translate := facades.Lang(ctx)
token := ctx.Request().Header("Authorization", ctx.Request().Header("Sec-WebSocket-Protocol"))
if len(token) == 0 {
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": "未登录",
"message": translate.Get("auth.token.missing"),
})
return
}
@@ -26,7 +27,7 @@ func Jwt() http.Middleware {
if err != nil {
// 到达刷新时间上限
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": "登录已过期",
"message": translate.Get("auth.token.expired"),
})
return
}
@@ -34,7 +35,7 @@ func Jwt() http.Middleware {
token = "Bearer " + token
} else {
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": "登录已过期",
"message": translate.Get("auth.token.expired"),
})
return
}
+22 -5
View File
@@ -4,6 +4,8 @@ import (
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/translation"
"github.com/goravel/framework/facades"
"panel/internal/services"
)
@@ -12,6 +14,7 @@ import (
func MustInstall() http.Middleware {
return func(ctx http.Context) {
path := ctx.Request().Path()
translate := facades.Lang(ctx)
var slug string
if strings.HasPrefix(path, "/api/panel/website") {
slug = "openresty"
@@ -19,7 +22,7 @@ func MustInstall() http.Middleware {
pathArr := strings.Split(path, "/")
if len(pathArr) < 4 {
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
"message": "插件不存在",
"message": translate.Get("errors.plugin.notExist"),
})
return
}
@@ -31,14 +34,18 @@ func MustInstall() http.Middleware {
installedPlugins, err := services.NewPluginImpl().AllInstalled()
if err != nil {
ctx.Request().AbortWithStatusJson(http.StatusInternalServerError, http.Json{
"message": "系统内部错误",
"message": translate.Get("errors.internal"),
})
return
}
if installedPlugin.Slug != plugin.Slug {
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
"message": "插件 " + slug + " 未安装",
"message": translate.Get("errors.plugin.notInstalled", translation.Option{
Replace: map[string]string{
"slug": slug,
},
}),
})
return
}
@@ -53,7 +60,12 @@ func MustInstall() http.Middleware {
_, requireFound := pluginsMap[require]
if !requireFound {
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
"message": "插件 " + slug + " 需要依赖 " + require + " 插件",
"message": translate.Get("errors.plugin.dependent", translation.Option{
Replace: map[string]string{
"slug": slug,
"dependency": require,
},
}),
})
return
}
@@ -63,7 +75,12 @@ func MustInstall() http.Middleware {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
"message": "插件 " + slug + " 不兼容 " + exclude + " 插件",
"message": translate.Get("errors.plugin.incompatible", translation.Option{
Replace: map[string]string{
"slug": slug,
"exclude": exclude,
},
}),
})
return
}
+6 -4
View File
@@ -2,6 +2,7 @@ package middleware
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"panel/internal"
)
@@ -9,25 +10,26 @@ import (
// Status 检查程序状态
func Status() http.Middleware {
return func(ctx http.Context) {
translate := facades.Lang(ctx)
switch internal.Status {
case internal.StatusUpgrade:
ctx.Request().AbortWithStatusJson(http.StatusServiceUnavailable, http.Json{
"message": "面板升级中,请稍后",
"message": translate.Get("status.upgrade"),
})
return
case internal.StatusMaintain:
ctx.Request().AbortWithStatusJson(http.StatusServiceUnavailable, http.Json{
"message": "面板正在运行维护,请稍后",
"message": translate.Get("status.maintain"),
})
return
case internal.StatusClosed:
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
"message": "面板已关闭",
"message": translate.Get("status.closed"),
})
return
case internal.StatusFailed:
ctx.Request().AbortWithStatusJson(http.StatusInternalServerError, http.Json{
"message": "面板运行出错,请检查排除或联系支持",
"message": translate.Get("status.failed"),
})
return
default:
+2 -2
View File
@@ -6,8 +6,8 @@ import (
)
type CertDeploy struct {
ID uint `form:"id" json:"id"`
WebsiteID uint `form:"website_id" json:"website_id"`
ID uint `form:"id" json:"id" filter:"uint"`
WebsiteID uint `form:"website_id" json:"website_id" filter:"uint"`
}
func (r *CertDeploy) Authorize(ctx http.Context) error {
-1
View File
@@ -26,7 +26,6 @@ func (r *DNSStore) Rules(ctx http.Context) map[string]string {
"data.token": "required_if:type,dnspod",
"data.access_key": "required_if:type,aliyun",
"data.secret_key": "required_if:type,aliyun",
"data.email": "required_if:type,cloudflare",
"data.api_key": "required_if:type,cloudflare",
}
}
+2 -2
View File
@@ -17,8 +17,8 @@ func (r *Archive) Authorize(ctx http.Context) error {
func (r *Archive) Rules(ctx http.Context) map[string]string {
return map[string]string{
"paths": "array",
"paths.*": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"file": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_not_exists",
"paths.*": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"file": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_not_exists`,
}
}
+4 -4
View File
@@ -6,8 +6,8 @@ import (
)
type Copy struct {
Old string `form:"old" json:"old"`
New string `form:"new" json:"new"`
Source string `form:"source" json:"source"`
Target string `form:"target" json:"target"`
}
func (r *Copy) Authorize(ctx http.Context) error {
@@ -16,8 +16,8 @@ func (r *Copy) Authorize(ctx http.Context) error {
func (r *Copy) Rules(ctx http.Context) map[string]string {
return map[string]string{
"old": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"new": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"source": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"target": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ func (r *Exist) Authorize(ctx http.Context) error {
func (r *Exist) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"path": `regex:^/([a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*)?$|path_exists`,
}
}
+4 -4
View File
@@ -6,8 +6,8 @@ import (
)
type Move struct {
Old string `form:"old" json:"old"`
New string `form:"new" json:"new"`
Source string `form:"source" json:"source"`
Target string `form:"target" json:"target"`
}
func (r *Move) Authorize(ctx http.Context) error {
@@ -16,8 +16,8 @@ func (r *Move) Authorize(ctx http.Context) error {
func (r *Move) Rules(ctx http.Context) map[string]string {
return map[string]string{
"old": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"new": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"source": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"target": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ func (r *NotExist) Authorize(ctx http.Context) error {
func (r *NotExist) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_not_exists",
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_not_exists`,
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ func (r *Permission) Authorize(ctx http.Context) error {
func (r *Permission) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"mode": "regex:^[0-7]{3}$|uint",
"owner": "regex:^[a-zA-Z0-9_-]+$",
"group": "regex:^[a-zA-Z0-9_-]+$",
+1 -1
View File
@@ -16,7 +16,7 @@ func (r *Save) Authorize(ctx http.Context) error {
func (r *Save) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"content": "required|string",
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ func (r *Search) Authorize(ctx http.Context) error {
func (r *Search) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"keyword": "required|string",
}
}
+2 -2
View File
@@ -16,8 +16,8 @@ func (r *UnArchive) Authorize(ctx http.Context) error {
func (r *UnArchive) Rules(ctx http.Context) map[string]string {
return map[string]string{
"file": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_not_exists",
"file": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_exists`,
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$|path_not_exists`,
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ func (r *Upload) Authorize(ctx http.Context) error {
func (r *Upload) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
"file": "required",
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ func (r *Create) Authorize(ctx http.Context) error {
func (r *Create) Rules(ctx http.Context) map[string]string {
return map[string]string{
"name": "required|regex:^[a-zA-Z0-9-_]+$",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
"comment": "string",
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
"secret": "required|min_len:8",
+1 -1
View File
@@ -21,7 +21,7 @@ func (r *Update) Authorize(ctx http.Context) error {
func (r *Update) Rules(ctx http.Context) map[string]string {
return map[string]string{
"name": "required|regex:^[a-zA-Z0-9-_]+$",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
"comment": "string",
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
"secret": "required|min_len:8",
+1 -1
View File
@@ -27,7 +27,7 @@ func (r *Add) Rules(ctx http.Context) map[string]string {
"name": "required|regex:^[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*$|not_exists:websites,name|not_in:phpmyadmin,mysql,panel,ssh",
"domains": "required|slice",
"ports": "required|slice",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"path": `regex:^/[a-zA-Z0-9_.@#$%\-\s\[\]()]+(/[a-zA-Z0-9_.@#$%\-\s\[\]()]+)*$`,
"php": "required",
"db": "bool",
"db_type": "required_if:db,true|in:0,mysql,postgresql",
+3 -1
View File
@@ -19,6 +19,7 @@ import (
"github.com/goravel/framework/schedule"
"github.com/goravel/framework/support/carbon"
"github.com/goravel/framework/testing"
"github.com/goravel/framework/translation"
"github.com/goravel/framework/validation"
"github.com/goravel/gin"
@@ -60,7 +61,7 @@ func init() {
// The application locale determines the default locale that will be used
// by the translation service provider.You are free to set this value
// to any of the locales which will be supported by the application.
"locale": "zh_CN",
"locale": config.Env("APP_LOCALE", "zh_CN"),
// Application Fallback Locale
//
@@ -96,6 +97,7 @@ func init() {
&crypt.ServiceProvider{},
&filesystem.ServiceProvider{},
&validation.ServiceProvider{},
&translation.ServiceProvider{},
&testing.ServiceProvider{},
&providers.AppServiceProvider{},
&providers.AuthServiceProvider{},
+1 -1
View File
@@ -8,7 +8,7 @@ func init() {
config := facades.Config()
config.Add("panel", map[string]any{
"name": "耗子 Linux 面板",
"version": "v2.1.43",
"version": "v2.2.2",
"ssl": config.Env("APP_SSL", false),
})
}
+66 -12
View File
@@ -270,6 +270,45 @@ const docTemplate = `{
}
}
},
"/panel/cert/deploy": {
"post": {
"security": [
{
"BearerToken": []
}
],
"description": "部署面板证书管理的证书",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"证书管理"
],
"summary": "部署证书",
"parameters": [
{
"description": "request",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.CertDeploy"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controllers.SuccessResponse"
}
}
}
}
},
"/panel/cert/dns": {
"get": {
"security": [
@@ -535,9 +574,9 @@ const docTemplate = `{
"type": "object",
"properties": {
"data": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/acme.Resolve"
"type": "array",
"items": {
"$ref": "#/definitions/acme.DNSRecord"
}
}
}
@@ -2300,6 +2339,16 @@ const docTemplate = `{
"type": "string",
"name": "path",
"in": "query"
},
{
"type": "integer",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"name": "page",
"in": "query"
}
],
"responses": {
@@ -3696,9 +3745,6 @@ const docTemplate = `{
"api_key": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
},
@@ -3710,12 +3756,9 @@ const docTemplate = `{
}
}
},
"acme.Resolve": {
"acme.DNSRecord": {
"type": "object",
"properties": {
"err": {
"type": "string"
},
"key": {
"type": "string"
},
@@ -4074,6 +4117,17 @@ const docTemplate = `{
}
}
},
"requests.CertDeploy": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"website_id": {
"type": "integer"
}
}
},
"requests.CertStore": {
"type": "object",
"properties": {
@@ -4329,10 +4383,10 @@ const docTemplate = `{
"requests.Move": {
"type": "object",
"properties": {
"new": {
"source": {
"type": "string"
},
"old": {
"target": {
"type": "string"
}
}
+66 -12
View File
@@ -263,6 +263,45 @@
}
}
},
"/panel/cert/deploy": {
"post": {
"security": [
{
"BearerToken": []
}
],
"description": "部署面板证书管理的证书",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"证书管理"
],
"summary": "部署证书",
"parameters": [
{
"description": "request",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.CertDeploy"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controllers.SuccessResponse"
}
}
}
}
},
"/panel/cert/dns": {
"get": {
"security": [
@@ -528,9 +567,9 @@
"type": "object",
"properties": {
"data": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/acme.Resolve"
"type": "array",
"items": {
"$ref": "#/definitions/acme.DNSRecord"
}
}
}
@@ -2293,6 +2332,16 @@
"type": "string",
"name": "path",
"in": "query"
},
{
"type": "integer",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"name": "page",
"in": "query"
}
],
"responses": {
@@ -3689,9 +3738,6 @@
"api_key": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
},
@@ -3703,12 +3749,9 @@
}
}
},
"acme.Resolve": {
"acme.DNSRecord": {
"type": "object",
"properties": {
"err": {
"type": "string"
},
"key": {
"type": "string"
},
@@ -4067,6 +4110,17 @@
}
}
},
"requests.CertDeploy": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"website_id": {
"type": "integer"
}
}
},
"requests.CertStore": {
"type": "object",
"properties": {
@@ -4322,10 +4376,10 @@
"requests.Move": {
"type": "object",
"properties": {
"new": {
"source": {
"type": "string"
},
"old": {
"target": {
"type": "string"
}
}
+43 -10
View File
@@ -6,8 +6,6 @@ definitions:
type: string
api_key:
type: string
email:
type: string
id:
type: string
secret_key:
@@ -15,10 +13,8 @@ definitions:
token:
type: string
type: object
acme.Resolve:
acme.DNSRecord:
properties:
err:
type: string
key:
type: string
value:
@@ -257,6 +253,13 @@ definitions:
type: string
type: array
type: object
requests.CertDeploy:
properties:
id:
type: integer
website_id:
type: integer
type: object
requests.CertStore:
properties:
auto_renew:
@@ -423,9 +426,9 @@ definitions:
type: object
requests.Move:
properties:
new:
source:
type: string
old:
target:
type: string
type: object
requests.NetworkConnectDisConnect:
@@ -797,6 +800,30 @@ paths:
summary: 更新证书
tags:
- 证书管理
/panel/cert/deploy:
post:
consumes:
- application/json
description: 部署面板证书管理的证书
parameters:
- description: request
in: body
name: data
required: true
schema:
$ref: '#/definitions/requests.CertDeploy'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controllers.SuccessResponse'
security:
- BearerToken: []
summary: 部署证书
tags:
- 证书管理
/panel/cert/dns:
get:
description: 获取面板证书管理的 DNS 接口列表
@@ -955,9 +982,9 @@ paths:
- $ref: '#/definitions/controllers.SuccessResponse'
- properties:
data:
additionalProperties:
$ref: '#/definitions/acme.Resolve'
type: object
items:
$ref: '#/definitions/acme.DNSRecord'
type: array
type: object
security:
- BearerToken: []
@@ -2024,6 +2051,12 @@ paths:
- in: query
name: path
type: string
- in: query
name: limit
type: integer
- in: query
name: page
type: integer
produces:
- application/json
responses:
+53 -60
View File
@@ -3,17 +3,19 @@ module panel
go 1.22
require (
github.com/docker/docker v25.0.5+incompatible
github.com/docker/docker v26.1.3+incompatible
github.com/docker/go-connections v0.5.0
github.com/gertd/go-pluralize v0.2.1
github.com/go-acme/lego/v4 v4.16.1
github.com/go-resty/resty/v2 v2.13.1
github.com/gookit/color v1.5.4
github.com/gookit/validate v1.5.2
github.com/goravel/framework v1.13.1-0.20240215091018-1a9f352e523c
github.com/goravel/gin v1.1.6-0.20240215075927-ab614bfae9f3
github.com/goravel/framework v1.13.1-0.20240430025652-cf76911d0286
github.com/goravel/gin v1.1.6-0.20240426085159-718e4d6d5f4f
github.com/gorilla/websocket v1.5.1
github.com/iancoleman/strcase v0.3.0
github.com/imroc/req/v3 v3.43.1
github.com/libdns/alidns v1.0.3
github.com/libdns/cloudflare v0.1.1
github.com/libdns/dnspod v0.0.3
github.com/libdns/libdns v0.2.2
github.com/mholt/acmez/v2 v2.0.1
github.com/mholt/archiver/v3 v3.5.1
github.com/mojocn/base64Captcha v1.3.6
github.com/shirou/gopsutil v3.21.11+incompatible
@@ -21,15 +23,18 @@ require (
github.com/stretchr/testify v1.9.0
github.com/swaggo/http-swagger/v2 v2.0.2
github.com/swaggo/swag v1.16.3
golang.org/x/crypto v0.21.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.23.0
golang.org/x/net v0.25.0
)
require (
cloud.google.com/go v0.111.0 // indirect
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go v0.112.0 // indirect
cloud.google.com/go/compute v1.24.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
cloud.google.com/go/pubsub v1.33.0 // indirect
cloud.google.com/go/iam v1.1.6 // indirect
cloud.google.com/go/pubsub v1.36.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
@@ -40,34 +45,30 @@ require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae // indirect
github.com/RichardKnop/machinery/v2 v2.0.12-0.20231012204029-bdb94a90ca41 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
github.com/RichardKnop/machinery/v2 v2.0.13 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/aws/aws-sdk-go v1.49.6 // indirect
github.com/bytedance/sonic v1.11.0 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cloudflare/cloudflare-go v0.86.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/glebarez/sqlite v1.10.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/glebarez/sqlite v1.11.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
@@ -79,22 +80,20 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.5 // indirect
github.com/go-redsync/redsync/v4 v4.8.1 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
github.com/golang-migrate/migrate/v4 v4.17.0 // indirect
github.com/golang-module/carbon/v2 v2.3.8 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang-migrate/migrate/v4 v4.17.1 // indirect
github.com/golang-module/carbon/v2 v2.3.12 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.6.0 // indirect
@@ -106,13 +105,12 @@ require (
github.com/goravel/file-rotatelogs/v2 v2.4.2 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.4.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
@@ -120,7 +118,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lib/pq v1.10.9 // indirect
@@ -128,14 +126,13 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/microsoft/go-mssqldb v1.6.0 // indirect
github.com/miekg/dns v1.1.58 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.0 // indirect
github.com/nrdcg/dnspod-go v0.4.0 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
@@ -144,15 +141,13 @@ require (
github.com/pierrec/lz4/v4 v4.1.16 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/quic-go v0.41.0 // indirect
github.com/redis/go-redis/v9 v9.4.0 // indirect
github.com/refraction-networking/utls v1.6.3 // indirect
github.com/rabbitmq/amqp091-go v1.9.0 // indirect
github.com/redis/go-redis/v9 v9.5.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rotisserie/eris v0.5.4 // indirect
github.com/rs/cors v1.10.1 // indirect
github.com/rs/cors v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
@@ -162,7 +157,6 @@ require (
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.18.2 // indirect
github.com/streadway/amqp v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
@@ -173,51 +167,50 @@ require (
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/unrolled/secure v1.14.0 // indirect
github.com/urfave/cli/v2 v2.27.1 // indirect
github.com/urfave/cli/v2 v2.27.2 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.mongodb.org/mongo-driver v1.11.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect
go.opentelemetry.io/otel v1.22.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0 // indirect
go.opentelemetry.io/otel/metric v1.22.0 // indirect
go.opentelemetry.io/otel/trace v1.22.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/oauth2 v0.16.0 // indirect
golang.org/x/oauth2 v0.17.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.19.0 // indirect
google.golang.org/api v0.153.0 // indirect
google.golang.org/api v0.162.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect
google.golang.org/grpc v1.61.1 // indirect
google.golang.org/protobuf v1.32.0 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.5.4 // indirect
gorm.io/driver/postgres v1.5.6 // indirect
gorm.io/driver/mysql v1.5.6 // indirect
gorm.io/driver/postgres v1.5.7 // indirect
gorm.io/driver/sqlserver v1.5.3 // indirect
gorm.io/gorm v1.25.7 // indirect
gorm.io/plugin/dbresolver v1.5.0 // indirect
gorm.io/gorm v1.25.10 // indirect
gorm.io/plugin/dbresolver v1.5.1 // indirect
gotest.tools/v3 v3.5.0 // indirect
modernc.org/libc v1.37.6 // indirect
modernc.org/mathutil v1.6.0 // indirect
+130 -145
View File
@@ -16,38 +16,39 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU=
cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=
cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM=
cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI=
cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
cloud.google.com/go/kms v1.15.7 h1:7caV9K3yIxvlQPAcaFffhlT7d1qpxjB1wHBtjWa13SM=
cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/pubsub v1.10.0/go.mod h1:eNpTrkOy7dCpkNyaSNetMa6udbgecJMd0ZsTJS/cuNo=
cloud.google.com/go/pubsub v1.33.0 h1:6SPCPvWav64tj0sVX/+npCBKhUi/UjJehy9op/V3p2g=
cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc=
cloud.google.com/go/pubsub v1.36.1 h1:dfEPuGCHGbWUhaMCTHUFjfroILEkx55iUmKBZTP5f+Y=
cloud.google.com/go/pubsub v1.36.1/go.mod h1:iYjCa9EzWOoBiTdd4ps7QoMtMln5NwaZQpK1hbRfBDE=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
@@ -71,7 +72,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw=
github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc=
github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
@@ -94,10 +94,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae h1:DcFpTQBYQ9Ct2d6sC7ol0/ynxc2pO1cpGUM+f4t5adg=
github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae/go.mod h1:rJJ84PyA/Wlmw1hO+xTzV2wsSUon6J5ktg0g8BF2PuU=
github.com/RichardKnop/machinery/v2 v2.0.12-0.20231012204029-bdb94a90ca41 h1:7fLtRodG39Hn6AKqKE+ejO0Jj9B8O9RQOto13lkoDac=
github.com/RichardKnop/machinery/v2 v2.0.12-0.20231012204029-bdb94a90ca41/go.mod h1:92dLVxckr2Lv7oKxoS3Uci0Of8Cuy+lmPi2dd6Euwkw=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/RichardKnop/machinery/v2 v2.0.13 h1:uo9htg+qNBi7UeUK3jcTBl3vTO/vvLKGaOdCOKePl50=
github.com/RichardKnop/machinery/v2 v2.0.13/go.mod h1:Yc2X/QRm9rRfAjB+93NGR+kSUqtnqqs8kME4L+TKKiw=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
@@ -118,10 +116,10 @@ github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATs
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.11.0 h1:FwNNv6Vu4z2Onf1++LNzxB/QhitD8wuTdpZzMTGITWo=
github.com/bytedance/sonic v1.11.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
@@ -130,21 +128,14 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpXTstVx0rqHI=
github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -152,25 +143,27 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ=
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dhui/dktest v0.4.0 h1:z05UmuXZHO/bgj/ds2bGMBu8FI4WA+Ag/m3ghL+om7M=
github.com/dhui/dktest v0.4.0/go.mod h1:v/Dbz1LgCBOi2Uki2nUqLBGa83hWBGFMu5MrgMDCc78=
github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg=
github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE=
github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo=
github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -187,8 +180,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@@ -199,8 +192,6 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA=
github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
@@ -208,15 +199,11 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ=
github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -254,15 +241,15 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-redsync/redsync/v4 v4.8.1 h1:rq2RvdTI0obznMdxKUWGdmmulo7lS9yCzb8fgDKOlbM=
github.com/go-redsync/redsync/v4 v4.8.1/go.mod h1:LmUAsQuQxhzZAoGY7JS6+dNhNmZyonMZiiEDY9plotM=
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
@@ -291,18 +278,17 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU=
github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM=
github.com/golang-module/carbon/v2 v2.3.8 h1:aowLnKwifhMqRDmzmXNtlxT7AJXZWKwaoTPcPLagAHg=
github.com/golang-module/carbon/v2 v2.3.8/go.mod h1:XDALX7KgqmHk95xyLeaqX9/LJGbfLATyruTziq68SZ8=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4=
github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM=
github.com/golang-module/carbon/v2 v2.3.12 h1:VC1DwN1kBwJkh5MjXmTFryjs5g4CWyoM8HAHffZPX/k=
github.com/golang-module/carbon/v2 v2.3.12/go.mod h1:HNsedGzXGuNciZImYP2OMnpiwq/vhIstR/vn45ib5cI=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@@ -338,13 +324,14 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -363,8 +350,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -408,10 +393,10 @@ github.com/gookit/validate v1.5.2 h1:i5I2OQ7WYHFRPRATGu9QarR9snnNHydvwSuHXaRWAV0
github.com/gookit/validate v1.5.2/go.mod h1:yuPy2WwDlwGRa06fFJ5XIO8QEwhRnTC2LmxmBa5SE14=
github.com/goravel/file-rotatelogs/v2 v2.4.2 h1:g68AzbePXcm0V2CpUMc9j4qVzcDn7+7aoWSjZ51C0m4=
github.com/goravel/file-rotatelogs/v2 v2.4.2/go.mod h1:23VuSW8cBS4ax5cmbV+5AaiLpq25b8UJ96IhbAkdo8I=
github.com/goravel/framework v1.13.1-0.20240215091018-1a9f352e523c h1:PahMfqzP8tJqagVsiWt0D41zo0vhdNRZW7VvgCdJUio=
github.com/goravel/framework v1.13.1-0.20240215091018-1a9f352e523c/go.mod h1:5yDV4oFTZCJibqKY4C+fxMq1/rtPoLg8evYVC2uGf+M=
github.com/goravel/gin v1.1.6-0.20240215075927-ab614bfae9f3 h1:sqXKSv1yvUZwbpv595BsE2fA4mQjTaR4x2mB7uQrzOY=
github.com/goravel/gin v1.1.6-0.20240215075927-ab614bfae9f3/go.mod h1:D/61whQQvC6HaUNSEy7UPJokJAa6csY8k9Cw37fpEQM=
github.com/goravel/framework v1.13.1-0.20240430025652-cf76911d0286 h1:SHlT5KRxcIOBprz8awwKv9TmXVnyIqrOf7eXMeTyj88=
github.com/goravel/framework v1.13.1-0.20240430025652-cf76911d0286/go.mod h1:ljsNqFtfujgHw/kq5T7J//3UEhHUV02COYUVr/gr6CI=
github.com/goravel/gin v1.1.6-0.20240426085159-718e4d6d5f4f h1:4+sGiuMXtJRgP1vDxOm5wP4z9cXr/noWDxTBU2hnvRM=
github.com/goravel/gin v1.1.6-0.20240426085159-718e4d6d5f4f/go.mod h1:kX2QEYJO8UJRTMb52g2L+Ec/53WdJ8CHwlFeqy4fRfo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
@@ -423,15 +408,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -439,19 +417,17 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imroc/req/v3 v3.43.1 h1:tsWAhvxik4egtHAvMlxcjaWJtHlJL8EpBqJMOm5rmyQ=
github.com/imroc/req/v3 v3.43.1/go.mod h1:SQIz5iYop16MJxbo8ib+4LnostGCok8NQf8ToyQc2xA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
@@ -463,7 +439,6 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -471,7 +446,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -490,8 +464,8 @@ github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLA
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@@ -513,30 +487,39 @@ github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2t
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
github.com/libdns/dnspod v0.0.3 h1:xJHDIujgLjvZnpB8/rMoCHUqA/KxSGBqRUXxSIzNzAA=
github.com/libdns/dnspod v0.0.3/go.mod h1:XLnqMmK7QlLPEbHwcOxbRvlzRvDgaaUlthRNFOPjXPI=
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc=
github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
@@ -557,14 +540,10 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
@@ -590,16 +569,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo=
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo=
@@ -614,8 +589,8 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rotisserie/eris v0.5.4 h1:Il6IvLdAapsMhvuOahHWiBnl1G++Q0/L5UIkI5mARSk=
github.com/rotisserie/eris v0.5.4/go.mod h1:Z/kgYTJiJtocxCbFfvRmO+QejApzG6zpyky9G1A4g9s=
github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -645,8 +620,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -694,8 +667,8 @@ github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o
github.com/unrolled/secure v1.14.0 h1:u9vJTU/pR4Bny0ntLUMxdfLtmIRGvQf2sEFuA0TG9AE=
github.com/unrolled/secure v1.14.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
@@ -708,8 +681,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -719,6 +692,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8=
go.einride.tech/aip v0.66.0/go.mod h1:qAhMsfT7plxBX+Oy7Huol6YUvZ0ZzdUz26yZsQwfl1M=
go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y=
go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
@@ -730,6 +705,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
@@ -744,8 +721,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1
go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg=
go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
@@ -756,12 +733,15 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
@@ -782,8 +762,9 @@ golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -873,8 +854,9 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -884,8 +866,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -961,8 +943,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -971,8 +954,9 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -987,8 +971,9 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1079,8 +1064,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.39.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4=
google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY=
google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps=
google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1128,12 +1113,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg=
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k=
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1153,8 +1138,8 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1168,8 +1153,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1177,7 +1162,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -1194,19 +1178,20 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
gorm.io/driver/mysql v1.5.4 h1:igQmHfKcbaTVyAIHNhhB888vvxh8EdQ2uSUT0LPcBso=
gorm.io/driver/mysql v1.5.4/go.mod h1:9rYxJph/u9SWkWc9yY4XJ1F/+xO0S/ChOmbk3+Z5Tvs=
gorm.io/driver/postgres v1.5.6 h1:ydr9xEd5YAM0vxVDY0X139dyzNz10spDiDlC7+ibLeU=
gorm.io/driver/postgres v1.5.6/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
gorm.io/driver/sqlserver v1.5.3 h1:rjupPS4PVw+rjJkfvr8jn2lJ8BMhT4UW5FwuJY0P3Z0=
gorm.io/driver/sqlserver v1.5.3/go.mod h1:B+CZ0/7oFJ6tAlefsKoyxdgDCXJKSgwS2bMOQZT0I00=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/plugin/dbresolver v1.5.0 h1:XVHLxh775eP0CqVh3vcfJtYqja3uFl5Wr3cKlY8jgDY=
gorm.io/plugin/dbresolver v1.5.0/go.mod h1:l4Cn87EHLEYuqUncpEeTC2tTJQkjngPSD+lo8hIvcT0=
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/plugin/dbresolver v1.5.1 h1:s9Dj9f7r+1rE3nx/Ywzc85nXptUEaeOO0pt27xdopM8=
gorm.io/plugin/dbresolver v1.5.1/go.mod h1:l4Cn87EHLEYuqUncpEeTC2tTJQkjngPSD+lo8hIvcT0=
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+5 -6
View File
@@ -1,8 +1,6 @@
package internal
import (
"github.com/go-acme/lego/v4/certificate"
requests "panel/app/http/requests/cert"
"panel/app/models"
"panel/pkg/acme"
@@ -21,8 +19,9 @@ type Cert interface {
CertUpdate(request requests.CertUpdate) error
CertShow(ID uint) (models.Cert, error)
CertDestroy(ID uint) error
ObtainAuto(ID uint) (certificate.Resource, error)
ObtainManual(ID uint) (certificate.Resource, error)
ManualDNS(ID uint) (map[string]acme.Resolve, error)
Renew(ID uint) (certificate.Resource, error)
ObtainAuto(ID uint) (acme.Certificate, error)
ObtainManual(ID uint) (acme.Certificate, error)
ManualDNS(ID uint) ([]acme.DNSRecord, error)
Renew(ID uint) (acme.Certificate, error)
Deploy(ID, WebsiteID uint) error
}
+3
View File
@@ -20,4 +20,7 @@ type Plugin interface {
All() []PanelPlugin
GetBySlug(slug string) PanelPlugin
GetInstalledBySlug(slug string) models.Plugin
Install(slug string) error
Uninstall(slug string) error
Update(slug string) error
}
+23 -11
View File
@@ -19,7 +19,7 @@ var PluginMySQL57 = PanelPlugin{
Slug: "mysql57",
Version: "5.7.44",
Requires: []string{},
Excludes: []string{"mysql80"},
Excludes: []string{"mysql80", "mysql84"},
Install: `bash /www/panel/scripts/mysql/install.sh 57`,
Uninstall: `bash /www/panel/scripts/mysql/uninstall.sh 57`,
Update: `bash /www/panel/scripts/mysql/update.sh 57`,
@@ -29,19 +29,31 @@ var PluginMySQL80 = PanelPlugin{
Name: "MySQL-8.0",
Description: "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。(建议内存 > 2G 安装)",
Slug: "mysql80",
Version: "8.0.36",
Version: "8.0.37",
Requires: []string{},
Excludes: []string{"mysql57"},
Excludes: []string{"mysql57", "mysql84"},
Install: `bash /www/panel/scripts/mysql/install.sh 80`,
Uninstall: `bash /www/panel/scripts/mysql/uninstall.sh 80`,
Update: `bash /www/panel/scripts/mysql/update.sh 80`,
}
var PluginMySQL84 = PanelPlugin{
Name: "MySQL-8.4",
Description: "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。(建议内存 > 2G 安装)",
Slug: "mysql84",
Version: "8.4.0",
Requires: []string{},
Excludes: []string{"mysql57", "mysql80"},
Install: `bash /www/panel/scripts/mysql/install.sh 84`,
Uninstall: `bash /www/panel/scripts/mysql/uninstall.sh 84`,
Update: `bash /www/panel/scripts/mysql/update.sh 84`,
}
var PluginPostgreSQL15 = PanelPlugin{
Name: "PostgreSQL-15",
Description: "PostgreSQL 是世界上最先进的开源关系数据库,在类似 BSD 与 MIT 许可的 PostgreSQL 许可下发行。",
Slug: "postgresql15",
Version: "15.6",
Version: "15.7",
Requires: []string{},
Excludes: []string{"postgresql16"},
Install: `bash /www/panel/scripts/postgresql/install.sh 15`,
@@ -53,7 +65,7 @@ var PluginPostgreSQL16 = PanelPlugin{
Name: "PostgreSQL-16",
Description: "PostgreSQL 是世界上最先进的开源关系数据库,在类似 BSD 与 MIT 许可的 PostgreSQL 许可下发行。",
Slug: "postgresql16",
Version: "16.2",
Version: "16.3",
Requires: []string{},
Excludes: []string{"postgresql15"},
Install: `bash /www/panel/scripts/postgresql/install.sh 16`,
@@ -89,7 +101,7 @@ var PluginPHP81 = PanelPlugin{
Name: "PHP-8.1",
Description: "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。",
Slug: "php81",
Version: "8.1.27",
Version: "8.1.28",
Requires: []string{},
Excludes: []string{},
Install: `bash /www/panel/scripts/php/install.sh 81`,
@@ -101,7 +113,7 @@ var PluginPHP82 = PanelPlugin{
Name: "PHP-8.2",
Description: "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。",
Slug: "php82",
Version: "8.2.16",
Version: "8.2.19",
Requires: []string{},
Excludes: []string{},
Install: `bash /www/panel/scripts/php/install.sh 82`,
@@ -113,7 +125,7 @@ var PluginPHP83 = PanelPlugin{
Name: "PHP-8.3",
Description: "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。",
Slug: "php83",
Version: "8.3.3",
Version: "8.3.7",
Requires: []string{},
Excludes: []string{},
Install: `bash /www/panel/scripts/php/install.sh 83`,
@@ -197,7 +209,7 @@ var PluginFail2ban = PanelPlugin{
Name: "Fail2ban",
Description: "Fail2ban 扫描系统日志文件并从中找出多次尝试失败的IP地址,将该IP地址加入防火墙的拒绝访问列表中。",
Slug: "fail2ban",
Version: "1.0.0",
Version: "1.0.2",
Requires: []string{},
Excludes: []string{},
Install: `bash /www/panel/scripts/fail2ban/install.sh`,
@@ -212,7 +224,7 @@ var PluginToolBox = PanelPlugin{
Version: "1.0.0",
Requires: []string{},
Excludes: []string{},
Install: `panel writePlugin toolbox 1.0`,
Install: `panel writePlugin toolbox 1.0.0`,
Uninstall: `panel deletePlugin toolbox`,
Update: `panel writePlugin toolbox 1.0`,
Update: `panel writePlugin toolbox 1.0.0`,
}
+148 -157
View File
@@ -2,10 +2,11 @@
package services
import (
"context"
"errors"
"strings"
"time"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/goravel/framework/facades"
requests "panel/app/http/requests/cert"
@@ -15,6 +16,7 @@ import (
)
type CertImpl struct {
client *acme.Client
}
func NewCertImpl() *CertImpl {
@@ -34,15 +36,15 @@ func (s *CertImpl) UserStore(request requests.UserStore) error {
var client *acme.Client
switch user.CA {
case "letsencrypt":
client, err = acme.NewRegisterClient(user.Email, acme.CALetEncrypt, certcrypto.KeyType(user.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CALetsEncrypt, nil, acme.KeyType(user.KeyType))
case "buypass":
client, err = acme.NewRegisterClient(user.Email, acme.CABuypass, certcrypto.KeyType(user.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CABuypass, nil, acme.KeyType(user.KeyType))
case "zerossl":
client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CAZeroSSL, certcrypto.KeyType(user.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType))
case "sslcom":
client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CASSLcom, certcrypto.KeyType(user.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CASSLcom, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType))
case "google":
client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CAGoogle, certcrypto.KeyType(user.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAGoogle, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType))
default:
return errors.New("CA 提供商不支持")
}
@@ -51,7 +53,7 @@ func (s *CertImpl) UserStore(request requests.UserStore) error {
return errors.New("向 CA 注册账号失败,请检查参数是否正确")
}
privateKey, err := acme.GetPrivateKey(client.User.GetPrivateKey(), acme.KeyType(user.KeyType))
privateKey, err := acme.EncodePrivateKey(client.Account.PrivateKey)
if err != nil {
return errors.New("获取私钥失败")
}
@@ -77,15 +79,15 @@ func (s *CertImpl) UserUpdate(request requests.UserUpdate) error {
var client *acme.Client
switch user.CA {
case "letsencrypt":
client, err = acme.NewRegisterClient(user.Email, acme.CALetEncrypt, certcrypto.KeyType(user.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CALetsEncrypt, nil, acme.KeyType(user.KeyType))
case "buypass":
client, err = acme.NewRegisterClient(user.Email, acme.CABuypass, certcrypto.KeyType(user.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CABuypass, nil, acme.KeyType(user.KeyType))
case "zerossl":
client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CAZeroSSL, certcrypto.KeyType(user.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType))
case "sslcom":
client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CASSLcom, certcrypto.KeyType(user.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CASSLcom, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType))
case "google":
client, err = acme.NewRegisterWithExternalAccountBindingClient(user.Email, *user.Kid, *user.HmacEncoded, acme.CAGoogle, certcrypto.KeyType(user.KeyType))
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAGoogle, &acme.EAB{KeyID: *user.Kid, MACKey: *user.HmacEncoded}, acme.KeyType(user.KeyType))
default:
return errors.New("CA 提供商不支持")
}
@@ -94,7 +96,7 @@ func (s *CertImpl) UserUpdate(request requests.UserUpdate) error {
return errors.New("向 CA 注册账号失败,请检查参数是否正确")
}
privateKey, err := acme.GetPrivateKey(client.User.GetPrivateKey(), acme.KeyType(user.KeyType))
privateKey, err := acme.EncodePrivateKey(client.Account.PrivateKey)
if err != nil {
return errors.New("获取私钥失败")
}
@@ -183,13 +185,8 @@ func (s *CertImpl) CertStore(request requests.CertStore) error {
cert.Domains = request.Domains
cert.AutoRenew = request.AutoRenew
cert.UserID = request.UserID
if request.DNSID != nil {
cert.DNSID = request.DNSID
}
if request.WebsiteID != nil {
cert.WebsiteID = request.WebsiteID
}
cert.DNSID = request.DNSID
cert.WebsiteID = request.WebsiteID
return facades.Orm().Query().Create(&cert)
}
@@ -206,13 +203,8 @@ func (s *CertImpl) CertUpdate(request requests.CertUpdate) error {
cert.Domains = request.Domains
cert.AutoRenew = request.AutoRenew
cert.UserID = request.UserID
if request.DNSID != nil {
cert.DNSID = request.DNSID
}
if request.WebsiteID != nil {
cert.WebsiteID = request.WebsiteID
}
cert.DNSID = request.DNSID
cert.WebsiteID = request.WebsiteID
return facades.Orm().Query().Save(&cert)
}
@@ -238,67 +230,55 @@ func (s *CertImpl) CertDestroy(ID uint) error {
}
// ObtainAuto 自动签发证书
func (s *CertImpl) ObtainAuto(ID uint) (certificate.Resource, error) {
func (s *CertImpl) ObtainAuto(ID uint) (acme.Certificate, error) {
var cert models.Cert
err := facades.Orm().Query().With("Website").With("User").With("DNS").Where("id = ?", ID).First(&cert)
if err != nil {
return certificate.Resource{}, err
return acme.Certificate{}, err
}
var ca string
switch cert.User.CA {
case "letsencrypt":
ca = acme.CALetEncrypt
case "buypass":
ca = acme.CABuypass
case "zerossl":
ca = acme.CAZeroSSL
case "sslcom":
ca = acme.CASSLcom
case "google":
ca = acme.CAGoogle
}
client, err := acme.NewPrivateKeyClient(cert.User.Email, cert.User.PrivateKey, ca, certcrypto.KeyType(cert.User.KeyType))
client, err := s.getClient(cert)
if err != nil {
return certificate.Resource{}, err
return acme.Certificate{}, err
}
if cert.DNS != nil {
err = client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data)
client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data)
} else {
if cert.Website == nil {
return certificate.Resource{}, errors.New("该证书没有关联网站,无法自动签发")
return acme.Certificate{}, errors.New("该证书没有关联网站,无法自动签发")
} else {
err = client.UseHTTP(cert.Website.Path)
for _, domain := range cert.Domains {
if strings.Contains(domain, "*") {
return acme.Certificate{}, errors.New("通配符域名无法使用 HTTP 验证")
}
}
client.UseHTTP(cert.Website.Path)
}
}
ssl, err := client.ObtainSSL(context.Background(), cert.Domains, acme.KeyType(cert.Type))
if err != nil {
return certificate.Resource{}, err
return acme.Certificate{}, err
}
ssl, err := client.ObtainSSL(cert.Domains)
if err != nil {
return certificate.Resource{}, err
}
cert.CertURL = &ssl.CertURL
cert.Cert = string(ssl.Certificate)
cert.CertURL = &ssl.URL
cert.Cert = string(ssl.ChainPEM)
cert.Key = string(ssl.PrivateKey)
err = facades.Orm().Query().Save(&cert)
if err != nil {
return certificate.Resource{}, err
return acme.Certificate{}, err
}
if cert.Website != nil {
if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", string(ssl.Certificate), 0644); err != nil {
return certificate.Resource{}, err
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil {
return acme.Certificate{}, err
}
if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", string(ssl.PrivateKey), 0644); err != nil {
return certificate.Resource{}, err
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil {
return acme.Certificate{}, err
}
if _, err := tools.Exec("systemctl reload openresty"); err != nil {
return certificate.Resource{}, err
if err = tools.ServiceReload("openresty"); err != nil {
return acme.Certificate{}, err
}
}
@@ -306,59 +286,39 @@ func (s *CertImpl) ObtainAuto(ID uint) (certificate.Resource, error) {
}
// ObtainManual 手动签发证书
func (s *CertImpl) ObtainManual(ID uint) (certificate.Resource, error) {
func (s *CertImpl) ObtainManual(ID uint) (acme.Certificate, error) {
var cert models.Cert
err := facades.Orm().Query().With("User").Where("id = ?", ID).First(&cert)
if err != nil {
return certificate.Resource{}, err
return acme.Certificate{}, err
}
var ca string
switch cert.User.CA {
case "letsencrypt":
ca = acme.CALetEncrypt
case "buypass":
ca = acme.CABuypass
case "zerossl":
ca = acme.CAZeroSSL
case "sslcom":
ca = acme.CASSLcom
case "google":
ca = acme.CAGoogle
if s.client == nil {
return acme.Certificate{}, errors.New("请重新获取 DNS 解析记录")
}
client, err := acme.NewPrivateKeyClient(cert.User.Email, cert.User.PrivateKey, ca, certcrypto.KeyType(cert.User.KeyType))
ssl, err := s.client.ObtainSSLManual()
if err != nil {
return certificate.Resource{}, err
return acme.Certificate{}, err
}
err = client.UseManualDns()
if err != nil {
return certificate.Resource{}, err
}
ssl, err := client.ObtainSSL(cert.Domains)
if err != nil {
return certificate.Resource{}, err
}
cert.CertURL = &ssl.CertURL
cert.Cert = string(ssl.Certificate)
cert.CertURL = &ssl.URL
cert.Cert = string(ssl.ChainPEM)
cert.Key = string(ssl.PrivateKey)
err = facades.Orm().Query().Save(&cert)
if err != nil {
return certificate.Resource{}, err
return acme.Certificate{}, err
}
if cert.Website != nil {
if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", string(ssl.Certificate), 0644); err != nil {
return certificate.Resource{}, err
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil {
return acme.Certificate{}, err
}
if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", string(ssl.PrivateKey), 0644); err != nil {
return certificate.Resource{}, err
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil {
return acme.Certificate{}, err
}
if _, err := tools.Exec("systemctl reload openresty"); err != nil {
return certificate.Resource{}, err
if err = tools.ServiceReload("openresty"); err != nil {
return acme.Certificate{}, err
}
}
@@ -366,108 +326,139 @@ func (s *CertImpl) ObtainManual(ID uint) (certificate.Resource, error) {
}
// ManualDNS 获取手动 DNS 解析信息
func (s *CertImpl) ManualDNS(ID uint) (map[string]acme.Resolve, error) {
func (s *CertImpl) ManualDNS(ID uint) ([]acme.DNSRecord, error) {
var cert models.Cert
err := facades.Orm().Query().With("User").Where("id = ?", ID).First(&cert)
if err != nil {
return nil, err
}
var ca string
switch cert.User.CA {
case "letsencrypt":
ca = acme.CALetEncrypt
case "buypass":
ca = acme.CABuypass
case "zerossl":
ca = acme.CAZeroSSL
case "sslcom":
ca = acme.CASSLcom
case "google":
ca = acme.CAGoogle
}
client, err := acme.NewPrivateKeyClient(cert.User.Email, cert.User.PrivateKey, ca, certcrypto.KeyType(cert.User.KeyType))
client, err := s.getClient(cert)
if err != nil {
return nil, err
}
err = client.UseManualDns()
if err != nil {
return nil, err
}
client.UseManualDns(len(cert.Domains))
records, err := client.GetDNSRecords(context.Background(), cert.Domains, acme.KeyType(cert.Type))
return client.GetDNSResolve(cert.Domains)
// 15 分钟后清理客户端
s.client = client
time.AfterFunc(15*time.Minute, func() {
s.client = nil
})
return records, err
}
// Renew 续签证书
func (s *CertImpl) Renew(ID uint) (certificate.Resource, error) {
func (s *CertImpl) Renew(ID uint) (acme.Certificate, error) {
var cert models.Cert
err := facades.Orm().Query().With("Website").With("User").With("DNS").Where("id = ?", ID).First(&cert)
if err != nil {
return certificate.Resource{}, err
return acme.Certificate{}, err
}
var ca string
switch cert.User.CA {
case "letsencrypt":
ca = acme.CALetEncrypt
case "buypass":
ca = acme.CABuypass
case "zerossl":
ca = acme.CAZeroSSL
case "sslcom":
ca = acme.CASSLcom
case "google":
ca = acme.CAGoogle
}
client, err := acme.NewPrivateKeyClient(cert.User.Email, cert.User.PrivateKey, ca, certcrypto.KeyType(cert.User.KeyType))
client, err := s.getClient(cert)
if err != nil {
return certificate.Resource{}, err
return acme.Certificate{}, err
}
if cert.CertURL == nil {
return certificate.Resource{}, errors.New("该证书没有签发成功,无法续签")
return acme.Certificate{}, errors.New("该证书没有签发成功,无法续签")
}
if cert.DNS != nil {
err = client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data)
client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data)
} else {
if cert.Website == nil {
return certificate.Resource{}, errors.New("该证书没有关联网站,无法续签,可以尝试手动签发")
return acme.Certificate{}, errors.New("该证书没有关联网站,无法续签,可以尝试手动签发")
} else {
err = client.UseHTTP(cert.Website.Path)
for _, domain := range cert.Domains {
if strings.Contains(domain, "*") {
return acme.Certificate{}, errors.New("通配符域名无法使用 HTTP 验证")
}
}
client.UseHTTP(cert.Website.Path)
}
}
ssl, err := client.RenewSSL(context.Background(), *cert.CertURL, cert.Domains, acme.KeyType(cert.Type))
if err != nil {
return certificate.Resource{}, err
return acme.Certificate{}, err
}
ssl, err := client.RenewSSL(*cert.CertURL)
if err != nil {
return certificate.Resource{}, err
}
cert.CertURL = &ssl.CertURL
cert.Cert = string(ssl.Certificate)
cert.CertURL = &ssl.URL
cert.Cert = string(ssl.ChainPEM)
cert.Key = string(ssl.PrivateKey)
err = facades.Orm().Query().Save(&cert)
if err != nil {
return certificate.Resource{}, err
return acme.Certificate{}, err
}
if cert.Website != nil {
if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", string(ssl.Certificate), 0644); err != nil {
return certificate.Resource{}, err
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil {
return acme.Certificate{}, err
}
if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", string(ssl.PrivateKey), 0644); err != nil {
return certificate.Resource{}, err
if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil {
return acme.Certificate{}, err
}
if _, err := tools.Exec("systemctl reload openresty"); err != nil {
return certificate.Resource{}, err
if err = tools.ServiceReload("openresty"); err != nil {
return acme.Certificate{}, err
}
}
return ssl, nil
}
// Deploy 部署证书
func (s *CertImpl) Deploy(ID, WebsiteID uint) error {
var cert models.Cert
err := facades.Orm().Query().Where("id = ?", ID).First(&cert)
if err != nil {
return err
}
if cert.Cert == "" || cert.Key == "" {
return errors.New("该证书没有签发成功,无法部署")
}
website := models.Website{}
err = facades.Orm().Query().Where("id = ?", WebsiteID).First(&website)
if err != nil {
return err
}
if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".pem", cert.Cert, 0644); err != nil {
return err
}
if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".key", cert.Key, 0644); err != nil {
return err
}
if err = tools.ServiceReload("openresty"); err != nil {
return err
}
return nil
}
func (s *CertImpl) getClient(cert models.Cert) (*acme.Client, error) {
var ca string
var eab *acme.EAB
switch cert.User.CA {
case "letsencrypt":
ca = acme.CALetsEncrypt
case "buypass":
ca = acme.CABuypass
case "zerossl":
ca = acme.CAZeroSSL
eab = &acme.EAB{KeyID: *cert.User.Kid, MACKey: *cert.User.HmacEncoded}
case "sslcom":
ca = acme.CASSLcom
eab = &acme.EAB{KeyID: *cert.User.Kid, MACKey: *cert.User.HmacEncoded}
case "google":
ca = acme.CAGoogle
eab = &acme.EAB{KeyID: *cert.User.Kid, MACKey: *cert.User.HmacEncoded}
}
return acme.NewPrivateKeyAccount(cert.User.Email, cert.User.PrivateKey, ca, eab)
}
+4 -4
View File
@@ -245,14 +245,14 @@ func (r *Container) NetworkPrune() error {
// ImageList 列出镜像
func (r *Container) ImageList() ([]image.Summary, error) {
return r.client.ImageList(context.Background(), types.ImageListOptions{
return r.client.ImageList(context.Background(), image.ListOptions{
All: true,
})
}
// ImageExist 判断镜像是否存在
func (r *Container) ImageExist(id string) (bool, error) {
var options types.ImageListOptions
var options image.ListOptions
options.Filters = filters.NewArgs(filters.Arg("reference", id))
images, err := r.client.ImageList(context.Background(), options)
if err != nil {
@@ -264,7 +264,7 @@ func (r *Container) ImageExist(id string) (bool, error) {
// ImagePull 拉取镜像
func (r *Container) ImagePull(config requests.ImagePull) error {
options := types.ImagePullOptions{}
options := image.PullOptions{}
if config.Auth {
authConfig := registry.AuthConfig{
Username: config.Username,
@@ -290,7 +290,7 @@ func (r *Container) ImagePull(config requests.ImagePull) error {
// ImageRemove 删除镜像
func (r *Container) ImageRemove(id string) error {
_, err := r.client.ImageRemove(context.Background(), id, types.ImageRemoveOptions{
_, err := r.client.ImageRemove(context.Background(), id, image.RemoveOptions{
Force: true,
PruneChildren: true,
})
+3 -3
View File
@@ -8,8 +8,8 @@ import (
"strings"
"time"
"github.com/go-resty/resty/v2"
"github.com/goravel/framework/facades"
"github.com/imroc/req/v3"
"github.com/spf13/cast"
"panel/app/models"
@@ -73,9 +73,9 @@ func (r *PHPImpl) SaveFPMConfig(config string) error {
}
func (r *PHPImpl) Load() ([]types.NV, error) {
client := req.C().SetTimeout(10 * time.Second)
client := resty.New().SetTimeout(10 * time.Second)
resp, err := client.R().Get("http://127.0.0.1/phpfpm_status/" + r.version)
if err != nil || !resp.IsSuccessState() {
if err != nil || !resp.IsSuccess() {
return nil, errors.New("获取 PHP-" + r.version + " 运行状态失败")
}
+145 -1
View File
@@ -2,6 +2,8 @@
package services
import (
"errors"
"github.com/goravel/framework/facades"
"panel/app/models"
@@ -9,10 +11,13 @@ import (
)
type PluginImpl struct {
task internal.Task
}
func NewPluginImpl() *PluginImpl {
return &PluginImpl{}
return &PluginImpl{
task: NewTaskImpl(),
}
}
// AllInstalled 获取已安装的所有插件
@@ -31,6 +36,7 @@ func (r *PluginImpl) All() []internal.PanelPlugin {
internal.PluginOpenResty,
internal.PluginMySQL57,
internal.PluginMySQL80,
internal.PluginMySQL84,
internal.PluginPostgreSQL15,
internal.PluginPostgreSQL16,
internal.PluginPHP74,
@@ -71,3 +77,141 @@ func (r *PluginImpl) GetInstalledBySlug(slug string) models.Plugin {
return plugin
}
// Install 安装插件
func (r *PluginImpl) Install(slug string) error {
plugin := r.GetBySlug(slug)
installedPlugin := r.GetInstalledBySlug(slug)
installedPlugins, err := r.AllInstalled()
if err != nil {
return err
}
if installedPlugin.ID != 0 {
return errors.New("插件已安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return errors.New("插件 " + slug + " 需要依赖 " + require + " 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return errors.New("插件 " + slug + " 不兼容 " + exclude + " 插件")
}
}
var task models.Task
task.Name = "安装插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Install + ` >> '/tmp/` + plugin.Slug + `.log' 2>&1`
task.Log = "/tmp/" + plugin.Slug + ".log"
if err = facades.Orm().Query().Create(&task); err != nil {
return errors.New("创建任务失败")
}
r.task.Process(task.ID)
return nil
}
// Uninstall 卸载插件
func (r *PluginImpl) Uninstall(slug string) error {
plugin := r.GetBySlug(slug)
installedPlugin := r.GetInstalledBySlug(slug)
installedPlugins, err := r.AllInstalled()
if err != nil {
return err
}
if installedPlugin.ID == 0 {
return errors.New("插件未安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return errors.New("插件 " + slug + " 需要依赖 " + require + " 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return errors.New("插件 " + slug + " 不兼容 " + exclude + " 插件")
}
}
var task models.Task
task.Name = "卸载插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Uninstall + " >> /tmp/" + plugin.Slug + ".log 2>&1"
task.Log = "/tmp/" + plugin.Slug + ".log"
if err = facades.Orm().Query().Create(&task); err != nil {
return errors.New("创建任务失败")
}
r.task.Process(task.ID)
return nil
}
// Update 更新插件
func (r *PluginImpl) Update(slug string) error {
plugin := r.GetBySlug(slug)
installedPlugin := r.GetInstalledBySlug(slug)
installedPlugins, err := r.AllInstalled()
if err != nil {
return err
}
if installedPlugin.ID == 0 {
return errors.New("插件未安装")
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
return errors.New("插件 " + slug + " 需要依赖 " + require + " 插件")
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
return errors.New("插件 " + slug + " 不兼容 " + exclude + " 插件")
}
}
var task models.Task
task.Name = "更新插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Update + " >> /tmp/" + plugin.Slug + ".log 2>&1"
task.Log = "/tmp/" + plugin.Slug + ".log"
if err = facades.Orm().Query().Create(&task); err != nil {
return errors.New("创建任务失败")
}
r.task.Process(task.ID)
return nil
}
+12 -2
View File
@@ -393,10 +393,10 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
// SSL
ssl := config.Ssl
website.Ssl = ssl
if err := tools.Write("/www/server/vhost/ssl/"+website.Name+".pem", config.SslCertificate, 0644); err != nil {
if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".pem", config.SslCertificate, 0644); err != nil {
return err
}
if err := tools.Write("/www/server/vhost/ssl/"+website.Name+".key", config.SslCertificateKey, 0644); err != nil {
if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".key", config.SslCertificateKey, 0644); err != nil {
return err
}
if ssl {
@@ -620,3 +620,13 @@ func (r *WebsiteImpl) GetConfigByName(name string) (internal.WebsiteSetting, err
return r.GetConfig(website.ID)
}
// GetIDByName 根据网站名称获取网站ID
func (r *WebsiteImpl) GetIDByName(name string) (uint, error) {
var website models.Website
if err := facades.Orm().Query().Where("name", name).First(&website); err != nil {
return 0, err
}
return website.ID, nil
}
+1
View File
@@ -12,6 +12,7 @@ type Website interface {
Delete(id uint) error
GetConfig(id uint) (WebsiteSetting, error)
GetConfigByName(name string) (WebsiteSetting, error)
GetIDByName(name string) (uint, error)
}
type PanelWebsite struct {
+205
View File
@@ -0,0 +1,205 @@
{
"auth": {
"token": {
"expired": "login has expired",
"missing": "not logged in"
}
},
"commands": {
"panel:cert-renew": {
"description": "[Panel] Certificate renewal"
},
"panel:monitoring": {
"description": "[Panel] System Monitoring",
"fail": "[Panel] System monitoring failed to save"
},
"panel": {
"description": "[Panel] Command line",
"forDeveloper": "Please use the following commands under the guidance of the developer",
"use": "Please use the following commands",
"tool": "command line tool",
"portFail": "failed to get panel port",
"port": "Panel port",
"entrance": "Panel entrance",
"update": {
"description": "update/fix panel to latest version",
"taskCheck": "There is currently a task being executed and updates are prohibited.",
"versionFail": "failed to get latest version",
"fail": "update failed",
"success": "update completed"
},
"getInfo": {
"description": "reinitialize panel account information",
"adminGetFail": "failed to get administrator information",
"adminSaveFail": "failed to save administrator information",
"passwordGenerationFail": "failed to generate password",
"username": "Username",
"password": "Password",
"address": "Panel address"
},
"getPort": {
"description": "get the panel access port"
},
"getEntrance": {
"description": "get panel access"
},
"deleteEntrance": {
"description": "delete panel access",
"fail": "failed to delete panel entrance",
"success": "panel entrance deleted successfully"
},
"cleanTask": {
"description": "clean up running and waiting tasks in the panel [used when tasks are stuck]",
"fail": "failed to clean up tasks",
"success": "tasks cleaned up successfully"
},
"backup": {
"description": "back up website/MySQL database/PostgreSQL database to the specified directory and retain the specified amount",
"paramFail": "backup type, path, name and keep amount are required",
"start": "start backup",
"backupDirFail": "failed to create backup directory",
"targetSite": "target website",
"siteNotExist": "website does not exist",
"backupFail": "backup failed",
"backupSuccess": "backup successful",
"mysqlBackupFail": "MySQL database backup failed",
"targetMysql": "target MySQL database",
"startExport": "start exporting",
"exportFail": "export failed",
"exportSuccess": "export successful",
"startCompress": "start compressing",
"compressFail": "compression failed",
"compressSuccess": "compression successful",
"startMove": "start moving",
"moveFail": "move failed",
"moveSuccess": "move successful",
"databaseGetFail": "failed to get database",
"databaseNotExist": "database does not exist",
"targetPostgres": "target PostgreSQL database",
"cleanBackup" : "clean backup",
"cleanupFail": "cleanup failed",
"cleanupSuccess": "cleanup successful",
"deleteFail": "failed to delete",
"success": "backup completed"
},
"cutoff": {
"description": "cut website logs and keep specified amount",
"paramFail": "website domain name and keep amount are required",
"start": "start cutting",
"targetSite": "target website",
"siteNotExist": "website does not exist",
"logNotExist": "log file does not exist",
"backupFail": "backup failed",
"clearFail": "clearing failed",
"cleanupFail": "cleanup failed",
"clearLog": "clear log",
"cutSuccess": "cutting successful",
"cleanupSuccess": "cleanup successful",
"end": "cutting completed"
},
"installPlugin": {
"description": "install plugin",
"paramFail": "plugin slug is required",
"success": "task has been submitted"
},
"uninstallPlugin": {
"description": "uninstall plugin",
"paramFail": "plugin slug is required",
"success": "task has been submitted"
},
"updatePlugin": {
"description": "update plugin",
"paramFail": "plugin slug is required",
"success": "task has been submitted"
},
"addSite": {
"description": "add website [domain name and port separated by commas]",
"paramFail": "name, domain, port and path are required",
"siteExist": "website already exists",
"success": "website added successfully"
},
"removeSite": {
"description": "remove website",
"paramFail": "name is required",
"siteNotExist": "website does not exist",
"success": "website deleted successfully"
},
"init": {
"description": "initialize the panel",
"exist": "panel has been initialized",
"success": "initialization successful",
"adminFound": "failed to create administrator",
"fail": "initialization failed"
},
"writePlugin": {
"description": "write plugin installation status",
"paramFail": "plugin slug and version are required",
"fail": "failed to write plugin installation status",
"success": "plugin installation status written successfully"
},
"deletePlugin": {
"description": "delete plugin installation status",
"paramFail": "plugin slug is required",
"fail": "failed to remove plugin installation status",
"success": "plugin installation status removed successfully"
},
"writeMysqlPassword": {
"description": "write MySQL root password",
"paramFail": "MySQL root password is required",
"fail": "failed to write MySQL root password",
"success": "MySQL root password written successfully"
},
"writeSite": {
"description": "write website data to the panel",
"paramFail": "name and path are required",
"siteExist": "website already exists",
"pathNotExist": "website directory does not exist",
"fail": "failed to write to website",
"success": "writing to website successfully"
},
"deleteSite": {
"description": "delete panel website data",
"paramFail": "website name is required",
"fail": "failed to delete website",
"success": "website deleted successfully"
},
"getSetting": {
"description": "get panel setting data",
"paramFail": "key is required"
},
"writeSetting": {
"description": "write/update panel setting data",
"paramFail": "key and value are required",
"fail": "Writing settings failed",
"success": "settings written successfully"
},
"deleteSetting": {
"description": "delete panel setting data",
"paramFail": "key is required",
"fail": "failed to delete settings",
"success": "settings deleted successfully"
}
},
"panel:task": {
"description": "[Panel] Daily tasks"
}
},
"errors": {
"internal": "internal system error",
"plugin": {
"notExist": "plugin does not exist",
"notInstalled": "plugin :slug is not installed",
"dependent": "plugin :slug requires dependency :dependency",
"incompatible": "plugin :slug is incompatible with :exclude plugin"
}
},
"messages": {
"mistake": "mistake"
},
"status": {
"upgrade": "Panel is currently undergoing an upgrade. Please try again later.",
"maintain": "Panel is currently undergoing maintenance. Please try again later.",
"closed": "Panel is closed.",
"failed": "Panel encountered an error during operation. Please check the troubleshooting or contact support."
}
}
+205
View File
@@ -0,0 +1,205 @@
{
"auth": {
"token": {
"expired": "登录已过期",
"missing": "未登录"
}
},
"commands": {
"panel:cert-renew": {
"description": "[面板] 证书续签"
},
"panel:monitoring": {
"description": "[面板] 系统监控",
"fail": "[面板] 系统监控保存失败"
},
"panel": {
"description": "[面板] 命令行",
"forDeveloper": "以下命令请在开发者指导下使用",
"use": "请使用以下命令",
"tool": "命令行工具",
"port": "面板端口",
"portFail": "获取面板端口失败",
"entrance": "面板入口",
"update": {
"description": "更新 / 修复面板到最新版本",
"taskCheck": "当前有任务正在执行,禁止更新",
"versionFail": "获取最新版本失败",
"fail": "更新失败",
"success": "更新成功"
},
"getInfo": {
"description": "重新初始化面板账号信息",
"adminGetFail": "获取管理员信息失败",
"adminSaveFail": "保存管理员信息失败",
"passwordGenerationFail": "生成密码失败",
"username": "用户名",
"password": "密码",
"address": "面板地址"
},
"getPort": {
"description": "获取面板访问端口"
},
"getEntrance": {
"description": "获取面板访问入口"
},
"deleteEntrance": {
"description": "删除面板访问入口",
"fail": "删除面板入口失败",
"success": "删除面板入口成功"
},
"cleanTask": {
"description": "清理面板运行中和等待中的任务[任务卡住时使用]",
"fail": "清理任务失败",
"success": "清理任务成功"
},
"backup": {
"description": "备份网站 / MySQL数据库 / PostgreSQL数据库到指定目录并保留指定数量",
"paramFail": "参数错误",
"start": "开始备份",
"backupDirFail": "创建备份目录失败",
"targetSite": "目标网站",
"siteNotExist": "网站不存在",
"backupFail": "备份失败",
"backupSuccess": "备份成功",
"mysqlBackupFail": "备份MySQL数据库失败",
"targetMysql": "目标MySQL数据库",
"startExport": "开始导出",
"exportFail": "导出失败",
"exportSuccess": "导出成功",
"startCompress": "开始压缩",
"compressFail": "压缩失败",
"compressSuccess": "压缩成功",
"startMove": "开始移动",
"moveFail": "移动失败",
"moveSuccess": "移动成功",
"databaseGetFail": "获取数据库失败",
"databaseNotExist": "数据库不存在",
"targetPostgres": "目标PostgreSQL数据库",
"cleanBackup": "清理备份",
"cleanupFail": "清理失败",
"cleanupSuccess": "清理完成",
"deleteFail": "删除失败",
"success": "备份完成"
},
"cutoff": {
"description": "切割网站日志并保留指定数量",
"paramFail": "参数错误",
"start": "开始切割",
"targetSite": "目标网站",
"siteNotExist": "网站不存在",
"logNotExist": "日志文件不存在",
"backupFail": "备份失败",
"clearFail": "清空失败",
"cleanupFail": "清理失败",
"clearLog": "清理日志",
"cutSuccess": "切割成功",
"cleanupSuccess": "清理完成",
"end": "切割完成"
},
"installPlugin": {
"description": "安装插件",
"paramFail": "参数错误",
"success": "任务已提交"
},
"uninstallPlugin": {
"description": "卸载插件",
"paramFail": "参数错误",
"success": "任务已提交"
},
"updatePlugin": {
"description": "更新插件",
"paramFail": "参数错误",
"success": "任务已提交"
},
"addSite": {
"description": "添加网站[域名和端口用英文逗号分隔]",
"paramFail": "参数错误",
"siteExist": "网站名已存在",
"success": "网站添加成功"
},
"removeSite": {
"description": "删除网站",
"paramFail": "参数错误",
"siteNotExist": "网站名不存在",
"success": "网站删除成功"
},
"init": {
"description": "初始化面板",
"exist": "面板已初始化",
"success": "初始化成功",
"adminFail": "创建管理员失败",
"fail": "初始化失败"
},
"writePlugin": {
"description": "写入插件安装状态",
"paramFail": "参数错误",
"fail": "写入插件安装状态失败",
"success": "写入插件安装状态成功"
},
"deletePlugin": {
"description": "移除插件安装状态",
"paramFail": "参数错误",
"fail": "移除插件安装状态失败",
"success": "移除插件安装状态成功"
},
"writeMysqlPassword": {
"description": "写入MySQL root密码",
"paramFail": "参数错误",
"fail": "写入MySQL root密码失败",
"success": "写入MySQL root密码成功"
},
"writeSite": {
"description": "写入网站数据到面板",
"paramFail": "参数错误",
"siteExist": "网站已存在",
"pathNotExist": "网站目录不存在",
"fail": "写入网站失败",
"success": "写入网站成功"
},
"deleteSite": {
"description": "删除面板网站数据",
"paramFail": "参数错误",
"fail": "删除网站失败",
"success": "删除网站成功"
},
"getSetting": {
"description": "获取面板设置数据",
"paramFail": "参数错误"
},
"writeSetting": {
"description": "写入 / 更新面板设置数据",
"paramFail": "参数错误",
"fail": "写入设置失败",
"success": "写入设置成功"
},
"deleteSetting": {
"description": "删除面板设置数据",
"paramFail": "参数错误",
"fail": "删除设置失败",
"success": "删除设置成功"
}
},
"panel:task": {
"description": "[面板] 每日任务"
}
},
"errors": {
"internal": "系统内部错误",
"plugin": {
"notExist": "插件不存在",
"notInstalled": "插件 :slug 未安装",
"dependent": "插件 :slug 需要依赖 :dependency 插件",
"incompatible": "插件 :slug 不兼容 :exclude 插件"
}
},
"messages": {
"mistake": "错误"
},
"status": {
"upgrade": "面板升级中,请稍后",
"maintain": "面板正在运行维护,请稍后",
"closed": "面板已关闭",
"failed": "面板运行出错,请检查排除或联系支持"
}
}
+1
View File
@@ -4,5 +4,6 @@ APP_DEBUG=false
APP_PORT=8888
APP_ENTRANCE=/
APP_SSL=false
APP_LOCALE=zh_CN
JWT_SECRET=
+155 -137
View File
@@ -1,177 +1,195 @@
package acme
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"net/http"
"strings"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v2/acme"
"go.uber.org/zap"
)
const (
CALetEncrypt = "https://acme-v02.api.letsencrypt.org/directory"
CAZeroSSL = "https://acme.zerossl.com/v2/DV90"
CAGoogle = "https://dv.acme-v02.api.pki.goog/directory"
CABuypass = "https://api.buypass.com/acme/directory"
CASSLcom = "https://acme.ssl.com/sslcom-dv-rsa"
CALetsEncryptStaging = "https://acme-staging-v02.api.letsencrypt.org/directory"
CALetsEncrypt = "https://acme-v02.api.letsencrypt.org/directory"
CAZeroSSL = "https://acme.zerossl.com/v2/DV90"
CAGoogle = "https://dv.acme-v02.api.pki.goog/directory"
CABuypass = "https://api.buypass.com/acme/directory"
CASSLcom = "https://acme.ssl.com/sslcom-dv-rsa"
)
type KeyType = certcrypto.KeyType
type KeyType string
const (
KeyEC256 = certcrypto.EC256
KeyEC384 = certcrypto.EC384
KeyRSA2048 = certcrypto.RSA2048
KeyRSA3072 = certcrypto.RSA3072
KeyRSA4096 = certcrypto.RSA4096
KeyEC256 = KeyType("P256")
KeyEC384 = KeyType("P384")
KeyRSA2048 = KeyType("2048")
KeyRSA3072 = KeyType("3072")
KeyRSA4096 = KeyType("4096")
)
type domainError struct {
Domain string
Error error
}
type EAB = acme.EAB
type User struct {
Email string
Registration *registration.Resource
Key crypto.PrivateKey
}
func NewRegisterAccount(ctx context.Context, email, CA string, eab *EAB, keyType KeyType) (*Client, error) {
client, err := getClient(CA)
if err != nil {
return nil, err
}
func (u *User) GetEmail() string {
return u.Email
}
func (u *User) GetRegistration() *registration.Resource {
return u.Registration
}
func (u *User) GetPrivateKey() crypto.PrivateKey {
return u.Key
}
func GetPrivateKey(priKey crypto.PrivateKey, keyType KeyType) ([]byte, error) {
var marshal []byte
var block *pem.Block
var err error
switch keyType {
case KeyEC256, KeyEC384:
key := priKey.(*ecdsa.PrivateKey)
marshal, err = x509.MarshalECPrivateKey(key)
accountPrivateKey, err := generatePrivateKey(keyType)
if err != nil {
return nil, err
}
account := acme.Account{
Contact: []string{"mailto:" + email},
TermsOfServiceAgreed: true,
PrivateKey: accountPrivateKey,
}
if eab != nil {
err = account.SetExternalAccountBinding(ctx, client.Client, *eab)
if err != nil {
return nil, err
}
block = &pem.Block{
Type: "PRIVATE KEY",
Bytes: marshal,
}
case KeyRSA2048, KeyRSA3072, KeyRSA4096:
key := priKey.(*rsa.PrivateKey)
marshal = x509.MarshalPKCS1PrivateKey(key)
block = &pem.Block{
Type: "privateKey",
Bytes: marshal,
}
account, err = client.NewAccount(ctx, account)
if err != nil {
return nil, err
}
return &Client{Account: account, zClient: client}, nil
}
func NewPrivateKeyAccount(email string, privateKey string, CA string, eab *EAB) (*Client, error) {
client, err := getClient(CA)
if err != nil {
return nil, err
}
key, err := parsePrivateKey([]byte(privateKey))
if err != nil {
return nil, err
}
account := acme.Account{
Contact: []string{"mailto:" + email},
TermsOfServiceAgreed: true,
PrivateKey: key,
}
if eab != nil {
err = account.SetExternalAccountBinding(context.Background(), client.Client, *eab)
if err != nil {
return nil, err
}
}
return pem.EncodeToMemory(block), nil
account, err = client.GetAccount(context.Background(), account)
if err != nil {
return nil, err
}
return &Client{Account: account, zClient: client}, nil
}
func NewRegisterClient(email string, CA string, keyType certcrypto.KeyType) (*Client, error) {
privateKey, err := certcrypto.GeneratePrivateKey(keyType)
if err != nil {
return nil, err
func parsePrivateKey(key []byte) (crypto.Signer, error) {
keyBlockDER, _ := pem.Decode(key)
if keyBlockDER == nil {
return nil, errors.New("invalid PEM block")
}
user := &User{
Email: email,
Key: privateKey,
}
config := lego.NewConfig(user)
config.CADirURL = CA
config.Certificate.KeyType = keyType
client, err := lego.NewClient(config)
if err != nil {
return nil, err
}
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, err
}
user.Registration = reg
acmeClient := &Client{
User: user,
Client: client,
Config: config,
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
}
return acmeClient, nil
if parse, err := x509.ParsePKCS1PrivateKey(keyBlockDER.Bytes); err == nil {
return parse, nil
}
if parse, err := x509.ParsePKCS8PrivateKey(keyBlockDER.Bytes); err == nil {
switch parse.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
return parse.(crypto.Signer), nil
default:
return nil, fmt.Errorf("found unknown private key type in PKCS#8 wrapping: %T", key)
}
}
if parse, err := x509.ParseECPrivateKey(keyBlockDER.Bytes); err == nil {
return parse, nil
}
return nil, errors.New("解析私钥失败")
}
func NewRegisterWithExternalAccountBindingClient(email, kid, hmac, CA string, keyType certcrypto.KeyType) (*Client, error) {
privateKey, err := certcrypto.GeneratePrivateKey(keyType)
if err != nil {
return nil, err
func generatePrivateKey(keyType KeyType) (crypto.Signer, error) {
switch keyType {
case KeyEC256:
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case KeyEC384:
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case KeyRSA2048:
return rsa.GenerateKey(rand.Reader, 2048)
case KeyRSA3072:
return rsa.GenerateKey(rand.Reader, 3072)
case KeyRSA4096:
return rsa.GenerateKey(rand.Reader, 4096)
}
user := &User{
Email: email,
Key: privateKey,
}
config := lego.NewConfig(user)
config.CADirURL = CA
config.Certificate.KeyType = keyType
client, err := lego.NewClient(config)
if err != nil {
return nil, err
}
reg, err := client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{TermsOfServiceAgreed: true, Kid: kid, HmacEncoded: hmac})
if err != nil {
return nil, err
}
user.Registration = reg
acmeClient := &Client{
User: user,
Client: client,
Config: config,
}
return acmeClient, nil
return nil, errors.New("未知的密钥类型")
}
func NewPrivateKeyClient(email string, privateKey string, CA string, keyType certcrypto.KeyType) (*Client, error) {
key, err := certcrypto.ParsePEMPrivateKey([]byte(privateKey))
if err != nil {
return nil, err
func EncodePrivateKey(key crypto.Signer) ([]byte, error) {
var pemType string
var keyBytes []byte
switch key := key.(type) {
case *ecdsa.PrivateKey:
var err error
pemType = "EC"
keyBytes, err = x509.MarshalECPrivateKey(key)
if err != nil {
return nil, err
}
case *rsa.PrivateKey:
pemType = "RSA"
keyBytes = x509.MarshalPKCS1PrivateKey(key)
case ed25519.PrivateKey:
var err error
pemType = "ED25519"
keyBytes, err = x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("未知的密钥类型 %T", key)
}
user := &User{
Email: email,
Key: key,
}
config := lego.NewConfig(user)
config.CADirURL = CA
config.Certificate.KeyType = keyType
client, err := lego.NewClient(config)
if err != nil {
return nil, err
}
reg, err := client.Registration.ResolveAccountByKey()
if err != nil {
return nil, err
}
user.Registration = reg
acmeClient := &Client{
User: user,
Client: client,
Config: config,
}
return acmeClient, nil
pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes}
return pem.EncodeToMemory(&pemKey), nil
}
func getClient(CA string) (acmez.Client, error) {
logger, err := zap.NewProduction()
if err != nil {
return acmez.Client{}, err
}
client := acmez.Client{
Client: &acme.Client{
Directory: CA,
HTTPClient: http.DefaultClient,
Logger: logger,
},
}
return client, nil
}
+104 -157
View File
@@ -1,192 +1,139 @@
package acme
import (
"time"
"context"
"sort"
"github.com/go-acme/lego/v4/acme"
"github.com/go-acme/lego/v4/acme/api"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/providers/dns/alidns"
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
"github.com/go-acme/lego/v4/providers/dns/dnspod"
"github.com/go-acme/lego/v4/providers/http/webroot"
"github.com/libdns/libdns"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v2/acme"
)
type Client struct {
Config *lego.Config
Client *lego.Client
User *User
type Certificate struct {
PrivateKey []byte
acme.Certificate
}
type DnsType string
const (
DnsPod DnsType = "dnspod"
AliYun DnsType = "aliyun"
CloudFlare DnsType = "cloudflare"
)
type DNSParam struct {
ID string `form:"id" json:"id"`
Token string `form:"token" json:"token"`
AccessKey string `form:"access_key" json:"access_key"`
SecretKey string `form:"secret_key" json:"secret_key"`
Email string `form:"email" json:"email"`
APIkey string `form:"api_key" json:"api_key"`
type Client struct {
Account acme.Account
zClient acmez.Client
// 手动 DNS 所需的信号通道
manualDNSSolver
}
// UseDns 使用 DNS 接口验证
func (c *Client) UseDns(dnsType DnsType, param DNSParam) error {
var p challenge.Provider
var err error
if dnsType == DnsPod {
dnsPodConfig := dnspod.NewDefaultConfig()
dnsPodConfig.LoginToken = param.ID + "," + param.Token
p, err = dnspod.NewDNSProviderConfig(dnsPodConfig)
if err != nil {
return err
}
func (c *Client) UseDns(dnsType DnsType, param DNSParam) {
c.zClient.ChallengeSolvers = map[string]acmez.Solver{
acme.ChallengeTypeDNS01: dnsSolver{
dns: dnsType,
param: param,
records: &[]libdns.Record{},
},
}
if dnsType == AliYun {
aliyunConfig := alidns.NewDefaultConfig()
aliyunConfig.SecretKey = param.SecretKey
aliyunConfig.APIKey = param.AccessKey
p, err = alidns.NewDNSProviderConfig(aliyunConfig)
if err != nil {
return err
}
}
if dnsType == CloudFlare {
cloudflareConfig := cloudflare.NewDefaultConfig()
cloudflareConfig.AuthEmail = param.Email
cloudflareConfig.AuthToken = param.APIkey
p, err = cloudflare.NewDNSProviderConfig(cloudflareConfig)
if err != nil {
return err
}
}
return c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(3*time.Minute))
}
// UseManualDns 使用手动 DNS 验证
func (c *Client) UseManualDns(checkDns ...bool) error {
p := &manualDnsProvider{}
var err error
if len(checkDns) > 0 && !checkDns[0] {
err = c.Client.Challenge.SetDNS01Provider(p, dns01.DisableCompletePropagationRequirement())
} else {
err = c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(3*time.Minute))
func (c *Client) UseManualDns(total int, check ...bool) {
c.controlChan = make(chan struct{})
c.dataChan = make(chan any)
c.zClient.ChallengeSolvers = map[string]acmez.Solver{
acme.ChallengeTypeDNS01: manualDNSSolver{
check: len(check) > 0 && check[0],
controlChan: c.controlChan,
dataChan: c.dataChan,
records: &[]DNSRecord{},
},
}
return err
}
// UseHTTP 使用 HTTP 验证
func (c *Client) UseHTTP(path string) error {
httpProvider, err := webroot.NewHTTPProvider(path)
if err != nil {
return err
func (c *Client) UseHTTP(path string) {
c.zClient.ChallengeSolvers = map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: httpSolver{
path: path,
},
}
err = c.Client.Challenge.SetHTTP01Provider(httpProvider)
if err != nil {
return err
}
return nil
}
// ObtainSSL 签发 SSL 证书
func (c *Client) ObtainSSL(domains []string) (certificate.Resource, error) {
request := certificate.ObtainRequest{
Domains: domains,
Bundle: true,
MustStaple: false,
}
certificates, err := c.Client.Certificate.Obtain(request)
func (c *Client) ObtainSSL(ctx context.Context, domains []string, keyType KeyType) (Certificate, error) {
certPrivateKey, err := generatePrivateKey(keyType)
if err != nil {
return certificate.Resource{}, err
return Certificate{}, err
}
pemPrivateKey, err := EncodePrivateKey(certPrivateKey)
if err != nil {
return Certificate{}, err
}
return *certificates, nil
certs, err := c.zClient.ObtainCertificateForSANs(ctx, c.Account, certPrivateKey, domains)
if err != nil {
return Certificate{}, err
}
cert := c.selectPreferredChain(certs)
return Certificate{PrivateKey: pemPrivateKey, Certificate: cert}, nil
}
// ObtainSSLManual 手动验证 SSL 证书
func (c *Client) ObtainSSLManual() (Certificate, error) {
// 发送信号,开始验证
c.controlChan <- struct{}{}
// 等待验证完成
data := <-c.dataChan
if err, ok := data.(error); ok {
return Certificate{}, err
}
return data.(Certificate), nil
}
// RenewSSL 续签 SSL 证书
func (c *Client) RenewSSL(certUrl string) (certificate.Resource, error) {
certificates, err := c.Client.Certificate.Get(certUrl, true)
func (c *Client) RenewSSL(ctx context.Context, certUrl string, domains []string, keyType KeyType) (Certificate, error) {
_, err := c.zClient.GetCertificateChain(ctx, c.Account, certUrl)
if err != nil {
return certificate.Resource{}, err
return Certificate{}, err
}
certificates, err = c.Client.Certificate.RenewWithOptions(*certificates, &certificate.RenewOptions{
Bundle: true,
MustStaple: false,
return c.ObtainSSL(ctx, domains, keyType)
}
// GetDNSRecords 获取 DNS 解析(手动设置)
func (c *Client) GetDNSRecords(ctx context.Context, domains []string, keyType KeyType) ([]DNSRecord, error) {
go func(ctx context.Context, domains []string, keyType KeyType) {
certs, err := c.ObtainSSL(ctx, domains, keyType)
// 将证书和错误信息发送到 dataChan
if err != nil {
c.dataChan <- err
return
}
c.dataChan <- certs
}(ctx, domains, keyType)
// 这里要少一次循环,因为需要卡住最后一次的 dataChan,等待手动 DNS 验证完成
for i := 1; i < len(domains); i++ {
<-c.dataChan
c.controlChan <- struct{}{}
}
// 因为上面少了一次循环,所以这里接收到的即为完整的 DNS 记录切片
data := <-c.dataChan
if err, ok := data.(error); ok {
return nil, err
}
return data.([]DNSRecord), nil
}
func (c *Client) selectPreferredChain(certChains []acme.Certificate) acme.Certificate {
if len(certChains) == 1 {
return certChains[0]
}
sort.Slice(certChains, func(i, j int) bool {
return len(certChains[i].ChainPEM) < len(certChains[j].ChainPEM)
})
if err != nil {
return certificate.Resource{}, err
}
return *certificates, nil
}
// GetDNSResolve 获取 DNS 解析(手动设置)
func (c *Client) GetDNSResolve(domains []string) (map[string]Resolve, error) {
core, err := api.New(c.Config.HTTPClient, c.Config.UserAgent, c.Config.CADirURL, c.User.Registration.URI, c.User.Key)
if err != nil {
return nil, err
}
order, err := core.Orders.New(domains)
if err != nil {
return nil, err
}
resolves := make(map[string]Resolve)
resChan, errChan := make(chan acme.Authorization), make(chan domainError)
for _, authzURL := range order.Authorizations {
go func(authzURL string) {
authz, err := core.Authorizations.Get(authzURL)
if err != nil {
errChan <- domainError{Domain: authz.Identifier.Value, Error: err}
return
}
resChan <- authz
}(authzURL)
}
var responses []acme.Authorization
for i := 0; i < len(order.Authorizations); i++ {
select {
case res := <-resChan:
responses = append(responses, res)
case err := <-errChan:
resolves[err.Domain] = Resolve{Err: err.Error.Error()}
}
}
close(resChan)
close(errChan)
for _, auth := range responses {
domain := challenge.GetTargetedDomain(auth)
acmeChallenge, err := challenge.FindChallenge(challenge.DNS01, auth)
if err != nil {
resolves[domain] = Resolve{Err: err.Error()}
continue
}
keyAuth, err := core.GetKeyAuthorization(acmeChallenge.Token)
if err != nil {
resolves[domain] = Resolve{Err: err.Error()}
continue
}
challengeInfo := dns01.GetChallengeInfo(domain, keyAuth)
resolves[domain] = Resolve{
Key: challengeInfo.FQDN,
Value: challengeInfo.Value,
}
}
return resolves, nil
return certChains[0]
}
+13 -11
View File
@@ -1,7 +1,7 @@
package acme
import (
"fmt"
"context"
"testing"
"github.com/stretchr/testify/suite"
@@ -16,24 +16,26 @@ func TestClientTestSuite(t *testing.T) {
}
func (s *ClientTestSuite) TestObtainSSL() {
client, err := NewRegisterClient("ci@haozi.net", "https://acme-staging-v02.api.letsencrypt.org/directory", KeyEC256)
ctx := context.Background()
client, err := NewRegisterAccount(ctx, "ci@haozi.net", CALetsEncryptStaging, nil, KeyEC256)
s.Nil(err)
err = client.UseDns(DnsPod, DNSParam{
ID: "xxx",
Token: "xxx",
client.UseDns(DnsPod, DNSParam{
ID: "123456",
Token: "654321",
})
s.Nil(err)
err = client.UseManualDns(false)
s.Nil(err)
/*client.UseManualDns(2)
resolves, err := client.GetDNSResolve([]string{"haozi.dev"})
resolves, err := client.GetDNSRecords(ctx, []string{"*.haozi.net", "haozi.net"}, KeyEC256)
debug.Dump(resolves)
s.Nil(err)
s.NotNil(resolves)
ssl, err := client.ObtainSSL([]string{"haozi.dev"})
fmt.Println(err.Error())
time.Sleep(2 * time.Minute)
ssl, err := client.ObtainSSLManual()*/
ssl, err := client.ObtainSSL(ctx, []string{"*.haozi.net", "haozi.net"}, KeyEC256)
s.Error(err)
s.NotNil(ssl)
}
-19
View File
@@ -1,19 +0,0 @@
package acme
type Resolve struct {
Key string `json:"key"`
Value string `json:"value"`
Err string `json:"err"`
}
type manualDnsProvider struct {
Resolve *Resolve
}
func (p *manualDnsProvider) Present(domain, token, keyAuth string) error {
return nil
}
func (p *manualDnsProvider) CleanUp(domain, token, keyAuth string) error {
return nil
}
+192
View File
@@ -0,0 +1,192 @@
package acme
import (
"context"
"fmt"
"os"
"path/filepath"
"time"
"github.com/libdns/alidns"
"github.com/libdns/cloudflare"
"github.com/libdns/dnspod"
"github.com/libdns/libdns"
"github.com/mholt/acmez/v2/acme"
"golang.org/x/net/publicsuffix"
)
type httpSolver struct {
path string
}
func (s httpSolver) Present(_ context.Context, challenge acme.Challenge) error {
var err error
if s.path == "" {
return nil
}
challengeFilePath := filepath.Join(s.path, challenge.HTTP01ResourcePath())
err = os.MkdirAll(filepath.Dir(challengeFilePath), 0o755)
if err != nil {
return fmt.Errorf("无法在网站目录创建 HTTP 挑战所需的目录: %w", err)
}
err = os.WriteFile(challengeFilePath, []byte(challenge.KeyAuthorization), 0o644)
if err != nil {
return fmt.Errorf("无法在网站目录创建 HTTP 挑战所需的文件: %w", err)
}
return nil
}
// CleanUp cleans up the HTTP server if it is the last one to finish.
func (s httpSolver) CleanUp(_ context.Context, challenge acme.Challenge) error {
if s.path == "" {
return nil
}
err := os.Remove(filepath.Join(s.path, challenge.HTTP01ResourcePath()))
if err != nil {
return fmt.Errorf("无法删除 HTTP 挑战文件: %w", err)
}
return nil
}
type dnsSolver struct {
dns DnsType
param DNSParam
records *[]libdns.Record
}
func (s dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error {
dnsName := challenge.DNS01TXTRecordName()
keyAuth := challenge.DNS01KeyAuthorization()
provider, err := s.getDNSProvider()
if err != nil {
return fmt.Errorf("获取 DNS 提供商失败: %w", err)
}
zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName)
if err != nil {
return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err)
}
rec := libdns.Record{
Type: "TXT",
Name: libdns.RelativeName(dnsName+".", zone+"."),
Value: keyAuth,
}
results, err := provider.AppendRecords(ctx, zone+".", []libdns.Record{rec})
if err != nil {
return fmt.Errorf("域名 %q 添加临时记录 %q 失败: %w", zone, dnsName, err)
}
if len(results) != 1 {
return fmt.Errorf("预期添加 1 条记录,但实际添加了 %d 条记录", len(results))
}
s.records = &results
return nil
}
func (s dnsSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error {
dnsName := challenge.DNS01TXTRecordName()
provider, err := s.getDNSProvider()
if err != nil {
return fmt.Errorf("获取 DNS 提供商失败: %w", err)
}
zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName)
if err != nil {
return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err)
}
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
_, err = provider.DeleteRecords(ctx, zone+".", *s.records)
if err != nil {
return fmt.Errorf("域名 %q 删除临时记录 %q 失败: %w", zone, dnsName, err)
}
return nil
}
func (s dnsSolver) getDNSProvider() (DNSProvider, error) {
var dns DNSProvider
switch s.dns {
case DnsPod:
dns = &dnspod.Provider{
APIToken: s.param.ID + "," + s.param.Token,
}
case AliYun:
dns = &alidns.Provider{
AccKeyID: s.param.AccessKey,
AccKeySecret: s.param.SecretKey,
}
case CloudFlare:
dns = &cloudflare.Provider{
APIToken: s.param.APIkey,
}
default:
return nil, fmt.Errorf("未知的 DNS 提供商 %q", s.dns)
}
return dns, nil
}
type DnsType string
const (
DnsPod DnsType = "dnspod"
AliYun DnsType = "aliyun"
CloudFlare DnsType = "cloudflare"
)
type DNSParam struct {
ID string `form:"id" json:"id"`
Token string `form:"token" json:"token"`
AccessKey string `form:"access_key" json:"access_key"`
SecretKey string `form:"secret_key" json:"secret_key"`
APIkey string `form:"api_key" json:"api_key"`
}
type DNSProvider interface {
libdns.RecordAppender
libdns.RecordDeleter
}
type manualDNSSolver struct {
check bool
controlChan chan struct{}
dataChan chan any
records *[]DNSRecord
}
func (s manualDNSSolver) Present(ctx context.Context, challenge acme.Challenge) error {
dnsName := challenge.DNS01TXTRecordName()
keyAuth := challenge.DNS01KeyAuthorization()
// 追加记录到 records 中
*s.records = append(*s.records, DNSRecord{
Key: dnsName,
Value: keyAuth,
})
s.dataChan <- *s.records
_, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
// 等待信号以继续
<-s.controlChan
return nil
}
func (s manualDNSSolver) CleanUp(_ context.Context, _ acme.Challenge) error {
return nil
}
type DNSRecord struct {
Key string `json:"key"`
Value string `json:"value"`
}
-41
View File
@@ -1,41 +0,0 @@
// Package str 字符串辅助方法
package str
import (
"github.com/gertd/go-pluralize"
"github.com/iancoleman/strcase"
)
// Plural 转为复数 user -> users
func Plural(word string) string {
return pluralize.NewClient().Plural(word)
}
// Singular 转为单数 users -> user
func Singular(word string) string {
return pluralize.NewClient().Singular(word)
}
// Snake 转为 snake_case,如 TopicComment -> topic_comment
func Snake(s string) string {
return strcase.ToSnake(s)
}
// Camel 转为 CamelCase,如 topic_comment -> TopicComment
func Camel(s string) string {
return strcase.ToCamel(s)
}
// LowerCamel 转为 lowerCamelCase,如 TopicComment -> topicComment
func LowerCamel(s string) string {
return strcase.ToLowerCamel(s)
}
func ContainsString(arr []string, str string) bool {
for _, s := range arr {
if s == str {
return true
}
}
return false
}
-45
View File
@@ -1,45 +0,0 @@
package str
import (
"testing"
"github.com/stretchr/testify/suite"
)
type StrTestSuite struct {
suite.Suite
}
func TestStrTestSuite(t *testing.T) {
suite.Run(t, &StrTestSuite{})
}
func (s *StrTestSuite) TestPlural() {
s.Equal("users", Plural("user"))
s.Equal("users", Plural("users"))
}
func (s *StrTestSuite) TestSingular() {
s.Equal("user", Singular("users"))
s.Equal("user", Singular("user"))
}
func (s *StrTestSuite) TestSnake() {
s.Equal("topic_comment", Snake("TopicComment"))
s.Equal("topic_comment", Snake("topic_comment"))
}
func (s *StrTestSuite) TestCamel() {
s.Equal("TopicComment", Camel("topic_comment"))
s.Equal("TopicComment", Camel("TopicComment"))
}
func (s *StrTestSuite) TestLowerCamel() {
s.Equal("topicComment", LowerCamel("topic_comment"))
s.Equal("topicComment", LowerCamel("TopicComment"))
}
func (s *StrTestSuite) TestContainsString() {
s.True(ContainsString([]string{"a", "b", "c"}, "a"))
s.False(ContainsString([]string{"a", "b", "c"}, "d"))
}
+46 -1
View File
@@ -7,12 +7,14 @@ import (
"io"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"github.com/goravel/framework/support"
"github.com/goravel/framework/support/env"
"github.com/mholt/archiver/v3"
"github.com/spf13/cast"
)
// Write 写入文件
@@ -111,7 +113,12 @@ func Mkdir(path string, permission os.FileMode) error {
// Chmod 修改文件/目录权限
func Chmod(path string, permission os.FileMode) error {
return os.Chmod(path, permission)
if env.IsWindows() {
return errors.New("chmod is not supported on Windows")
}
cmd := exec.Command("chmod", "-R", permission.String(), path)
return cmd.Run()
}
// Chown 修改文件或目录所有者
@@ -258,3 +265,41 @@ func TempDir(prefix string) (string, error) {
func TempFile(prefix string) (*os.File, error) {
return os.CreateTemp("", prefix)
}
// IsSymlink 判读是否为软链接
func IsSymlink(mode os.FileMode) bool {
return mode&os.ModeSymlink != 0
}
// IsHidden 判断是否为隐藏文件
func IsHidden(path string) bool {
_, file := filepath.Split(path)
return strings.HasPrefix(file, ".")
}
// GetSymlink 获取软链接目标
func GetSymlink(path string) string {
linkPath, err := os.Readlink(path)
if err != nil {
return ""
}
return linkPath
}
// GetUser 通过 uid 获取用户名
func GetUser(uid uint32) string {
usr, err := user.LookupId(cast.ToString(uid))
if err != nil {
return ""
}
return usr.Username
}
// GetGroup 通过 gid 获取组名
func GetGroup(gid uint32) string {
usr, err := user.LookupGroupId(cast.ToString(gid))
if err != nil {
return ""
}
return usr.Name
}
+1 -1
View File
@@ -175,8 +175,8 @@ func (s *SystemHelperTestSuite) TestChmod() {
err := Write(filePath.Name(), "test data", 0644)
s.Nil(err)
s.Nil(Chmod(filePath.Name(), 0755))
s.Nil(filePath.Close())
s.Nil(Chmod(filePath.Name(), 0755))
s.Nil(Remove(filePath.Name()))
}
+7 -12
View File
@@ -9,10 +9,10 @@ import (
"strings"
"time"
"github.com/go-resty/resty/v2"
"github.com/gookit/color"
"github.com/goravel/framework/support/carbon"
"github.com/goravel/framework/support/env"
"github.com/imroc/req/v3"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/host"
@@ -407,7 +407,6 @@ func UpdatePanel(panelInfo PanelInfo) error {
color.Greenln("前置检查...")
if Exists("/tmp/panel-storage.zip") || Exists("/tmp/panel.conf.bak") {
color.Redln("检测到 /tmp 存在临时文件,可能是上次更新失败导致的,请谨慎排除后重试")
return errors.New("检测到 /tmp 存在临时文件,可能是上次更新失败导致的,请谨慎排除后重试")
}
@@ -426,7 +425,6 @@ func UpdatePanel(panelInfo PanelInfo) error {
return err
}
if !Exists("/tmp/panel-storage.zip") || !Exists("/tmp/panel.conf.bak") {
color.Redln("备份面板数据失败")
return errors.New("备份面板数据失败")
}
color.Greenln("备份完成")
@@ -487,7 +485,6 @@ func UpdatePanel(panelInfo PanelInfo) error {
return err
}
if !Exists("/www/panel/storage/panel.db") || !Exists("/www/panel/panel.conf") {
color.Redln("恢复面板数据失败")
return errors.New("恢复面板数据失败")
}
color.Greenln("恢复完成")
@@ -529,13 +526,12 @@ func RestartPanel() {
// IsChina 是否中国大陆
func IsChina() bool {
client := req.C()
client := resty.New()
client.SetTimeout(5 * time.Second)
client.SetCommonRetryCount(2)
client.ImpersonateSafari()
client.SetRetryCount(2)
resp, err := client.R().Get("https://www.cloudflare-cn.com/cdn-cgi/trace")
if err != nil || !resp.IsSuccessState() {
if err != nil || !resp.IsSuccess() {
return false
}
@@ -548,13 +544,12 @@ func IsChina() bool {
// GetPublicIP 获取公网IP
func GetPublicIP() (string, error) {
client := req.C()
client := resty.New()
client.SetTimeout(5 * time.Second)
client.SetCommonRetryCount(2)
client.ImpersonateSafari()
client.SetRetryCount(2)
resp, err := client.R().Get("https://www.cloudflare-cn.com/cdn-cgi/trace")
if err != nil || !resp.IsSuccessState() {
if err != nil || !resp.IsSuccess() {
return "", errors.New("获取公网IP失败")
}
+1
View File
@@ -80,6 +80,7 @@ func Api() {
r.Post("obtain", certController.Obtain)
r.Post("renew", certController.Renew)
r.Post("manualDNS", certController.ManualDNS)
r.Post("deploy", certController.Deploy)
})
r.Prefix("plugin").Middleware(middleware.Jwt()).Group(func(r route.Router) {
pluginController := controllers.NewPluginController()
+86 -56
View File
@@ -25,64 +25,94 @@ func Plugin() {
route.Post("clearErrorLog", openRestyController.ClearErrorLog)
})
r.Prefix("mysql57").Group(func(route route.Router) {
mysql57Controller := plugins.NewMysql57Controller()
route.Get("status", mysql57Controller.Status)
route.Post("reload", mysql57Controller.Reload)
route.Post("start", mysql57Controller.Start)
route.Post("stop", mysql57Controller.Stop)
route.Post("restart", mysql57Controller.Restart)
route.Get("load", mysql57Controller.Load)
route.Get("config", mysql57Controller.GetConfig)
route.Post("config", mysql57Controller.SaveConfig)
route.Get("errorLog", mysql57Controller.ErrorLog)
route.Post("clearErrorLog", mysql57Controller.ClearErrorLog)
route.Get("slowLog", mysql57Controller.SlowLog)
route.Post("clearSlowLog", mysql57Controller.ClearSlowLog)
route.Get("rootPassword", mysql57Controller.GetRootPassword)
route.Post("rootPassword", mysql57Controller.SetRootPassword)
route.Get("databases", mysql57Controller.DatabaseList)
route.Post("databases", mysql57Controller.AddDatabase)
route.Delete("databases", mysql57Controller.DeleteDatabase)
route.Get("backups", mysql57Controller.BackupList)
route.Post("backups", mysql57Controller.CreateBackup)
route.Put("backups", mysql57Controller.UploadBackup)
route.Delete("backups", mysql57Controller.DeleteBackup)
route.Post("backups/restore", mysql57Controller.RestoreBackup)
route.Get("users", mysql57Controller.UserList)
route.Post("users", mysql57Controller.AddUser)
route.Delete("users", mysql57Controller.DeleteUser)
route.Post("users/password", mysql57Controller.SetUserPassword)
route.Post("users/privileges", mysql57Controller.SetUserPrivileges)
mySQLController := plugins.NewMySQLController()
route.Get("status", mySQLController.Status)
route.Post("reload", mySQLController.Reload)
route.Post("start", mySQLController.Start)
route.Post("stop", mySQLController.Stop)
route.Post("restart", mySQLController.Restart)
route.Get("load", mySQLController.Load)
route.Get("config", mySQLController.GetConfig)
route.Post("config", mySQLController.SaveConfig)
route.Get("errorLog", mySQLController.ErrorLog)
route.Post("clearErrorLog", mySQLController.ClearErrorLog)
route.Get("slowLog", mySQLController.SlowLog)
route.Post("clearSlowLog", mySQLController.ClearSlowLog)
route.Get("rootPassword", mySQLController.GetRootPassword)
route.Post("rootPassword", mySQLController.SetRootPassword)
route.Get("databases", mySQLController.DatabaseList)
route.Post("databases", mySQLController.AddDatabase)
route.Delete("databases", mySQLController.DeleteDatabase)
route.Get("backups", mySQLController.BackupList)
route.Post("backups", mySQLController.CreateBackup)
route.Put("backups", mySQLController.UploadBackup)
route.Delete("backups", mySQLController.DeleteBackup)
route.Post("backups/restore", mySQLController.RestoreBackup)
route.Get("users", mySQLController.UserList)
route.Post("users", mySQLController.AddUser)
route.Delete("users", mySQLController.DeleteUser)
route.Post("users/password", mySQLController.SetUserPassword)
route.Post("users/privileges", mySQLController.SetUserPrivileges)
})
r.Prefix("mysql80").Group(func(route route.Router) {
mysql80Controller := plugins.NewMysql80Controller()
route.Get("status", mysql80Controller.Status)
route.Post("reload", mysql80Controller.Reload)
route.Post("start", mysql80Controller.Start)
route.Post("stop", mysql80Controller.Stop)
route.Post("restart", mysql80Controller.Restart)
route.Get("load", mysql80Controller.Load)
route.Get("config", mysql80Controller.GetConfig)
route.Post("config", mysql80Controller.SaveConfig)
route.Get("errorLog", mysql80Controller.ErrorLog)
route.Post("clearErrorLog", mysql80Controller.ClearErrorLog)
route.Get("slowLog", mysql80Controller.SlowLog)
route.Post("clearSlowLog", mysql80Controller.ClearSlowLog)
route.Get("rootPassword", mysql80Controller.GetRootPassword)
route.Post("rootPassword", mysql80Controller.SetRootPassword)
route.Get("databases", mysql80Controller.DatabaseList)
route.Post("databases", mysql80Controller.AddDatabase)
route.Delete("databases", mysql80Controller.DeleteDatabase)
route.Get("backups", mysql80Controller.BackupList)
route.Post("backups", mysql80Controller.CreateBackup)
route.Put("backups", mysql80Controller.UploadBackup)
route.Delete("backups", mysql80Controller.DeleteBackup)
route.Post("backups/restore", mysql80Controller.RestoreBackup)
route.Get("users", mysql80Controller.UserList)
route.Post("users", mysql80Controller.AddUser)
route.Delete("users", mysql80Controller.DeleteUser)
route.Post("users/password", mysql80Controller.SetUserPassword)
route.Post("users/privileges", mysql80Controller.SetUserPrivileges)
mySQLController := plugins.NewMySQLController()
route.Get("status", mySQLController.Status)
route.Post("reload", mySQLController.Reload)
route.Post("start", mySQLController.Start)
route.Post("stop", mySQLController.Stop)
route.Post("restart", mySQLController.Restart)
route.Get("load", mySQLController.Load)
route.Get("config", mySQLController.GetConfig)
route.Post("config", mySQLController.SaveConfig)
route.Get("errorLog", mySQLController.ErrorLog)
route.Post("clearErrorLog", mySQLController.ClearErrorLog)
route.Get("slowLog", mySQLController.SlowLog)
route.Post("clearSlowLog", mySQLController.ClearSlowLog)
route.Get("rootPassword", mySQLController.GetRootPassword)
route.Post("rootPassword", mySQLController.SetRootPassword)
route.Get("databases", mySQLController.DatabaseList)
route.Post("databases", mySQLController.AddDatabase)
route.Delete("databases", mySQLController.DeleteDatabase)
route.Get("backups", mySQLController.BackupList)
route.Post("backups", mySQLController.CreateBackup)
route.Put("backups", mySQLController.UploadBackup)
route.Delete("backups", mySQLController.DeleteBackup)
route.Post("backups/restore", mySQLController.RestoreBackup)
route.Get("users", mySQLController.UserList)
route.Post("users", mySQLController.AddUser)
route.Delete("users", mySQLController.DeleteUser)
route.Post("users/password", mySQLController.SetUserPassword)
route.Post("users/privileges", mySQLController.SetUserPrivileges)
})
r.Prefix("mysql84").Group(func(route route.Router) {
mySQLController := plugins.NewMySQLController()
route.Get("status", mySQLController.Status)
route.Post("reload", mySQLController.Reload)
route.Post("start", mySQLController.Start)
route.Post("stop", mySQLController.Stop)
route.Post("restart", mySQLController.Restart)
route.Get("load", mySQLController.Load)
route.Get("config", mySQLController.GetConfig)
route.Post("config", mySQLController.SaveConfig)
route.Get("errorLog", mySQLController.ErrorLog)
route.Post("clearErrorLog", mySQLController.ClearErrorLog)
route.Get("slowLog", mySQLController.SlowLog)
route.Post("clearSlowLog", mySQLController.ClearSlowLog)
route.Get("rootPassword", mySQLController.GetRootPassword)
route.Post("rootPassword", mySQLController.SetRootPassword)
route.Get("databases", mySQLController.DatabaseList)
route.Post("databases", mySQLController.AddDatabase)
route.Delete("databases", mySQLController.DeleteDatabase)
route.Get("backups", mySQLController.BackupList)
route.Post("backups", mySQLController.CreateBackup)
route.Put("backups", mySQLController.UploadBackup)
route.Delete("backups", mySQLController.DeleteBackup)
route.Post("backups/restore", mySQLController.RestoreBackup)
route.Get("users", mySQLController.UserList)
route.Post("users", mySQLController.AddUser)
route.Delete("users", mySQLController.DeleteUser)
route.Post("users/password", mySQLController.SetUserPassword)
route.Post("users/privileges", mySQLController.SetUserPrivileges)
})
r.Prefix("postgresql15").Group(func(route route.Router) {
postgresql15Controller := plugins.NewPostgresql15Controller()
+9 -7
View File
@@ -111,7 +111,7 @@ Prepare_System() {
fi
# 自动开启 BBR
isBBRSupported=$(sysctl net.ipv4.tcp_available_congestion_control | grep -c bbr)
isBBRSupported=$(ls -l /lib/modules/*/kernel/net/ipv4/tcp_bbr.ko.xz | grep -c bbr)
if [ "${isBBRSupported}" != "0" ]; then
qdisc=$(sysctl net.core.default_qdisc | awk '{print $3}')
echo "net.core.default_qdisc=${qdisc}" >> /etc/sysctl.conf
@@ -140,21 +140,21 @@ Prepare_System() {
sed -i 's|^metalink|#metalink|' /etc/yum.repos.d/epel*
dnf makecache -y
fi
dnf config-manager --set-enabled PowerTools
# EL 8
dnf config-manager --set-enabled powertools
dnf config-manager --set-enabled CRB
dnf config-manager --set-enabled Crb
# EL 9
dnf config-manager --set-enabled crb
# Rocky Linux
/usr/bin/crb enable
dnf makecache -y
dnf install -y curl wget zip unzip tar git jq git-core dos2unix podman
dnf install -y curl wget zip unzip tar p7zip p7zip-plugins git jq git-core dos2unix podman rsyslog
elif [ "${OS}" == "debian" ]; then
if ${inChina}; then
sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list
sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list
fi
apt-get update -y
apt-get install -y curl wget zip unzip tar git jq git dos2unix podman rsyslog
apt-get install -y curl wget zip unzip tar p7zip p7zip-full git jq git dos2unix podman rsyslog
fi
if [ "$?" != "0" ]; then
echo -e $HR
@@ -162,7 +162,9 @@ Prepare_System() {
exit 1
fi
systemctl enable rsyslog
systemctl enable podman
systemctl start rsyslog
systemctl start podman
if [ "$?" != "0" ]; then
echo -e $HR
@@ -273,7 +275,7 @@ Init_Panel() {
chmod -R 700 /usr/bin/panel
# 防火墙放行
if [ "${OS}" == "centos" ]; then
yum install firewalld -y
dnf install firewalld -y
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --set-default-zone=public > /dev/null 2>&1
+23 -39
View File
@@ -22,7 +22,7 @@ HR="+----------------------------------------------------"
ARCH=$(uname -m)
memTotal=$(LC_ALL=C free -m | grep Mem | awk '{print $2}')
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/mysql"
downloadUrl="https://dl.cdn.haozi.net/panel/mysql"
setupPath="/www"
mysqlPath="${setupPath}/server/mysql"
mysqlVersion=""
@@ -31,8 +31,11 @@ cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l)
source ${setupPath}/panel/scripts/calculate_j.sh
j=$(calculate_j)
if [[ "${1}" == "80" ]]; then
mysqlVersion="8.0.36"
if [[ "${1}" == "84" ]]; then
mysqlVersion="8.4.0"
j=$(calculate_j2)
elif [[ "${1}" == "80" ]]; then
mysqlVersion="8.0.37"
j=$(calculate_j2)
elif [[ "${1}" == "57" ]]; then
mysqlVersion="5.7.44"
@@ -69,53 +72,34 @@ mkdir -p ${mysqlPath}
cd ${mysqlPath}
# 下载源码
wget -T 120 -t 3 -O ${mysqlPath}/mysql-boost-${mysqlVersion}.tar.gz ${downloadUrl}/mysql-boost-${mysqlVersion}.tar.gz
wget -T 20 -t 3 -O ${mysqlPath}/mysql-boost-${mysqlVersion}.tar.gz.checksum.txt ${downloadUrl}/mysql-boost-${mysqlVersion}.tar.gz.checksum.txt
# 校验
if ! sha256sum --status -c mysql-boost-${mysqlVersion}.tar.gz.checksum.txt; then
wget -T 120 -t 3 -O ${mysqlPath}/mysql-${mysqlVersion}.7z ${downloadUrl}/mysql-${mysqlVersion}.7z
wget -T 20 -t 3 -O ${mysqlPath}/mysql-${mysqlVersion}.7z.checksum.txt ${downloadUrl}/mysql-${mysqlVersion}.7z.checksum.txt
if ! sha256sum --status -c mysql-${mysqlVersion}.7z.checksum.txt; then
echo -e $HR
echo "错误:MySQL 源码 checksum 校验失败,文件可能被篡改或不完整,已终止操作"
rm -rf ${mysqlPath}
exit 1
fi
tar -zxvf mysql-boost-${mysqlVersion}.tar.gz
rm -f mysql-boost-${mysqlVersion}.tar.gz
rm -f mysql-boost-${mysqlVersion}.tar.gz.checksum.txt
mv mysql-${mysqlVersion} src
# openssl
wget -T 120 -t 3 -O ${mysqlPath}/openssl-1.1.1w.tar.gz ${downloadUrl}/openssl/openssl-1.1.1w.tar.gz
wget -T 20 -t 3 -O ${mysqlPath}/openssl-1.1.1w.tar.gz.checksum.txt ${downloadUrl}/openssl/openssl-1.1.1w.tar.gz.checksum.txt
# 校验
if ! sha256sum --status -c openssl-1.1.1w.tar.gz.checksum.txt; then
echo -e $HR
echo "错误:OpenSSL 源码 checksum 校验失败,文件可能被篡改或不完整,已终止操作"
rm -rf ${mysqlPath}
exit 1
fi
tar -zxvf openssl-1.1.1w.tar.gz
rm -f openssl-1.1.1w.tar.gz
rm -f openssl-1.1.1w.tar.gz.checksum.txt
mv openssl-1.1.1w openssl
cd openssl
./config --prefix=/usr/local/openssl-1.1 --openssldir=/usr/local/openssl-1.1 no-tests
make -j$(nproc)
make install
echo "/usr/local/openssl-1.1/lib" > /etc/ld.so.conf.d/openssl-1.1.conf
ldconfig
cd ..
rm -rf openssl
7z x mysql-${mysqlVersion}.7z
rm -f mysql-${mysqlVersion}.7z
rm -f mysql-${mysqlVersion}.7z.checksum.txt
# 编译
mv mysql-${mysqlVersion} src
chmod -R 755 src
cd src
rm mysql-test/CMakeLists.txt
sed -i 's/ADD_SUBDIRECTORY(mysql-test)//g' CMakeLists.txt
mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=${mysqlPath} -DMYSQL_DATADIR=${mysqlPath}/data -DSYSCONFDIR=${mysqlPath}/conf -DWITH_MYISAM_STORAGE_ENGINE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_ARCHIVE_STORAGE_ENGINE=1 -DWITH_FEDERATED_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DDEFAULT_CHARSET=utf8mb4 -DDEFAULT_COLLATION=utf8mb4_general_ci -DENABLED_LOCAL_INFILE=1 -DWITH_DEBUG=0 -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DWITH_SYSTEMD=1 -DSYSTEMD_PID_DIR=${mysqlPath} -DWITH_SSL=/usr/local/openssl-1.1 -DWITH_BOOST=../boost
# 5.7 需要 boost
if [[ "${1}" == "57" ]]; then
MAYBE_WITH_BOOST="-DWITH_BOOST=../boost"
fi
cmake .. -DCMAKE_INSTALL_PREFIX=${mysqlPath} -DMYSQL_DATADIR=${mysqlPath}/data -DSYSCONFDIR=${mysqlPath}/conf -DWITH_MYISAM_STORAGE_ENGINE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_ARCHIVE_STORAGE_ENGINE=1 -DWITH_FEDERATED_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DDEFAULT_CHARSET=utf8mb4 -DDEFAULT_COLLATION=utf8mb4_general_ci -DENABLED_LOCAL_INFILE=1 -DWITH_DEBUG=0 -DWITH_UNIT_TESTS=OFF -DINSTALL_MYSQLTESTDIR= -DCMAKE_BUILD_TYPE=Release -DWITH_SYSTEMD=1 -DSYSTEMD_PID_DIR=${mysqlPath} ${MAYBE_WITH_BOOST}
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误:MySQL 编译初始化失败,请截图错误信息寻求帮助。"
@@ -210,7 +194,7 @@ EOF
sed -i 's/innodb_write_io_threads = 4/innodb_write_io_threads = '${cpuCore}'/g' ${mysqlPath}/conf/my.cnf
sed -i 's/innodb_read_io_threads = 4/innodb_read_io_threads = '${cpuCore}'/g' ${mysqlPath}/conf/my.cnf
if [[ "${1}" == "80" ]]; then
if [[ "${1}" == "84" ]] || [[ "${1}" == "80" ]]; then
sed -i '/query_cache_size/d' ${mysqlPath}/conf/my.cnf
fi
if [[ "${1}" == "57" ]]; then
+24 -37
View File
@@ -22,7 +22,7 @@ HR="+----------------------------------------------------"
ARCH=$(uname -m)
memTotal=$(LC_ALL=C free -m | grep Mem | awk '{print $2}')
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/mysql"
downloadUrl="https://dl.cdn.haozi.net/panel/mysql"
setupPath="/www"
mysqlPath="${setupPath}/server/mysql"
mysqlVersion=""
@@ -32,8 +32,11 @@ cpuCore=$(cat /proc/cpuinfo | grep "processor" | wc -l)
source ${setupPath}/panel/scripts/calculate_j.sh
j=$(calculate_j)
if [[ "${1}" == "80" ]]; then
mysqlVersion="8.0.36"
if [[ "${1}" == "84" ]]; then
mysqlVersion="8.4.0"
j=$(calculate_j2)
elif [[ "${1}" == "80" ]]; then
mysqlVersion="8.0.37"
j=$(calculate_j2)
elif [[ "${1}" == "57" ]]; then
mysqlVersion="5.7.44"
@@ -47,11 +50,11 @@ fi
if [ "${OS}" == "centos" ]; then
dnf makecache -y
dnf groupinstall "Development Tools" -y
dnf install cmake bison ncurses-devel libtirpc-devel openssl-devel pkg-config openldap-devel libudev-devel cyrus-sasl-devel patchelf rpcgen rpcsvc-proto-devel -y
dnf install cmake bison ncurses-devel libtirpc-devel openssl-devel pkg-config openldap-devel libudev-devel cyrus-sasl-devel patchelf rpcgen rpcsvc-proto-devel p7zip p7zip-plugins -y
dnf install gcc-toolset-12-gcc gcc-toolset-12-gcc-c++ gcc-toolset-12-binutils gcc-toolset-12-annobin-annocheck gcc-toolset-12-annobin-plugin-gcc -y
elif [ "${OS}" == "debian" ]; then
apt-get update
apt-get install build-essential cmake bison libncurses5-dev libtirpc-dev libssl-dev pkg-config libldap2-dev libudev-dev libsasl2-dev patchelf -y
apt-get install build-essential cmake bison libncurses5-dev libtirpc-dev libssl-dev pkg-config libldap2-dev libudev-dev libsasl2-dev patchelf p7zip p7zip-full -y
else
echo -e $HR
echo "错误:耗子 Linux 面板不支持该系统"
@@ -65,48 +68,32 @@ systemctl stop mysqld
cd ${mysqlPath}
# 下载源码
wget -T 120 -t 3 -O ${mysqlPath}/mysql-boost-${mysqlVersion}.tar.gz ${downloadUrl}/mysql-boost-${mysqlVersion}.tar.gz
wget -T 20 -t 3 -O ${mysqlPath}/mysql-boost-${mysqlVersion}.tar.gz.checksum.txt ${downloadUrl}/mysql-boost-${mysqlVersion}.tar.gz.checksum.txt
if ! sha256sum --status -c mysql-boost-${mysqlVersion}.tar.gz.checksum.txt; then
wget -T 120 -t 3 -O ${mysqlPath}/mysql-${mysqlVersion}.7z ${downloadUrl}/mysql-${mysqlVersion}.7z
wget -T 20 -t 3 -O ${mysqlPath}/mysql-${mysqlVersion}.7z.checksum.txt ${downloadUrl}/mysql-${mysqlVersion}.7z.checksum.txt
if ! sha256sum --status -c mysql-${mysqlVersion}.7z.checksum.txt; then
echo -e $HR
echo "错误:MySQL 源码 checksum 校验失败,文件可能被篡改或不完整,已终止操作"
rm -rf ${mysqlPath}
exit 1
fi
tar -zxvf mysql-boost-${mysqlVersion}.tar.gz
rm -f mysql-boost-${mysqlVersion}.tar.gz
rm -f mysql-boost-${mysqlVersion}.tar.gz.checksum.txt
mv mysql-${mysqlVersion} src
# openssl
wget -T 120 -t 3 -O ${mysqlPath}/openssl-1.1.1w.tar.gz ${downloadUrl}/openssl/openssl-1.1.1w.tar.gz
wget -T 20 -t 3 -O ${mysqlPath}/openssl-1.1.1w.tar.gz.checksum.txt ${downloadUrl}/openssl/openssl-1.1.1w.tar.gz.checksum.txt
if ! sha256sum --status -c openssl-1.1.1w.tar.gz.checksum.txt; then
echo -e $HR
echo "错误:OpenSSL 源码 checksum 校验失败,文件可能被篡改或不完整,已终止操作"
exit 1
fi
tar -zxvf openssl-1.1.1w.tar.gz
rm -f openssl-1.1.1w.tar.gz
rm -f openssl-1.1.1w.tar.gz.checksum.txt
mv openssl-1.1.1w openssl
cd openssl
./config --prefix=/usr/local/openssl-1.1 --openssldir=/usr/local/openssl-1.1 no-tests
make "-j${j}"
make install
echo "/usr/local/openssl-1.1/lib" > /etc/ld.so.conf.d/openssl-1.1.conf
ldconfig
cd ..
rm -rf openssl
7z x mysql-${mysqlVersion}.7z
rm -f mysql-${mysqlVersion}.7z
rm -f mysql-${mysqlVersion}.7z.checksum.txt
# 编译
mv mysql-${mysqlVersion} src
chmod -R 755 src
cd src
mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=${mysqlPath} -DMYSQL_DATADIR=${mysqlPath}/data -DSYSCONFDIR=${mysqlPath}/conf -DWITH_MYISAM_STORAGE_ENGINE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_PARTITION_STORAGE_ENGINE=1 -DWITH_ARCHIVE_STORAGE_ENGINE=1 -DWITH_FEDERATED_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DWITH_EXTRA_CHARSETS=all -DEXTRA_CHARSETS=all -DDEFAULT_CHARSET=utf8mb4 -DDEFAULT_COLLATION=utf8mb4_general_ci -DENABLED_LOCAL_INFILE=1 -DWITH_SYSTEMD=1 -DSYSTEMD_PID_DIR=${mysqlPath} -DWITH_SSL=/usr/local/openssl-1.1 -DWITH_BOOST=../boost
# 5.7 需要 boost
if [[ "${1}" == "57" ]]; then
MAYBE_WITH_BOOST="-DWITH_BOOST=../boost"
fi
cmake .. -DCMAKE_INSTALL_PREFIX=${mysqlPath} -DMYSQL_DATADIR=${mysqlPath}/data -DSYSCONFDIR=${mysqlPath}/conf -DWITH_MYISAM_STORAGE_ENGINE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_ARCHIVE_STORAGE_ENGINE=1 -DWITH_FEDERATED_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DDEFAULT_CHARSET=utf8mb4 -DDEFAULT_COLLATION=utf8mb4_general_ci -DENABLED_LOCAL_INFILE=1 -DWITH_DEBUG=0 -DWITH_UNIT_TESTS=OFF -DINSTALL_MYSQLTESTDIR= -DCMAKE_BUILD_TYPE=Release -DWITH_SYSTEMD=1 -DSYSTEMD_PID_DIR=${mysqlPath} ${MAYBE_WITH_BOOST}
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误:MySQL 编译初始化失败,请截图错误信息寻求帮助。"
+16 -14
View File
@@ -21,7 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
ARCH=$(uname -m)
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/openresty"
downloadUrl="https://dl.cdn.haozi.net/panel/openresty"
setupPath="/www"
openrestyPath="${setupPath}/server/openresty"
openrestyVersion="1.25.3.1"
@@ -66,20 +66,21 @@ mv openresty-${openrestyVersion} src
cd src
# openssl
wget -T 120 -t 3 -O openssl-3.0.12.tar.gz ${downloadUrl}/openssl/openssl-3.0.12.tar.gz
wget -T 20 -t 3 -O openssl-3.0.12.tar.gz.checksum.txt ${downloadUrl}/openssl/openssl-3.0.12.tar.gz.checksum.txt
wget -T 120 -t 3 -O openssl-3.0.12.7z ${downloadUrl}/openssl/openssl-3.0.12.7z
wget -T 20 -t 3 -O openssl-3.0.12.7z.checksum.txt ${downloadUrl}/openssl/openssl-3.0.12.7z.checksum.txt
if ! sha256sum --status -c openssl-3.0.12.tar.gz.checksum.txt; then
if ! sha256sum --status -c openssl-3.0.12.7z.checksum.txt; then
echo -e $HR
echo "错误:OpenSSL 源码 checksum 校验失败,文件可能被篡改或不完整,已终止操作"
rm -rf ${openrestyPath}
exit 1
fi
tar -zxvf openssl-3.0.12.tar.gz
rm -f openssl-3.0.12.tar.gz
rm -f openssl-3.0.12.tar.gz.checksum.txt
7z x openssl-3.0.12.7z
rm -f openssl-3.0.12.7z
rm -f openssl-3.0.12.7z.checksum.txt
mv openssl-3.0.12 openssl
chmod -R 755 openssl
# patch openssl
cd openssl
@@ -99,20 +100,21 @@ rm -f openssl-3.0.12-sess_set_get_cb_yield.patch.checksum.txt
cd ../
# pcre2
wget -T 60 -t 3 -O pcre2-10.42.tar.gz ${downloadUrl}/pcre/pcre2-10.42.tar.gz
wget -T 20 -t 3 -O pcre2-10.42.tar.gz.checksum.txt ${downloadUrl}/pcre/pcre2-10.42.tar.gz.checksum.txt
wget -T 60 -t 3 -O pcre2-10.43.7z ${downloadUrl}/pcre/pcre2-10.43.7z
wget -T 20 -t 3 -O pcre2-10.43.7z.checksum.txt ${downloadUrl}/pcre/pcre2-10.43.7z.checksum.txt
if ! sha256sum --status -c pcre2-10.42.tar.gz.checksum.txt; then
if ! sha256sum --status -c pcre2-10.43.7z.checksum.txt; then
echo -e $HR
echo "错误:pcre2 源码 checksum 校验失败,文件可能被篡改或不完整,已终止操作"
rm -rf ${openrestyPath}
exit 1
fi
tar -zxvf pcre2-10.42.tar.gz
rm -f pcre2-10.42.tar.gz
rm -f pcre2-10.42.tar.gz.checksum.txt
mv pcre2-10.42 pcre2
7z x pcre2-10.43.7z
rm -f pcre2-10.43.7z
rm -f pcre2-10.43.7z.checksum.txt
mv pcre2-10.43 pcre2
chmod -R 755 pcre2
# ngx_cache_purge
wget -T 20 -t 3 -O ngx_cache_purge-2.3.tar.gz ${downloadUrl}/modules/ngx_cache_purge-2.3.tar.gz
+11 -10
View File
@@ -21,7 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
ARCH=$(uname -m)
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/php"
downloadUrl="https://dl.cdn.haozi.net/panel/php"
setupPath="/www"
phpVersion="${1}"
phpVersionCode=""
@@ -65,31 +65,32 @@ if [ "${phpVersion}" == "74" ]; then
elif [ "${phpVersion}" == "80" ]; then
phpVersionCode="8.0.30"
elif [ "${phpVersion}" == "81" ]; then
phpVersionCode="8.1.27"
phpVersionCode="8.1.28"
elif [ "${phpVersion}" == "82" ]; then
phpVersionCode="8.2.16"
phpVersionCode="8.2.19"
elif [ "${phpVersion}" == "83" ]; then
phpVersionCode="8.3.3"
phpVersionCode="8.3.7"
else
echo -e $HR
echo "错误:PHP-${phpVersion}不支持,请检查版本号是否正确。"
exit 1
fi
wget -T 120 -t 3 -O ${phpPath}/php-${phpVersionCode}.tar.gz ${downloadUrl}/php-${phpVersionCode}.tar.gz
wget -T 20 -t 3 -O ${phpPath}/php-${phpVersionCode}.tar.gz.checksum.txt ${downloadUrl}/php-${phpVersionCode}.tar.gz.checksum.txt
wget -T 120 -t 3 -O ${phpPath}/php-${phpVersionCode}.7z ${downloadUrl}/php-${phpVersionCode}.7z
wget -T 20 -t 3 -O ${phpPath}/php-${phpVersionCode}.7z.checksum.txt ${downloadUrl}/php-${phpVersionCode}.7z.checksum.txt
if ! sha256sum --status -c php-${phpVersionCode}.tar.gz.checksum.txt; then
if ! sha256sum --status -c php-${phpVersionCode}.7z.checksum.txt; then
echo -e $HR
echo "错误:PHP-${phpVersion}源码 checksum 校验失败,文件可能被篡改或不完整,已终止操作"
rm -rf ${phpPath}
exit 1
fi
tar -xvf php-${phpVersionCode}.tar.gz
rm -f php-${phpVersionCode}.tar.gz
rm -f php-${phpVersionCode}.tar.gz.checksum.txt
7z x php-${phpVersionCode}.7z
rm -f php-${phpVersionCode}.7z
rm -f php-${phpVersionCode}.7z.checksum.txt
mv php-* src
chmod -R 755 src
if [ "${phpVersion}" -le "80" ]; then
wget -T 120 -t 3 -O ${phpPath}/openssl-1.1.1w.tar.gz ${downloadUrl}/openssl/openssl-1.1.1w.tar.gz
+1 -1
View File
@@ -20,7 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/php_extensions"
downloadUrl="https://dl.cdn.haozi.net/panel/php_extensions"
action="$1"
phpVersion="$2"
swowVersion="1.4.1"
+1 -1
View File
@@ -20,7 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/php_extensions"
downloadUrl="https://dl.cdn.haozi.net/panel/php_extensions"
action="$1"
phpVersion="$2"
igbinaryVersion="3.2.15"
+1 -1
View File
@@ -21,7 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/php_extensions"
downloadUrl="https://dl.cdn.haozi.net/panel/php_extensions"
action="$1"
phpVersion="$2"
imagickVersion="3.7.0"
+1 -1
View File
@@ -20,7 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/php_extensions"
downloadUrl="https://dl.cdn.haozi.net/panel/php_extensions"
action="$1"
phpVersion="$2"
+1 -1
View File
@@ -20,7 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/php_extensions"
downloadUrl="https://dl.cdn.haozi.net/panel/php_extensions"
action="$1"
phpVersion="$2"
phpredisVersion="5.3.7"
+1 -1
View File
@@ -20,7 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/php_extensions"
downloadUrl="https://dl.cdn.haozi.net/panel/php_extensions"
action="$1"
phpVersion="$2"
swooleVersion="5.1.2"
+1 -1
View File
@@ -21,7 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
ARCH=$(uname -m)
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/phpmyadmin"
downloadUrl="https://dl.cdn.haozi.net/panel/phpmyadmin"
setupPath="/www"
phpmyadminPath="${setupPath}/server/phpmyadmin"
phpmyadminVersion="5.2.1"
+10 -9
View File
@@ -22,7 +22,7 @@ HR="+----------------------------------------------------"
ARCH=$(uname -m)
memTotal=$(LC_ALL=C free -m | grep Mem | awk '{print $2}')
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/postgresql"
downloadUrl="https://dl.cdn.haozi.net/panel/postgresql"
setupPath="/www"
postgresqlPath="${setupPath}/server/postgresql"
postgresqlVersion=""
@@ -31,9 +31,9 @@ source ${setupPath}/panel/scripts/calculate_j.sh
j=$(calculate_j)
if [[ "${1}" == "15" ]]; then
postgresqlVersion="15.6"
postgresqlVersion="15.7"
elif [[ "${1}" == "16" ]]; then
postgresqlVersion="16.2"
postgresqlVersion="16.3"
else
echo -e $HR
echo "错误:不支持的 PostgreSQL 版本!"
@@ -66,20 +66,21 @@ mkdir -p ${postgresqlPath}
cd ${postgresqlPath}
# 下载源码
wget -T 120 -t 3 -O ${postgresqlPath}/postgresql-${postgresqlVersion}.tar.gz ${downloadUrl}/postgresql-${postgresqlVersion}.tar.gz
wget -T 20 -t 3 -O ${postgresqlPath}/postgresql-${postgresqlVersion}.tar.gz.checksum.txt ${downloadUrl}/postgresql-${postgresqlVersion}.tar.gz.checksum.txt
wget -T 120 -t 3 -O ${postgresqlPath}/postgresql-${postgresqlVersion}.7z ${downloadUrl}/postgresql-${postgresqlVersion}.7z
wget -T 20 -t 3 -O ${postgresqlPath}/postgresql-${postgresqlVersion}.7z.checksum.txt ${downloadUrl}/postgresql-${postgresqlVersion}.7z.checksum.txt
if ! sha256sum --status -c postgresql-${postgresqlVersion}.tar.gz.checksum.txt; then
if ! sha256sum --status -c postgresql-${postgresqlVersion}.7z.checksum.txt; then
echo -e $HR
echo "错误:PostgreSQL 源码 checksum 校验失败,文件可能被篡改或不完整,已终止操作"
rm -rf ${postgresqlPath}
exit 1
fi
tar -zxvf postgresql-${postgresqlVersion}.tar.gz
rm -f postgresql-${postgresqlVersion}.tar.gz
rm -f postgresql-${postgresqlVersion}.tar.gz.checksum.txt
7z x postgresql-${postgresqlVersion}.7z
rm -f postgresql-${postgresqlVersion}.7z
rm -f postgresql-${postgresqlVersion}.7z.checksum.txt
mv postgresql-${postgresqlVersion} src
chmod -R 755 src
# 编译
cd src
+10 -9
View File
@@ -22,7 +22,7 @@ HR="+----------------------------------------------------"
ARCH=$(uname -m)
memTotal=$(LC_ALL=C free -m | grep Mem | awk '{print $2}')
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/postgresql"
downloadUrl="https://dl.cdn.haozi.net/panel/postgresql"
setupPath="/www"
postgresqlPath="${setupPath}/server/postgresql"
postgresqlVersion=""
@@ -31,9 +31,9 @@ source ${setupPath}/panel/scripts/calculate_j.sh
j=$(calculate_j)
if [[ "${1}" == "15" ]]; then
postgresqlVersion="15.6"
postgresqlVersion="15.7"
elif [[ "${1}" == "16" ]]; then
postgresqlVersion="16.2"
postgresqlVersion="16.3"
else
echo -e $HR
echo "错误:不支持的 PostgreSQL 版本!"
@@ -62,19 +62,20 @@ rm -rf ${postgresqlPath}/src
cd ${postgresqlPath}
# 下载源码
wget -T 120 -t 3 -O ${postgresqlPath}/postgresql-${postgresqlVersion}.tar.gz ${downloadUrl}/postgresql-${postgresqlVersion}.tar.gz
wget -T 20 -t 3 -O ${postgresqlPath}/postgresql-${postgresqlVersion}.tar.gz.checksum.txt ${downloadUrl}/postgresql-${postgresqlVersion}.tar.gz.checksum.txt
wget -T 120 -t 3 -O ${postgresqlPath}/postgresql-${postgresqlVersion}.7z ${downloadUrl}/postgresql-${postgresqlVersion}.7z
wget -T 20 -t 3 -O ${postgresqlPath}/postgresql-${postgresqlVersion}.7z.checksum.txt ${downloadUrl}/postgresql-${postgresqlVersion}.7z.checksum.txt
if ! sha256sum --status -c postgresql-${postgresqlVersion}.tar.gz.checksum.txt; then
if ! sha256sum --status -c postgresql-${postgresqlVersion}.7z.checksum.txt; then
echo -e $HR
echo "错误:PostgreSQL 源码 checksum 校验失败,文件可能被篡改或不完整,已终止操作"
exit 1
fi
tar -zxvf postgresql-${postgresqlVersion}.tar.gz
rm -f postgresql-${postgresqlVersion}.tar.gz
rm -f postgresql-${postgresqlVersion}.tar.gz.checksum.txt
7z x postgresql-${postgresqlVersion}.7z
rm -f postgresql-${postgresqlVersion}.7z
rm -f postgresql-${postgresqlVersion}.7z.checksum.txt
mv postgresql-${postgresqlVersion} src
chmod -R 755 src
# 编译
cd src
+1 -1
View File
@@ -20,7 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/pure-ftpd"
downloadUrl="https://dl.cdn.haozi.net/panel/pure-ftpd"
setupPath="/www"
pureftpdPath="${setupPath}/server/pure-ftpd"
pureftpdVersion="1.0.50"
+1 -1
View File
@@ -20,7 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/pure-ftpd"
downloadUrl="https://dl.cdn.haozi.net/panel/pure-ftpd"
setupPath="/www"
pureftpdPath="${setupPath}/server/pure-ftpd"
pureftpdVersion="1.0.50"
+1 -1
View File
@@ -21,7 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
ARCH=$(uname -m)
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/redis"
downloadUrl="https://dl.cdn.haozi.net/panel/redis"
setupPath="/www"
redisPath="${setupPath}/server/redis"
redisVersion="7.2.4"
+1 -1
View File
@@ -21,7 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
HR="+----------------------------------------------------"
ARCH=$(uname -m)
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://git.haozi.net/opensource/download/-/raw/main/panel/redis"
downloadUrl="https://dl.cdn.haozi.net/panel/redis"
setupPath="/www"
redisPath="${setupPath}/server/redis"
redisVersion="7.2.4"
+17 -2
View File
@@ -90,8 +90,8 @@ if version_lt "$oldVersion" "2.1.39"; then
echo "更新面板到 v2.1.39 ..."
echo "Update panel to v2.1.39 ..."
if [ "${OS}" == "centos" ]; then
yum makecache
yum install -y podman
dnf makecache
dnf install -y podman
else
apt-get update -y
apt-get install -y podman
@@ -101,6 +101,21 @@ if version_lt "$oldVersion" "2.1.39"; then
systemctl start podman
fi
if version_lt "$oldVersion" "2.2.0"; then
echo "更新面板到 v2.2.0 ..."
echo "Update panel to v2.2.0 ..."
echo "APP_LOCALE=zh_CN" >> $panelPath/panel.conf
if [ "${OS}" == "centos" ]; then
dnf makecache
dnf install -y p7zip p7zip-plugins rsyslog
systemctl enable rsyslog
systemctl start rsyslog
else
apt-get update -y
apt-get install -y p7zip p7zip-full
fi
fi
echo $HR
echo "更新结束"
echo "Update finished"