Compare commits

...

120 Commits

Author SHA1 Message Date
耗子 51ec5e137b feat: 发布 v2.1.34 2023-12-22 21:36:22 +08:00
耗子 8684727267 fix: 设置保存 2023-12-22 21:35:56 +08:00
耗子 c884dd6fd7 fix: 修复面板入口信息错误 2023-12-22 21:14:33 +08:00
耗子 35d2838036 fix: 修复面板入口信息错误 2023-12-22 21:12:48 +08:00
耗子 0acf996981 feat: 发布 v2.1.33 2023-12-22 18:05:43 +08:00
耗子 2be12f20f2 fix: 开关面板 SSL 自动重启面板 2023-12-22 18:05:29 +08:00
耗子 07b0c7a010 feat: 发布 v2.1.32 2023-12-22 18:02:45 +08:00
耗子 cf4bc94e6d fix: 修复更新脚本问题 2023-12-22 18:02:18 +08:00
耗子 d89f9553e9 chore: 调整编译参数 2023-12-22 17:32:09 +08:00
耗子 e4536360bd fix: 修复 Auth 问题 2023-12-22 17:22:38 +08:00
耗子 8bfef8030c feat: 发布 v2.1.31 2023-12-22 17:17:16 +08:00
耗子 69e972fa5e feat: 发布 v2.1.30 2023-12-22 17:06:19 +08:00
耗子 a33bd8309c feat: 优化面板证书 2023-12-22 16:31:51 +08:00
耗子 dacdfc6d45 feat: 不再自动添加 Alt-Svc 头 2023-12-22 15:49:15 +08:00
耗子 1c4e34e8fe chore: 去掉清华镜像 2023-12-20 23:43:21 +08:00
耗子 c9f650ff66 Merge branch 'renovate/major-all' into 'main'
chore(deps): Update actions/upload-artifact action to v4

See merge request opensource/panel!28
2023-12-20 14:47:13 +00:00
机器人 255f94d588 chore(deps): Update actions/upload-artifact action to v4 2023-12-14 19:03:48 +00:00
耗子 a3aaaa37ef feat: 调整 j 值算法 2023-12-14 12:22:14 +08:00
耗子 f31e3a7d8e fix: tests 2023-12-14 02:15:47 +08:00
耗子 1e462bf0e6 fix: 移除不必要的互斥锁 2023-12-14 02:12:15 +08:00
耗子 1ed79a86b2 refactor: 移动服务到根目录 2023-12-14 02:09:33 +08:00
耗子 0e4d593d6c fix: 优化 .gitignore 2023-12-14 01:45:15 +08:00
耗子 849e6602dd feat: 自动设置面板入口与显示地址信息 2023-12-14 01:42:42 +08:00
耗子 4dc16b1e03 fix: Swagger 仅在 DEBUG 模式可用 2023-12-14 01:27:34 +08:00
耗子 599f3b5cb4 fix: Swagger name 2023-12-14 01:21:39 +08:00
耗子 ccc50ed6ee refactor: 移除返回定义 2023-12-14 01:20:32 +08:00
耗子 8e84d2553f refactor: 分离服务与接口 2023-12-14 01:11:39 +08:00
耗子 d731efb082 feat: 支持 IPV6 和优化数据库位置 2023-12-14 00:51:30 +08:00
耗子 11ad8e524e feat(证书管理): 优化 CloudFlare 使用 AuthToken 2023-12-13 17:14:16 +08:00
耗子 87e975df28 fix: swagger 2023-12-13 12:26:36 +08:00
耗子 00d0cadb03 feat: 发布 v2.1.29 2023-12-10 17:21:58 +08:00
耗子 10db77ffd3 feat: 调整 QUIC 配置 2023-12-10 15:40:00 +08:00
耗子 3c82631d7b feat: 调优 WAF 配置 2023-12-10 14:06:30 +08:00
耗子 cd27c05d1c feat: 调优 OpenResty 配置 2023-12-10 13:25:10 +08:00
耗子 3a8f850a50 Merge remote-tracking branch 'origin/main' 2023-12-08 19:53:00 +08:00
耗子 06e66eaa0a Merge branch 'renovate/major-all' into 'main'
chore(deps): Update actions/setup-go action to v5

See merge request opensource/panel!27
2023-12-07 18:15:47 +00:00
耗子 2fcd3db4a4 feat: 尝试不使用包存储 2023-12-08 01:59:26 +08:00
耗子 2ec74e774b feat: 发布 v2.1.28 2023-12-08 01:38:31 +08:00
耗子 b26f75f3a3 feat: 调整 reuseport 启用位置 2023-12-08 01:38:04 +08:00
耗子 7e685dd43c feat: 发布 v2.1.27 2023-12-08 01:24:07 +08:00
耗子 3d3dcbef8d feat: 更新版本限制 2023-12-08 01:23:47 +08:00
耗子 bc2166ee66 fix: 修复 QUIC 重用端口错误 2023-12-08 01:14:05 +08:00
耗子 7cf577ff5b docs: 吹吹牛逼 2023-12-08 01:08:36 +08:00
耗子 87cdda782e feat: 发布 v2.1.26 2023-12-08 00:11:52 +08:00
耗子 24106288cb feat: QUIC 与 IPV6 实验性支持 2023-12-08 00:11:11 +08:00
机器人 3f258a5f81 chore(deps): Update actions/setup-go action to v5 2023-12-06 16:03:41 +00:00
耗子 ce8addfecd feat: 发布 v2.1.25 2023-12-02 14:24:59 +08:00
耗子 d969360b54 fix: 优化 OpenResty 编译脚本 2023-12-02 14:24:37 +08:00
耗子 1f43a12f81 fix: patch OpenSSL for OpenResty 2023-12-01 22:22:18 +08:00
耗子 930b86a671 feat: 更新 OpenSSL 2023-12-01 21:58:55 +08:00
耗子 790e56003e feat: 发布 v2.1.24 2023-12-01 16:13:19 +08:00
耗子 c38736abb6 fix: 网站配置读取防跨站可能出错 2023-11-30 22:22:04 +08:00
耗子 befad1d3ac fix: 创建网站时创建 PostgreSQL 未设置用户权限 2023-11-30 21:30:19 +08:00
耗子 53d8c2ff6d feat: 发布 v2.1.23 2023-11-29 21:23:46 +08:00
耗子 d07314b5be feat: 优化 supervisor 添加 2023-11-29 21:12:43 +08:00
耗子 f04cafbcee fix: lint 2023-11-29 02:38:06 +08:00
耗子 86199b9eec feat: 替换为自建 Git 2023-11-29 02:29:22 +08:00
耗子 e4e3ad5821 feat: 日志倒序输出 2023-11-29 02:26:23 +08:00
耗子 a80ddfaebc feat: 文件管理接口 2023-11-24 02:49:40 +08:00
耗子 026e1319fe feat: 发布 v2.1.22 2023-11-23 23:39:04 +08:00
耗子 b532064cf0 feat: PHP 83 2023-11-23 23:38:44 +08:00
耗子 f900ada2c6 feat: 优化 supervisor 配置保存 2023-11-23 22:37:21 +08:00
耗子 468b20348a fix: 优化 Rsync 卸载 2023-11-23 22:31:39 +08:00
耗子 862686e4e2 fix: 修复 Rsync secret 读取 2023-11-23 21:48:50 +08:00
耗子 5a3a1e694b feat: Rsync 插件添加配置接口 2023-11-23 21:42:50 +08:00
耗子 a368117e00 feat: Rsync 插件 2023-11-23 21:38:48 +08:00
耗子 596cd68728 feat: 更新前检查临时文件 2023-11-23 16:38:41 +08:00
耗子 d55bb5ffe1 feat: Rsync 插件 2023-11-23 02:56:20 +08:00
耗子 94ff9e2dbc feat: 更新 s3fs 和 supervisor 的升级脚本 2023-11-22 15:44:40 +08:00
耗子 f4cb1a850a fix: 同步框架更新 2023-11-22 15:36:41 +08:00
耗子 7d1acae966 feat: 发布 v2.1.20 2023-11-21 20:37:28 +08:00
耗子 18b524bddc feat: 优化 MySQL 安装 2023-11-21 20:36:54 +08:00
耗子 70596a7906 chore: update framework 2023-11-21 11:55:51 +08:00
耗子 fbf0b7acce fix: mysql_upgrade 只在 57 版本运行 2023-11-21 11:19:46 +08:00
耗子 12ad6e8879 feat: 发布 v2.1.19 2023-11-21 11:13:46 +08:00
耗子 b4ea470099 feat: 优化部分软件多线程编译参数 2023-11-21 11:13:14 +08:00
耗子 bb9f629b8e feat: 发布 v2.1.18 2023-11-21 07:51:20 +08:00
耗子 fb099b1124 fix: 漏了一个 2023-11-21 03:30:55 +08:00
耗子 8584f0809e fix: OpenSSL checksums 错误 2023-11-21 03:29:15 +08:00
耗子 8ae3751f0b fix: OpenSSL checksums 错误 2023-11-21 03:27:10 +08:00
耗子 0e0fbb9a93 feat: 发布 v2.1.17 2023-11-21 02:53:31 +08:00
耗子 7349e027e2 fix: 修复 MySQL checksums 校验 2023-11-21 02:52:39 +08:00
耗子 b5ba1018fa feat: 发布 v2.1.16 2023-11-21 02:27:13 +08:00
耗子 b1479b9051 feat: 修复 Redis 更新 2023-11-21 02:26:55 +08:00
耗子 3a9b2ab10a feat: 发布 v2.1.15 2023-11-21 01:42:01 +08:00
耗子 f61f1fa213 fix: 默认站点无效 2023-11-21 01:36:30 +08:00
耗子 73c0f41e3d feat: 发布 v2.1.14 2023-11-21 00:52:43 +08:00
耗子 361199ddb5 feat: 网站备份管理与上传备份500修复 2023-11-21 00:50:41 +08:00
耗子 a86badc64d fix: lint 2023-11-20 22:29:28 +08:00
耗子 154e0afbc3 fix: lint and test 2023-11-20 22:24:15 +08:00
耗子 58a13f0636 fix: lint and test 2023-11-20 22:15:33 +08:00
耗子 4ab4f273e7 feat: 优化部分命令处理方式 2023-11-20 21:26:02 +08:00
耗子 5e7859f063 feat: 发布 v2.1.13 2023-11-20 18:11:22 +08:00
耗子 50662ecfa1 feat: 添加 OpenResty 默认配置 2023-11-20 18:04:46 +08:00
耗子 007bbb0ce4 feat: 优化 OpenResty 默认页 2023-11-20 01:49:15 +08:00
耗子 6b36e1659c refactor: 使用中间件判断安装状态 2023-11-20 01:20:19 +08:00
耗子 4b4c2c4a0b feat: 更新一批软件 2023-11-19 02:28:19 +08:00
耗子 fc9ca25a46 feat: 优化对 OpenResty 端口的处理 2023-11-19 02:10:39 +08:00
耗子 ff60fb44b7 chore: 更新框架 2023-11-19 02:09:59 +08:00
耗子 37f677bca3 chore: 更新 Issue 模板 2023-11-18 18:09:31 +08:00
耗子 a9000f851c fix(网站管理): 修复一些错误 2023-11-18 18:05:32 +08:00
耗子 437b88eaf2 fix: lint 2023-11-14 02:28:05 +08:00
耗子 12563e0cd2 fix: lint 2023-11-14 02:27:28 +08:00
耗子 f7d65fc22e refactor: 重构 tools.Remove 函数 2023-11-14 02:16:18 +08:00
耗子 83cbae034c refactor: 重构 tools.Read 函数 2023-11-14 02:08:26 +08:00
耗子 2b1b58ea2b refactor: 重构 tools.Exec 函数 2023-11-14 01:54:26 +08:00
耗子 1c5b32a7a9 refactor: 重构 tools.Chown 函数 2023-11-12 03:18:54 +08:00
耗子 edd526d3b8 refactor: 重构 tools.Chmod 函数 2023-11-12 03:12:35 +08:00
耗子 4c41ec036c refactor: 重构 tools.Mkdir 函数 2023-11-12 03:09:41 +08:00
耗子 ac420debbb refactor: 重构 tools.Write 函数 2023-11-12 03:02:45 +08:00
耗子 46e027b18c refactor: 重构网站管理 2023-11-11 03:19:06 +08:00
耗子 fe98a5be1b feat: 更新测试 2023-11-10 01:59:20 +08:00
耗子 63c4e27806 fix: test 2023-11-10 01:49:05 +08:00
耗子 b769032990 feat(面板设置): 修改端口时自动放行 2023-11-10 00:34:44 +08:00
耗子 e56fcac3b8 feat: 发布 v2.1.12 2023-11-09 19:54:02 +08:00
耗子 b0a6405b8e fix: 修复面板入口设置不生效 2023-11-09 19:53:40 +08:00
耗子 43f12873c0 feat: 发布 v2.0.11 2023-11-09 18:30:05 +08:00
耗子 89004f4d04 fix: 更新后脚本 2023-11-09 18:29:33 +08:00
耗子 b0c072f78b feat: 发布 v2.1.10 2023-11-09 17:53:21 +08:00
耗子 8559fb9d0c fix: 修复入口设置 2023-11-09 17:53:01 +08:00
168 changed files with 12519 additions and 6321 deletions
-9
View File
@@ -132,12 +132,3 @@ body:
Anything that can give us more insight into the problem you're having.
validations:
required: false
- type: textarea
id: yellows
attributes:
label: 来点色图 (Porn Pictures)
description: |
如果你不提供色图而你的 Issue 中又有其他不符合标准的地方,那么你的 Issue 可能会被直接关闭。
If you don't provide some porn pictures and there are other non-conformities in your issue, then your issue may be closed directly.
validations:
required: false
@@ -53,12 +53,3 @@ body:
Anything that can help us more with the implementation of this feature.
validations:
required: false
- type: textarea
id: yellows
attributes:
label: 来点色图 (Porn Pictures)
description: |
如果你不提供色图而你的 Issue 中又有其他不符合标准的地方,那么你的 Issue 可能会被直接关闭。
If you don't provide some porn pictures and there are other non-conformities in your issue, then your issue may be closed directly.
validations:
required: false
+3 -3
View File
@@ -13,7 +13,7 @@ jobs:
fail-fast: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
cache: true
go-version: 'stable'
@@ -24,9 +24,9 @@ jobs:
CGO_ENABLED: 0
GOOS: linux
GOARCH: ${{ matrix.goarch }}
run: go build -ldflags '-s -w --extldflags "-static -fpic"' -o panel-${{ matrix.goarch }}
run: go build -ldflags '-s -w --extldflags "-static"' -o panel-${{ matrix.goarch }}
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: panel-${{ matrix.goarch }}
path: panel-${{ matrix.goarch }}
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: 'stable'
- name: Install dependencies
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: 'stable'
- name: Fetch Frontend
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: 'stable'
cache: false
+2 -2
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: 'stable'
- name: Install dependencies
@@ -17,7 +17,7 @@ jobs:
- name: Set up environment
run: |
cp panel-example.conf .env
echo "DB_FILE=database/panel.db" >> .env
echo "DB_FILE=$(pwd)/storage/panel.db" >> .env
go run . artisan key:generate
go run . artisan migrate
- name: Run tests
+4 -4
View File
@@ -44,7 +44,7 @@ unit_test:
- apt-get update
- apt-get install -y curl jq
- cp panel-example.conf .env
- echo "DB_FILE=database/panel.db" >> .env
- echo "DB_FILE=$(pwd)/storage/panel.db" >> .env
- go run . artisan key:generate
- go run . artisan migrate
- go test -v -coverprofile=coverage.txt -covermode=atomic ./...
@@ -54,7 +54,7 @@ build:
extends: .go_cache
script:
- go mod download
- CGO_ENABLED=0 go build -ldflags '-s -w --extldflags "-static -fpic"' -o $OUTPUT_NAME
- CGO_ENABLED=0 go build -ldflags '-s -w --extldflags "-static"' -o $OUTPUT_NAME
artifacts:
name: "$OUTPUT_NAME"
paths:
@@ -65,10 +65,10 @@ fetch:
stage: build
image: alpine:latest
before_script:
- sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
- sed -i 's/dl-cdn.alpinelinux.org/mirror.sjtu.edu.cn/g' /etc/apk/repositories
- apk add --no-cache curl jq unzip zip
script:
- curl -sSL "https://jihulab.com/api/v4/projects/haozi-team%2Fpanel-frontend/releases" | jq -r '.[0].assets.links[] | select(.name | contains("dist")) | .direct_asset_url' | xargs curl -L -o frontend.zip
- curl -sSL "https://git.haozi.net/api/v4/projects/opensource%2Fpanel-frontend/releases" | jq -r '.[0].assets.links[] | select(.name | contains("dist")) | .direct_asset_url' | xargs curl -L -o frontend.zip
- rm -rf public
- unzip frontend.zip
- mv dist public
+3 -3
View File
@@ -14,7 +14,7 @@ builds:
goamd64:
- v2
ldflags:
- -s -w --extldflags "-static -fpic"
- -s -w --extldflags "-static"
archives:
-
id: panel-archive
@@ -33,8 +33,8 @@ archives:
- panel-example.conf
gitlab_urls:
api: https://jihulab.com/api/v4/
download: https://jihulab.com
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
+10 -8
View File
@@ -20,10 +20,12 @@
## 优势
1. **极低占用:** 在 Debian 12 下部署面板 + LNMP 环境,内存占用不到 500 MB,遥遥领先于使用容器化的其他面板。
2. **高效运维:** 面板 UI 界面简洁,操作简单,无需繁琐的配置,即可快速部署各类环境、调整应用设置
3. **离线运行:** 面板运行可不依赖任何外部服务,你甚至可以在部署完成后停止面板进程,不会对已部署服务造成任何影响
4. **久经考验:** 我们生产环境自 2022 年即开始使用,已稳定运行 1 年无事故
5. **开源开放:** 面板开源,你可以自由修改、审计面板源码,安全性有保障
2. **低破坏性:** 面板的设计理念是尽可能减少对系统的额外修改,在同类面板中,我们对系统的修改最少
3. **追随时代:** 面板所有组件均走在时代前沿,更新快,功能强大,安全性有保障
4. **高效运维:** 面板 UI 界面简洁,操作简单,无需繁琐的配置,即可快速部署各类环境、调整应用设置
5. **离线运行:** 面板运行可不依赖任何外部服务,你甚至可以在部署完成后停止面板进程,不会对已部署服务造成任何影响
6. **久经考验:** 我们生产环境自 2022 年即开始使用,已稳定运行 1 年无事故。
7. **开源开放:** 面板开源,你可以自由修改、审计面板源码,安全性有保障。
## UI 截图
@@ -35,7 +37,7 @@
低配机器建议使用 `Debian`,资源占用较 `RHEL` 系更低。其他机器建议使用 `AlmaLinux` | `RockyLinux`,维护周期更长也更稳定。
不在下表中的其他系统OpenCloudOS 8、Anolis 8、CentOS Stream 8/9、Debian 11等),可自行尝试安装,但不保证能够正常运行,且不提供无偿技术支持(理论上不会有大问题)。
不在下表中的其他 RHEL 9.x 系统,可自行尝试安装,但不保证能够正常运行,且不提供无偿技术支持(理论上不会有大问题)。
CentOS Stream 可使用迁移脚本迁移至支持的系统: [CentOS 8/9 迁移脚本](https://github.com/haozi-team/byecentos)
@@ -56,7 +58,7 @@ CentOS Stream 可使用迁移脚本迁移至支持的系统: [CentOS 8/9 迁移
如果你决定继续,请以`root`用户登录服务器,执行以下命令安装面板:
```shell
HAOZI_DL_URL="https://jihulab.com/haozi-team/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://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 验证失败,文件可能被篡改,已终止操作"
```
## 卸载面板
@@ -66,7 +68,7 @@ HAOZI_DL_URL="https://jihulab.com/haozi-team/download/-/raw/main/panel"; curl -s
如果你无法重装系统,请以`root`用户登录服务器,执行以下命令卸载面板:
```shell
HAOZI_DL_URL="https://jihulab.com/haozi-team/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://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 验证失败,文件可能被篡改,已终止操作"
```
卸载面板前请务必备份好所有数据,提前卸载面板全部插件。卸载后数据将**无法恢复**!
@@ -96,7 +98,7 @@ panel
### 创建 PR
- 在开发过程中,如果遇到问题可以随时在 Issue 中详尽描述该问题,以进一步沟通,但在此之前请确保自己已通过 Google 等方式尽可能的尝试解决问题;
- PR 须提交至我们的极狐 GitLab 仓库[https://jihulab.com/haozi-team/panel](https://jihulab.com/haozi-team/panel),勿在 GitHub 上提交;
- PR 须提交至我们的极狐 GitLab 仓库[https://git.haozi.net/opensource/panel](https://git.haozi.net/opensource/panel),勿在 GitHub 上提交;
- 当 PR 开发完毕后,请为其添加 `🚀 Review Ready` 标签,维护者将及时进行评审;
- 我们非常欢迎您的贡献,将在下次发版时将您添加到首页贡献者中;❤️
+10 -8
View File
@@ -20,10 +20,12 @@ Communication QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | QQ
## Advantages
1. **Extremely low usage:** Deploying the panel + LNMP environment under Debian 12, the memory usage is less than 500 MB, far ahead of other panels using containerization.
2. **Efficient operation and maintenance:** The panel UI interface is simple and easy to operate, and you can quickly deploy various environments and adjust application settings without complicated configuration.
3. **Offline operation:** The panel can run without relying on any external services. You can even stop the panel process after deployment is complete, and it will not affect the deployed services.
4. **Tested by time:** We have been using it in production environment since 2022, and it has been running stably for 1 year without any accidents.
5. **Open source and open:** The panel is open source, you can freely modify and audit the panel source code, and the security is guaranteed.
2. **Low destructiveness:** The design concept of the panel is to minimize the additional modifications to the system. Among similar panels, we have made the least modifications to the system.
3. **Follow the times:** All components of the panel are at the forefront of the times, updated quickly, powerful functions, and guaranteed security.
4. **Efficient operation and maintenance:** The panel UI interface is simple and easy to operate, and you can quickly deploy various environments and adjust application settings without complicated configuration.
5. **Offline operation:** The panel can run without relying on any external services. You can even stop the panel process after deployment is complete, and it will not affect the deployed services.
6. **Tested by time:** We have been using it in production environment since 2022, and it has been running stably for 1 year without any accidents.
7. **Open source and open:** The panel is open source, you can freely modify and audit the panel source code, and the security is guaranteed.
## UI Screenshots
@@ -35,7 +37,7 @@ HaoZi Linux Panel only supports the latest version of mainstream systems under t
Recommended to use `Debian` for low-configuration machines, as its resource usage is lower than that of the `RHEL` system. For other machines, recommended to use `RockyLinux` | `AlmaLinux`, which has a longer maintenance cycle and is more stable.
For other systems not in the table below (OpenCloudOS 8, Anolis 8, CentOS Stream 8/9, Debian 11, etc.), you can try to install it yourself, but normal operation is not guaranteed, and free technical support is not provided (theoretically there will be no major question).
For other RHEL 9.x systems not in the table, you can try to install it yourself, but normal operation is not guaranteed, and free technical support is not provided (theoretically there will be no major question).
CentOS Stream can be migrated to a supported system using the migration script: [CentOS 8/9 Migration Script](https://github.com/haozi-team/byecentos)
@@ -56,7 +58,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://jihulab.com/haozi-team/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://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"
```
## Uninstall Panel
@@ -66,7 +68,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://jihulab.com/haozi-team/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://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"
```
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!
@@ -96,7 +98,7 @@ You can find or create an Issue in [Issue List](https://github.com/haozi-team/pa
### 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://jihulab.com/haozi-team/panel](https://jihulab.com/haozi-team/panel). Do not submit on GitHub;
- 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; ❤️
+2 -2
View File
@@ -10,7 +10,7 @@ import (
"github.com/goravel/framework/support/carbon"
"panel/app/models"
"panel/app/services"
"panel/internal/services"
)
type CertRenew struct {
@@ -66,7 +66,7 @@ func (receiver *CertRenew) Handle(ctx console.Context) error {
facades.Log().Tags("面板", "证书管理").With(map[string]any{
"cert_id": cert.ID,
"error": err.Error(),
}).Errorf("证书续签失败")
}).Infof("证书续签失败")
}
}
+3 -3
View File
@@ -11,7 +11,7 @@ import (
"github.com/spf13/cast"
"panel/app/models"
"panel/app/services"
"panel/internal/services"
"panel/pkg/tools"
)
@@ -49,7 +49,7 @@ func (receiver *Monitoring) Handle(ctx console.Context) error {
Info: info,
})
if err != nil {
facades.Log().Errorf("[面板] 系统监控保存失败: %s", err.Error())
facades.Log().Infof("[面板] 系统监控保存失败: %s", err.Error())
color.Redf("[面板] 系统监控保存失败: %s", err.Error())
return nil
}
@@ -69,7 +69,7 @@ func (receiver *Monitoring) Handle(ctx console.Context) error {
}
_, err = facades.Orm().Query().Where("created_at < ?", carbon.Now().SubDays(days).ToDateTimeString()).Delete(&models.Monitor{})
if err != nil {
facades.Log().Errorf("[面板] 系统监控删除过期数据失败: %s", err.Error())
facades.Log().Infof("[面板] 系统监控删除过期数据失败: %s", err.Error())
return nil
}
+91 -23
View File
@@ -15,7 +15,7 @@ import (
"github.com/spf13/cast"
"panel/app/models"
"panel/app/services"
"panel/internal/services"
"panel/pkg/tools"
)
@@ -115,9 +115,11 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Redln("生成密码失败")
return nil
}
user.Username = tools.RandomString(8)
user.Password = hash
if user.Email == "" {
user.Email = tools.RandomString(8) + "@example.com"
}
err = facades.Orm().Query().Save(&user)
if err != nil {
@@ -125,23 +127,48 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
port := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
color.Redln("获取面板端口失败")
return nil
}
ip, err := tools.GetPublicIP()
if err != nil {
ip = "127.0.0.1"
}
protocol := "http"
if facades.Config().GetBool("panel.ssl") {
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"))
case "getPort":
port := tools.Exec("cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}'")
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
color.Redln("获取面板端口失败")
return nil
}
color.Greenln("面板端口: " + port)
case "getEntrance":
color.Greenln("面板入口: " + facades.Config().GetString("http.entrance"))
case "deleteEntrance":
oldEntrance := tools.Exec(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
tools.Exec("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=/!g' /www/panel/panel.conf")
oldEntrance, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
color.Redln("获取面板入口失败")
return nil
}
if _, err = tools.Exec("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=/!g' /www/panel/panel.conf"); err != nil {
color.Redln("删除面板入口失败")
return nil
}
color.Greenln("删除面板入口成功")
@@ -228,7 +255,10 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Greenln(hr)
if !tools.Exists(path) {
tools.Mkdir(path, 0644)
if err := tools.Mkdir(path, 0644); err != nil {
color.Redln("|-创建备份目录失败: " + err.Error())
return nil
}
}
switch backupType {
@@ -242,7 +272,10 @@ func (receiver *Panel) Handle(ctx console.Context) error {
}
backupFile := path + "/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".zip"
tools.Exec(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`)
if _, err := tools.Exec(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`); err != nil {
color.Redln("|-备份失败: " + err.Error())
return nil
}
color.Greenln("|-备份成功")
case "mysql":
@@ -258,14 +291,23 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Greenln("|-目标MySQL数据库: " + name)
color.Greenln("|-开始导出")
tools.Exec(`mysqldump -uroot ` + name + ` > /tmp/` + backupFile + ` 2>&1`)
if _, err = tools.Exec(`mysqldump -uroot ` + name + ` > /tmp/` + backupFile + ` 2>&1`); err != nil {
color.Redln("|-导出失败: " + err.Error())
return nil
}
color.Greenln("|-导出成功")
color.Greenln("|-开始压缩")
tools.Exec("cd /tmp && zip -r " + backupFile + ".zip " + backupFile)
tools.Remove("/tmp/" + backupFile)
if _, err = tools.Exec("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
color.Redln("|-压缩失败: " + err.Error())
return nil
}
if err := tools.Remove("/tmp/" + backupFile); err != nil {
color.Redln("|-删除失败: " + err.Error())
return nil
}
color.Greenln("|-压缩成功")
color.Greenln("|-开始移动")
if _, err := tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
if err := tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
color.Redln("|-移动失败: " + err.Error())
return nil
}
@@ -275,7 +317,12 @@ func (receiver *Panel) Handle(ctx console.Context) error {
case "postgresql":
backupFile := name + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
check := tools.Exec(`su - postgres -c "psql -l" 2>&1`)
check, err := tools.Exec(`su - postgres -c "psql -l" 2>&1`)
if err != nil {
color.Redln("|-获取数据库失败: " + err.Error())
color.Greenln(hr)
return nil
}
if strings.Contains(check, name) {
color.Redln("|-数据库不存在")
color.Greenln(hr)
@@ -284,14 +331,23 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Greenln("|-目标PostgreSQL数据库: " + name)
color.Greenln("|-开始导出")
tools.Exec(`su - postgres -c "pg_dump '` + name + `'" > /tmp/` + backupFile + ` 2>&1`)
if _, err = tools.Exec(`su - postgres -c "pg_dump '` + name + `'" > /tmp/` + backupFile + ` 2>&1`); err != nil {
color.Redln("|-导出失败: " + err.Error())
return nil
}
color.Greenln("|-导出成功")
color.Greenln("|-开始压缩")
tools.Exec("cd /tmp && zip -r " + backupFile + ".zip " + backupFile)
tools.Remove("/tmp/" + backupFile)
if _, err = tools.Exec("cd /tmp && zip -r " + backupFile + ".zip " + backupFile); err != nil {
color.Redln("|-压缩失败: " + err.Error())
return nil
}
if err := tools.Remove("/tmp/" + backupFile); err != nil {
color.Redln("|-删除失败: " + err.Error())
return nil
}
color.Greenln("|-压缩成功")
color.Greenln("|-开始移动")
if _, err := tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
if err := tools.Mv("/tmp/"+backupFile+".zip", path+"/"+backupFile+".zip"); err != nil {
color.Redln("|-移动失败: " + err.Error())
return nil
}
@@ -321,7 +377,10 @@ 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)
tools.Remove(fileToDelete)
if err := tools.Remove(fileToDelete); err != nil {
color.Redln("|-清理失败: " + err.Error())
return nil
}
}
color.Greenln("|-清理完成")
color.Greenln(hr)
@@ -357,8 +416,14 @@ func (receiver *Panel) Handle(ctx console.Context) error {
}
backupPath := "/www/wwwlogs/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".log.zip"
tools.Exec(`cd /www/wwwlogs && zip -r ` + backupPath + ` ` + website.Name + ".log")
tools.Exec(`echo "" > ` + logPath)
if _, err := tools.Exec(`cd /www/wwwlogs && zip -r ` + backupPath + ` ` + website.Name + ".log"); err != nil {
color.Redln("|-备份失败: " + err.Error())
return nil
}
if _, err := tools.Exec(`echo "" > ` + logPath); err != nil {
color.Redln("|-清空失败: " + err.Error())
return nil
}
color.Greenln("|-切割成功")
color.Greenln(hr)
@@ -383,7 +448,10 @@ 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)
tools.Remove(fileToDelete)
if err := tools.Remove(fileToDelete); err != nil {
color.Redln("|-清理失败: " + err.Error())
return nil
}
}
color.Greenln("|-清理完成")
color.Greenln(hr)
@@ -393,8 +461,8 @@ func (receiver *Panel) Handle(ctx console.Context) error {
case "writeSite":
name := arg1
status := cast.ToBool(arg2)
path := ctx.Argument(3)
php := cast.ToInt(ctx.Argument(4))
path := arg3
php := cast.ToInt(arg4)
ssl := cast.ToBool(ctx.Argument(5))
if len(name) == 0 || len(path) == 0 {
color.Redln("参数错误")
+9 -8
View File
@@ -7,12 +7,13 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type AssetController struct {
setting services.Setting
setting internal.Setting
}
func NewAssetController() *AssetController {
@@ -33,7 +34,7 @@ func (r *AssetController) Index(ctx http.Context) http.Response {
}
// 拒绝访问非入口文件
if !strings.HasPrefix(ctx.Request().Path(), entrance) {
return ctx.Response().Status(http.StatusNotFound).String(http.StatusText(http.StatusNotFound))
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
path := strings.TrimPrefix(ctx.Request().Path(), entrance)
@@ -43,21 +44,21 @@ func (r *AssetController) Index(ctx http.Context) http.Response {
}
if !tools.Exists("public" + path) {
return ctx.Response().Status(http.StatusNotFound).String(http.StatusText(http.StatusNotFound))
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
file, err := os.Open("public" + path)
if err != nil {
return ctx.Response().Status(http.StatusInternalServerError).String(http.StatusText(http.StatusInternalServerError))
return Error(ctx, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
stat, err := file.Stat()
if err != nil {
return ctx.Response().Status(http.StatusInternalServerError).String(http.StatusText(http.StatusInternalServerError))
return Error(ctx, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if stat.IsDir() {
return ctx.Response().Status(http.StatusForbidden).String(http.StatusText(http.StatusForbidden))
return Error(ctx, http.StatusForbidden, http.StatusText(http.StatusForbidden))
}
return ctx.Response().Header("Cache-Control", "no-cache").File("public" + path)
@@ -72,5 +73,5 @@ func (r *AssetController) Robots(ctx http.Context) http.Response {
}
func (r *AssetController) NotFound(ctx http.Context) http.Response {
return ctx.Response().Status(http.StatusNotFound).String(http.StatusText(http.StatusNotFound))
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
+233 -248
View File
@@ -6,15 +6,15 @@ import (
requests "panel/app/http/requests/cert"
commonrequests "panel/app/http/requests/common"
responses "panel/app/http/responses/cert"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/acme"
)
type CertController struct {
cron services.Cron
cert services.Cert
cron internal.Cron
cert internal.Cert
}
func NewCertController() *CertController {
@@ -25,14 +25,14 @@ func NewCertController() *CertController {
}
// CAProviders
// @Summary 获取 CA 提供商
// @Description 获取面板证书管理支持的 CA 提供商
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Router /panel/cert/caProviders [get]
//
// @Summary 获取 CA 提供商
// @Description 获取面板证书管理支持的 CA 提供商
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/caProviders [get]
func (r *CertController) CAProviders(ctx http.Context) http.Response {
return Success(ctx, []map[string]string{
{
@@ -59,14 +59,14 @@ func (r *CertController) CAProviders(ctx http.Context) http.Response {
}
// DNSProviders
// @Summary 获取 DNS 提供商
// @Description 获取面板证书管理支持的 DNS 提供商
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Router /panel/cert/dnsProviders [get]
//
// @Summary 获取 DNS 提供商
// @Description 获取面板证书管理支持的 DNS 提供商
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/dnsProviders [get]
func (r *CertController) DNSProviders(ctx http.Context) http.Response {
return Success(ctx, []map[string]any{
{
@@ -85,14 +85,14 @@ func (r *CertController) DNSProviders(ctx http.Context) http.Response {
}
// Algorithms
// @Summary 获取算法列表
// @Description 获取面板证书管理支持的算法列表
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Router /panel/cert/algorithms [get]
//
// @Summary 获取算法列表
// @Description 获取面板证书管理支持的算法列表
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/algorithms [get]
func (r *CertController) Algorithms(ctx http.Context) http.Response {
return Success(ctx, []map[string]any{
{
@@ -115,15 +115,15 @@ func (r *CertController) Algorithms(ctx http.Context) http.Response {
}
// UserList
// @Summary 获取用户列表
// @Description 获取面板证书管理的 ACME 用户列表
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse{data=responses.CertList}
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/users [get]
//
// @Summary 获取用户列表
// @Description 获取面板证书管理的 ACME 用户列表
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param data body commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/users [get]
func (r *CertController) UserList(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := Sanitize(ctx, &paginateRequest)
@@ -137,28 +137,27 @@ func (r *CertController) UserList(ctx http.Context) http.Response {
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"error": err.Error(),
}).Error("获取ACME用户列表失败")
}).Info("获取ACME用户列表失败")
return ErrorSystem(ctx)
}
return Success(ctx, &responses.UserList{
Total: total,
Items: users,
return Success(ctx, http.Json{
"total": total,
"items": users,
})
}
// UserStore
// @Summary 添加 ACME 用户
// @Description 添加 ACME 用户到面板证书管理
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.UserStore true "用户信息"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/users [post]
//
// @Summary 添加 ACME 用户
// @Description 添加 ACME 用户到面板证书管理
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.UserStore true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/users [post]
func (r *CertController) UserStore(ctx http.Context) http.Response {
var storeRequest requests.UserStore
sanitize := Sanitize(ctx, &storeRequest)
@@ -170,7 +169,7 @@ func (r *CertController) UserStore(ctx http.Context) http.Response {
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"error": err.Error(),
}).Error("添加ACME用户失败")
}).Info("添加ACME用户失败")
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -178,18 +177,17 @@ func (r *CertController) UserStore(ctx http.Context) http.Response {
}
// UserUpdate
// @Summary 更新 ACME 用户
// @Description 更新面板证书管理的 ACME 用户
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "用户 ID"
// @Param data body requests.UserUpdate true "用户信息"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/users/{id} [put]
//
// @Summary 更新 ACME 用户
// @Description 更新面板证书管理的 ACME 用户
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "用户 ID"
// @Param data body requests.UserUpdate true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/users/{id} [put]
func (r *CertController) UserUpdate(ctx http.Context) http.Response {
var updateRequest requests.UserUpdate
sanitize := Sanitize(ctx, &updateRequest)
@@ -202,7 +200,7 @@ func (r *CertController) UserUpdate(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"userID": updateRequest.ID,
"error": err.Error(),
}).Error("更新ACME用户失败")
}).Info("更新ACME用户失败")
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -210,16 +208,15 @@ func (r *CertController) UserUpdate(ctx http.Context) http.Response {
}
// UserShow
// @Summary 获取 ACME 用户
// @Description 获取面板证书管理的 ACME 用户
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param id path int true "用户 ID"
// @Success 200 {object} SuccessResponse{data=models.CertUser}
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/users/{id} [get]
//
// @Summary 获取 ACME 用户
// @Description 获取面板证书管理的 ACME 用户
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param id path int true "用户 ID"
// @Success 200 {object} SuccessResponse{data=models.CertUser}
// @Router /panel/cert/users/{id} [get]
func (r *CertController) UserShow(ctx http.Context) http.Response {
var showAndDestroyRequest requests.UserShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
@@ -232,7 +229,7 @@ func (r *CertController) UserShow(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"userID": showAndDestroyRequest.ID,
"error": err.Error(),
}).Error("获取ACME用户失败")
}).Info("获取ACME用户失败")
return ErrorSystem(ctx)
}
@@ -240,17 +237,16 @@ func (r *CertController) UserShow(ctx http.Context) http.Response {
}
// UserDestroy
// @Summary 删除 ACME 用户
// @Description 删除面板证书管理的 ACME 用户
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "用户 ID"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/users/{id} [delete]
//
// @Summary 删除 ACME 用户
// @Description 删除面板证书管理的 ACME 用户
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "用户 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/users/{id} [delete]
func (r *CertController) UserDestroy(ctx http.Context) http.Response {
var showAndDestroyRequest requests.UserShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
@@ -263,7 +259,7 @@ func (r *CertController) UserDestroy(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"userID": showAndDestroyRequest.ID,
"error": err.Error(),
}).Error("删除ACME用户失败")
}).Info("删除ACME用户失败")
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -271,15 +267,15 @@ func (r *CertController) UserDestroy(ctx http.Context) http.Response {
}
// DNSList
// @Summary 获取 DNS 接口列表
// @Description 获取面板证书管理的 DNS 接口列表
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse{data=responses.DNSList}
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/dns [get]
//
// @Summary 获取 DNS 接口列表
// @Description 获取面板证书管理的 DNS 接口列表
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param data body commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/dns [get]
func (r *CertController) DNSList(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := Sanitize(ctx, &paginateRequest)
@@ -293,28 +289,27 @@ func (r *CertController) DNSList(ctx http.Context) http.Response {
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"error": err.Error(),
}).Error("获取DNS接口列表失败")
}).Info("获取DNS接口列表失败")
return ErrorSystem(ctx)
}
return Success(ctx, &responses.DNSList{
Total: total,
Items: dns,
return Success(ctx, http.Json{
"total": total,
"items": dns,
})
}
// DNSStore
// @Summary 添加 DNS 接口
// @Description 添加 DNS 接口到面板证书管理
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.DNSStore true "DNS 接口信息"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/dns [post]
//
// @Summary 添加 DNS 接口
// @Description 添加 DNS 接口到面板证书管理
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.DNSStore true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/dns [post]
func (r *CertController) DNSStore(ctx http.Context) http.Response {
var storeRequest requests.DNSStore
sanitize := Sanitize(ctx, &storeRequest)
@@ -326,7 +321,7 @@ func (r *CertController) DNSStore(ctx http.Context) http.Response {
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"error": err.Error(),
}).Error("添加DNS接口失败")
}).Info("添加DNS接口失败")
return ErrorSystem(ctx)
}
@@ -334,16 +329,15 @@ func (r *CertController) DNSStore(ctx http.Context) http.Response {
}
// DNSShow
// @Summary 获取 DNS 接口
// @Description 获取面板证书管理的 DNS 接口
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param id path int true "DNS 接口 ID"
// @Success 200 {object} SuccessResponse{data=models.CertDNS}
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/dns/{id} [get]
//
// @Summary 获取 DNS 接口
// @Description 获取面板证书管理的 DNS 接口
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param id path int true "DNS 接口 ID"
// @Success 200 {object} SuccessResponse{data=models.CertDNS}
// @Router /panel/cert/dns/{id} [get]
func (r *CertController) DNSShow(ctx http.Context) http.Response {
var showAndDestroyRequest requests.DNSShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
@@ -356,7 +350,7 @@ func (r *CertController) DNSShow(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"dnsID": showAndDestroyRequest.ID,
"error": err.Error(),
}).Error("获取DNS接口失败")
}).Info("获取DNS接口失败")
return ErrorSystem(ctx)
}
@@ -364,18 +358,17 @@ func (r *CertController) DNSShow(ctx http.Context) http.Response {
}
// DNSUpdate
// @Summary 更新 DNS 接口
// @Description 更新面板证书管理的 DNS 接口
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "DNS 接口 ID"
// @Param data body requests.DNSUpdate true "DNS 接口信息"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/dns/{id} [put]
//
// @Summary 更新 DNS 接口
// @Description 更新面板证书管理的 DNS 接口
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "DNS 接口 ID"
// @Param data body requests.DNSUpdate true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/dns/{id} [put]
func (r *CertController) DNSUpdate(ctx http.Context) http.Response {
var updateRequest requests.DNSUpdate
sanitize := Sanitize(ctx, &updateRequest)
@@ -388,7 +381,7 @@ func (r *CertController) DNSUpdate(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"dnsID": updateRequest.ID,
"error": err.Error(),
}).Error("更新DNS接口失败")
}).Info("更新DNS接口失败")
return ErrorSystem(ctx)
}
@@ -396,17 +389,16 @@ func (r *CertController) DNSUpdate(ctx http.Context) http.Response {
}
// DNSDestroy
// @Summary 删除 DNS 接口
// @Description 删除面板证书管理的 DNS 接口
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "DNS 接口 ID"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/dns/{id} [delete]
//
// @Summary 删除 DNS 接口
// @Description 删除面板证书管理的 DNS 接口
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "DNS 接口 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/dns/{id} [delete]
func (r *CertController) DNSDestroy(ctx http.Context) http.Response {
var showAndDestroyRequest requests.DNSShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
@@ -419,7 +411,7 @@ func (r *CertController) DNSDestroy(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"dnsID": showAndDestroyRequest.ID,
"error": err.Error(),
}).Error("删除DNS接口失败")
}).Info("删除DNS接口失败")
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -427,15 +419,15 @@ func (r *CertController) DNSDestroy(ctx http.Context) http.Response {
}
// CertList
// @Summary 获取证书列表
// @Description 获取面板证书管理的证书列表
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse{data=responses.CertList}
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/certs [get]
//
// @Summary 获取证书列表
// @Description 获取面板证书管理的证书列表
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param data body commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/certs [get]
func (r *CertController) CertList(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := Sanitize(ctx, &paginateRequest)
@@ -449,28 +441,27 @@ func (r *CertController) CertList(ctx http.Context) http.Response {
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"error": err.Error(),
}).Error("获取证书列表失败")
}).Info("获取证书列表失败")
return ErrorSystem(ctx)
}
return Success(ctx, &responses.CertList{
Total: total,
Items: certs,
return Success(ctx, http.Json{
"total": total,
"items": certs,
})
}
// CertStore
// @Summary 添加证书
// @Description 添加证书到面板证书管理
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.CertStore true "证书信息"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/certs [post]
//
// @Summary 添加证书
// @Description 添加证书到面板证书管理
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.CertStore true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/certs [post]
func (r *CertController) CertStore(ctx http.Context) http.Response {
var storeRequest requests.CertStore
sanitize := Sanitize(ctx, &storeRequest)
@@ -482,7 +473,7 @@ func (r *CertController) CertStore(ctx http.Context) http.Response {
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"error": err.Error(),
}).Error("添加证书失败")
}).Info("添加证书失败")
return ErrorSystem(ctx)
}
@@ -490,18 +481,17 @@ func (r *CertController) CertStore(ctx http.Context) http.Response {
}
// CertUpdate
// @Summary 更新证书
// @Description 更新面板证书管理的证书
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "证书 ID"
// @Param data body requests.CertUpdate true "证书信息"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/certs/{id} [put]
//
// @Summary 更新证书
// @Description 更新面板证书管理的证书
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "证书 ID"
// @Param data body requests.CertUpdate true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/certs/{id} [put]
func (r *CertController) CertUpdate(ctx http.Context) http.Response {
var updateRequest requests.CertUpdate
sanitize := Sanitize(ctx, &updateRequest)
@@ -514,7 +504,7 @@ func (r *CertController) CertUpdate(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"certID": updateRequest.ID,
"error": err.Error(),
}).Error("更新证书失败")
}).Info("更新证书失败")
return ErrorSystem(ctx)
}
@@ -522,16 +512,15 @@ func (r *CertController) CertUpdate(ctx http.Context) http.Response {
}
// CertShow
// @Summary 获取证书
// @Description 获取面板证书管理的证书
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param id path int true "证书 ID"
// @Success 200 {object} SuccessResponse{data=models.Cert}
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/certs/{id} [get]
//
// @Summary 获取证书
// @Description 获取面板证书管理的证书
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param id path int true "证书 ID"
// @Success 200 {object} SuccessResponse{data=models.Cert}
// @Router /panel/cert/certs/{id} [get]
func (r *CertController) CertShow(ctx http.Context) http.Response {
var showAndDestroyRequest requests.CertShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
@@ -544,7 +533,7 @@ func (r *CertController) CertShow(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"certID": showAndDestroyRequest.ID,
"error": err.Error(),
}).Error("获取证书失败")
}).Info("获取证书失败")
return ErrorSystem(ctx)
}
@@ -552,17 +541,16 @@ func (r *CertController) CertShow(ctx http.Context) http.Response {
}
// CertDestroy
// @Summary 删除证书
// @Description 删除面板证书管理的证书
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "证书 ID"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/certs/{id} [delete]
//
// @Summary 删除证书
// @Description 删除面板证书管理的证书
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "证书 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/certs/{id} [delete]
func (r *CertController) CertDestroy(ctx http.Context) http.Response {
var showAndDestroyRequest requests.CertShowAndDestroy
sanitize := Sanitize(ctx, &showAndDestroyRequest)
@@ -575,7 +563,7 @@ func (r *CertController) CertDestroy(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"certID": showAndDestroyRequest.ID,
"error": err.Error(),
}).Error("删除证书失败")
}).Info("删除证书失败")
return ErrorSystem(ctx)
}
@@ -583,17 +571,16 @@ func (r *CertController) CertDestroy(ctx http.Context) http.Response {
}
// Obtain
// @Summary 签发证书
// @Description 签发面板证书管理的证书
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Obtain true "证书信息"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/obtain [post]
//
// @Summary 签发证书
// @Description 签发面板证书管理的证书
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Obtain true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/obtain [post]
func (r *CertController) Obtain(ctx http.Context) http.Response {
var obtainRequest requests.Obtain
sanitize := Sanitize(ctx, &obtainRequest)
@@ -606,7 +593,7 @@ func (r *CertController) Obtain(ctx http.Context) http.Response {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"certID": obtainRequest.ID,
"error": err.Error(),
}).Error("获取证书失败")
}).Info("获取证书失败")
return ErrorSystem(ctx)
}
@@ -618,7 +605,7 @@ func (r *CertController) Obtain(ctx http.Context) http.Response {
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"error": err.Error(),
}).Error("签发证书失败")
}).Info("签发证书失败")
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -626,17 +613,16 @@ func (r *CertController) Obtain(ctx http.Context) http.Response {
}
// Renew
// @Summary 续签证书
// @Description 续签面板证书管理的证书
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Renew true "证书信息"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/renew [post]
//
// @Summary 续签证书
// @Description 续签面板证书管理的证书
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Renew true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/renew [post]
func (r *CertController) Renew(ctx http.Context) http.Response {
var renewRequest requests.Renew
sanitize := Sanitize(ctx, &renewRequest)
@@ -648,7 +634,7 @@ func (r *CertController) Renew(ctx http.Context) http.Response {
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"error": err.Error(),
}).Error("续签证书失败")
}).Info("续签证书失败")
return Error(ctx, http.StatusInternalServerError, err.Error())
}
@@ -656,17 +642,16 @@ func (r *CertController) Renew(ctx http.Context) http.Response {
}
// ManualDNS
// @Summary 获取手动 DNS 记录
// @Description 获取签发证书所需的 DNS 记录
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Obtain true "证书信息"
// @Success 200 {object} SuccessResponse{data=map[string]acme.Resolve}
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/cert/manualDNS [post]
//
// @Summary 获取手动 DNS 记录
// @Description 获取签发证书所需的 DNS 记录
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Obtain true "request"
// @Success 200 {object} SuccessResponse{data=map[string]acme.Resolve}
// @Router /panel/cert/manualDNS [post]
func (r *CertController) ManualDNS(ctx http.Context) http.Response {
var obtainRequest requests.Obtain
sanitize := Sanitize(ctx, &obtainRequest)
@@ -678,7 +663,7 @@ func (r *CertController) ManualDNS(ctx http.Context) http.Response {
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"error": err.Error(),
}).Error("获取手动DNS记录失败")
}).Info("获取手动DNS记录失败")
return Error(ctx, http.StatusInternalServerError, err.Error())
}
+1 -50
View File
@@ -1,12 +1,7 @@
package controllers
import (
"sync"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"panel/app/services"
)
// SuccessResponse 通用成功响应
@@ -35,7 +30,7 @@ func Success(ctx http.Context, data any) http.Response {
func Error(ctx http.Context, code int, message string) http.Response {
return ctx.Response().Json(http.StatusOK, &ErrorResponse{
Code: code,
Message: message,
Message: "错误: " + message,
})
}
@@ -59,47 +54,3 @@ func Sanitize(ctx http.Context, request http.FormRequest) http.Response {
return nil
}
// Check 检查插件是否可用
func Check(ctx http.Context, slug string) http.Response {
plugin := services.NewPluginImpl().GetBySlug(slug)
installedPlugin := services.NewPluginImpl().GetInstalledBySlug(slug)
installedPlugins, err := services.NewPluginImpl().AllInstalled()
if err != nil {
facades.Log().Error("[面板][插件] 获取已安装插件失败")
return ErrorSystem(ctx)
}
if installedPlugin.Version != plugin.Version || installedPlugin.Slug != plugin.Slug {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要更新至 "+plugin.Version+" 版本")
}
var lock sync.RWMutex
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
lock.Lock()
pluginsMap[p.Slug] = true
lock.Unlock()
}
for _, require := range plugin.Requires {
lock.RLock()
_, requireFound := pluginsMap[require]
lock.RUnlock()
if !requireFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
}
}
for _, exclude := range plugin.Excludes {
lock.RLock()
_, excludeFound := pluginsMap[exclude]
lock.RUnlock()
if excludeFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
}
}
return nil
}
+72 -41
View File
@@ -8,14 +8,16 @@ import (
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/carbon"
"github.com/spf13/cast"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type CronController struct {
cron services.Cron
setting services.Setting
cron internal.Cron
setting internal.Setting
}
func NewCronController() *CronController {
@@ -26,7 +28,7 @@ func NewCronController() *CronController {
}
// List 获取计划任务列表
func (c *CronController) List(ctx http.Context) http.Response {
func (r *CronController) List(ctx http.Context) http.Response {
limit := ctx.Request().QueryInt("limit", 10)
page := ctx.Request().QueryInt("page", 1)
@@ -34,7 +36,9 @@ func (c *CronController) List(ctx http.Context) http.Response {
var total int64
err := facades.Orm().Query().Paginate(page, limit, &crons, &total)
if err != nil {
facades.Log().Error("[面板][CronController] 查询计划任务列表失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "计划任务").With(map[string]any{
"error": err.Error(),
}).Info("查询计划任务列表失败")
return ErrorSystem(ctx)
}
@@ -45,7 +49,7 @@ func (c *CronController) List(ctx http.Context) http.Response {
}
// Add 添加计划任务
func (c *CronController) Add(ctx http.Context) http.Response {
func (r *CronController) Add(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|min_len:1|max_len:255",
"time": "required",
@@ -75,7 +79,7 @@ func (c *CronController) Add(ctx http.Context) http.Response {
}
backupPath := ctx.Request().Input("backup_path")
if len(backupPath) == 0 {
backupPath = c.setting.Get(models.SettingKeyBackupPath) + "/" + backupType
backupPath = r.setting.Get(models.SettingKeyBackupPath) + "/" + backupType
}
backupSave := ctx.Request().InputInt("save", 10)
shell = `#!/bin/bash
@@ -111,19 +115,18 @@ panel cutoff ${name} ${save} 2>&1
shellDir := "/www/server/cron/"
shellLogDir := "/www/server/cron/logs/"
if !tools.Exists(shellDir) {
facades.Log().Error("[面板][CronController] 计划任务目录不存在")
return Error(ctx, http.StatusInternalServerError, "计划任务目录不存在")
}
if !tools.Exists(shellLogDir) {
facades.Log().Error("[面板][CronController] 计划任务日志目录不存在")
return Error(ctx, http.StatusInternalServerError, "计划任务日志目录不存在")
}
shellFile := strconv.Itoa(int(carbon.Now().Timestamp())) + tools.RandomString(16)
if !tools.Write(shellDir+shellFile+".sh", shell, 0700) {
facades.Log().Error("[面板][CronController] 创建计划任务脚本失败 ", err)
return ErrorSystem(ctx)
if err = tools.Write(shellDir+shellFile+".sh", shell, 0700); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("dos2unix " + shellDir + shellFile + ".sh"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("dos2unix " + shellDir + shellFile + ".sh")
var cron models.Cron
cron.Name = ctx.Request().Input("name")
@@ -135,11 +138,15 @@ panel cutoff ${name} ${save} 2>&1
err = facades.Orm().Query().Create(&cron)
if err != nil {
facades.Log().Error("[面板][CronController] 创建计划任务失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "计划任务").With(map[string]any{
"error": err.Error(),
}).Info("保存计划任务失败")
return ErrorSystem(ctx)
}
c.cron.AddToSystem(cron)
if err := r.cron.AddToSystem(cron); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, http.Json{
"id": cron.ID,
@@ -147,18 +154,23 @@ panel cutoff ${name} ${save} 2>&1
}
// Script 获取脚本内容
func (c *CronController) Script(ctx http.Context) http.Response {
func (r *CronController) Script(ctx http.Context) http.Response {
var cron models.Cron
err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
if err != nil {
return Error(ctx, http.StatusUnprocessableEntity, "计划任务不存在")
}
return Success(ctx, tools.Read(cron.Shell))
shell, err := tools.Read(cron.Shell)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, shell)
}
// Update 更新计划任务
func (c *CronController) Update(ctx http.Context) http.Response {
func (r *CronController) Update(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|min_len:1|max_len:255",
"time": "required",
@@ -190,38 +202,49 @@ func (c *CronController) Update(ctx http.Context) http.Response {
cron.Name = ctx.Request().Input("name")
err = facades.Orm().Query().Save(&cron)
if err != nil {
facades.Log().Error("[面板][CronController] 更新计划任务失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "计划任务").With(map[string]any{
"error": err.Error(),
}).Info("更新计划任务失败")
return ErrorSystem(ctx)
}
if !tools.Write(cron.Shell, ctx.Request().Input("script"), 0644) {
facades.Log().Error("[面板][CronController] 更新计划任务脚本失败 ", err)
return ErrorSystem(ctx)
if err = tools.Write(cron.Shell, ctx.Request().Input("script"), 0644); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("dos2unix " + cron.Shell); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("dos2unix " + cron.Shell)
c.cron.DeleteFromSystem(cron)
if err := r.cron.DeleteFromSystem(cron); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if cron.Status {
c.cron.AddToSystem(cron)
if err := r.cron.AddToSystem(cron); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
}
return Success(ctx, nil)
}
// Delete 删除计划任务
func (c *CronController) Delete(ctx http.Context) http.Response {
func (r *CronController) Delete(ctx http.Context) http.Response {
var cron models.Cron
err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
if err != nil {
if err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron); err != nil {
return Error(ctx, http.StatusUnprocessableEntity, "计划任务不存在")
}
c.cron.DeleteFromSystem(cron)
tools.Remove(cron.Shell)
if err := r.cron.DeleteFromSystem(cron); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.Remove(cron.Shell); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
_, err = facades.Orm().Query().Delete(&cron)
if err != nil {
facades.Log().Error("[面板][CronController] 删除计划任务失败 ", err)
if _, err := facades.Orm().Query().Delete(&cron); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "计划任务").With(map[string]any{
"error": err.Error(),
}).Info("删除计划任务失败")
return ErrorSystem(ctx)
}
@@ -229,7 +252,7 @@ func (c *CronController) Delete(ctx http.Context) http.Response {
}
// Status 更新计划任务状态
func (c *CronController) Status(ctx http.Context) http.Response {
func (r *CronController) Status(ctx http.Context) http.Response {
validator, err := ctx.Request().Validate(map[string]string{
"status": "bool",
})
@@ -249,23 +272,28 @@ func (c *CronController) Status(ctx http.Context) http.Response {
cron.Status = ctx.Request().InputBool("status")
err = facades.Orm().Query().Save(&cron)
if err != nil {
facades.Log().Error("[面板][CronController] 更新计划任务状态失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "计划任务").With(map[string]any{
"error": err.Error(),
}).Info("更新计划任务状态失败")
return ErrorSystem(ctx)
}
c.cron.DeleteFromSystem(cron)
if err := r.cron.DeleteFromSystem(cron); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if cron.Status {
c.cron.AddToSystem(cron)
if err := r.cron.AddToSystem(cron); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
}
return Success(ctx, nil)
}
// Log 获取计划任务日志
func (c *CronController) Log(ctx http.Context) http.Response {
func (r *CronController) Log(ctx http.Context) http.Response {
var cron models.Cron
err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron)
if err != nil {
if err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&cron); err != nil {
return Error(ctx, http.StatusUnprocessableEntity, "计划任务不存在")
}
@@ -273,7 +301,10 @@ func (c *CronController) Log(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "日志文件不存在")
}
log := tools.Exec("tail -n 1000 " + cron.Log)
log, err := tools.Exec("tail -n 1000 " + cron.Log)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, log)
}
+450 -4
View File
@@ -1,11 +1,457 @@
package controllers
import (
"os"
"path/filepath"
"strings"
"github.com/goravel/framework/contracts/http"
requests "panel/app/http/requests/file"
"panel/pkg/tools"
)
type FileController struct {
// Dependent services
}
func NewFileController() *FileController {
return &FileController{
// Inject services
}
return &FileController{}
}
// Create
//
// @Summary 创建文件/目录
// @Description 创建文件/目录到给定路径
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.NotExist true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/create [post]
func (r *FileController) Create(ctx http.Context) http.Response {
var request requests.NotExist
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
isDir := ctx.Request().InputBool("dir")
if !isDir {
if out, err := tools.Exec("touch " + request.Path); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if err := tools.Mkdir(request.Path, 0755); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
}
if err := tools.Chmod(request.Path, 0755); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.Chown(request.Path, "www", "www"); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
// Content
//
// @Summary 获取文件内容
// @Description 获取给定路径的文件内容
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data query requests.Exist true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/content [get]
func (r *FileController) Content(ctx http.Context) http.Response {
var request requests.Exist
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
content, err := tools.Read(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, content)
}
// Save
//
// @Summary 保存文件内容
// @Description 保存给定路径的文件内容
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Exist true "request"
// @Param content body string true "content"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/save [post]
func (r *FileController) Save(ctx http.Context) http.Response {
var request requests.Exist
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
content := ctx.Request().Input("content")
fileInfo, err := tools.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.Write(request.Path, content, fileInfo.Mode()); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
// Delete
//
// @Summary 删除文件/目录
// @Description 删除给定路径的文件/目录
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Exist true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/delete [post]
func (r *FileController) Delete(ctx http.Context) http.Response {
var request requests.Exist
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
if err := tools.Remove(request.Path); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
// Upload
//
// @Summary 上传文件
// @Description 上传文件到给定路径
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.NotExist true "request"
// @Param file formData file true "file"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/upload [post]
func (r *FileController) Upload(ctx http.Context) http.Response {
var request requests.NotExist
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
pathInfo, err := tools.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if !pathInfo.IsDir() {
return Error(ctx, http.StatusInternalServerError, "目标路径不是目录")
}
file, err := ctx.Request().File("file")
if err != nil {
return Error(ctx, http.StatusInternalServerError, "上传文件失败")
}
_, err = file.Store(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
// Move
//
// @Summary 移动文件/目录
// @Description 移动文件/目录到给定路径,等效于重命名
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Move true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/move [post]
func (r *FileController) Move(ctx http.Context) http.Response {
var request requests.Move
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
if tools.Exists(request.New) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusInternalServerError, "目标路径已存在,是否覆盖?")
}
if err := tools.Mv(request.Old, request.New); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
// Copy
//
// @Summary 复制文件/目录
// @Description 复制文件/目录到给定路径
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Copy true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/copy [post]
func (r *FileController) Copy(ctx http.Context) http.Response {
var request requests.Copy
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
if tools.Exists(request.New) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusInternalServerError, "目标路径已存在,是否覆盖?")
}
if err := tools.Cp(request.Old, request.New); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
// Download
//
// @Summary 下载文件
// @Description 下载给定路径的文件
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data query requests.NotExist true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/download [get]
func (r *FileController) Download(ctx http.Context) http.Response {
var request requests.NotExist
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
info, err := tools.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if info.IsDir() {
return Error(ctx, http.StatusInternalServerError, "不能下载目录")
}
return ctx.Response().Download(request.Path, info.Name())
}
// RemoteDownload
//
// @Summary 下载远程文件
// @Description 下载远程文件到给定路径
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.NotExist true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/remoteDownload [post]
func (r *FileController) RemoteDownload(ctx http.Context) http.Response {
var request requests.NotExist
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
// TODO 使用异步任务下载文件
return nil
}
// Info
//
// @Summary 获取文件/目录信息
// @Description 获取给定路径的文件/目录信息
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data query requests.Exist true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/info [get]
func (r *FileController) Info(ctx http.Context) http.Response {
var request requests.Exist
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
fileInfo, err := tools.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, fileInfo)
}
// Permission
//
// @Summary 修改文件/目录权限
// @Description 修改给定路径的文件/目录权限
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Permission true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/permission [post]
func (r *FileController) Permission(ctx http.Context) http.Response {
var request requests.Permission
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
if err := tools.Chmod(request.Path, os.FileMode(request.Mode)); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
// Archive
//
// @Summary 压缩文件/目录
// @Description 压缩文件/目录到给定路径
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Archive true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/archive [post]
func (r *FileController) Archive(ctx http.Context) http.Response {
var request requests.Archive
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
if err := tools.Archive(request.Paths, request.File); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
// UnArchive
//
// @Summary 解压文件/目录
// @Description 解压文件/目录到给定路径
// @Tags 文件管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.UnArchive true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/unArchive [post]
func (r *FileController) UnArchive(ctx http.Context) http.Response {
var request requests.UnArchive
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
if err := tools.UnArchive(request.File, request.Path); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
// Search
//
// @Summary 搜索文件/目录
// @Description 通过关键词搜索给定路径的文件/目录
// @Tags 文件管理
// @Accept json
// @Produce json
// @Param data body requests.Search true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/search [post]
func (r *FileController) Search(ctx http.Context) http.Response {
var request requests.Search
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
paths := make(map[string]os.FileInfo)
err := filepath.Walk(request.Path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if strings.Contains(info.Name(), request.KeyWord) {
paths[path] = info
}
return nil
})
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, paths)
}
// List
//
// @Summary 获取文件/目录列表
// @Description 获取给定路径的文件/目录列表
// @Tags 文件管理
// @Accept json
// @Produce json
// @Param data query requests.Exist true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/list [get]
func (r *FileController) List(ctx http.Context) http.Response {
var request requests.Exist
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
paths := make(map[string]os.FileInfo)
err := filepath.Walk(request.Path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
paths[path] = info
return nil
})
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, paths)
}
+49 -51
View File
@@ -10,7 +10,8 @@ import (
"github.com/goravel/framework/facades"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
@@ -22,8 +23,8 @@ type MenuItem struct {
}
type InfoController struct {
plugin services.Plugin
setting services.Setting
plugin internal.Plugin
setting internal.Setting
}
func NewInfoController() *InfoController {
@@ -34,25 +35,20 @@ func NewInfoController() *InfoController {
}
// Name 获取面板名称
func (c *InfoController) Name(ctx http.Context) http.Response {
var setting models.Setting
err := facades.Orm().Query().Where("key", "name").First(&setting)
if err != nil {
facades.Log().Error("[面板][InfoController] 查询面板名称失败 ", err)
return ErrorSystem(ctx)
}
func (r *InfoController) Name(ctx http.Context) http.Response {
return Success(ctx, http.Json{
"name": setting.Value,
"name": r.setting.Get(models.SettingKeyName),
})
}
// HomePlugins 获取首页插件
func (c *InfoController) HomePlugins(ctx http.Context) http.Response {
func (r *InfoController) HomePlugins(ctx http.Context) http.Response {
var plugins []models.Plugin
err := facades.Orm().Query().Where("show", 1).Find(&plugins)
if err != nil {
facades.Log().Error("[面板][InfoController] 查询首页插件失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "基础信息").With(map[string]any{
"error": err.Error(),
}).Info("获取首页插件失败")
return ErrorSystem(ctx)
}
@@ -65,7 +61,7 @@ func (c *InfoController) HomePlugins(ctx http.Context) http.Response {
for _, plugin := range plugins {
pluginsJson = append(pluginsJson, pluginsData{
Plugin: plugin,
Name: services.NewPluginImpl().GetBySlug(plugin.Slug).Name,
Name: r.plugin.GetBySlug(plugin.Slug).Name,
})
}
@@ -73,12 +69,12 @@ func (c *InfoController) HomePlugins(ctx http.Context) http.Response {
}
// NowMonitor 获取当前监控信息
func (c *InfoController) NowMonitor(ctx http.Context) http.Response {
func (r *InfoController) NowMonitor(ctx http.Context) http.Response {
return Success(ctx, tools.GetMonitoringInfo())
}
// SystemInfo 获取系统信息
func (c *InfoController) SystemInfo(ctx http.Context) http.Response {
func (r *InfoController) SystemInfo(ctx http.Context) http.Response {
monitorInfo := tools.GetMonitoringInfo()
return Success(ctx, http.Json{
@@ -89,7 +85,7 @@ func (c *InfoController) SystemInfo(ctx http.Context) http.Response {
}
// CountInfo 获取面板统计信息
func (c *InfoController) CountInfo(ctx http.Context) http.Response {
func (r *InfoController) CountInfo(ctx http.Context) http.Response {
var websiteCount int64
err := facades.Orm().Query().Model(models.Website{}).Count(&websiteCount)
if err != nil {
@@ -110,26 +106,26 @@ func (c *InfoController) CountInfo(ctx http.Context) http.Response {
}
var databaseCount int64
if mysqlInstalled {
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if status == "active" {
rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword)
status, err := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if status == "active" && err == nil {
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 {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "基础信息").With(map[string]any{
"error": err.Error(),
}).Error("[面板][InfoController] 获取数据库列表失败")
}).Info("获取数据库列表失败")
databaseCount = -1
} else {
defer db.Close()
rows, err := db.Query("SHOW DATABASES")
if err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "基础信息").With(map[string]any{
"error": err.Error(),
}).Error("[面板][InfoController] 获取数据库列表失败")
}).Info("获取数据库列表失败")
databaseCount = -1
} else {
defer rows.Close()
@@ -152,29 +148,31 @@ func (c *InfoController) CountInfo(ctx http.Context) http.Response {
}
}
if postgresqlInstalled {
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if status == "active" {
raw := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
databases := strings.Split(raw, "\n")
if len(databases) >= 4 {
databases = databases[3 : len(databases)-1]
for _, db := range databases {
parts := strings.Split(db, "|")
if len(parts) != 9 || len(strings.TrimSpace(parts[0])) == 0 || strings.TrimSpace(parts[0]) == "template0" || strings.TrimSpace(parts[0]) == "template1" || strings.TrimSpace(parts[0]) == "postgres" {
continue
}
status, err := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if status == "active" && err == nil {
raw, err := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
if err == nil {
databases := strings.Split(raw, "\n")
if len(databases) >= 4 {
databases = databases[3 : len(databases)-1]
for _, db := range databases {
parts := strings.Split(db, "|")
if len(parts) != 9 || len(strings.TrimSpace(parts[0])) == 0 || strings.TrimSpace(parts[0]) == "template0" || strings.TrimSpace(parts[0]) == "template1" || strings.TrimSpace(parts[0]) == "postgres" {
continue
}
databaseCount++
databaseCount++
}
}
}
}
}
var ftpCount int64
var ftpPlugin = c.plugin.GetInstalledBySlug("pureftpd")
var ftpPlugin = r.plugin.GetInstalledBySlug("pureftpd")
if ftpPlugin.ID != 0 {
listRaw := tools.Exec("pure-pw list")
if len(listRaw) != 0 {
listRaw, err := tools.Exec("pure-pw list")
if len(listRaw) != 0 && err == nil {
listArr := strings.Split(listRaw, "\n")
ftpCount = int64(len(listArr))
}
@@ -195,7 +193,7 @@ func (c *InfoController) CountInfo(ctx http.Context) http.Response {
}
// InstalledDbAndPhp 获取已安装的数据库和 PHP 版本
func (c *InfoController) InstalledDbAndPhp(ctx http.Context) http.Response {
func (r *InfoController) InstalledDbAndPhp(ctx http.Context) http.Response {
var php []models.Plugin
err := facades.Orm().Query().Where("slug like ?", "php%").Find(&php)
if err != nil {
@@ -230,7 +228,7 @@ func (c *InfoController) InstalledDbAndPhp(ctx http.Context) http.Response {
continue
}
phpData = append(phpData, data{Value: strings.ReplaceAll(p.Slug, "php", ""), Label: c.plugin.GetBySlug(p.Slug).Name})
phpData = append(phpData, data{Value: strings.ReplaceAll(p.Slug, "php", ""), Label: r.plugin.GetBySlug(p.Slug).Name})
}
if mysqlInstalled {
@@ -247,7 +245,7 @@ func (c *InfoController) InstalledDbAndPhp(ctx http.Context) http.Response {
}
// CheckUpdate 检查面板更新
func (c *InfoController) CheckUpdate(ctx http.Context) http.Response {
func (r *InfoController) CheckUpdate(ctx http.Context) http.Response {
version := facades.Config().GetString("panel.version")
remote, err := tools.GetLatestPanelVersion()
if err != nil {
@@ -266,7 +264,7 @@ func (c *InfoController) CheckUpdate(ctx http.Context) http.Response {
}
// UpdateInfo 获取更新信息
func (c *InfoController) UpdateInfo(ctx http.Context) http.Response {
func (r *InfoController) UpdateInfo(ctx http.Context) http.Response {
version := facades.Config().GetString("panel.version")
current, err := tools.GetLatestPanelVersion()
if err != nil {
@@ -296,7 +294,7 @@ func (c *InfoController) UpdateInfo(ctx http.Context) http.Response {
}
// Update 更新面板
func (c *InfoController) Update(ctx http.Context) http.Response {
func (r *InfoController) Update(ctx http.Context) http.Response {
var task models.Task
err := facades.Orm().Query().Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).FirstOrFail(&task)
if err == nil {
@@ -305,18 +303,18 @@ func (c *InfoController) Update(ctx http.Context) http.Response {
panel, err := tools.GetLatestPanelVersion()
if err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "基础信息").With(map[string]any{
"error": err.Error(),
}).Error("[面板][InfoController] 获取最新版本失败")
}).Info("获取最新版本失败")
return Error(ctx, http.StatusInternalServerError, "获取最新版本失败")
}
err = tools.UpdatePanel(panel)
if err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "基础信息").With(map[string]any{
"error": err.Error(),
}).Error("[面板][InfoController] 更新面板失败")
return Error(ctx, http.StatusInternalServerError, "更新失败: "+err.Error())
}).Info("更新面板失败")
return Error(ctx, http.StatusInternalServerError, err.Error())
}
tools.RestartPanel()
@@ -324,7 +322,7 @@ func (c *InfoController) Update(ctx http.Context) http.Response {
}
// Restart 重启面板
func (c *InfoController) Restart(ctx http.Context) http.Response {
func (r *InfoController) Restart(ctx http.Context) http.Response {
var task models.Task
err := facades.Orm().Query().Where("status", models.TaskStatusRunning).OrWhere("status", models.TaskStatusWaiting).FirstOrFail(&task)
if err == nil {
+20 -6
View File
@@ -7,12 +7,14 @@ import (
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/carbon"
"github.com/spf13/cast"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
)
type MonitorController struct {
setting services.Setting
setting internal.Setting
}
func NewMonitorController() *MonitorController {
@@ -26,7 +28,10 @@ func (r *MonitorController) Switch(ctx http.Context) http.Response {
value := ctx.Request().InputBool("monitor")
err := r.setting.Set(models.SettingKeyMonitor, cast.ToString(value))
if err != nil {
facades.Log().Error("[面板][MonitorController] 更新监控开关失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "资源监控").With(map[string]any{
"monitor": value,
"error": err.Error(),
}).Info("更新监控开关失败")
return ErrorSystem(ctx)
}
@@ -38,7 +43,10 @@ func (r *MonitorController) SaveDays(ctx http.Context) http.Response {
days := ctx.Request().Input("days")
err := r.setting.Set(models.SettingKeyMonitorDays, days)
if err != nil {
facades.Log().Error("[面板][MonitorController] 更新监控天数失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "资源监控").With(map[string]any{
"days": days,
"error": err.Error(),
}).Info("更新监控开关失败")
return ErrorSystem(ctx)
}
@@ -60,7 +68,9 @@ func (r *MonitorController) SwitchAndDays(ctx http.Context) http.Response {
func (r *MonitorController) Clear(ctx http.Context) http.Response {
_, err := facades.Orm().Query().Where("1 = 1").Delete(&models.Monitor{})
if err != nil {
facades.Log().Error("[面板][MonitorController] 清空监控数据失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "资源监控").With(map[string]any{
"error": err.Error(),
}).Info("清空监控数据失败")
return ErrorSystem(ctx)
}
@@ -77,7 +87,11 @@ func (r *MonitorController) List(ctx http.Context) http.Response {
var monitors []models.Monitor
err := facades.Orm().Query().Where("created_at >= ?", startTime.ToDateTimeString()).Where("created_at <= ?", endTime.ToDateTimeString()).Get(&monitors)
if err != nil {
facades.Log().Error("[面板][MonitorController] 查询监控数据失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "资源监控").With(map[string]any{
"start": startTime.ToDateTimeString(),
"end": endTime.ToDateTimeString(),
"error": err.Error(),
}).Info("获取监控数据失败")
return ErrorSystem(ctx)
}
+33 -37
View File
@@ -1,18 +1,17 @@
package controllers
import (
"sync"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
)
type PluginController struct {
plugin services.Plugin
task services.Task
plugin internal.Plugin
task internal.Task
}
func NewPluginController() *PluginController {
@@ -30,13 +29,10 @@ func (r *PluginController) List(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
var lock sync.RWMutex
installedPluginsMap := make(map[string]models.Plugin)
for _, p := range installedPlugins {
lock.Lock()
installedPluginsMap[p.Slug] = p
lock.Unlock()
}
type plugin struct {
@@ -100,7 +96,9 @@ func (r *PluginController) Install(ctx http.Context) http.Response {
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Error("[面板][PluginController] 获取已安装插件失败")
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
return ErrorSystem(ctx)
}
@@ -108,28 +106,21 @@ func (r *PluginController) Install(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "插件已安装")
}
var lock sync.RWMutex
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
lock.Lock()
pluginsMap[p.Slug] = true
lock.Unlock()
}
for _, require := range plugin.Requires {
lock.RLock()
_, requireFound := pluginsMap[require]
lock.RUnlock()
if !requireFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
}
}
for _, exclude := range plugin.Excludes {
lock.RLock()
_, excludeFound := pluginsMap[exclude]
lock.RUnlock()
if excludeFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
}
@@ -141,7 +132,10 @@ func (r *PluginController) Install(ctx http.Context) http.Response {
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().Error("[面板][PluginController] 创建任务失败: " + err.Error())
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
@@ -156,7 +150,9 @@ func (r *PluginController) Uninstall(ctx http.Context) http.Response {
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Error("[面板][PluginController] 获取已安装插件失败")
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
return ErrorSystem(ctx)
}
@@ -164,28 +160,21 @@ func (r *PluginController) Uninstall(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
}
var lock sync.RWMutex
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
lock.Lock()
pluginsMap[p.Slug] = true
lock.Unlock()
}
for _, require := range plugin.Requires {
lock.RLock()
_, requireFound := pluginsMap[require]
lock.RUnlock()
if !requireFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
}
}
for _, exclude := range plugin.Excludes {
lock.RLock()
_, excludeFound := pluginsMap[exclude]
lock.RUnlock()
if excludeFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
}
@@ -197,7 +186,10 @@ func (r *PluginController) Uninstall(ctx http.Context) http.Response {
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().Error("[面板][PluginController] 创建任务失败: " + err.Error())
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
@@ -212,7 +204,9 @@ func (r *PluginController) Update(ctx http.Context) http.Response {
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Error("[面板][PluginController] 获取已安装插件失败")
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
return ErrorSystem(ctx)
}
@@ -220,28 +214,21 @@ func (r *PluginController) Update(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
}
var lock sync.RWMutex
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
lock.Lock()
pluginsMap[p.Slug] = true
lock.Unlock()
}
for _, require := range plugin.Requires {
lock.RLock()
_, requireFound := pluginsMap[require]
lock.RUnlock()
if !requireFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 需要依赖 "+require+" 插件")
}
}
for _, exclude := range plugin.Excludes {
lock.RLock()
_, excludeFound := pluginsMap[exclude]
lock.RUnlock()
if excludeFound {
return Error(ctx, http.StatusForbidden, "插件 "+slug+" 不兼容 "+exclude+" 插件")
}
@@ -253,7 +240,10 @@ func (r *PluginController) Update(ctx http.Context) http.Response {
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().Error("[面板][PluginController] 创建任务失败: " + err.Error())
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
@@ -268,7 +258,10 @@ func (r *PluginController) UpdateShow(ctx http.Context) http.Response {
var plugin models.Plugin
if err := facades.Orm().Query().Where("slug", slug).First(&plugin); err != nil {
facades.Log().Error("[面板][PluginController] 查询插件失败: " + err.Error())
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("获取插件失败")
return ErrorSystem(ctx)
}
if plugin.ID == 0 {
@@ -277,7 +270,10 @@ func (r *PluginController) UpdateShow(ctx http.Context) http.Response {
plugin.Show = show
if err := facades.Orm().Query().Save(&plugin); err != nil {
facades.Log().Error("[面板][PluginController] 更新插件失败: " + err.Error())
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("更新插件失败")
return ErrorSystem(ctx)
}
+10
View File
@@ -27,3 +27,13 @@ type S3fsMount struct {
Bucket string `json:"bucket"`
Url string `json:"url"`
}
type RsyncModule struct {
Name string `json:"name"`
Path string `json:"path"`
Comment string `json:"comment"`
ReadOnly bool `json:"read_only"`
AuthUser string `json:"auth_user"`
Secret string `json:"secret"`
HostsAllow string `json:"hosts_allow"`
}
@@ -10,12 +10,13 @@ import (
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type Fail2banController struct {
website services.Website
website internal.Website
}
func NewFail2banController() *Fail2banController {
@@ -26,115 +27,61 @@ func NewFail2banController() *Fail2banController {
// Status 获取运行状态
func (r *Fail2banController) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
}
status := tools.Exec("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("fail2ban")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
// Reload 重载配置
func (r *Fail2banController) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
if _, err := tools.Exec("systemctl reload fail2ban"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
}
tools.Exec("systemctl reload fail2ban")
status := tools.Exec("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Restart 重启服务
func (r *Fail2banController) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
if err := tools.ServiceRestart("fail2ban"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启服务失败")
}
tools.Exec("systemctl restart fail2ban")
status := tools.Exec("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *Fail2banController) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
if err := tools.ServiceStart("fail2ban"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动服务失败")
}
tools.Exec("systemctl start fail2ban")
status := tools.Exec("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *Fail2banController) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
if err := tools.ServiceStop("fail2ban"); err != nil {
return nil
}
tools.Exec("systemctl stop fail2ban")
status := tools.Exec("systemctl status fail2ban | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("fail2ban")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, !status)
}
// List 所有 Fail2ban 规则
func (r *Fail2banController) List(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
raw := tools.Read("/etc/fail2ban/jail.local")
if len(raw) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "Fail2ban 规则为空")
raw, err := tools.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
jailList := regexp.MustCompile(`\[(.*?)]`).FindAllStringSubmatch(raw, -1)
@@ -193,11 +140,6 @@ func (r *Fail2banController) List(ctx http.Context) http.Response {
// Add 添加 Fail2ban 规则
func (r *Fail2banController) Add(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"name": "required",
"type": "required|in:website,service",
@@ -224,7 +166,10 @@ func (r *Fail2banController) Add(ctx http.Context) http.Response {
jailWebsiteMode := ctx.Request().Input("website_mode")
jailWebsitePath := ctx.Request().Input("website_path")
raw := tools.Read("/etc/fail2ban/jail.local")
raw, err := tools.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if (strings.Contains(raw, "["+jailName+"]") && jailType == "service") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-cc") && jailType == "website" && jailWebsiteMode == "cc") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-path") && jailType == "website" && jailWebsiteMode == "path") {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "规则已存在")
}
@@ -236,17 +181,14 @@ func (r *Fail2banController) Add(ctx http.Context) http.Response {
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "网站不存在")
}
config, err := r.website.GetConfig(int(website.ID))
config, err := r.website.GetConfig(website.ID)
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取网站配置失败")
}
var ports string
for _, port := range config.Ports {
if len(strings.Split(port, " ")) > 1 {
ports += strings.Split(port, " ")[0] + ","
} else {
ports += port + ","
}
fields := strings.Fields(cast.ToString(port))
ports += fields[0] + ","
}
rule := `
@@ -263,7 +205,9 @@ logpath = /www/wwwlogs/` + website.Name + `.log
# ` + jailWebsiteName + `-` + jailWebsiteMode + `-END
`
raw += rule
tools.Write("/etc/fail2ban/jail.local", raw, 0644)
if err = tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
var filter string
if jailWebsiteMode == "cc" {
@@ -279,12 +223,15 @@ failregex = ^<HOST>\s-.*\s` + jailWebsitePath + `.*HTTP/.*$
ignoreregex =
`
}
tools.Write("/etc/fail2ban/filter.d/haozi-"+jailWebsiteName+"-"+jailWebsiteMode+".conf", filter, 0644)
if err = tools.Write("/etc/fail2ban/filter.d/haozi-"+jailWebsiteName+"-"+jailWebsiteMode+".conf", filter, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
case "service":
var logPath string
var filter string
var port string
var err error
switch jailName {
case "ssh":
if tools.IsDebian() {
@@ -293,19 +240,19 @@ ignoreregex =
logPath = "/var/log/secure"
}
filter = "sshd"
port = tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
port, err = tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
case "mysql":
logPath = "/www/server/mysql/mysql-error.log"
filter = "mysqld-auth"
port = tools.Exec("cat /www/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'")
port, err = tools.Exec("cat /www/server/mysql/conf/my.cnf | grep 'port' | head -n 1 | awk '{print $3}'")
case "pure-ftpd":
logPath = "/var/log/messages"
filter = "pure-ftpd"
port = tools.Exec(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
port, err = tools.Exec(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
default:
return controllers.Error(ctx, http.StatusUnprocessableEntity, "未知服务")
}
if len(port) == 0 {
if len(port) == 0 || err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取服务端口失败,请检查是否安装")
}
@@ -323,22 +270,25 @@ logpath = ` + logPath + `
# ` + jailName + `-END
`
raw += rule
tools.Write("/etc/fail2ban/jail.local", raw, 0644)
if err := tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
}
if _, err := tools.Exec("fail2ban-client reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
}
tools.Exec("fail2ban-client reload")
return controllers.Success(ctx, nil)
}
// Delete 删除规则
func (r *Fail2banController) Delete(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
}
jailName := ctx.Request().Input("name")
raw := tools.Read("/etc/fail2ban/jail.local")
raw, err := tools.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if !strings.Contains(raw, "["+jailName+"]") {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "规则不存在")
}
@@ -346,27 +296,36 @@ func (r *Fail2banController) Delete(ctx http.Context) http.Response {
rule := tools.Cut(raw, "# "+jailName+"-START", "# "+jailName+"-END")
raw = strings.Replace(raw, "\n# "+jailName+"-START"+rule+"# "+jailName+"-END", "", -1)
raw = strings.TrimSpace(raw)
tools.Write("/etc/fail2ban/jail.local", raw, 0644)
if err := tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
if _, err := tools.Exec("fail2ban-client reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
}
tools.Exec("fail2ban-client reload")
return controllers.Success(ctx, nil)
}
// BanList 获取封禁列表
func (r *Fail2banController) BanList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
}
name := ctx.Request().Input("name")
if len(name) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
}
currentlyBan := tools.Exec(`fail2ban-client status ` + name + ` | grep "Currently banned" | awk '{print $4}'`)
totalBan := tools.Exec(`fail2ban-client status ` + name + ` | grep "Total banned" | awk '{print $4}'`)
bannedIp := tools.Exec(`fail2ban-client status ` + name + ` | grep "Banned IP list" | awk -F ":" '{print $2}'`)
currentlyBan, err := tools.Exec(`fail2ban-client status ` + name + ` | grep "Currently banned" | awk '{print $4}'`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取封禁列表失败")
}
totalBan, err := tools.Exec(`fail2ban-client status ` + name + ` | grep "Total banned" | awk '{print $4}'`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取封禁列表失败")
}
bannedIp, err := tools.Exec(`fail2ban-client status ` + name + ` | grep "Banned IP list" | awk -F ":" '{print $2}'`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取封禁列表失败")
}
bannedIpList := strings.Split(bannedIp, " ")
var list []map[string]string
@@ -391,34 +350,30 @@ func (r *Fail2banController) BanList(ctx http.Context) http.Response {
// Unban 解封
func (r *Fail2banController) Unban(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
}
name := ctx.Request().Input("name")
ip := ctx.Request().Input("ip")
if len(name) == 0 || len(ip) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
}
tools.Exec("fail2ban-client set " + name + " unbanip " + ip)
if _, err := tools.Exec("fail2ban-client set " + name + " unbanip " + ip); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "解封失败")
}
return controllers.Success(ctx, nil)
}
// SetWhiteList 设置白名单
func (r *Fail2banController) SetWhiteList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
}
ip := ctx.Request().Input("ip")
if len(ip) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "缺少参数")
}
raw := tools.Read("/etc/fail2ban/jail.local")
raw, err := tools.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
// 正则替换
reg := regexp.MustCompile(`ignoreip\s*=\s*.*\n`)
if reg.MatchString(raw) {
@@ -427,19 +382,22 @@ func (r *Fail2banController) SetWhiteList(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏")
}
tools.Write("/etc/fail2ban/jail.local", raw, 0644)
tools.Exec("fail2ban-client reload")
if err := tools.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Fail2ban规则失败")
}
if _, err := tools.Exec("fail2ban-client reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载配置失败")
}
return controllers.Success(ctx, nil)
}
// GetWhiteList 获取白名单
func (r *Fail2banController) GetWhiteList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "fail2ban")
if check != nil {
return check
raw, err := tools.Read("/etc/fail2ban/jail.local")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
raw := tools.Read("/etc/fail2ban/jail.local")
reg := regexp.MustCompile(`ignoreip\s*=\s*(.*)\n`)
if reg.MatchString(raw) {
ignoreIp := reg.FindStringSubmatch(raw)[1]
+125 -245
View File
@@ -4,21 +4,20 @@ import (
"database/sql"
"fmt"
"regexp"
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/spf13/cast"
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type Mysql57Controller struct {
setting services.Setting
backup services.Backup
setting internal.Setting
backup internal.Backup
}
func NewMysql57Controller() *Mysql57Controller {
@@ -30,113 +29,54 @@ func NewMysql57Controller() *Mysql57Controller {
// Status 获取运行状态
func (r *Mysql57Controller) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
// Reload 重载配置
func (r *Mysql57Controller) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
if err := tools.ServiceReload("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载MySQL失败")
}
tools.Exec("systemctl reload mysqld")
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Restart 重启服务
func (r *Mysql57Controller) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
if err := tools.ServiceRestart("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启MySQL服务失败")
}
tools.Exec("systemctl restart mysqld")
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *Mysql57Controller) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
if err := tools.ServiceStart("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动MySQL服务失败")
}
tools.Exec("systemctl start mysqld")
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *Mysql57Controller) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
if err := tools.ServiceStop("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止MySQL服务失败")
}
tools.Exec("systemctl stop mysqld")
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// GetConfig 获取配置
func (r *Mysql57Controller) GetConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
// 获取配置
config := tools.Read("/www/server/mysql/conf/my.cnf")
if len(config) == 0 {
config, err := tools.Read("/www/server/mysql/conf/my.cnf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
}
@@ -145,17 +85,12 @@ func (r *Mysql57Controller) GetConfig(ctx http.Context) http.Response {
// SaveConfig 保存配置
func (r *Mysql57Controller) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if !tools.Write("/www/server/mysql/conf/my.cnf", config, 0644) {
if err := tools.Write("/www/server/mysql/conf/my.cnf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入MySQL配置失败")
}
@@ -164,26 +99,21 @@ func (r *Mysql57Controller) SaveConfig(ctx http.Context) http.Response {
// Load 获取负载
func (r *Mysql57Controller) Load(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
}
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if status != "active" {
return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 已停止运行")
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 := tools.Exec("/www/server/mysql/bin/mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
if strings.Contains(raw, "Access denied for user") {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码错误")
}
if !strings.Contains(raw, "Uptime") {
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负载失败")
}
@@ -239,55 +169,43 @@ func (r *Mysql57Controller) Load(ctx http.Context) http.Response {
// ErrorLog 获取错误日志
func (r *Mysql57Controller) ErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-error.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/mysql/mysql-error.log"))
return controllers.Success(ctx, log)
}
// ClearErrorLog 清空错误日志
func (r *Mysql57Controller) ClearErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-error.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/mysql/mysql-error.log")
return controllers.Success(ctx, "清空错误日志成功")
return controllers.Success(ctx, nil)
}
// SlowLog 获取慢查询日志
func (r *Mysql57Controller) SlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-slow.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/mysql/mysql-slow.log"))
return controllers.Success(ctx, log)
}
// ClearSlowLog 清空慢查询日志
func (r *Mysql57Controller) ClearSlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-slow.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/mysql/mysql-slow.log")
return controllers.Success(ctx, "清空慢查询日志成功")
return controllers.Success(ctx, nil)
}
// GetRootPassword 获取root密码
func (r *Mysql57Controller) GetRootPassword(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
@@ -298,16 +216,11 @@ func (r *Mysql57Controller) GetRootPassword(ctx http.Context) http.Response {
// SetRootPassword 设置root密码
func (r *Mysql57Controller) SetRootPassword(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status != "active" {
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行")
}
@@ -318,26 +231,29 @@ func (r *Mysql57Controller) SetRootPassword(ctx http.Context) http.Response {
oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if oldRootPassword != rootPassword {
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"FLUSH PRIVILEGES;\"")
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 {
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
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, "设置root密码成功")
return controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (r *Mysql57Controller) DatabaseList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
type database struct {
Name string `json:"name"`
@@ -345,15 +261,13 @@ func (r *Mysql57Controller) DatabaseList(ctx http.Context) http.Response {
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
if err != nil {
facades.Log().Error("[MySQL57] 连接数据库失败" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "连接数据库失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer db.Close()
rows, err := db.Query("SHOW DATABASES")
if err != nil {
facades.Log().Error("[MySQL57] 获取数据库列表失败" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer rows.Close()
@@ -369,7 +283,6 @@ func (r *Mysql57Controller) DatabaseList(ctx http.Context) http.Response {
}
if err := rows.Err(); err != nil {
facades.Log().Error("[MySQL57] 获取数据库列表失败" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
}
@@ -396,11 +309,6 @@ func (r *Mysql57Controller) DatabaseList(ctx http.Context) http.Response {
// AddDatabase 添加数据库
func (r *Mysql57Controller) AddDatabase(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
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_]+$",
@@ -418,21 +326,24 @@ func (r *Mysql57Controller) AddDatabase(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + database + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
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, "添加数据库成功")
return controllers.Success(ctx, nil)
}
// DeleteDatabase 删除数据库
func (r *Mysql57Controller) DeleteDatabase(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
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",
})
@@ -445,22 +356,18 @@ func (r *Mysql57Controller) DeleteDatabase(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + 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, "删除数据库成功")
return controllers.Success(ctx, nil)
}
// BackupList 获取备份列表
func (r *Mysql57Controller) BackupList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
backupList, err := r.backup.MysqlList()
if err != nil {
facades.Log().Error("[MySQL57] 获取备份列表失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
page := ctx.Request().QueryInt("page", 1)
@@ -470,7 +377,7 @@ func (r *Mysql57Controller) BackupList(ctx http.Context) http.Response {
if startIndex > len(backupList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []services.BackupFile{},
"items": []internal.BackupFile{},
})
}
if endIndex > len(backupList) {
@@ -478,7 +385,7 @@ func (r *Mysql57Controller) BackupList(ctx http.Context) http.Response {
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []services.BackupFile{}
pagedBackupList = []internal.BackupFile{}
}
return controllers.Success(ctx, http.Json{
@@ -489,11 +396,6 @@ func (r *Mysql57Controller) BackupList(ctx http.Context) http.Response {
// UploadBackup 上传备份
func (r *Mysql57Controller) UploadBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
file, err := ctx.Request().File("file")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
@@ -501,7 +403,9 @@ func (r *Mysql57Controller) UploadBackup(ctx http.Context) http.Response {
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
if err = tools.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
name := file.GetClientOriginalName()
@@ -510,16 +414,11 @@ func (r *Mysql57Controller) UploadBackup(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
}
return controllers.Success(ctx, "上传文件成功")
return controllers.Success(ctx, nil)
}
// CreateBackup 创建备份
func (r *Mysql57Controller) CreateBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
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",
})
@@ -533,20 +432,14 @@ func (r *Mysql57Controller) CreateBackup(ctx http.Context) http.Response {
database := ctx.Request().Input("database")
err = r.backup.MysqlBackup(database)
if err != nil {
facades.Log().Error("[MYSQL57] 创建备份失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "创建备份失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, "备份成功")
return controllers.Success(ctx, nil)
}
// DeleteBackup 删除备份
func (r *Mysql57Controller) DeleteBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|min_len:1|max_len:255",
})
@@ -559,18 +452,15 @@ func (r *Mysql57Controller) DeleteBackup(ctx http.Context) http.Response {
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
fileName := ctx.Request().Input("name")
tools.Remove(backupPath + "/" + fileName)
if err := tools.Remove(backupPath + "/" + fileName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, "删除备份成功")
return controllers.Success(ctx, nil)
}
// RestoreBackup 还原备份
func (r *Mysql57Controller) RestoreBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
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",
@@ -584,20 +474,14 @@ func (r *Mysql57Controller) RestoreBackup(ctx http.Context) http.Response {
err = r.backup.MysqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup"))
if err != nil {
facades.Log().Error("[MYSQL57] 还原失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, "还原成功")
return controllers.Success(ctx, nil)
}
// UserList 用户列表
func (r *Mysql57Controller) UserList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
type user struct {
User string `json:"user"`
Host string `json:"host"`
@@ -607,15 +491,13 @@ func (r *Mysql57Controller) UserList(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
if err != nil {
facades.Log().Error("[MYSQL57] 连接数据库失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "连接数据库失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer db.Close()
rows, err := db.Query("SELECT user, host FROM mysql.user")
if err != nil {
facades.Log().Error("[MYSQL57] 查询数据库失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "查询数据库失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer rows.Close()
@@ -679,11 +561,6 @@ func (r *Mysql57Controller) UserList(ctx http.Context) http.Response {
// AddUser 添加用户
func (r *Mysql57Controller) AddUser(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
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_]+$",
@@ -700,20 +577,21 @@ func (r *Mysql57Controller) AddUser(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + ";'\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
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, "添加成功")
return controllers.Success(ctx, nil)
}
// DeleteUser 删除用户
func (r *Mysql57Controller) DeleteUser(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
})
@@ -726,18 +604,15 @@ func (r *Mysql57Controller) DeleteUser(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\"")
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, "删除成功")
return controllers.Success(ctx, nil)
}
// SetUserPassword 设置用户密码
func (r *Mysql57Controller) SetUserPassword(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
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",
@@ -752,19 +627,18 @@ func (r *Mysql57Controller) SetUserPassword(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
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, "修改成功")
return controllers.Success(ctx, nil)
}
// SetUserPrivileges 设置用户权限
func (r *Mysql57Controller) SetUserPrivileges(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql57")
if check != nil {
return check
}
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",
@@ -779,9 +653,15 @@ func (r *Mysql57Controller) SetUserPrivileges(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
database := ctx.Request().Input("database")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
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, "修改成功")
return controllers.Success(ctx, nil)
}
+125 -245
View File
@@ -4,21 +4,20 @@ import (
"database/sql"
"fmt"
"regexp"
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/spf13/cast"
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type Mysql80Controller struct {
setting services.Setting
backup services.Backup
setting internal.Setting
backup internal.Backup
}
func NewMysql80Controller() *Mysql80Controller {
@@ -30,113 +29,54 @@ func NewMysql80Controller() *Mysql80Controller {
// Status 获取运行状态
func (r *Mysql80Controller) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
// Reload 重载配置
func (r *Mysql80Controller) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
if err := tools.ServiceReload("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载MySQL配置失败")
}
tools.Exec("systemctl reload mysqld")
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Restart 重启服务
func (r *Mysql80Controller) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
if err := tools.ServiceRestart("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启MySQL服务失败")
}
tools.Exec("systemctl restart mysqld")
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *Mysql80Controller) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
if err := tools.ServiceStart("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动MySQL服务失败")
}
tools.Exec("systemctl start mysqld")
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *Mysql80Controller) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
if err := tools.ServiceStop("mysqld"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止MySQL服务失败")
}
tools.Exec("systemctl stop mysqld")
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// GetConfig 获取配置
func (r *Mysql80Controller) GetConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
// 获取配置
config := tools.Read("/www/server/mysql/conf/my.cnf")
if len(config) == 0 {
config, err := tools.Read("/www/server/mysql/conf/my.cnf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
}
@@ -145,17 +85,12 @@ func (r *Mysql80Controller) GetConfig(ctx http.Context) http.Response {
// SaveConfig 保存配置
func (r *Mysql80Controller) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if !tools.Write("/www/server/mysql/conf/my.cnf", config, 0644) {
if err := tools.Write("/www/server/mysql/conf/my.cnf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入MySQL配置失败")
}
@@ -164,26 +99,21 @@ func (r *Mysql80Controller) SaveConfig(ctx http.Context) http.Response {
// Load 获取负载
func (r *Mysql80Controller) Load(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
}
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if status != "active" {
return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 已停止运行")
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 := tools.Exec("/www/server/mysql/bin/mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
if strings.Contains(raw, "Access denied for user") {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码错误")
}
if !strings.Contains(raw, "Uptime") {
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负载失败")
}
@@ -239,55 +169,43 @@ func (r *Mysql80Controller) Load(ctx http.Context) http.Response {
// ErrorLog 获取错误日志
func (r *Mysql80Controller) ErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-error.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/mysql/mysql-error.log"))
return controllers.Success(ctx, log)
}
// ClearErrorLog 清空错误日志
func (r *Mysql80Controller) ClearErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-error.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/mysql/mysql-error.log")
return controllers.Success(ctx, "清空错误日志成功")
return controllers.Success(ctx, nil)
}
// SlowLog 获取慢查询日志
func (r *Mysql80Controller) SlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/mysql/mysql-slow.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/mysql/mysql-slow.log"))
return controllers.Success(ctx, log)
}
// ClearSlowLog 清空慢查询日志
func (r *Mysql80Controller) ClearSlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/mysql/mysql-slow.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/mysql/mysql-slow.log")
return controllers.Success(ctx, "清空慢查询日志成功")
return controllers.Success(ctx, nil)
}
// GetRootPassword 获取root密码
func (r *Mysql80Controller) GetRootPassword(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
@@ -298,16 +216,11 @@ func (r *Mysql80Controller) GetRootPassword(ctx http.Context) http.Response {
// SetRootPassword 设置root密码
func (r *Mysql80Controller) SetRootPassword(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
status := tools.Exec("systemctl status mysqld | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("mysqld")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
}
if status != "active" {
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行")
}
@@ -318,26 +231,29 @@ func (r *Mysql80Controller) SetRootPassword(ctx http.Context) http.Response {
oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if oldRootPassword != rootPassword {
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + oldRootPassword + " -e \"FLUSH PRIVILEGES;\"")
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 {
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
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, "设置root密码成功")
return controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (r *Mysql80Controller) DatabaseList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
type database struct {
Name string `json:"name"`
@@ -345,15 +261,13 @@ func (r *Mysql80Controller) DatabaseList(ctx http.Context) http.Response {
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
if err != nil {
facades.Log().Error("[MySQL80] 连接数据库失败" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "连接数据库失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer db.Close()
rows, err := db.Query("SHOW DATABASES")
if err != nil {
facades.Log().Error("[MySQL80] 获取数据库列表失败" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer rows.Close()
@@ -369,7 +283,6 @@ func (r *Mysql80Controller) DatabaseList(ctx http.Context) http.Response {
}
if err := rows.Err(); err != nil {
facades.Log().Error("[MySQL80] 获取数据库列表失败" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
}
@@ -396,11 +309,6 @@ func (r *Mysql80Controller) DatabaseList(ctx http.Context) http.Response {
// AddDatabase 添加数据库
func (r *Mysql80Controller) AddDatabase(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
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_]+$",
@@ -418,21 +326,24 @@ func (r *Mysql80Controller) AddDatabase(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + database + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
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, "添加数据库成功")
return controllers.Success(ctx, nil)
}
// DeleteDatabase 删除数据库
func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
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",
})
@@ -445,22 +356,18 @@ func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + 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, "删除数据库成功")
return controllers.Success(ctx, nil)
}
// BackupList 获取备份列表
func (r *Mysql80Controller) BackupList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
backupList, err := r.backup.MysqlList()
if err != nil {
facades.Log().Error("[MySQL80] 获取备份列表失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
page := ctx.Request().QueryInt("page", 1)
@@ -470,7 +377,7 @@ func (r *Mysql80Controller) BackupList(ctx http.Context) http.Response {
if startIndex > len(backupList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []services.BackupFile{},
"items": []internal.BackupFile{},
})
}
if endIndex > len(backupList) {
@@ -478,7 +385,7 @@ func (r *Mysql80Controller) BackupList(ctx http.Context) http.Response {
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []services.BackupFile{}
pagedBackupList = []internal.BackupFile{}
}
return controllers.Success(ctx, http.Json{
@@ -489,11 +396,6 @@ func (r *Mysql80Controller) BackupList(ctx http.Context) http.Response {
// UploadBackup 上传备份
func (r *Mysql80Controller) UploadBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
file, err := ctx.Request().File("file")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
@@ -501,7 +403,9 @@ func (r *Mysql80Controller) UploadBackup(ctx http.Context) http.Response {
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
if err = tools.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
name := file.GetClientOriginalName()
@@ -510,16 +414,11 @@ func (r *Mysql80Controller) UploadBackup(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
}
return controllers.Success(ctx, "上传文件成功")
return controllers.Success(ctx, nil)
}
// CreateBackup 创建备份
func (r *Mysql80Controller) CreateBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
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",
})
@@ -533,20 +432,14 @@ func (r *Mysql80Controller) CreateBackup(ctx http.Context) http.Response {
database := ctx.Request().Input("database")
err = r.backup.MysqlBackup(database)
if err != nil {
facades.Log().Error("[MYSQL80] 创建备份失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "创建备份失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, "备份成功")
return controllers.Success(ctx, nil)
}
// DeleteBackup 删除备份
func (r *Mysql80Controller) DeleteBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|min_len:1|max_len:255",
})
@@ -559,18 +452,15 @@ func (r *Mysql80Controller) DeleteBackup(ctx http.Context) http.Response {
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
fileName := ctx.Request().Input("name")
tools.Remove(backupPath + "/" + fileName)
if err := tools.Remove(backupPath + "/" + fileName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, "删除备份成功")
return controllers.Success(ctx, nil)
}
// RestoreBackup 还原备份
func (r *Mysql80Controller) RestoreBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
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",
@@ -584,20 +474,14 @@ func (r *Mysql80Controller) RestoreBackup(ctx http.Context) http.Response {
err = r.backup.MysqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup"))
if err != nil {
facades.Log().Error("[MYSQL80] 还原失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, "还原成功")
return controllers.Success(ctx, nil)
}
// UserList 用户列表
func (r *Mysql80Controller) UserList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
type user struct {
User string `json:"user"`
Host string `json:"host"`
@@ -607,15 +491,13 @@ func (r *Mysql80Controller) UserList(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
if err != nil {
facades.Log().Error("[MYSQL80] 连接数据库失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "连接数据库失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer db.Close()
rows, err := db.Query("SELECT user, host FROM mysql.user")
if err != nil {
facades.Log().Error("[MYSQL80] 查询数据库失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "查询数据库失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
defer rows.Close()
@@ -679,11 +561,6 @@ func (r *Mysql80Controller) UserList(ctx http.Context) http.Response {
// AddUser 添加用户
func (r *Mysql80Controller) AddUser(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
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_]+$",
@@ -700,20 +577,21 @@ func (r *Mysql80Controller) AddUser(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + ";'\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
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, "添加成功")
return controllers.Success(ctx, nil)
}
// DeleteUser 删除用户
func (r *Mysql80Controller) DeleteUser(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
})
@@ -726,18 +604,15 @@ func (r *Mysql80Controller) DeleteUser(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\"")
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, "删除成功")
return controllers.Success(ctx, nil)
}
// SetUserPassword 设置用户密码
func (r *Mysql80Controller) SetUserPassword(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
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",
@@ -752,19 +627,18 @@ func (r *Mysql80Controller) SetUserPassword(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
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, "修改成功")
return controllers.Success(ctx, nil)
}
// SetUserPrivileges 设置用户权限
func (r *Mysql80Controller) SetUserPrivileges(ctx http.Context) http.Response {
check := controllers.Check(ctx, "mysql80")
if check != nil {
return check
}
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",
@@ -779,9 +653,15 @@ func (r *Mysql80Controller) SetUserPrivileges(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
database := ctx.Request().Input("database")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\"")
tools.Exec("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
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, "修改成功")
return controllers.Success(ctx, nil)
}
@@ -5,7 +5,6 @@ import (
"time"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/imroc/req/v3"
"github.com/spf13/cast"
@@ -25,112 +24,54 @@ func NewOpenrestyController() *OpenRestyController {
// Status 获取运行状态
func (r *OpenRestyController) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "openresty")
if check != nil {
return check
}
status := tools.Exec("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("openresty")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
// Reload 重载配置
func (r *OpenRestyController) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "openresty")
if check != nil {
return check
if err := tools.ServiceReload("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载OpenResty失败")
}
tools.Exec("systemctl reload openresty")
status := tools.Exec("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败")
}
if status == "active" {
return controllers.Success(ctx, "重载OpenResty成功")
} else {
return controllers.Error(ctx, 1, "重载OpenResty失败: "+status)
}
return controllers.Success(ctx, nil)
}
// Start 启动OpenResty
func (r *OpenRestyController) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "openresty")
if check != nil {
return check
if err := tools.ServiceStart("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动OpenResty失败")
}
tools.Exec("systemctl start openresty")
status := tools.Exec("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败")
}
if status == "active" {
return controllers.Success(ctx, "启动OpenResty成功")
} else {
return controllers.Error(ctx, 1, "启动OpenResty失败: "+status)
}
return controllers.Success(ctx, nil)
}
// Stop 停止OpenResty
func (r *OpenRestyController) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "openresty")
if check != nil {
return check
if err := tools.ServiceStop("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止OpenResty失败")
}
tools.Exec("systemctl stop openresty")
status := tools.Exec("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败")
}
if status != "active" {
return controllers.Success(ctx, "停止OpenResty成功")
} else {
return controllers.Error(ctx, 1, "停止OpenResty失败: "+status)
}
return controllers.Success(ctx, nil)
}
// Restart 重启OpenResty
func (r *OpenRestyController) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "openresty")
if check != nil {
return check
if err := tools.ServiceRestart("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启OpenResty失败")
}
tools.Exec("systemctl restart openresty")
status := tools.Exec("systemctl status openresty | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty状态失败")
}
if status == "active" {
return controllers.Success(ctx, "重启OpenResty成功")
} else {
return controllers.Error(ctx, 1, "重启OpenResty失败: "+status)
}
return controllers.Success(ctx, nil)
}
// GetConfig 获取配置
func (r *OpenRestyController) GetConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "openresty")
if check != nil {
return check
}
config := tools.Read("/www/server/openresty/conf/nginx.conf")
if len(config) == 0 {
config, err := tools.Read("/www/server/openresty/conf/nginx.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty配置失败")
}
@@ -139,17 +80,12 @@ func (r *OpenRestyController) GetConfig(ctx http.Context) http.Response {
// SaveConfig 保存配置
func (r *OpenRestyController) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "openresty")
if check != nil {
return check
}
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "配置不能为空")
}
if !tools.Write("/www/server/openresty/conf/nginx.conf", config, 0644) {
if err := tools.Write("/www/server/openresty/conf/nginx.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "保存OpenResty配置失败")
}
@@ -158,41 +94,32 @@ func (r *OpenRestyController) SaveConfig(ctx http.Context) http.Response {
// ErrorLog 获取错误日志
func (r *OpenRestyController) ErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "openresty")
if check != nil {
return check
}
if !tools.Exists("/www/wwwlogs/nginx_error.log") {
return controllers.Success(ctx, "")
}
out := tools.Exec("tail -n 100 /www/wwwlogs/nginx_error.log")
out, err := tools.Exec("tail -n 100 /www/wwwlogs/openresty_error.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, out)
}
// ClearErrorLog 清空错误日志
func (r *OpenRestyController) ClearErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "openresty")
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/wwwlogs/openresty_error.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/wwwlogs/nginx_error.log")
return controllers.Success(ctx, "清空OpenResty错误日志成功")
return controllers.Success(ctx, nil)
}
// Load 获取负载
func (r *OpenRestyController) Load(ctx http.Context) http.Response {
check := controllers.Check(ctx, "openresty")
if check != nil {
return check
}
client := req.C().SetTimeout(10 * time.Second)
resp, err := client.R().Get("http://127.0.0.1/nginx_status")
if err != nil || !resp.IsSuccessState() {
facades.Log().Error("[OpenResty] 获取OpenResty负载失败: " + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty负载失败")
}
@@ -203,13 +130,19 @@ func (r *OpenRestyController) Load(ctx http.Context) http.Response {
}
var data []nginxStatus
workers := tools.Exec("ps aux | grep nginx | grep 'worker process' | wc -l")
workers, err := tools.Exec("ps aux | grep nginx | grep 'worker process' | wc -l")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty负载失败")
}
data = append(data, nginxStatus{
Name: "工作进程",
Value: workers,
})
out := tools.Exec("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
out, err := tools.Exec("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取OpenResty负载失败")
}
mem := tools.FormatBytes(cast.ToFloat64(out))
data = append(data, nginxStatus{
Name: "内存占用",
+45 -129
View File
@@ -12,13 +12,14 @@ import (
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type Php74Controller struct {
setting services.Setting
task services.Task
setting internal.Setting
task internal.Task
version string
}
@@ -31,134 +32,68 @@ func NewPhp74Controller() *Php74Controller {
}
func (r *Php74Controller) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
status := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("php-fpm-" + r.version)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
func (r *Php74Controller) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceReload("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载PHP-"+r.version+"失败")
}
tools.Exec("systemctl reload php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php74Controller) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceStart("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动PHP-"+r.version+"失败")
}
tools.Exec("systemctl start php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php74Controller) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceStop("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止PHP-"+r.version+"失败")
}
tools.Exec("systemctl stop php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php74Controller) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceRestart("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启PHP-"+r.version+"失败")
}
tools.Exec("systemctl restart php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php74Controller) GetConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
config, err := tools.Read("/www/server/php/" + r.version + "/etc/php.ini")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"配置失败")
}
config := tools.Read("/www/server/php/" + r.version + "/etc/php.ini")
return controllers.Success(ctx, config)
}
func (r *Php74Controller) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
config := ctx.Request().Input("config")
tools.Write("/www/server/php/"+r.version+"/etc/php.ini", config, 0644)
if err := tools.Write("/www/server/php/"+r.version+"/etc/php.ini", config, 0644); err != nil {
return nil
}
return r.Reload(ctx)
}
func (r *Php74Controller) Load(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
client := req.C().SetTimeout(10 * time.Second)
resp, err := client.R().Get("http://127.0.0.1/phpfpm_status/" + r.version)
if err != nil || !resp.IsSuccessState() {
facades.Log().Error("获取PHP-" + r.version + "运行状态失败")
facades.Log().Info("获取PHP-" + r.version + "运行状态失败")
return controllers.Error(ctx, http.StatusInternalServerError, "[PHP-"+r.version+"] 获取运行状态失败")
}
@@ -186,61 +121,44 @@ func (r *Php74Controller) Load(ctx http.Context) http.Response {
}
func (r *Php74Controller) ErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/php-fpm.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/php-fpm.log"))
return controllers.Success(ctx, log)
}
func (r *Php74Controller) SlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/slow.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/slow.log"))
return controllers.Success(ctx, log)
}
func (r *Php74Controller) ClearErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/php-fpm.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/php-fpm.log")
return controllers.Success(ctx, true)
return controllers.Success(ctx, nil)
}
func (r *Php74Controller) ClearSlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/slow.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/slow.log")
return controllers.Success(ctx, true)
return controllers.Success(ctx, nil)
}
func (r *Php74Controller) GetExtensionList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
extensions := r.GetExtensions()
return controllers.Success(ctx, extensions)
}
func (r *Php74Controller) InstallExtension(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
@@ -259,7 +177,7 @@ func (r *Php74Controller) InstallExtension(ctx http.Context) http.Response {
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' install ` + r.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + r.version + "] 创建安装拓展任务失败:" + err.Error())
facades.Log().Info("[PHP-" + r.version + "] 创建安装拓展任务失败:" + err.Error())
return controllers.ErrorSystem(ctx)
}
@@ -273,11 +191,6 @@ func (r *Php74Controller) InstallExtension(ctx http.Context) http.Response {
}
func (r *Php74Controller) UninstallExtension(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
@@ -296,7 +209,7 @@ func (r *Php74Controller) UninstallExtension(ctx http.Context) http.Response {
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' uninstall ` + r.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + r.version + "] 创建卸载拓展任务失败:" + err.Error())
facades.Log().Info("[PHP-" + r.version + "] 创建卸载拓展任务失败:" + err.Error())
return controllers.ErrorSystem(ctx)
}
@@ -348,9 +261,12 @@ func (r *Php74Controller) GetExtensions() []PHPExtension {
Installed: false,
})
raw := tools.Exec("/www/server/php/" + r.version + "/bin/php -m")
rawExtensionList := strings.Split(raw, "\n")
raw, err := tools.Exec("/www/server/php/" + r.version + "/bin/php -m")
if err != nil {
return extensions
}
rawExtensionList := strings.Split(raw, "\n")
for _, item := range rawExtensionList {
if !strings.Contains(item, "[") && item != "" {
for i := range extensions {
+45 -129
View File
@@ -12,13 +12,14 @@ import (
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type Php80Controller struct {
setting services.Setting
task services.Task
setting internal.Setting
task internal.Task
version string
}
@@ -31,134 +32,68 @@ func NewPhp80Controller() *Php80Controller {
}
func (r *Php80Controller) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
status := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("php-fpm-" + r.version)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
func (r *Php80Controller) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceReload("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载PHP-"+r.version+"失败")
}
tools.Exec("systemctl reload php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php80Controller) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceStart("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动PHP-"+r.version+"失败")
}
tools.Exec("systemctl start php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php80Controller) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceStop("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止PHP-"+r.version+"失败")
}
tools.Exec("systemctl stop php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php80Controller) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceRestart("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启PHP-"+r.version+"失败")
}
tools.Exec("systemctl restart php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php80Controller) GetConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
config, err := tools.Read("/www/server/php/" + r.version + "/etc/php.ini")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"配置失败")
}
config := tools.Read("/www/server/php/" + r.version + "/etc/php.ini")
return controllers.Success(ctx, config)
}
func (r *Php80Controller) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
config := ctx.Request().Input("config")
tools.Write("/www/server/php/"+r.version+"/etc/php.ini", config, 0644)
if err := tools.Write("/www/server/php/"+r.version+"/etc/php.ini", config, 0644); err != nil {
return nil
}
return r.Reload(ctx)
}
func (r *Php80Controller) Load(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
client := req.C().SetTimeout(10 * time.Second)
resp, err := client.R().Get("http://127.0.0.1/phpfpm_status/" + r.version)
if err != nil || !resp.IsSuccessState() {
facades.Log().Error("获取PHP-" + r.version + "运行状态失败")
facades.Log().Info("获取PHP-" + r.version + "运行状态失败")
return controllers.Error(ctx, http.StatusInternalServerError, "[PHP-"+r.version+"] 获取运行状态失败")
}
@@ -186,61 +121,44 @@ func (r *Php80Controller) Load(ctx http.Context) http.Response {
}
func (r *Php80Controller) ErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/php-fpm.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/php-fpm.log"))
return controllers.Success(ctx, log)
}
func (r *Php80Controller) SlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/slow.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/slow.log"))
return controllers.Success(ctx, log)
}
func (r *Php80Controller) ClearErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/php-fpm.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/php-fpm.log")
return controllers.Success(ctx, true)
return controllers.Success(ctx, nil)
}
func (r *Php80Controller) ClearSlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/slow.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/slow.log")
return controllers.Success(ctx, true)
return controllers.Success(ctx, nil)
}
func (r *Php80Controller) GetExtensionList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
extensions := r.GetExtensions()
return controllers.Success(ctx, extensions)
}
func (r *Php80Controller) InstallExtension(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
@@ -259,7 +177,7 @@ func (r *Php80Controller) InstallExtension(ctx http.Context) http.Response {
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' install ` + r.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + r.version + "] 创建安装拓展任务失败:" + err.Error())
facades.Log().Info("[PHP-" + r.version + "] 创建安装拓展任务失败:" + err.Error())
return controllers.ErrorSystem(ctx)
}
@@ -273,11 +191,6 @@ func (r *Php80Controller) InstallExtension(ctx http.Context) http.Response {
}
func (r *Php80Controller) UninstallExtension(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
@@ -296,7 +209,7 @@ func (r *Php80Controller) UninstallExtension(ctx http.Context) http.Response {
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' uninstall ` + r.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + r.version + "] 创建卸载拓展任务失败:" + err.Error())
facades.Log().Info("[PHP-" + r.version + "] 创建卸载拓展任务失败:" + err.Error())
return controllers.ErrorSystem(ctx)
}
@@ -348,9 +261,12 @@ func (r *Php80Controller) GetExtensions() []PHPExtension {
Installed: false,
})
raw := tools.Exec("/www/server/php/" + r.version + "/bin/php -m")
rawExtensionList := strings.Split(raw, "\n")
raw, err := tools.Exec("/www/server/php/" + r.version + "/bin/php -m")
if err != nil {
return extensions
}
rawExtensionList := strings.Split(raw, "\n")
for _, item := range rawExtensionList {
if !strings.Contains(item, "[") && item != "" {
for i := range extensions {
+45 -129
View File
@@ -12,13 +12,14 @@ import (
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type Php81Controller struct {
setting services.Setting
task services.Task
setting internal.Setting
task internal.Task
version string
}
@@ -31,134 +32,68 @@ func NewPhp81Controller() *Php81Controller {
}
func (r *Php81Controller) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
status := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("php-fpm-" + r.version)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
func (r *Php81Controller) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceReload("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载PHP-"+r.version+"失败")
}
tools.Exec("systemctl reload php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php81Controller) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceStart("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动PHP-"+r.version+"失败")
}
tools.Exec("systemctl start php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php81Controller) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceStop("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止PHP-"+r.version+"失败")
}
tools.Exec("systemctl stop php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php81Controller) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceRestart("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启PHP-"+r.version+"失败")
}
tools.Exec("systemctl restart php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php81Controller) GetConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
config, err := tools.Read("/www/server/php/" + r.version + "/etc/php.ini")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"配置失败")
}
config := tools.Read("/www/server/php/" + r.version + "/etc/php.ini")
return controllers.Success(ctx, config)
}
func (r *Php81Controller) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
config := ctx.Request().Input("config")
tools.Write("/www/server/php/"+r.version+"/etc/php.ini", config, 0644)
if err := tools.Write("/www/server/php/"+r.version+"/etc/php.ini", config, 0644); err != nil {
return nil
}
return r.Reload(ctx)
}
func (r *Php81Controller) Load(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
client := req.C().SetTimeout(10 * time.Second)
resp, err := client.R().Get("http://127.0.0.1/phpfpm_status/" + r.version)
if err != nil || !resp.IsSuccessState() {
facades.Log().Error("获取PHP-" + r.version + "运行状态失败")
facades.Log().Info("获取PHP-" + r.version + "运行状态失败")
return controllers.Error(ctx, http.StatusInternalServerError, "[PHP-"+r.version+"] 获取运行状态失败")
}
@@ -186,61 +121,44 @@ func (r *Php81Controller) Load(ctx http.Context) http.Response {
}
func (r *Php81Controller) ErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/php-fpm.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/php-fpm.log"))
return controllers.Success(ctx, log)
}
func (r *Php81Controller) SlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/slow.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/slow.log"))
return controllers.Success(ctx, log)
}
func (r *Php81Controller) ClearErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/php-fpm.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/php-fpm.log")
return controllers.Success(ctx, true)
return controllers.Success(ctx, nil)
}
func (r *Php81Controller) ClearSlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/slow.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/slow.log")
return controllers.Success(ctx, true)
return controllers.Success(ctx, nil)
}
func (r *Php81Controller) GetExtensionList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
extensions := r.GetExtensions()
return controllers.Success(ctx, extensions)
}
func (r *Php81Controller) InstallExtension(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
@@ -259,7 +177,7 @@ func (r *Php81Controller) InstallExtension(ctx http.Context) http.Response {
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' install ` + r.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + r.version + "] 创建安装拓展任务失败:" + err.Error())
facades.Log().Info("[PHP-" + r.version + "] 创建安装拓展任务失败:" + err.Error())
return controllers.ErrorSystem(ctx)
}
@@ -273,11 +191,6 @@ func (r *Php81Controller) InstallExtension(ctx http.Context) http.Response {
}
func (r *Php81Controller) UninstallExtension(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
@@ -296,7 +209,7 @@ func (r *Php81Controller) UninstallExtension(ctx http.Context) http.Response {
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' uninstall ` + r.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + r.version + "] 创建卸载拓展任务失败:" + err.Error())
facades.Log().Info("[PHP-" + r.version + "] 创建卸载拓展任务失败:" + err.Error())
return controllers.ErrorSystem(ctx)
}
@@ -348,9 +261,12 @@ func (r *Php81Controller) GetExtensions() []PHPExtension {
Installed: false,
})
raw := tools.Exec("/www/server/php/" + r.version + "/bin/php -m")
rawExtensionList := strings.Split(raw, "\n")
raw, err := tools.Exec("/www/server/php/" + r.version + "/bin/php -m")
if err != nil {
return extensions
}
rawExtensionList := strings.Split(raw, "\n")
for _, item := range rawExtensionList {
if !strings.Contains(item, "[") && item != "" {
for i := range extensions {
+45 -129
View File
@@ -12,13 +12,14 @@ import (
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type Php82Controller struct {
setting services.Setting
task services.Task
setting internal.Setting
task internal.Task
version string
}
@@ -31,134 +32,68 @@ func NewPhp82Controller() *Php82Controller {
}
func (r *Php82Controller) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
status := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("php-fpm-" + r.version)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
func (r *Php82Controller) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceReload("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载PHP-"+r.version+"失败")
}
tools.Exec("systemctl reload php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php82Controller) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceStart("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动PHP-"+r.version+"失败")
}
tools.Exec("systemctl start php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php82Controller) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceStop("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止PHP-"+r.version+"失败")
}
tools.Exec("systemctl stop php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php82Controller) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if err := tools.ServiceRestart("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启PHP-"+r.version+"失败")
}
tools.Exec("systemctl restart php-fpm-" + r.version)
out := tools.Exec("systemctl status php-fpm-" + r.version + " | grep Active | grep -v grep | awk '{print $2}'")
status := strings.TrimSpace(out)
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
func (r *Php82Controller) GetConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
config, err := tools.Read("/www/server/php/" + r.version + "/etc/php.ini")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"配置失败")
}
config := tools.Read("/www/server/php/" + r.version + "/etc/php.ini")
return controllers.Success(ctx, config)
}
func (r *Php82Controller) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
config := ctx.Request().Input("config")
tools.Write("/www/server/php/"+r.version+"/etc/php.ini", config, 0644)
if err := tools.Write("/www/server/php/"+r.version+"/etc/php.ini", config, 0644); err != nil {
return nil
}
return r.Reload(ctx)
}
func (r *Php82Controller) Load(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
client := req.C().SetTimeout(10 * time.Second)
resp, err := client.R().Get("http://127.0.0.1/phpfpm_status/" + r.version)
if err != nil || !resp.IsSuccessState() {
facades.Log().Error("获取PHP-" + r.version + "运行状态失败")
facades.Log().Info("获取PHP-" + r.version + "运行状态失败")
return controllers.Error(ctx, http.StatusInternalServerError, "[PHP-"+r.version+"] 获取运行状态失败")
}
@@ -186,61 +121,44 @@ func (r *Php82Controller) Load(ctx http.Context) http.Response {
}
func (r *Php82Controller) ErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/php-fpm.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/php-fpm.log"))
return controllers.Success(ctx, log)
}
func (r *Php82Controller) SlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/slow.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Escape(tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/slow.log"))
return controllers.Success(ctx, log)
}
func (r *Php82Controller) ClearErrorLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/php-fpm.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/php-fpm.log")
return controllers.Success(ctx, true)
return controllers.Success(ctx, nil)
}
func (r *Php82Controller) ClearSlowLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/slow.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/slow.log")
return controllers.Success(ctx, true)
return controllers.Success(ctx, nil)
}
func (r *Php82Controller) GetExtensionList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
extensions := r.GetExtensions()
return controllers.Success(ctx, extensions)
}
func (r *Php82Controller) InstallExtension(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
@@ -259,7 +177,7 @@ func (r *Php82Controller) InstallExtension(ctx http.Context) http.Response {
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' install ` + r.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + r.version + "] 创建安装拓展任务失败:" + err.Error())
facades.Log().Info("[PHP-" + r.version + "] 创建安装拓展任务失败:" + err.Error())
return controllers.ErrorSystem(ctx)
}
@@ -273,11 +191,6 @@ func (r *Php82Controller) InstallExtension(ctx http.Context) http.Response {
}
func (r *Php82Controller) UninstallExtension(ctx http.Context) http.Response {
check := controllers.Check(ctx, "php"+r.version)
if check != nil {
return check
}
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
@@ -296,7 +209,7 @@ func (r *Php82Controller) UninstallExtension(ctx http.Context) http.Response {
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' uninstall ` + r.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Error("[PHP-" + r.version + "] 创建卸载拓展任务失败:" + err.Error())
facades.Log().Info("[PHP-" + r.version + "] 创建卸载拓展任务失败:" + err.Error())
return controllers.ErrorSystem(ctx)
}
@@ -342,9 +255,12 @@ func (r *Php82Controller) GetExtensions() []PHPExtension {
Installed: false,
})
raw := tools.Exec("/www/server/php/" + r.version + "/bin/php -m")
rawExtensionList := strings.Split(raw, "\n")
raw, err := tools.Exec("/www/server/php/" + r.version + "/bin/php -m")
if err != nil {
return extensions
}
rawExtensionList := strings.Split(raw, "\n")
for _, item := range rawExtensionList {
if !strings.Contains(item, "[") && item != "" {
for i := range extensions {
@@ -0,0 +1,275 @@
package plugins
import (
"fmt"
"regexp"
"strings"
"time"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/imroc/req/v3"
"panel/app/http/controllers"
"panel/app/models"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type Php83Controller struct {
setting internal.Setting
task internal.Task
version string
}
func NewPhp83Controller() *Php83Controller {
return &Php83Controller{
setting: services.NewSettingImpl(),
task: services.NewTaskImpl(),
version: "83",
}
}
func (r *Php83Controller) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("php-fpm-" + r.version)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"运行状态失败")
}
return controllers.Success(ctx, status)
}
func (r *Php83Controller) Reload(ctx http.Context) http.Response {
if err := tools.ServiceReload("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载PHP-"+r.version+"失败")
}
return controllers.Success(ctx, nil)
}
func (r *Php83Controller) Start(ctx http.Context) http.Response {
if err := tools.ServiceStart("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动PHP-"+r.version+"失败")
}
return controllers.Success(ctx, nil)
}
func (r *Php83Controller) Stop(ctx http.Context) http.Response {
if err := tools.ServiceStop("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止PHP-"+r.version+"失败")
}
return controllers.Success(ctx, nil)
}
func (r *Php83Controller) Restart(ctx http.Context) http.Response {
if err := tools.ServiceRestart("php-fpm-" + r.version); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启PHP-"+r.version+"失败")
}
return controllers.Success(ctx, nil)
}
func (r *Php83Controller) GetConfig(ctx http.Context) http.Response {
config, err := tools.Read("/www/server/php/" + r.version + "/etc/php.ini")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PHP-"+r.version+"配置失败")
}
return controllers.Success(ctx, config)
}
func (r *Php83Controller) SaveConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
if err := tools.Write("/www/server/php/"+r.version+"/etc/php.ini", config, 0644); err != nil {
return nil
}
return r.Reload(ctx)
}
func (r *Php83Controller) Load(ctx http.Context) http.Response {
client := req.C().SetTimeout(10 * time.Second)
resp, err := client.R().Get("http://127.0.0.1/phpfpm_status/" + r.version)
if err != nil || !resp.IsSuccessState() {
facades.Log().Info("获取PHP-" + r.version + "运行状态失败")
return controllers.Error(ctx, http.StatusInternalServerError, "[PHP-"+r.version+"] 获取运行状态失败")
}
raw := resp.String()
dataKeys := []string{"应用池", "工作模式", "启动时间", "接受连接", "监听队列", "最大监听队列", "监听队列长度", "空闲进程数量", "活动进程数量", "总进程数量", "最大活跃进程数量", "达到进程上限次数", "慢请求"}
regexKeys := []string{"pool", "process manager", "start time", "accepted conn", "listen queue", "max listen queue", "listen queue len", "idle processes", "active processes", "total processes", "max active processes", "max children reached", "slow requests"}
type Data struct {
Name string `json:"name"`
Value string `json:"value"`
}
data := make([]Data, len(dataKeys))
for i := range dataKeys {
data[i].Name = dataKeys[i]
r := regexp.MustCompile(fmt.Sprintf("%s:\\s+(.*)", regexKeys[i]))
match := r.FindStringSubmatch(raw)
if len(match) > 1 {
data[i].Value = strings.TrimSpace(match[1])
}
}
return controllers.Success(ctx, data)
}
func (r *Php83Controller) ErrorLog(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/php-fpm.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
return controllers.Success(ctx, log)
}
func (r *Php83Controller) SlowLog(ctx http.Context) http.Response {
log, err := tools.Exec("tail -n 100 /www/server/php/" + r.version + "/var/log/slow.log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
return controllers.Success(ctx, log)
}
func (r *Php83Controller) ClearErrorLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/php-fpm.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
func (r *Php83Controller) ClearSlowLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/server/php/" + r.version + "/var/log/slow.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
func (r *Php83Controller) GetExtensionList(ctx http.Context) http.Response {
extensions := r.GetExtensions()
return controllers.Success(ctx, extensions)
}
func (r *Php83Controller) InstallExtension(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
extensions := r.GetExtensions()
for _, item := range extensions {
if item.Slug == slug {
if item.Installed {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "扩展已安装")
}
var task models.Task
task.Name = "安装PHP-" + r.version + "扩展-" + item.Name
task.Status = models.TaskStatusWaiting
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' install ` + r.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Info("[PHP-" + r.version + "] 创建安装拓展任务失败:" + err.Error())
return controllers.ErrorSystem(ctx)
}
r.task.Process(task.ID)
return controllers.Success(ctx, true)
}
}
return controllers.Error(ctx, http.StatusUnprocessableEntity, "扩展不存在")
}
func (r *Php83Controller) UninstallExtension(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
extensions := r.GetExtensions()
for _, item := range extensions {
if item.Slug == slug {
if !item.Installed {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "扩展未安装")
}
var task models.Task
task.Name = "卸载PHP-" + r.version + "扩展-" + item.Name
task.Status = models.TaskStatusWaiting
task.Shell = `bash '/www/panel/scripts/php_extensions/` + item.Slug + `.sh' uninstall ` + r.version + ` >> /tmp/` + item.Slug + `.log 2>&1`
task.Log = "/tmp/" + item.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Info("[PHP-" + r.version + "] 创建卸载拓展任务失败:" + err.Error())
return controllers.ErrorSystem(ctx)
}
r.task.Process(task.ID)
return controllers.Success(ctx, true)
}
}
return controllers.Error(ctx, http.StatusUnprocessableEntity, "扩展不存在")
}
func (r *Php83Controller) GetExtensions() []PHPExtension {
var extensions []PHPExtension
extensions = append(extensions, PHPExtension{
Name: "OPcache",
Slug: "Zend OPcache",
Description: "OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码可以省去每次加载和解析 PHP 脚本的开销。",
Installed: false,
})
extensions = append(extensions, PHPExtension{
Name: "PhpRedis",
Slug: "redis",
Description: "PhpRedis 是一个用C语言编写的PHP模块,用来连接并操作 Redis 数据库上的数据。",
Installed: false,
})
extensions = append(extensions, PHPExtension{
Name: "ImageMagick",
Slug: "imagick",
Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件。",
Installed: false,
})
extensions = append(extensions, PHPExtension{
Name: "Exif",
Slug: "exif",
Description: "通过 exif 扩展,你可以操作图像元数据。",
Installed: false,
})
extensions = append(extensions, PHPExtension{
Name: "pdo_pgsql",
Slug: "pdo_pgsql",
Description: "(需先安装PostgreSQLpdo_pgsql 是一个驱动程序,它实现了 PHP 数据对象(PDO)接口以启用从 PHP 到 PostgreSQL 数据库的访问。",
Installed: false,
})
raw, err := tools.Exec("/www/server/php/" + r.version + "/bin/php -m")
if err != nil {
return extensions
}
rawExtensionList := strings.Split(raw, "\n")
for _, item := range rawExtensionList {
if !strings.Contains(item, "[") && item != "" {
for i := range extensions {
if extensions[i].Slug == item {
extensions[i].Installed = true
}
}
}
}
return extensions
}
@@ -6,6 +6,7 @@ import (
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/spf13/cast"
"panel/app/http/controllers"
@@ -20,14 +21,9 @@ func NewPhpMyAdminController() *PhpMyAdminController {
}
func (r *PhpMyAdminController) Info(ctx http.Context) http.Response {
check := controllers.Check(ctx, "phpmyadmin")
if check != nil {
return check
}
files, err := os.ReadDir("/www/server/phpmyadmin")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "找不到 phpMyAdmin 目录")
return controllers.Error(ctx, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
}
var phpmyadmin string
@@ -37,13 +33,16 @@ func (r *PhpMyAdminController) Info(ctx http.Context) http.Response {
}
}
if len(phpmyadmin) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "找不到 phpMyAdmin 目录")
return controllers.Error(ctx, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
}
conf := tools.Read("/www/server/vhost/phpmyadmin.conf")
conf, err := tools.Read("/www/server/vhost/phpmyadmin.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
match := regexp.MustCompile(`listen\s+(\d+);`).FindStringSubmatch(conf)
if len(match) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "找不到 phpMyAdmin 端口")
return controllers.Error(ctx, http.StatusInternalServerError, "找不到 phpMyAdmin 端口")
}
return controllers.Success(ctx, http.Json{
@@ -53,28 +52,42 @@ func (r *PhpMyAdminController) Info(ctx http.Context) http.Response {
}
func (r *PhpMyAdminController) SetPort(ctx http.Context) http.Response {
check := controllers.Check(ctx, "phpmyadmin")
if check != nil {
return check
}
port := ctx.Request().Input("port")
if len(port) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "端口不能为空")
return controllers.Error(ctx, http.StatusInternalServerError, "端口不能为空")
}
conf := tools.Read("/www/server/vhost/phpmyadmin.conf")
conf, err := tools.Read("/www/server/vhost/phpmyadmin.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
conf = regexp.MustCompile(`listen\s+(\d+);`).ReplaceAllString(conf, "listen "+port+";")
tools.Write("/www/server/vhost/phpmyadmin.conf", conf, 0644)
if err := tools.Write("/www/server/vhost/phpmyadmin.conf", conf, 0644); err != nil {
facades.Log().Request(ctx.Request()).Tags("插件", "phpMyAdmin").With(map[string]any{
"error": err.Error(),
}).Info("修改 phpMyAdmin 端口失败")
return controllers.ErrorSystem(ctx)
}
if tools.IsRHEL() {
tools.Exec("firewall-cmd --zone=public --add-port=" + port + "/tcp --permanent")
tools.Exec("firewall-cmd --reload")
if out, err := tools.Exec("firewall-cmd --zone=public --add-port=" + port + "/tcp --permanent"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
} else {
tools.Exec("ufw allow " + port + "/tcp")
tools.Exec("ufw reload")
if out, err := tools.Exec("ufw allow " + port + "/tcp"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
if err := tools.ServiceReload("openresty"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载OpenResty失败")
}
tools.Exec("systemctl reload openresty")
return controllers.Success(ctx, nil)
}
@@ -4,18 +4,18 @@ import (
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/carbon"
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type Postgresql15Controller struct {
setting services.Setting
backup services.Backup
setting internal.Setting
backup internal.Backup
}
func NewPostgresql15Controller() *Postgresql15Controller {
@@ -27,113 +27,55 @@ func NewPostgresql15Controller() *Postgresql15Controller {
// Status 获取运行状态
func (r *Postgresql15Controller) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
// Reload 重载配置
func (r *Postgresql15Controller) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
if err := tools.ServiceReload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载PostgreSQL失败")
}
tools.Exec("systemctl reload postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Restart 重启服务
func (r *Postgresql15Controller) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
if err := tools.ServiceRestart("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启PostgreSQL失败")
}
tools.Exec("systemctl restart postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *Postgresql15Controller) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
if err := tools.ServiceStart("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动PostgreSQL失败")
}
tools.Exec("systemctl start postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *Postgresql15Controller) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
if err := tools.ServiceStop("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止PostgreSQL失败")
}
tools.Exec("systemctl stop postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// GetConfig 获取配置
func (r *Postgresql15Controller) GetConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
// 获取配置
config := tools.Read("/www/server/postgresql/data/postgresql.conf")
if len(config) == 0 {
config, err := tools.Read("/www/server/postgresql/data/postgresql.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
@@ -142,14 +84,9 @@ func (r *Postgresql15Controller) GetConfig(ctx http.Context) http.Response {
// GetUserConfig 获取用户配置
func (r *Postgresql15Controller) GetUserConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
// 获取配置
config := tools.Read("/www/server/postgresql/data/pg_hba.conf")
if len(config) == 0 {
config, err := tools.Read("/www/server/postgresql/data/pg_hba.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
@@ -158,17 +95,12 @@ func (r *Postgresql15Controller) GetUserConfig(ctx http.Context) http.Response {
// SaveConfig 保存配置
func (r *Postgresql15Controller) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if !tools.Write("/www/server/postgresql/data/postgresql.conf", config, 0644) {
if err := tools.Write("/www/server/postgresql/data/postgresql.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
@@ -177,17 +109,12 @@ func (r *Postgresql15Controller) SaveConfig(ctx http.Context) http.Response {
// SaveUserConfig 保存用户配置
func (r *Postgresql15Controller) SaveUserConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if !tools.Write("/www/server/postgresql/data/pg_hba.conf", config, 0644) {
if err := tools.Write("/www/server/postgresql/data/pg_hba.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
@@ -196,22 +123,41 @@ func (r *Postgresql15Controller) SaveUserConfig(ctx http.Context) http.Response
// Load 获取负载
func (r *Postgresql15Controller) Load(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL已停止运行")
}
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if status != "active" {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL 已停止运行")
time, err := tools.Exec(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL启动时间失败")
}
pid, err := tools.Exec(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程PID失败")
}
process, err := tools.Exec(`ps aux | grep postgres | grep -v grep | wc -l`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程数失败")
}
connections, err := tools.Exec(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL连接数失败")
}
storage, err := tools.Exec(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL空间占用失败")
}
data := []LoadInfo{
{"启动时间", carbon.Parse(tools.Exec(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)).ToDateTimeString()},
{"进程 PID", tools.Exec(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)},
{"进程数", tools.Exec(`ps aux | grep postgres | grep -v grep | wc -l`)},
{"总连接数", tools.Exec(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)},
{"空间占用", tools.Exec(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)},
{"启动时间", carbon.Parse(time).ToDateTimeString()},
{"进程 PID", pid},
{"进程数", process},
{"总连接数", connections},
{"空间占用", storage},
}
return controllers.Success(ctx, data)
@@ -219,36 +165,31 @@ func (r *Postgresql15Controller) Load(ctx http.Context) http.Response {
// Log 获取日志
func (r *Postgresql15Controller) Log(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Exec("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
return controllers.Success(ctx, log)
}
// ClearLog 清空日志
func (r *Postgresql15Controller) ClearLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
return controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (r *Postgresql15Controller) DatabaseList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if status != "active" {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL 已停止运行")
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL已停止运行")
}
type database struct {
@@ -257,7 +198,10 @@ func (r *Postgresql15Controller) DatabaseList(ctx http.Context) http.Response {
Encoding string `json:"encoding"`
}
raw := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
raw, err := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, raw)
}
databases := strings.Split(raw, "\n")
if len(databases) >= 4 {
databases = databases[3 : len(databases)-1]
@@ -305,11 +249,6 @@ func (r *Postgresql15Controller) DatabaseList(ctx http.Context) http.Response {
// AddDatabase 添加数据库
func (r *Postgresql15Controller) AddDatabase(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
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_]+$",
@@ -326,24 +265,29 @@ func (r *Postgresql15Controller) AddDatabase(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.Exec(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`)
tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
tools.Exec(`echo "ALTER DATABASE ` + database + ` OWNER TO ` + user + `;" | su - postgres -c "psql"`)
tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`)
if out, err := tools.Exec(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "ALTER DATABASE ` + database + ` OWNER TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`)
if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// DeleteDatabase 删除数据库
func (r *Postgresql15Controller) DeleteDatabase(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
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:postgres,template0,template1",
})
@@ -355,21 +299,17 @@ func (r *Postgresql15Controller) DeleteDatabase(ctx http.Context) http.Response
}
database := ctx.Request().Input("database")
tools.Exec(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`)
if out, err := tools.Exec(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// BackupList 获取备份列表
func (r *Postgresql15Controller) BackupList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
backupList, err := r.backup.PostgresqlList()
if err != nil {
facades.Log().Error("[PostgreSQL] 获取备份列表失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败")
}
@@ -380,7 +320,7 @@ func (r *Postgresql15Controller) BackupList(ctx http.Context) http.Response {
if startIndex > len(backupList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []services.BackupFile{},
"items": []internal.BackupFile{},
})
}
if endIndex > len(backupList) {
@@ -388,7 +328,7 @@ func (r *Postgresql15Controller) BackupList(ctx http.Context) http.Response {
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []services.BackupFile{}
pagedBackupList = []internal.BackupFile{}
}
return controllers.Success(ctx, http.Json{
@@ -399,11 +339,6 @@ func (r *Postgresql15Controller) BackupList(ctx http.Context) http.Response {
// UploadBackup 上传备份
func (r *Postgresql15Controller) UploadBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
file, err := ctx.Request().File("file")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
@@ -411,7 +346,9 @@ func (r *Postgresql15Controller) UploadBackup(ctx http.Context) http.Response {
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
if err = tools.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
name := file.GetClientOriginalName()
@@ -425,11 +362,6 @@ func (r *Postgresql15Controller) UploadBackup(ctx http.Context) http.Response {
// CreateBackup 创建备份
func (r *Postgresql15Controller) CreateBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
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",
})
@@ -443,8 +375,7 @@ func (r *Postgresql15Controller) CreateBackup(ctx http.Context) http.Response {
database := ctx.Request().Input("database")
err = r.backup.PostgresqlBackup(database)
if err != nil {
facades.Log().Error("[PostgreSQL] 创建备份失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "创建备份失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -452,11 +383,6 @@ func (r *Postgresql15Controller) CreateBackup(ctx http.Context) http.Response {
// DeleteBackup 删除备份
func (r *Postgresql15Controller) DeleteBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|min_len:1|max_len:255",
})
@@ -469,18 +395,15 @@ func (r *Postgresql15Controller) DeleteBackup(ctx http.Context) http.Response {
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
fileName := ctx.Request().Input("name")
tools.Remove(backupPath + "/" + fileName)
if err := tools.Remove(backupPath + "/" + fileName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// RestoreBackup 还原备份
func (r *Postgresql15Controller) RestoreBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
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",
@@ -494,7 +417,6 @@ func (r *Postgresql15Controller) RestoreBackup(ctx http.Context) http.Response {
err = r.backup.PostgresqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup"))
if err != nil {
facades.Log().Error("[PostgreSQL] 还原失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error())
}
@@ -503,17 +425,15 @@ func (r *Postgresql15Controller) RestoreBackup(ctx http.Context) http.Response {
// UserList 用户列表
func (r *Postgresql15Controller) UserList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
type user struct {
User string `json:"user"`
Role string `json:"role"`
}
raw := tools.Exec(`echo "\du" | su - postgres -c "psql"`)
raw, err := tools.Exec(`echo "\du" | su - postgres -c "psql"`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, raw)
}
users := strings.Split(raw, "\n")
if len(users) < 4 {
return controllers.Error(ctx, http.StatusInternalServerError, "用户列表为空")
@@ -556,11 +476,6 @@ func (r *Postgresql15Controller) UserList(ctx http.Context) http.Response {
// AddUser 添加用户
func (r *Postgresql15Controller) AddUser(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
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_]+$",
@@ -576,22 +491,23 @@ func (r *Postgresql15Controller) AddUser(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`)
if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`)
if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// DeleteUser 删除用户
func (r *Postgresql15Controller) DeleteUser(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
})
@@ -603,19 +519,18 @@ func (r *Postgresql15Controller) DeleteUser(ctx http.Context) http.Response {
}
user := ctx.Request().Input("user")
tools.Exec(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`)
tools.Exec(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`)
if out, err := tools.Exec(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// SetUserPassword 设置用户密码
func (r *Postgresql15Controller) SetUserPassword(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql15")
if check != nil {
return check
}
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",
@@ -629,7 +544,9 @@ func (r *Postgresql15Controller) SetUserPassword(ctx http.Context) http.Response
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.Exec(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
if out, err := tools.Exec(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
@@ -4,18 +4,18 @@ import (
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/carbon"
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type Postgresql16Controller struct {
setting services.Setting
backup services.Backup
setting internal.Setting
backup internal.Backup
}
func NewPostgresql16Controller() *Postgresql16Controller {
@@ -27,113 +27,55 @@ func NewPostgresql16Controller() *Postgresql16Controller {
// Status 获取运行状态
func (r *Postgresql16Controller) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
// Reload 重载配置
func (r *Postgresql16Controller) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
if err := tools.ServiceReload("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载PostgreSQL失败")
}
tools.Exec("systemctl reload postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Restart 重启服务
func (r *Postgresql16Controller) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
if err := tools.ServiceRestart("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启PostgreSQL失败")
}
tools.Exec("systemctl restart postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *Postgresql16Controller) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
if err := tools.ServiceStart("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动PostgreSQL失败")
}
tools.Exec("systemctl start postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *Postgresql16Controller) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
if err := tools.ServiceStop("postgresql"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止PostgreSQL失败")
}
tools.Exec("systemctl stop postgresql")
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// GetConfig 获取配置
func (r *Postgresql16Controller) GetConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
// 获取配置
config := tools.Read("/www/server/postgresql/data/postgresql.conf")
if len(config) == 0 {
config, err := tools.Read("/www/server/postgresql/data/postgresql.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
@@ -142,14 +84,9 @@ func (r *Postgresql16Controller) GetConfig(ctx http.Context) http.Response {
// GetUserConfig 获取用户配置
func (r *Postgresql16Controller) GetUserConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
// 获取配置
config := tools.Read("/www/server/postgresql/data/pg_hba.conf")
if len(config) == 0 {
config, err := tools.Read("/www/server/postgresql/data/pg_hba.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL配置失败")
}
@@ -158,17 +95,12 @@ func (r *Postgresql16Controller) GetUserConfig(ctx http.Context) http.Response {
// SaveConfig 保存配置
func (r *Postgresql16Controller) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if !tools.Write("/www/server/postgresql/data/postgresql.conf", config, 0644) {
if err := tools.Write("/www/server/postgresql/data/postgresql.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
@@ -177,17 +109,12 @@ func (r *Postgresql16Controller) SaveConfig(ctx http.Context) http.Response {
// SaveUserConfig 保存用户配置
func (r *Postgresql16Controller) SaveUserConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if !tools.Write("/www/server/postgresql/data/pg_hba.conf", config, 0644) {
if err := tools.Write("/www/server/postgresql/data/pg_hba.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入PostgreSQL配置失败")
}
@@ -196,22 +123,41 @@ func (r *Postgresql16Controller) SaveUserConfig(ctx http.Context) http.Response
// Load 获取负载
func (r *Postgresql16Controller) Load(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL已停止运行")
}
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if status != "active" {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL 已停止运行")
time, err := tools.Exec(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL启动时间失败")
}
pid, err := tools.Exec(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程PID失败")
}
process, err := tools.Exec(`ps aux | grep postgres | grep -v grep | wc -l`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL进程数失败")
}
connections, err := tools.Exec(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL连接数失败")
}
storage, err := tools.Exec(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL空间占用失败")
}
data := []LoadInfo{
{"启动时间", carbon.Parse(tools.Exec(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)).ToDateTimeString()},
{"进程 PID", tools.Exec(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)},
{"进程数", tools.Exec(`ps aux | grep postgres | grep -v grep | wc -l`)},
{"总连接数", tools.Exec(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)},
{"空间占用", tools.Exec(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)},
{"启动时间", carbon.Parse(time).ToDateTimeString()},
{"进程 PID", pid},
{"进程数", process},
{"总连接数", connections},
{"空间占用", storage},
}
return controllers.Success(ctx, data)
@@ -219,36 +165,31 @@ func (r *Postgresql16Controller) Load(ctx http.Context) http.Response {
// Log 获取日志
func (r *Postgresql16Controller) Log(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
log, err := tools.Exec("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Exec("tail -n 100 /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
return controllers.Success(ctx, log)
}
// ClearLog 清空日志
func (r *Postgresql16Controller) ClearLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
if out, err := tools.Exec("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec("echo '' > /www/server/postgresql/logs/postgresql-" + carbon.Now().ToDateString() + ".log")
return controllers.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (r *Postgresql16Controller) DatabaseList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
status, err := tools.ServiceStatus("postgresql")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL状态失败")
}
status := tools.Exec("systemctl status postgresql | grep Active | grep -v grep | awk '{print $2}'")
if status != "active" {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL 已停止运行")
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "PostgreSQL已停止运行")
}
type database struct {
@@ -257,7 +198,10 @@ func (r *Postgresql16Controller) DatabaseList(ctx http.Context) http.Response {
Encoding string `json:"encoding"`
}
raw := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
raw, err := tools.Exec(`echo "\l" | su - postgres -c "psql"`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, raw)
}
databases := strings.Split(raw, "\n")
if len(databases) >= 4 {
databases = databases[3 : len(databases)-1]
@@ -305,11 +249,6 @@ func (r *Postgresql16Controller) DatabaseList(ctx http.Context) http.Response {
// AddDatabase 添加数据库
func (r *Postgresql16Controller) AddDatabase(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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_]+$",
@@ -326,24 +265,29 @@ func (r *Postgresql16Controller) AddDatabase(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.Exec(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`)
tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
tools.Exec(`echo "ALTER DATABASE ` + database + ` OWNER TO ` + user + `;" | su - postgres -c "psql"`)
tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`)
if out, err := tools.Exec(`echo "CREATE DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "ALTER DATABASE ` + database + ` OWNER TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`)
if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// DeleteDatabase 删除数据库
func (r *Postgresql16Controller) DeleteDatabase(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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:postgres,template0,template1",
})
@@ -355,21 +299,17 @@ func (r *Postgresql16Controller) DeleteDatabase(ctx http.Context) http.Response
}
database := ctx.Request().Input("database")
tools.Exec(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`)
if out, err := tools.Exec(`echo "DROP DATABASE ` + database + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// BackupList 获取备份列表
func (r *Postgresql16Controller) BackupList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
backupList, err := r.backup.PostgresqlList()
if err != nil {
facades.Log().Error("[PostgreSQL] 获取备份列表失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败")
}
@@ -380,7 +320,7 @@ func (r *Postgresql16Controller) BackupList(ctx http.Context) http.Response {
if startIndex > len(backupList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []services.BackupFile{},
"items": []internal.BackupFile{},
})
}
if endIndex > len(backupList) {
@@ -388,7 +328,7 @@ func (r *Postgresql16Controller) BackupList(ctx http.Context) http.Response {
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []services.BackupFile{}
pagedBackupList = []internal.BackupFile{}
}
return controllers.Success(ctx, http.Json{
@@ -399,11 +339,6 @@ func (r *Postgresql16Controller) BackupList(ctx http.Context) http.Response {
// UploadBackup 上传备份
func (r *Postgresql16Controller) UploadBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
file, err := ctx.Request().File("file")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
@@ -411,7 +346,9 @@ func (r *Postgresql16Controller) UploadBackup(ctx http.Context) http.Response {
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
if err = tools.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
name := file.GetClientOriginalName()
@@ -425,11 +362,6 @@ func (r *Postgresql16Controller) UploadBackup(ctx http.Context) http.Response {
// CreateBackup 创建备份
func (r *Postgresql16Controller) CreateBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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",
})
@@ -443,8 +375,7 @@ func (r *Postgresql16Controller) CreateBackup(ctx http.Context) http.Response {
database := ctx.Request().Input("database")
err = r.backup.PostgresqlBackup(database)
if err != nil {
facades.Log().Error("[PostgreSQL] 创建备份失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "创建备份失败")
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
@@ -452,11 +383,6 @@ func (r *Postgresql16Controller) CreateBackup(ctx http.Context) http.Response {
// DeleteBackup 删除备份
func (r *Postgresql16Controller) DeleteBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|min_len:1|max_len:255",
})
@@ -469,18 +395,15 @@ func (r *Postgresql16Controller) DeleteBackup(ctx http.Context) http.Response {
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
fileName := ctx.Request().Input("name")
tools.Remove(backupPath + "/" + fileName)
if err := tools.Remove(backupPath + "/" + fileName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// RestoreBackup 还原备份
func (r *Postgresql16Controller) RestoreBackup(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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",
@@ -494,7 +417,6 @@ func (r *Postgresql16Controller) RestoreBackup(ctx http.Context) http.Response {
err = r.backup.PostgresqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup"))
if err != nil {
facades.Log().Error("[PostgreSQL] 还原失败:" + err.Error())
return controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error())
}
@@ -503,17 +425,15 @@ func (r *Postgresql16Controller) RestoreBackup(ctx http.Context) http.Response {
// UserList 用户列表
func (r *Postgresql16Controller) UserList(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
type user struct {
User string `json:"user"`
Role string `json:"role"`
}
raw := tools.Exec(`echo "\du" | su - postgres -c "psql"`)
raw, err := tools.Exec(`echo "\du" | su - postgres -c "psql"`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, raw)
}
users := strings.Split(raw, "\n")
if len(users) < 4 {
return controllers.Error(ctx, http.StatusInternalServerError, "用户列表为空")
@@ -556,11 +476,6 @@ func (r *Postgresql16Controller) UserList(ctx http.Context) http.Response {
// AddUser 添加用户
func (r *Postgresql16Controller) AddUser(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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_]+$",
@@ -576,22 +491,23 @@ func (r *Postgresql16Controller) AddUser(ctx http.Context) http.Response {
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`)
if out, err := tools.Exec(`echo "CREATE USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + database + ` TO ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
userConfig := "host " + database + " " + user + " 127.0.0.1/32 scram-sha-256"
tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`)
if out, err := tools.Exec(`echo "` + userConfig + `" >> /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// DeleteUser 删除用户
func (r *Postgresql16Controller) DeleteUser(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"user": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$",
})
@@ -603,19 +519,18 @@ func (r *Postgresql16Controller) DeleteUser(ctx http.Context) http.Response {
}
user := ctx.Request().Input("user")
tools.Exec(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`)
tools.Exec(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`)
if out, err := tools.Exec(`echo "DROP USER ` + user + `;" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`sed -i '/` + user + `/d' /www/server/postgresql/data/pg_hba.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return r.Reload(ctx)
}
// SetUserPassword 设置用户密码
func (r *Postgresql16Controller) SetUserPassword(ctx http.Context) http.Response {
check := controllers.Check(ctx, "postgresql16")
if check != nil {
return check
}
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",
@@ -629,7 +544,9 @@ func (r *Postgresql16Controller) SetUserPassword(ctx http.Context) http.Response
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
tools.Exec(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`)
if out, err := tools.Exec(`echo "ALTER USER ` + user + ` WITH PASSWORD '` + password + `';" | su - postgres -c "psql"`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
@@ -25,92 +25,48 @@ func NewPureFtpdController() *PureFtpdController {
// Status 获取运行状态
func (r *PureFtpdController) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "pureftpd")
if check != nil {
return check
}
status := tools.Exec("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("pure-ftpd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
// Restart 重启服务
func (r *PureFtpdController) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "pureftpd")
if check != nil {
return check
err := tools.ServiceRestart("pure-ftpd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启PureFtpd失败")
}
tools.Exec("systemctl restart pure-ftpd")
status := tools.Exec("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *PureFtpdController) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "pureftpd")
if check != nil {
return check
err := tools.ServiceStart("pure-ftpd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动PureFtpd失败")
}
tools.Exec("systemctl start pure-ftpd")
status := tools.Exec("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *PureFtpdController) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "pureftpd")
if check != nil {
return check
err := tools.ServiceStop("pure-ftpd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止PureFtpd失败")
}
tools.Exec("systemctl stop pure-ftpd")
status := tools.Exec("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// List 获取用户列表
func (r *PureFtpdController) List(ctx http.Context) http.Response {
check := controllers.Check(ctx, "pureftpd")
if check != nil {
return check
}
listRaw := tools.Exec("pure-pw list")
if len(listRaw) == 0 {
listRaw, err := tools.Exec("pure-pw list")
if err != nil {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []User{},
@@ -154,11 +110,6 @@ func (r *PureFtpdController) List(ctx http.Context) http.Response {
// Add 添加用户
func (r *PureFtpdController) Add(ctx http.Context) http.Response {
check := controllers.Check(ctx, "pureftpd")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"username": "required",
"password": "required|min_len:6",
@@ -182,21 +133,24 @@ func (r *PureFtpdController) Add(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "目录不存在")
}
tools.Chmod(path, 0755)
tools.Chown(path, "www", "www")
tools.Exec(`yes '` + password + `' | pure-pw useradd ` + username + ` -u www -g www -d ` + path)
tools.Exec("pure-pw mkdb")
if err = tools.Chmod(path, 0755); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "修改目录权限失败")
}
if err = tools.Chown(path, "www", "www"); err != nil {
return nil
}
if out, err := tools.Exec(`yes '` + password + `' | pure-pw useradd ` + username + ` -u www -g www -d ` + path); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("pure-pw mkdb"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// Delete 删除用户
func (r *PureFtpdController) Delete(ctx http.Context) http.Response {
check := controllers.Check(ctx, "pureftpd")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"username": "required",
})
@@ -209,19 +163,18 @@ func (r *PureFtpdController) Delete(ctx http.Context) http.Response {
username := ctx.Request().Input("username")
tools.Exec("pure-pw userdel " + username + " -m")
tools.Exec("pure-pw mkdb")
if out, err := tools.Exec("pure-pw userdel " + username + " -m"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("pure-pw mkdb"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// ChangePassword 修改密码
func (r *PureFtpdController) ChangePassword(ctx http.Context) http.Response {
check := controllers.Check(ctx, "pureftpd")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"username": "required",
"password": "required|min_len:6",
@@ -236,21 +189,20 @@ func (r *PureFtpdController) ChangePassword(ctx http.Context) http.Response {
username := ctx.Request().Input("username")
password := ctx.Request().Input("password")
tools.Exec(`yes '` + password + `' | pure-pw passwd ` + username + ` -m`)
tools.Exec("pure-pw mkdb")
if out, err := tools.Exec(`yes '` + password + `' | pure-pw passwd ` + username + ` -m`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("pure-pw mkdb"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
return controllers.Success(ctx, nil)
}
// GetPort 获取端口
func (r *PureFtpdController) GetPort(ctx http.Context) http.Response {
check := controllers.Check(ctx, "pureftpd")
if check != nil {
return check
}
port := tools.Exec(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
if len(port) == 0 {
port, err := tools.Exec(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd端口失败")
}
@@ -259,11 +211,6 @@ func (r *PureFtpdController) GetPort(ctx http.Context) http.Response {
// SetPort 设置端口
func (r *PureFtpdController) SetPort(ctx http.Context) http.Response {
check := controllers.Check(ctx, "pureftpd")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"port": "required",
})
@@ -275,15 +222,24 @@ func (r *PureFtpdController) SetPort(ctx http.Context) http.Response {
}
port := ctx.Request().Input("port")
tools.Exec(`sed -i "s/Bind.*/Bind 0.0.0.0,` + port + `/g" /www/server/pure-ftpd/etc/pure-ftpd.conf`)
if tools.IsRHEL() {
tools.Exec("firewall-cmd --zone=public --add-port=" + port + "/tcp --permanent")
tools.Exec("firewall-cmd --reload")
} else {
tools.Exec("ufw allow " + port + "/tcp")
tools.Exec("ufw reload")
if out, err := tools.Exec(`sed -i "s/Bind.*/Bind 0.0.0.0,` + port + `/g" /www/server/pure-ftpd/etc/pure-ftpd.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --zone=public --add-port=" + port + "/tcp --permanent"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("ufw allow " + port + "/tcp"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
tools.Exec("systemctl restart pure-ftpd")
return controllers.Success(ctx, nil)
return r.Restart(ctx)
}
@@ -18,93 +18,46 @@ func NewRedisController() *RedisController {
// Status 获取运行状态
func (r *RedisController) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "redis")
if check != nil {
return check
}
status := tools.Exec("systemctl status redis | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
status, err := tools.ServiceStatus("redis")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
// Restart 重启服务
func (r *RedisController) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "redis")
if check != nil {
return check
if err := tools.ServiceRestart("redis"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启Redis失败")
}
tools.Exec("systemctl restart redis")
status := tools.Exec("systemctl status redis | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Start 启动服务
func (r *RedisController) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "redis")
if check != nil {
return check
if err := tools.ServiceStart("redis"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动Redis失败")
}
tools.Exec("systemctl start redis")
status := tools.Exec("systemctl status redis | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
}
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Stop 停止服务
func (r *RedisController) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "redis")
if check != nil {
return check
if err := tools.ServiceStop("redis"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止Redis失败")
}
tools.Exec("systemctl stop redis")
status := tools.Exec("systemctl status redis | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
}
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// GetConfig 获取配置
func (r *RedisController) GetConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "redis")
if check != nil {
return check
}
// 获取配置
config := tools.Read("/www/server/redis/redis.conf")
if len(config) == 0 {
config, err := tools.Read("/www/server/redis/redis.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis配置失败")
}
@@ -113,17 +66,12 @@ func (r *RedisController) GetConfig(ctx http.Context) http.Response {
// SaveConfig 保存配置
func (r *RedisController) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "redis")
if check != nil {
return check
}
config := ctx.Request().Input("config")
if len(config) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if !tools.Write("/www/server/redis/redis.conf", config, 0644) {
if err := tools.Write("/www/server/redis/redis.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入Redis配置失败")
}
@@ -132,18 +80,16 @@ func (r *RedisController) SaveConfig(ctx http.Context) http.Response {
// Load 获取负载
func (r *RedisController) Load(ctx http.Context) http.Response {
check := controllers.Check(ctx, "redis")
if check != nil {
return check
status, err := tools.ServiceStatus("redis")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis状态失败")
}
if !status {
return controllers.Error(ctx, http.StatusInternalServerError, "Redis已停止运行")
}
status := tools.Exec("systemctl status redis | grep Active | grep -v grep | awk '{print $2}'")
if status != "active" {
return controllers.Error(ctx, http.StatusInternalServerError, "Redis 已停止运行")
}
raw := tools.Exec("redis-cli info")
if len(raw) == 0 {
raw, err := tools.Exec("redis-cli info")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Redis负载失败")
}
@@ -0,0 +1,394 @@
package plugins
import (
"regexp"
"strings"
"github.com/goravel/framework/contracts/http"
"panel/app/http/controllers"
commonrequests "panel/app/http/requests/common"
requests "panel/app/http/requests/plugins/rsync"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type RsyncController struct {
setting internal.Setting
}
func NewRsyncController() *RsyncController {
return &RsyncController{
setting: services.NewSettingImpl(),
}
}
// Status
//
// @Summary 服务状态
// @Description 获取 Rsync 服务状态
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/status [get]
func (r *RsyncController) Status(ctx http.Context) http.Response {
status, err := tools.ServiceStatus("rsyncd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
}
return controllers.Success(ctx, status)
}
// Restart
//
// @Summary 重启服务
// @Description 重启 Rsync 服务
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/restart [post]
func (r *RsyncController) Restart(ctx http.Context) http.Response {
if err := tools.ServiceRestart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启服务失败")
}
return controllers.Success(ctx, nil)
}
// Start
//
// @Summary 启动服务
// @Description 启动 Rsync 服务
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/start [post]
func (r *RsyncController) Start(ctx http.Context) http.Response {
if err := tools.ServiceStart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动服务失败")
}
return controllers.Success(ctx, nil)
}
// Stop
//
// @Summary 停止服务
// @Description 停止 Rsync 服务
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/stop [post]
func (r *RsyncController) Stop(ctx http.Context) http.Response {
if err := tools.ServiceStop("rsyncd"); err != nil {
return nil
}
status, err := tools.ServiceStatus("rsyncd")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取服务运行状态失败")
}
return controllers.Success(ctx, !status)
}
// List
//
// @Summary 列出模块
// @Description 列出所有 Rsync 模块
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Param data body commonrequests.Paginate true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/modules [get]
func (r *RsyncController) List(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := controllers.Sanitize(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
config, err := tools.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
var modules []RsyncModule
lines := strings.Split(config, "\n")
var currentModule *RsyncModule
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
if currentModule != nil {
modules = append(modules, *currentModule)
}
moduleName := line[1 : len(line)-1]
currentModule = &RsyncModule{
Name: moduleName,
}
} else if currentModule != nil {
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "path":
currentModule.Path = value
case "comment":
currentModule.Comment = value
case "read only":
currentModule.ReadOnly = value == "yes" || value == "true"
case "auth users":
currentModule.AuthUser = value
currentModule.Secret, err = tools.Exec("grep -E '^" + currentModule.AuthUser + ":.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取模块"+currentModule.AuthUser+"的密钥失败")
}
case "hosts allow":
currentModule.HostsAllow = value
}
}
}
}
if currentModule != nil {
modules = append(modules, *currentModule)
}
startIndex := (paginateRequest.Page - 1) * paginateRequest.Limit
endIndex := paginateRequest.Page * paginateRequest.Limit
if startIndex > len(modules) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []RsyncModule{},
})
}
if endIndex > len(modules) {
endIndex = len(modules)
}
pagedModules := modules[startIndex:endIndex]
if pagedModules == nil {
pagedModules = []RsyncModule{}
}
return controllers.Success(ctx, http.Json{
"total": len(modules),
"items": pagedModules,
})
}
// Create
//
// @Summary 添加模块
// @Description 添加 Rsync 模块
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Param data body requests.Create true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/modules [post]
func (r *RsyncController) Create(ctx http.Context) http.Response {
var createRequest requests.Create
sanitize := controllers.Sanitize(ctx, &createRequest)
if sanitize != nil {
return sanitize
}
config, err := tools.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if strings.Contains(config, "["+createRequest.Name+"]") {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "模块 "+createRequest.Name+" 已存在")
}
conf := `# ` + createRequest.Name + `-START
[` + createRequest.Name + `]
path = ` + createRequest.Path + `
comment = ` + createRequest.Comment + `
read only = no
auth users = ` + createRequest.AuthUser + `
hosts allow = ` + createRequest.HostsAllow + `
secrets file = /etc/rsyncd.secrets
# ` + createRequest.Name + `-END
`
if err := tools.WriteAppend("/etc/rsyncd.conf", conf); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("echo '" + createRequest.AuthUser + ":" + createRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err := tools.ServiceRestart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// Destroy
//
// @Summary 删除模块
// @Description 删除 Rsync 模块
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Param name path string true "模块名称"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/modules/{name} [delete]
func (r *RsyncController) Destroy(ctx http.Context) http.Response {
name := ctx.Request().Input("name")
if len(name) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "name 不能为空")
}
config, err := tools.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if !strings.Contains(config, "["+name+"]") {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "模块 "+name+" 不存在")
}
module := tools.Cut(config, "# "+name+"-START", "# "+name+"-END")
config = strings.Replace(config, "\n# "+name+"-START"+module+"# "+name+"-END", "", -1)
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
if len(match) == 2 {
authUser := match[1]
if out, err := tools.Exec("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
if err = tools.Write("/etc/rsyncd.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.ServiceRestart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// Update
//
// @Summary 更新模块
// @Description 更新 Rsync 模块
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Param name path string true "模块名称"
// @Param data body requests.Update true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/modules/{name} [post]
func (r *RsyncController) Update(ctx http.Context) http.Response {
var updateRequest requests.Update
sanitize := controllers.Sanitize(ctx, &updateRequest)
if sanitize != nil {
return sanitize
}
config, err := tools.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if !strings.Contains(config, "["+updateRequest.Name+"]") {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "模块 "+updateRequest.Name+" 不存在")
}
newConf := `# ` + updateRequest.Name + `-START
[` + updateRequest.Name + `]
path = ` + updateRequest.Path + `
comment = ` + updateRequest.Comment + `
read only = no
auth users = ` + updateRequest.AuthUser + `
hosts allow = ` + updateRequest.HostsAllow + `
secrets file = /etc/rsyncd.secrets
# ` + updateRequest.Name + `-END`
module := tools.Cut(config, "# "+updateRequest.Name+"-START", "# "+updateRequest.Name+"-END")
config = strings.Replace(config, "# "+updateRequest.Name+"-START"+module+"# "+updateRequest.Name+"-END", newConf, -1)
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
if len(match) == 2 {
authUser := match[1]
if out, err := tools.Exec("sed -i '/^" + authUser + ":.*$/d' /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
}
if err = tools.Write("/etc/rsyncd.conf", config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec("echo '" + updateRequest.AuthUser + ":" + updateRequest.Secret + "' >> /etc/rsyncd.secrets"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if err = tools.ServiceRestart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
// GetConfig
//
// @Summary 获取配置
// @Description 获取 Rsync 配置
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/config [get]
func (r *RsyncController) GetConfig(ctx http.Context) http.Response {
config, err := tools.Read("/etc/rsyncd.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, config)
}
// UpdateConfig
//
// @Summary 更新配置
// @Description 更新 Rsync 配置
// @Tags 插件-Rsync
// @Produce json
// @Security BearerToken
// @Param data body requests.UpdateConfig true "request"
// @Success 200 {object} controllers.SuccessResponse
// @Router /plugins/rsync/config [post]
func (r *RsyncController) UpdateConfig(ctx http.Context) http.Response {
var updateRequest requests.UpdateConfig
sanitize := controllers.Sanitize(ctx, &updateRequest)
if sanitize != nil {
return sanitize
}
if err := tools.Write("/etc/rsyncd.conf", updateRequest.Config, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err := tools.ServiceRestart("rsyncd"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
+31 -33
View File
@@ -9,12 +9,13 @@ import (
"github.com/spf13/cast"
"panel/app/http/controllers"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type S3fsController struct {
setting services.Setting
setting internal.Setting
}
func NewS3fsController() *S3fsController {
@@ -25,18 +26,13 @@ func NewS3fsController() *S3fsController {
// List 所有 S3fs 挂载
func (r *S3fsController) List(ctx http.Context) http.Response {
check := controllers.Check(ctx, "s3fs")
if check != nil {
return check
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
var s3fsList []S3fsMount
err := json.UnmarshalString(r.setting.Get("s3fs", "[]"), &s3fsList)
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取 S3fs 挂载失败")
return controllers.Error(ctx, http.StatusInternalServerError, "获取 S3fs 挂载失败")
}
startIndex := (page - 1) * limit
@@ -63,11 +59,6 @@ func (r *S3fsController) List(ctx http.Context) http.Response {
// Add 添加 S3fs 挂载
func (r *S3fsController) Add(ctx http.Context) http.Response {
check := controllers.Check(ctx, "s3fs")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"ak": "required|regex:^[a-zA-Z0-9]*$",
"sk": "required|regex:^[a-zA-Z0-9]*$",
@@ -95,7 +86,9 @@ func (r *S3fsController) Add(ctx http.Context) http.Response {
// 检查挂载目录是否存在且为空
if !tools.Exists(path) {
tools.Mkdir(path, 0755)
if err = tools.Mkdir(path, 0755); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载目录创建失败")
}
}
if !tools.Empty(path) {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载目录必须为空")
@@ -115,16 +108,19 @@ func (r *S3fsController) Add(ctx http.Context) http.Response {
id := carbon.Now().TimestampMilli()
password := ak + ":" + sk
tools.Write("/etc/passwd-s3fs-"+cast.ToString(id), password, 0600)
tools.Exec(`echo 's3fs#` + bucket + ` ` + path + ` fuse _netdev,allow_other,nonempty,url=` + url + `,passwd_file=/etc/passwd-s3fs-` + cast.ToString(id) + ` 0 0' >> /etc/fstab`)
mountCheck := tools.Exec("mount -a 2>&1")
if len(mountCheck) != 0 {
tools.Exec(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
if err = tools.Write("/etc/passwd-s3fs-"+cast.ToString(id), password, 0600); err != nil {
return nil
}
out, err := tools.Exec(`echo 's3fs#` + bucket + ` ` + path + ` fuse _netdev,allow_other,nonempty,url=` + url + `,passwd_file=/etc/passwd-s3fs-` + cast.ToString(id) + ` 0 0' >> /etc/fstab`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if mountCheck, err := tools.Exec("mount -a 2>&1"); err != nil {
_, _ = tools.Exec(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
return controllers.Error(ctx, http.StatusInternalServerError, "检测到/etc/fstab有误: "+mountCheck)
}
dfCheck := tools.Exec("df -h | grep " + path + " 2>&1")
if len(dfCheck) == 0 {
tools.Exec(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
if _, err := tools.Exec("df -h | grep " + path + " 2>&1"); err != nil {
_, _ = tools.Exec(`sed -i 's@^s3fs#` + bucket + `\s` + path + `.*$@@g' /etc/fstab`)
return controllers.Error(ctx, http.StatusInternalServerError, "挂载失败,请检查配置是否正确")
}
@@ -148,11 +144,6 @@ func (r *S3fsController) Add(ctx http.Context) http.Response {
// Delete 删除 S3fs 挂载
func (r *S3fsController) Delete(ctx http.Context) http.Response {
check := controllers.Check(ctx, "s3fs")
if check != nil {
return check
}
id := ctx.Request().Input("id")
if len(id) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载ID不能为空")
@@ -175,14 +166,21 @@ func (r *S3fsController) Delete(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载ID不存在")
}
tools.Exec(`fusermount -u '` + mount.Path + `' 2>&1`)
tools.Exec(`umount '` + mount.Path + `' 2>&1`)
tools.Exec(`sed -i 's@^s3fs#` + mount.Bucket + `\s` + mount.Path + `.*$@@g' /etc/fstab`)
mountCheck := tools.Exec("mount -a 2>&1")
if len(mountCheck) != 0 {
if out, err := tools.Exec(`fusermount -u '` + mount.Path + `' 2>&1`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`umount '` + mount.Path + `' 2>&1`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`sed -i 's@^s3fs#` + mount.Bucket + `\s` + mount.Path + `.*$@@g' /etc/fstab`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if mountCheck, err := tools.Exec("mount -a 2>&1"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "检测到/etc/fstab有误: "+mountCheck)
}
tools.Remove("/etc/passwd-s3fs-" + cast.ToString(mount.ID))
if err := tools.Remove("/etc/passwd-s3fs-" + cast.ToString(mount.ID)); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
var newS3fsList []S3fsMount
for _, s := range s3fsList {
@@ -29,133 +29,98 @@ func NewSupervisorController() *SupervisorController {
// Status 状态
func (r *SupervisorController) Status(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
status, err := tools.ServiceStatus(r.ServiceName)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取Supervisor状态失败")
}
status := tools.Exec(`systemctl status ` + r.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`)
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, status)
}
// Start 启动
func (r *SupervisorController) Start(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
if err := tools.ServiceStart(r.ServiceName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "启动Supervisor失败")
}
tools.Exec(`systemctl start ` + r.ServiceName)
status := tools.Exec(`systemctl status ` + r.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`)
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Stop 停止
func (r *SupervisorController) Stop(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
if err := tools.ServiceStop(r.ServiceName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "停止Supervisor失败")
}
tools.Exec(`systemctl stop ` + r.ServiceName)
status := tools.Exec(`systemctl status ` + r.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`)
if status != "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Restart 重启
func (r *SupervisorController) Restart(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
if err := tools.ServiceRestart(r.ServiceName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重启Supervisor失败")
}
tools.Exec(`systemctl restart ` + r.ServiceName)
status := tools.Exec(`systemctl status ` + r.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`)
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Reload 重载
func (r *SupervisorController) Reload(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
if err := tools.ServiceReload(r.ServiceName); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "重载Supervisor失败")
}
tools.Exec(`systemctl reload ` + r.ServiceName)
status := tools.Exec(`systemctl status ` + r.ServiceName + ` | grep Active | grep -v grep | awk '{print $2}'`)
if status == "active" {
return controllers.Success(ctx, true)
} else {
return controllers.Success(ctx, false)
}
return controllers.Success(ctx, nil)
}
// Log 日志
func (r *SupervisorController) Log(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
log, err := tools.Exec(`tail -n 200 /var/log/supervisor/supervisord.log`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Exec(`tail -n 200 /var/log/supervisor/supervisord.log`)
return controllers.Success(ctx, log)
}
// ClearLog 清空日志
func (r *SupervisorController) ClearLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
if out, err := tools.Exec(`echo "" > /var/log/supervisor/supervisord.log`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec(`echo "" > /var/log/supervisor/supervisord.log`)
return controllers.Success(ctx, nil)
}
// Config 获取配置
func (r *SupervisorController) Config(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
var config string
var err error
if tools.IsRHEL() {
config, err = tools.Read(`/etc/supervisord.conf`)
} else {
config, err = tools.Read(`/etc/supervisor/supervisord.conf`)
}
var config string
if tools.IsRHEL() {
config = tools.Read(`/etc/supervisord.conf`)
} else {
config = tools.Read(`/etc/supervisor/supervisord.conf`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, config)
}
// SaveConfig 保存配置
func (r *SupervisorController) SaveConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
config := ctx.Request().Input("config")
var err error
if tools.IsRHEL() {
err = tools.Write(`/etc/supervisord.conf`, config, 0644)
} else {
err = tools.Write(`/etc/supervisor/supervisord.conf`, config, 0644)
}
config := ctx.Request().Input("config")
if tools.IsRHEL() {
tools.Write(`/etc/supervisord.conf`, config, 0644)
} else {
tools.Write(`/etc/supervisor/supervisord.conf`, config, 0644)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return r.Restart(ctx)
@@ -163,11 +128,6 @@ func (r *SupervisorController) SaveConfig(ctx http.Context) http.Response {
// Processes 进程列表
func (r *SupervisorController) Processes(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
@@ -178,7 +138,11 @@ func (r *SupervisorController) Processes(ctx http.Context) http.Response {
Uptime string `json:"uptime"`
}
out := tools.Exec(`supervisorctl status | awk '{print $1}'`)
out, err := tools.Exec(`supervisorctl status | awk '{print $1}'`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
var processList []process
for _, line := range strings.Split(out, "\n") {
if len(line) == 0 {
@@ -187,10 +151,14 @@ func (r *SupervisorController) Processes(ctx http.Context) http.Response {
var p process
p.Name = line
p.Status = tools.Exec(`supervisorctl status ` + line + ` | awk '{print $2}'`)
if status, err := tools.Exec(`supervisorctl status ` + line + ` | awk '{print $2}'`); err == nil {
p.Status = status
}
if p.Status == "RUNNING" {
p.Pid = strings.ReplaceAll(tools.Exec(`supervisorctl status `+line+` | awk '{print $4}'`), ",", "")
p.Uptime = tools.Exec(`supervisorctl status ` + line + ` | awk '{print $6}'`)
pid, _ := tools.Exec(`supervisorctl status ` + line + ` | awk '{print $4}'`)
p.Pid = strings.ReplaceAll(pid, ",", "")
uptime, _ := tools.Exec(`supervisorctl status ` + line + ` | awk '{print $6}'`)
p.Uptime = uptime
} else {
p.Pid = "-"
p.Uptime = "-"
@@ -222,91 +190,92 @@ func (r *SupervisorController) Processes(ctx http.Context) http.Response {
// StartProcess 启动进程
func (r *SupervisorController) StartProcess(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl start ` + process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
process := ctx.Request().Input("process")
tools.Exec(`supervisorctl start ` + process)
return controllers.Success(ctx, nil)
}
// StopProcess 停止进程
func (r *SupervisorController) StopProcess(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl stop ` + process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
process := ctx.Request().Input("process")
tools.Exec(`supervisorctl stop ` + process)
return controllers.Success(ctx, nil)
}
// RestartProcess 重启进程
func (r *SupervisorController) RestartProcess(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl restart ` + process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
process := ctx.Request().Input("process")
tools.Exec(`supervisorctl restart ` + process)
return controllers.Success(ctx, nil)
}
// ProcessLog 进程日志
func (r *SupervisorController) ProcessLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
}
process := ctx.Request().Input("process")
var logPath string
var err error
if tools.IsRHEL() {
logPath = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
logPath, err = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
} else {
logPath = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
logPath, err = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
}
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "无法从进程"+process+"的配置文件中获取日志路径")
}
log, err := tools.Exec(`tail -n 200 ` + logPath)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, log)
}
log := tools.Exec(`tail -n 200 ` + logPath)
return controllers.Success(ctx, log)
}
// ClearProcessLog 清空进程日志
func (r *SupervisorController) ClearProcessLog(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
}
process := ctx.Request().Input("process")
var logPath string
var err error
if tools.IsRHEL() {
logPath = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
logPath, err = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
} else {
logPath = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
logPath, err = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
}
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "无法从进程"+process+"的配置文件中获取日志路径")
}
if out, err := tools.Exec(`echo "" > ` + logPath); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
tools.Exec(`echo "" > ` + logPath)
return controllers.Success(ctx, nil)
}
// ProcessConfig 获取进程配置
func (r *SupervisorController) ProcessConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
}
process := ctx.Request().Query("process")
var config string
var err error
if tools.IsRHEL() {
config = tools.Read(`/etc/supervisord.d/` + process + `.conf`)
config, err = tools.Read(`/etc/supervisord.d/` + process + `.conf`)
} else {
config = tools.Read(`/etc/supervisor/conf.d/` + process + `.conf`)
config, err = tools.Read(`/etc/supervisor/conf.d/` + process + `.conf`)
}
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, config)
@@ -314,32 +283,28 @@ func (r *SupervisorController) ProcessConfig(ctx http.Context) http.Response {
// SaveProcessConfig 保存进程配置
func (r *SupervisorController) SaveProcessConfig(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
}
process := ctx.Request().Input("process")
config := ctx.Request().Input("config")
var err error
if tools.IsRHEL() {
tools.Write(`/etc/supervisord.d/`+process+`.conf`, config, 0644)
err = tools.Write(`/etc/supervisord.d/`+process+`.conf`, config, 0644)
} else {
tools.Write(`/etc/supervisor/conf.d/`+process+`.conf`, config, 0644)
err = tools.Write(`/etc/supervisor/conf.d/`+process+`.conf`, config, 0644)
}
tools.Exec(`supervisorctl reread`)
tools.Exec(`supervisorctl update`)
tools.Exec(`supervisorctl start ` + process)
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
_, _ = tools.Exec(`supervisorctl restart ` + process)
return controllers.Success(ctx, nil)
}
// AddProcess 添加进程
func (r *SupervisorController) AddProcess(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|alpha_dash",
"user": "required|alpha_dash",
@@ -372,37 +337,52 @@ stdout_logfile=/var/log/supervisor/` + name + `.log
stdout_logfile_maxbytes=2MB
`
if tools.IsRHEL() {
tools.Write(`/etc/supervisord.d/`+name+`.conf`, config, 0644)
err = tools.Write(`/etc/supervisord.d/`+name+`.conf`, config, 0644)
} else {
tools.Write(`/etc/supervisor/conf.d/`+name+`.conf`, config, 0644)
err = tools.Write(`/etc/supervisor/conf.d/`+name+`.conf`, config, 0644)
}
tools.Exec(`supervisorctl reread`)
tools.Exec(`supervisorctl update`)
tools.Exec(`supervisorctl start ` + name)
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
_, _ = tools.Exec(`supervisorctl start ` + name)
return controllers.Success(ctx, nil)
}
// DeleteProcess 删除进程
func (r *SupervisorController) DeleteProcess(ctx http.Context) http.Response {
check := controllers.Check(ctx, "supervisor")
if check != nil {
return check
process := ctx.Request().Input("process")
if out, err := tools.Exec(`supervisorctl stop ` + process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
process := ctx.Request().Input("process")
tools.Exec(`supervisorctl stop ` + process)
var logPath string
var err error
if tools.IsRHEL() {
logPath = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
tools.Remove(`/etc/supervisord.d/` + process + `.conf`)
logPath, err = tools.Exec(`cat '/etc/supervisord.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
if err := tools.Remove(`/etc/supervisord.d/` + process + `.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
} else {
logPath = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
tools.Remove(`/etc/supervisor/conf.d/` + process + `.conf`)
logPath, err = tools.Exec(`cat '/etc/supervisor/conf.d/` + process + `.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`)
if err := tools.Remove(`/etc/supervisor/conf.d/` + process + `.conf`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
}
tools.Remove(logPath)
tools.Exec(`supervisorctl reread`)
tools.Exec(`supervisorctl update`)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "无法从进程"+process+"的配置文件中获取日志路径")
}
if err := tools.Remove(logPath); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
return controllers.Success(ctx, nil)
}
@@ -20,15 +20,13 @@ func NewToolBoxController() *ToolBoxController {
// GetDNS 获取 DNS 信息
func (r *ToolBoxController) GetDNS(ctx http.Context) http.Response {
check := controllers.Check(ctx, "toolbox")
if check != nil {
return check
raw, err := tools.Read("/etc/resolv.conf")
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
raw := tools.Read("/etc/resolv.conf")
match := regexp.MustCompile(`nameserver\s+(\S+)`).FindAllStringSubmatch(raw, -1)
if len(match) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "找不到 DNS 信息")
return controllers.Error(ctx, http.StatusInternalServerError, "找不到 DNS 信息")
}
var dns []string
@@ -41,11 +39,6 @@ func (r *ToolBoxController) GetDNS(ctx http.Context) http.Response {
// SetDNS 设置 DNS 信息
func (r *ToolBoxController) SetDNS(ctx http.Context) http.Response {
check := controllers.Check(ctx, "toolbox")
if check != nil {
return check
}
dns1 := ctx.Request().Input("dns1")
dns2 := ctx.Request().Input("dns2")
@@ -57,29 +50,35 @@ func (r *ToolBoxController) SetDNS(ctx http.Context) http.Response {
dns += "nameserver " + dns1 + "\n"
dns += "nameserver " + dns2 + "\n"
tools.Write("/etc/resolv.conf", dns, 0644)
if err := tools.Write("/etc/resolv.conf", dns, 0644); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "写入 DNS 信息失败")
}
return controllers.Success(ctx, nil)
}
// GetSWAP 获取 SWAP 信息
func (r *ToolBoxController) GetSWAP(ctx http.Context) http.Response {
check := controllers.Check(ctx, "toolbox")
if check != nil {
return check
}
var total, used, free string
var size int64
if tools.Exists("/www/swap") {
s, _ := tools.FileSize("/www/swap")
size = s / 1024 / 1024
total = tools.FormatBytes(float64(s))
file, err := tools.FileInfo("/www/swap")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取 SWAP 信息失败")
}
size = file.Size() / 1024 / 1024
total = tools.FormatBytes(float64(file.Size()))
} else {
size = 0
total = "0.00 B"
}
raw := tools.Exec("LC_ALL=C free | grep Swap")
raw, err := tools.Exec("free | grep Swap")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取 SWAP 信息失败")
}
match := regexp.MustCompile(`Swap:\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
if len(match) > 0 {
used = tools.FormatBytes(cast.ToFloat64(match[2]) * 1024)
@@ -96,34 +95,51 @@ func (r *ToolBoxController) GetSWAP(ctx http.Context) http.Response {
// SetSWAP 设置 SWAP 信息
func (r *ToolBoxController) SetSWAP(ctx http.Context) http.Response {
check := controllers.Check(ctx, "toolbox")
if check != nil {
return check
}
size := ctx.Request().InputInt("size")
if tools.Exists("/www/swap") {
tools.Exec("swapoff /www/swap")
tools.Exec("rm -f /www/swap")
tools.Exec("sed -i '/www\\/swap/d' /etc/fstab")
if out, err := tools.Exec("swapoff /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("rm -f /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("sed -i '/www\\/swap/d' /etc/fstab"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
}
if size > 1 {
free := tools.Exec("df -k /www | awk '{print $4}' | tail -n 1")
free, err := tools.Exec("df -k /www | awk '{print $4}' | tail -n 1")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取磁盘空间失败")
}
if cast.ToInt64(free)*1024 < int64(size)*1024*1024 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "磁盘空间不足,当前剩余 "+tools.FormatBytes(cast.ToFloat64(free)))
}
if strings.Contains(tools.Exec("df -T /www | awk '{print $2}' | tail -n 1"), "btrfs") {
tools.Exec("btrfs filesystem mkswapfile --size " + cast.ToString(size) + "M --uuid clear /www/swap")
btrfsCheck, _ := tools.Exec("df -T /www | awk '{print $2}' | tail -n 1")
if strings.Contains(btrfsCheck, "btrfs") {
if out, err := tools.Exec("btrfs filesystem mkswapfile --size " + cast.ToString(size) + "M --uuid clear /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
} else {
tools.Exec("dd if=/dev/zero of=/www/swap bs=1M count=" + cast.ToString(size))
tools.Exec("mkswap -f /www/swap")
tools.Chmod("/www/swap", 0600)
if out, err := tools.Exec("dd if=/dev/zero of=/www/swap bs=1M count=" + cast.ToString(size)); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("mkswap -f /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if err := tools.Chmod("/www/swap", 0600); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "设置 SWAP 权限失败")
}
}
if out, err := tools.Exec("swapon /www/swap"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
if out, err := tools.Exec("echo '/www/swap swap swap defaults 0 0' >> /etc/fstab"); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
tools.Exec("swapon /www/swap")
tools.Exec("echo '/www/swap swap swap defaults 0 0' >> /etc/fstab")
}
return controllers.Success(ctx, nil)
@@ -131,12 +147,11 @@ func (r *ToolBoxController) SetSWAP(ctx http.Context) http.Response {
// GetTimezone 获取时区
func (r *ToolBoxController) GetTimezone(ctx http.Context) http.Response {
check := controllers.Check(ctx, "toolbox")
if check != nil {
return check
raw, err := tools.Exec("timedatectl | grep zone")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取时区信息失败")
}
raw := tools.Exec("LC_ALL=C timedatectl | grep zone")
match := regexp.MustCompile(`zone:\s+(\S+)`).FindStringSubmatch(raw)
if len(match) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "找不到时区信息")
@@ -147,7 +162,10 @@ func (r *ToolBoxController) GetTimezone(ctx http.Context) http.Response {
Value string `json:"value"`
}
zonesRaw := tools.Exec("LC_ALL=C timedatectl list-timezones")
zonesRaw, err := tools.Exec("timedatectl list-timezones")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "获取时区列表失败")
}
zones := strings.Split(zonesRaw, "\n")
var zonesList []zone
@@ -166,55 +184,44 @@ func (r *ToolBoxController) GetTimezone(ctx http.Context) http.Response {
// SetTimezone 设置时区
func (r *ToolBoxController) SetTimezone(ctx http.Context) http.Response {
check := controllers.Check(ctx, "toolbox")
if check != nil {
return check
}
timezone := ctx.Request().Input("timezone")
if len(timezone) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "时区不能为空")
}
tools.Exec("timedatectl set-timezone " + timezone)
if out, err := tools.Exec("timedatectl set-timezone " + timezone); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
return controllers.Success(ctx, nil)
}
// GetHosts 获取 hosts 信息
func (r *ToolBoxController) GetHosts(ctx http.Context) http.Response {
check := controllers.Check(ctx, "toolbox")
if check != nil {
return check
hosts, err := tools.Read("/etc/hosts")
if err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
return controllers.Success(ctx, tools.Read("/etc/hosts"))
return controllers.Success(ctx, hosts)
}
// SetHosts 设置 hosts 信息
func (r *ToolBoxController) SetHosts(ctx http.Context) http.Response {
check := controllers.Check(ctx, "toolbox")
if check != nil {
return check
}
hosts := ctx.Request().Input("hosts")
if len(hosts) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "hosts 信息不能为空")
}
tools.Write("/etc/hosts", hosts, 0644)
if err := tools.Write("/etc/hosts", hosts, 0644); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "写入 hosts 信息失败")
}
return controllers.Success(ctx, nil)
}
// SetRootPassword 设置 root 密码
func (r *ToolBoxController) SetRootPassword(ctx http.Context) http.Response {
check := controllers.Check(ctx, "toolbox")
if check != nil {
return check
}
password := ctx.Request().Input("password")
if len(password) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "密码不能为空")
@@ -224,7 +231,9 @@ func (r *ToolBoxController) SetRootPassword(ctx http.Context) http.Response {
}
password = strings.ReplaceAll(password, `'`, `\'`)
tools.Exec(`yes '` + password + `' | passwd root`)
if out, err := tools.Exec(`yes '` + password + `' | passwd root`); err != nil {
return controllers.Error(ctx, http.StatusUnprocessableEntity, out)
}
return controllers.Success(ctx, nil)
}
+155 -85
View File
@@ -6,6 +6,7 @@ import (
"github.com/goravel/framework/contracts/http"
"github.com/spf13/cast"
commonrequests "panel/app/http/requests/common"
"panel/pkg/tools"
)
@@ -28,33 +29,47 @@ func (r *SafeController) GetFirewallStatus(ctx http.Context) http.Response {
// SetFirewallStatus 设置防火墙状态
func (r *SafeController) SetFirewallStatus(ctx http.Context) http.Response {
var out string
var err error
if ctx.Request().InputBool("status") {
if tools.IsRHEL() {
out = tools.Exec("systemctl start firewalld")
out, err = tools.Exec("systemctl start firewalld")
} else {
out = tools.Exec("echo y | ufw enable")
out, err = tools.Exec("echo y | ufw enable")
}
} else {
if tools.IsRHEL() {
out = tools.Exec("systemctl stop firewalld")
out, err = tools.Exec("systemctl stop firewalld")
} else {
out = tools.Exec("ufw disable")
out, err = tools.Exec("ufw disable")
}
}
return Success(ctx, out)
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
return Success(ctx, nil)
}
// GetFirewallRules 获取防火墙规则
func (r *SafeController) GetFirewallRules(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := Sanitize(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
if !r.firewallStatus() {
return Success(ctx, nil)
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
var rules []map[string]string
if tools.IsRHEL() {
out := tools.Exec("firewall-cmd --list-all 2>&1")
out, err := tools.Exec("firewall-cmd --list-all 2>&1")
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
match := regexp.MustCompile(`ports: (.*)`).FindStringSubmatch(out)
if len(match) == 0 {
return Success(ctx, http.Json{
@@ -63,7 +78,6 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) http.Response {
})
}
ports := strings.Split(match[1], " ")
var rules []map[string]string
for _, port := range ports {
rule := strings.Split(port, "/")
rules = append(rules, map[string]string{
@@ -71,33 +85,18 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) http.Response {
"protocol": rule[1],
})
}
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(rules) {
return Success(ctx, http.Json{
"total": 0,
"items": []map[string]string{},
})
}
if endIndex > len(rules) {
endIndex = len(rules)
}
pagedRules := rules[startIndex:endIndex]
return Success(ctx, http.Json{
"total": len(rules),
"items": pagedRules,
})
} else {
out := tools.Exec("ufw status | grep -v '(v6)' | grep ALLOW | awk '{print $1}'")
out, err := tools.Exec("ufw status | grep -v '(v6)' | grep ALLOW | awk '{print $1}'")
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if len(out) == 0 {
return Success(ctx, http.Json{
"total": 0,
"items": []map[string]string{},
})
}
var rules []map[string]string
for _, port := range strings.Split(out, "\n") {
rule := strings.Split(port, "/")
rules = append(rules, map[string]string{
@@ -105,31 +104,31 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) http.Response {
"protocol": rule[1],
})
}
}
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(rules) {
return Success(ctx, http.Json{
"total": 0,
"items": []map[string]string{},
})
}
if endIndex > len(rules) {
endIndex = len(rules)
}
pagedRules := rules[startIndex:endIndex]
startIndex := (paginateRequest.Page - 1) * paginateRequest.Limit
endIndex := paginateRequest.Page * paginateRequest.Limit
if startIndex > len(rules) {
return Success(ctx, http.Json{
"total": len(rules),
"items": pagedRules,
"total": 0,
"items": []map[string]string{},
})
}
if endIndex > len(rules) {
endIndex = len(rules)
}
pagedRules := rules[startIndex:endIndex]
return Success(ctx, http.Json{
"total": len(rules),
"items": pagedRules,
})
}
// AddFirewallRule 添加防火墙规则
func (r *SafeController) AddFirewallRule(ctx http.Context) http.Response {
if !r.firewallStatus() {
return Error(ctx, http.StatusUnprocessableEntity, "防火墙未启动")
return Error(ctx, http.StatusInternalServerError, "防火墙未启动")
}
port := ctx.Request().Input("port")
@@ -153,17 +152,29 @@ func (r *SafeController) AddFirewallRule(ctx http.Context) http.Response {
}
if tools.IsRHEL() {
tools.Exec("firewall-cmd --remove-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1")
tools.Exec("firewall-cmd --add-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1")
tools.Exec("firewall-cmd --reload")
if out, err := tools.Exec("firewall-cmd --remove-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --add-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
// ufw 需要替换 - 为 : 添加
if strings.Contains(port, "-") {
port = strings.ReplaceAll(port, "-", ":")
}
tools.Exec("ufw delete allow " + cast.ToString(port) + "/" + protocol)
tools.Exec("ufw allow " + cast.ToString(port) + "/" + protocol)
tools.Exec("ufw reload")
if out, err := tools.Exec("ufw delete allow " + cast.ToString(port) + "/" + protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw allow " + cast.ToString(port) + "/" + protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
return Success(ctx, nil)
@@ -182,11 +193,19 @@ func (r *SafeController) DeleteFirewallRule(ctx http.Context) http.Response {
}
if tools.IsRHEL() {
tools.Exec("firewall-cmd --remove-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1")
tools.Exec("firewall-cmd --reload")
if out, err := tools.Exec("firewall-cmd --remove-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
tools.Exec("ufw delete allow " + cast.ToString(port) + "/" + protocol)
tools.Exec("ufw reload")
if out, err := tools.Exec("ufw delete allow " + cast.ToString(port) + "/" + protocol); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
return Success(ctx, nil)
@@ -195,16 +214,17 @@ func (r *SafeController) DeleteFirewallRule(ctx http.Context) http.Response {
// firewallStatus 获取防火墙状态
func (r *SafeController) firewallStatus() bool {
var out string
var err error
var running bool
if tools.IsRHEL() {
out = tools.Exec("systemctl status firewalld | grep Active | awk '{print $3}'")
out, err = tools.Exec("systemctl status firewalld | grep Active | awk '{print $3}'")
if out == "(running)" {
running = true
} else {
running = false
}
} else {
out = tools.Exec("ufw status | grep Status | awk '{print $2}'")
out, err = tools.Exec("ufw status | grep Status | awk '{print $2}'")
if out == "active" {
running = true
} else {
@@ -212,43 +232,63 @@ func (r *SafeController) firewallStatus() bool {
}
}
if err != nil {
return false
}
return running
}
// GetSshStatus 获取 SSH 状态
func (r *SafeController) GetSshStatus(ctx http.Context) http.Response {
var out string
var err error
if tools.IsRHEL() {
out = tools.Exec("systemctl status sshd | grep Active | awk '{print $3}'")
out, err = tools.Exec("systemctl status sshd | grep Active | awk '{print $3}'")
} else {
out = tools.Exec("systemctl status ssh | grep Active | awk '{print $3}'")
out, err = tools.Exec("systemctl status ssh | grep Active | awk '{print $3}'")
}
running := false
if out == "(running)" {
running = true
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
return Success(ctx, running)
return Success(ctx, out == "(running)")
}
// SetSshStatus 设置 SSH 状态
func (r *SafeController) SetSshStatus(ctx http.Context) http.Response {
if ctx.Request().InputBool("status") {
if tools.IsRHEL() {
tools.Exec("systemctl enable sshd")
tools.Exec("systemctl start sshd")
if out, err := tools.Exec("systemctl enable sshd"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("systemctl start sshd"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
tools.Exec("systemctl enable ssh")
tools.Exec("systemctl start ssh")
if out, err := tools.Exec("systemctl enable ssh"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("systemctl start ssh"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
} else {
if tools.IsRHEL() {
tools.Exec("systemctl stop sshd")
tools.Exec("systemctl disable sshd")
if out, err := tools.Exec("systemctl stop sshd"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("systemctl disable sshd"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
tools.Exec("systemctl stop ssh")
tools.Exec("systemctl disable ssh")
if out, err := tools.Exec("systemctl stop ssh"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("systemctl disable ssh"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
}
@@ -257,7 +297,11 @@ func (r *SafeController) SetSshStatus(ctx http.Context) http.Response {
// GetSshPort 获取 SSH 端口
func (r *SafeController) GetSshPort(ctx http.Context) http.Response {
out := tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
out, err := tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
return Success(ctx, out)
}
@@ -268,28 +312,40 @@ func (r *SafeController) SetSshPort(ctx http.Context) http.Response {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
oldPort := tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
tools.Exec("sed -i 's/#Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
tools.Exec("sed -i 's/Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
oldPort, err := tools.Exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'")
if err != nil {
return Error(ctx, http.StatusInternalServerError, oldPort)
}
_, _ = tools.Exec("sed -i 's/#Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
_, _ = tools.Exec("sed -i 's/Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
if status := tools.Exec("systemctl status sshd | grep Active | awk '{print $3}'"); status == "(running)" {
tools.Exec("systemctl restart sshd")
out, err := tools.Exec("systemctl status sshd | grep Active | awk '{print $3}'")
if err != nil || out != "(running)" {
Error(ctx, http.StatusInternalServerError, out)
}
_, _ = tools.Exec("systemctl restart sshd")
return Success(ctx, nil)
}
// GetPingStatus 获取 Ping 状态
func (r *SafeController) GetPingStatus(ctx http.Context) http.Response {
if tools.IsRHEL() {
out := tools.Exec(`firewall-cmd --list-all 2>&1`)
out, err := tools.Exec(`firewall-cmd --list-all 2>&1`)
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if !strings.Contains(out, `rule protocol value="icmp" drop`) {
return Success(ctx, true)
} else {
return Success(ctx, false)
}
} else {
config := tools.Read("/etc/ufw/before.rules")
config, err := tools.Read("/etc/ufw/before.rules")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if strings.Contains(config, "-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT") {
return Success(ctx, true)
} else {
@@ -300,20 +356,34 @@ func (r *SafeController) GetPingStatus(ctx http.Context) http.Response {
// SetPingStatus 设置 Ping 状态
func (r *SafeController) SetPingStatus(ctx http.Context) http.Response {
var out string
var err error
if tools.IsRHEL() {
if ctx.Request().InputBool("status") {
tools.Exec(`firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'`)
out, err = tools.Exec(`firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'`)
} else {
tools.Exec(`firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'`)
out, err = tools.Exec(`firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'`)
}
tools.Exec(`firewall-cmd --reload`)
} else {
if ctx.Request().InputBool("status") {
tools.Exec(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/g' /etc/ufw/before.rules`)
out, err = tools.Exec(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/g' /etc/ufw/before.rules`)
} else {
tools.Exec(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules`)
out, err = tools.Exec(`sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules`)
}
tools.Exec(`ufw reload`)
}
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if tools.IsRHEL() {
out, err = tools.Exec(`firewall-cmd --reload`)
} else {
out, err = tools.Exec(`ufw reload`)
}
if err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
return Success(ctx, nil)
+124 -58
View File
@@ -6,14 +6,14 @@ import (
"github.com/spf13/cast"
requests "panel/app/http/requests/setting"
responses "panel/app/http/responses/setting"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type SettingController struct {
setting services.Setting
setting internal.Setting
}
func NewSettingController() *SettingController {
@@ -23,55 +23,65 @@ func NewSettingController() *SettingController {
}
// List
// @Summary 设置列表
// @Description 获取面板设置列表
// @Tags 面板设置
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse{data=responses.Settings}
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/setting/list [get]
//
// @Summary 设置列表
// @Description 获取面板设置列表
// @Tags 面板设置
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/list [get]
func (r *SettingController) List(ctx http.Context) http.Response {
var settings []models.Setting
err := facades.Orm().Query().Get(&settings)
if err != nil {
facades.Log().Error("[面板][SettingController] 查询设置列表失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
}).Info("获取面板设置列表失败")
return ErrorSystem(ctx)
}
var result responses.Settings
result.Name = r.setting.Get(models.SettingKeyName)
result.Entrance = facades.Config().GetString("http.entrance")
result.WebsitePath = r.setting.Get(models.SettingKeyWebsitePath)
result.BackupPath = r.setting.Get(models.SettingKeyBackupPath)
var user models.User
err = facades.Auth().User(ctx, &user)
err = facades.Auth(ctx).User(&user)
if err != nil {
facades.Log().Error("[面板][SettingController] 获取用户失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
}).Info("获取用户信息失败")
return ErrorSystem(ctx)
}
result.Username = user.Username
result.Email = user.Email
result.Port = tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
port, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
}).Info("获取面板端口失败")
return ErrorSystem(ctx)
}
return Success(ctx, result)
return Success(ctx, http.Json{
"name": r.setting.Get(models.SettingKeyName),
"entrance": facades.Config().GetString("http.entrance"),
"ssl": facades.Config().GetBool("panel.ssl"),
"website_path": r.setting.Get(models.SettingKeyWebsitePath),
"backup_path": r.setting.Get(models.SettingKeyBackupPath),
"username": user.Username,
"password": "",
"email": user.Email,
"port": port,
})
}
// Update
// @Summary 更新设置
// @Description 更新面板设置
// @Tags 面板设置
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Update true "更新设置"
// @Success 200 {object} SuccessResponse
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/setting/update [post]
//
// @Summary 更新设置
// @Description 更新面板设置
// @Tags 面板设置
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Update true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/update [post]
func (r *SettingController) Update(ctx http.Context) http.Response {
var updateRequest requests.Update
sanitize := Sanitize(ctx, &updateRequest)
@@ -81,36 +91,42 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
err := r.setting.Set(models.SettingKeyName, updateRequest.Name)
if err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
}).Tags("面板", "面板设置").Error("保存面板名称失败")
}).Info("保存面板名称失败")
return ErrorSystem(ctx)
}
if !tools.Exists(updateRequest.BackupPath) {
tools.Mkdir(updateRequest.BackupPath, 0644)
if err = tools.Mkdir(updateRequest.BackupPath, 0644); err != nil {
return ErrorSystem(ctx)
}
}
err = r.setting.Set(models.SettingKeyBackupPath, updateRequest.BackupPath)
if err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
}).Tags("面板", "面板设置").Error("保存备份目录失败")
}).Info("保存备份目录失败")
return ErrorSystem(ctx)
}
if !tools.Exists(updateRequest.WebsitePath) {
tools.Mkdir(updateRequest.WebsitePath, 0755)
tools.Chown(updateRequest.WebsitePath, "www", "www")
if err = tools.Mkdir(updateRequest.WebsitePath, 0755); err != nil {
return ErrorSystem(ctx)
}
if err = tools.Chown(updateRequest.WebsitePath, "www", "www"); err != nil {
return ErrorSystem(ctx)
}
}
err = r.setting.Set(models.SettingKeyWebsitePath, updateRequest.WebsitePath)
if err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
}).Tags("面板", "面板设置").Error("保存建站目录失败")
}).Info("保存建站目录失败")
return ErrorSystem(ctx)
}
var user models.User
err = facades.Auth().User(ctx, &user)
err = facades.Auth(ctx).User(&user)
if err != nil {
return ErrorSystem(ctx)
}
@@ -124,24 +140,74 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
user.Password = hash
}
if err = facades.Orm().Query().Save(&user); err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
}).Tags("面板", "面板设置").Error("保存用户信息失败")
}).Info("保存用户信息失败")
return ErrorSystem(ctx)
}
oldPort := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
port := cast.ToString(updateRequest.Port)
if oldPort != port {
tools.Exec("sed -i 's/APP_PORT=" + oldPort + "/APP_PORT=" + port + "/g' /www/panel/panel.conf")
}
oldEntrance := tools.Exec(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
entrance := cast.ToString(updateRequest.Entrance)
if oldEntrance != entrance {
tools.Exec("sed -i 's/APP_ENTRANCE=" + oldEntrance + "/APP_ENTRANCE=" + entrance + "/g' /www/panel/panel.conf")
oldPort, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
}).Info("获取面板端口失败")
return ErrorSystem(ctx)
}
if oldPort != port || oldEntrance != entrance {
port := cast.ToString(updateRequest.Port)
if oldPort != port {
if out, err := tools.Exec("sed -i 's/APP_PORT=" + oldPort + "/APP_PORT=" + port + "/g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if tools.IsRHEL() {
if out, err := tools.Exec("firewall-cmd --remove-port=" + cast.ToString(port) + "/tcp --permanent 2>&1"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --add-port=" + cast.ToString(port) + "/tcp --permanent 2>&1"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("firewall-cmd --reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("ufw delete allow " + cast.ToString(port) + "/tcp"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw allow " + cast.ToString(port) + "/tcp"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec("ufw reload"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
}
oldEntrance, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_ENTRANCE | awk -F '=' '{print $2}' | tr -d '\n'`)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "面板设置").With(map[string]any{
"error": err.Error(),
}).Info("获取面板入口失败")
return ErrorSystem(ctx)
}
entrance := cast.ToString(updateRequest.Entrance)
if oldEntrance != entrance {
if out, err := tools.Exec("sed -i 's!APP_ENTRANCE=" + oldEntrance + "!APP_ENTRANCE=" + entrance + "!g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
if updateRequest.SSL {
if out, err := tools.Exec("sed -i 's/APP_SSL=false/APP_SSL=true/g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
} else {
if out, err := tools.Exec("sed -i 's/APP_SSL=true/APP_SSL=false/g' /www/panel/panel.conf"); err != nil {
return Error(ctx, http.StatusInternalServerError, out)
}
}
if oldPort != port || oldEntrance != entrance || updateRequest.SSL != facades.Config().GetBool("panel.ssl") {
tools.RestartPanel()
}
+17 -21
View File
@@ -14,13 +14,14 @@ import (
"github.com/valyala/fasthttp"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/ssh"
)
type SshController struct {
AuthMethod ssh.AuthMethod
setting services.Setting
setting internal.Setting
}
func NewSshController() *SshController {
@@ -67,25 +68,17 @@ func (r *SshController) UpdateInfo(ctx http.Context) http.Response {
port := ctx.Request().Input("port")
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
err = r.setting.Set(models.SettingKeySshHost, host)
if err != nil {
facades.Log().Error("[面板][SSH] 更新配置失败 ", err)
return ErrorSystem(ctx)
if err = r.setting.Set(models.SettingKeySshHost, host); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
err = r.setting.Set(models.SettingKeySshPort, port)
if err != nil {
facades.Log().Error("[面板][SSH] 更新配置失败 ", err)
return ErrorSystem(ctx)
if err = r.setting.Set(models.SettingKeySshPort, port); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
err = r.setting.Set(models.SettingKeySshUser, user)
if err != nil {
facades.Log().Error("[面板][SSH] 更新配置失败 ", err)
return ErrorSystem(ctx)
if err = r.setting.Set(models.SettingKeySshUser, user); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
err = r.setting.Set(models.SettingKeySshPassword, password)
if err != nil {
facades.Log().Error("[面板][SSH] 更新配置失败 ", err)
return ErrorSystem(ctx)
if err = r.setting.Set(models.SettingKeySshPassword, password); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
@@ -142,21 +135,24 @@ func (r *SshController) Session(ctx http.Context) http.Response {
defer wg.Done()
err := turn.LoopRead(logBuff, ctx2)
if err != nil {
facades.Log().Error("[面板][SSH] 读取数据失败 ", err.Error())
facades.Log().Request(ctx.Request()).Tags("面板", "SSH").With(map[string]any{
"error": err.Error(),
}).Info("SSH 会话错误")
}
}()
go func() {
defer wg.Done()
err := turn.SessionWait()
if err != nil {
facades.Log().Error("[面板][SSH] 会话失败 ", err.Error())
facades.Log().Request(ctx.Request()).Tags("面板", "SSH").With(map[string]any{
"error": err.Error(),
}).Info("SSH 会话错误")
}
cancel()
}()
wg.Wait()
})
if err != nil {
facades.Log().Error("[面板][SSH] 建立连接失败 ", err)
return ErrorSystem(ctx)
}
+19 -154
View File
@@ -1,15 +1,12 @@
package controllers
import (
"html/template"
"io"
"path/filepath"
"regexp"
"strings"
"github.com/gofiber/swagger"
"github.com/goravel/fiber"
"github.com/goravel/framework/contracts/http"
swaggerFiles "github.com/swaggo/files/v2"
"github.com/swaggo/swag"
"github.com/goravel/framework/facades"
_ "panel/docs"
)
type SwaggerController struct {
@@ -33,156 +30,24 @@ func NewSwaggerController() *SwaggerController {
}
// Index
// @Summary Swagger UI
// @Description Swagger UI
// @Tags Swagger
// @Success 200
// @Failure 500
// @Router /swagger [get]
//
// @Summary Swagger UI
// @Description Swagger UI
// @Tags Swagger
// @Success 200
// @Failure 500
// @Router /swagger [get]
func (r *SwaggerController) Index(ctx http.Context) http.Response {
config := Config{
URL: "swagger.json",
DocExpansion: "list",
DomID: "swagger-ui",
InstanceName: swag.Name,
DeepLinking: true,
if !facades.Config().GetBool("app.debug") {
return Error(ctx, http.StatusNotFound, http.StatusText(http.StatusNotFound))
}
// create a template with name
index, _ := template.New("swagger_index.html").Parse(indexTemplate)
var re = regexp.MustCompile(`^(.*/)([^?].*)?[?|.]*$`)
matches := re.FindStringSubmatch(ctx.Request().Path())
path := matches[2]
fileExt := filepath.Ext(path)
switch path {
case "":
return ctx.Response().Redirect(http.StatusMovedPermanently, filepath.Join(matches[1], "index.html"))
case "index.html":
var builder strings.Builder
err := index.Execute(&builder, config)
if err != nil {
return ctx.Response().Status(http.StatusInternalServerError).String(http.StatusText(http.StatusInternalServerError))
}
return ctx.Response().Success().String("html", builder.String())
case "swagger.json":
return ctx.Response().File("docs/swagger.json")
}
file, err := swaggerFiles.FS.Open(path)
err := swagger.New(swagger.Config{
Title: "耗子面板 Swagger",
})(ctx.(*fiber.Context).Instance())
if err != nil {
return ctx.Response().Status(http.StatusInternalServerError).String(http.StatusText(http.StatusInternalServerError))
return Error(ctx, http.StatusNotFound, err.Error())
}
defer file.Close()
content, err := io.ReadAll(file)
if err != nil {
return ctx.Response().Status(http.StatusInternalServerError).String(http.StatusText(http.StatusInternalServerError))
}
return ctx.Response().Success().String(fileExt[0:], string(content))
return nil
}
const indexTemplate = `<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://gfont.cdn.haozi.net/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "{{.URL}}",
deepLinking: {{.DeepLinking}},
docExpansion: "{{.DocExpansion}}",
dom_id: "#{{.DomID}}",
persistAuthorization: {{.PersistAuthorization}},
validatorUrl: null,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>
`
+11 -7
View File
@@ -3,6 +3,7 @@ package controllers
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"panel/app/models"
"panel/pkg/tools"
)
@@ -38,9 +39,9 @@ func (r *TaskController) List(ctx http.Context) http.Response {
var total int64
err := facades.Orm().Query().Order("id desc").Paginate(ctx.Request().QueryInt("page", 1), ctx.Request().QueryInt("limit", 10), &tasks, &total)
if err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "任务中心").With(map[string]any{
"error": err.Error(),
}).Error("[面板][TaskController] 查询任务列表失败")
}).Info("查询任务列表失败")
return ErrorSystem(ctx)
}
@@ -55,14 +56,17 @@ func (r *TaskController) Log(ctx http.Context) http.Response {
var task models.Task
err := facades.Orm().Query().Where("id", ctx.Request().QueryInt("id")).FirstOrFail(&task)
if err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "任务中心").With(map[string]any{
"id": ctx.Request().QueryInt("id"),
"error": err.Error(),
}).Error("[面板][TaskController] 查询任务失败")
}).Info("查询任务失败")
return ErrorSystem(ctx)
}
log := tools.Exec("tail -n 1000 " + task.Log)
log, err := tools.Exec("tail -n 500 " + task.Log)
if err != nil {
return Error(ctx, http.StatusInternalServerError, "日志已被清理")
}
return Success(ctx, log)
}
@@ -72,10 +76,10 @@ func (r *TaskController) Delete(ctx http.Context) http.Response {
var task models.Task
_, err := facades.Orm().Query().Where("id", ctx.Request().Input("id")).Delete(&task)
if err != nil {
facades.Log().With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "任务中心").With(map[string]any{
"id": ctx.Request().QueryInt("id"),
"error": err.Error(),
}).Error("[面板][TaskController] 删除任务失败")
}).Info("删除任务失败")
return ErrorSystem(ctx)
}
+34 -35
View File
@@ -5,7 +5,6 @@ import (
"github.com/goravel/framework/facades"
"panel/app/http/requests/user"
responses "panel/app/http/responses/user"
"panel/app/models"
)
@@ -20,16 +19,17 @@ func NewUserController() *UserController {
}
// Login
// @Summary 登录
// @Description 通过用户名和密码获取访问令牌
// @Tags 用户鉴权
// @Accept json
// @Produce json
// @Param data body requests.Login true "登录信息"
// @Success 200 {object} SuccessResponse
// @Failure 403 {object} ErrorResponse "用户名或密码错误"
// @Failure 500 {object} ErrorResponse "系统内部错误
// @Router /panel/user/login [post]
//
// @Summary 登录
// @Description 通过用户名和密码获取访问令牌
// @Tags 用户鉴权
// @Accept json
// @Produce json
// @Param data body requests.Login true "request"
// @Success 200 {object} SuccessResponse
// @Failure 403 {object} ErrorResponse "用户名或密码错误"
// @Failure 500 {object} ErrorResponse "系统内部错误
// @Router /panel/user/login [post]
func (r *UserController) Login(ctx http.Context) http.Response {
var loginRequest requests.Login
sanitize := Sanitize(ctx, &loginRequest)
@@ -40,9 +40,9 @@ func (r *UserController) Login(ctx http.Context) http.Response {
var user models.User
err := facades.Orm().Query().Where("username", loginRequest.Username).First(&user)
if err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "用户").With(map[string]any{
"error": err.Error(),
}).Tags("面板", "用户").Error("查询用户失败")
}).Info("查询用户失败")
return ErrorSystem(ctx)
}
@@ -53,18 +53,18 @@ func (r *UserController) Login(ctx http.Context) http.Response {
if facades.Hash().NeedsRehash(user.Password) {
user.Password, err = facades.Hash().Make(loginRequest.Password)
if err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "用户").With(map[string]any{
"error": err.Error(),
}).Tags("面板", "用户").Error("更新密码失败")
}).Info("更新密码失败")
return ErrorSystem(ctx)
}
}
token, loginErr := facades.Auth().LoginUsingID(ctx, user.ID)
token, loginErr := facades.Auth(ctx).LoginUsingID(user.ID)
if loginErr != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "用户").With(map[string]any{
"error": err.Error(),
}).Tags("面板", "用户").Error("登录失败")
}).Info("登录失败")
return ErrorSystem(ctx)
}
@@ -74,29 +74,28 @@ func (r *UserController) Login(ctx http.Context) http.Response {
}
// Info
// @Summary 用户信息
// @Description 获取当前登录用户信息
// @Tags 用户鉴权
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse{data=responses.Info}
// @Failure 401 {object} ErrorResponse "登录已过期"
// @Failure 500 {object} ErrorResponse "系统内部错误"
// @Router /panel/user/info [get]
//
// @Summary 用户信息
// @Description 获取当前登录用户信息
// @Tags 用户鉴权
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse
// @Router /panel/user/info [get]
func (r *UserController) Info(ctx http.Context) http.Response {
var user models.User
err := facades.Auth().User(ctx, &user)
err := facades.Auth(ctx).User(&user)
if err != nil {
facades.Log().Request(ctx.Request()).With(map[string]any{
facades.Log().Request(ctx.Request()).Tags("面板", "用户").With(map[string]any{
"error": err.Error(),
}).Tags("面板", "用户").Error("获取用户信息失败")
}).Info("获取用户信息失败")
return ErrorSystem(ctx)
}
return Success(ctx, responses.Info{
ID: user.ID,
Role: []string{"admin"},
Username: user.Username,
Email: user.Email,
return Success(ctx, http.Json{
"id": user.ID,
"role": []string{"admin"},
"username": user.Username,
"email": user.Email,
})
}
+401 -403
View File
@@ -3,21 +3,23 @@ package controllers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
commonrequests "panel/app/http/requests/common"
requests "panel/app/http/requests/website"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
type WebsiteController struct {
website services.Website
setting services.Setting
backup services.Backup
website internal.Website
setting internal.Setting
backup internal.Backup
}
func NewWebsiteController() *WebsiteController {
@@ -28,14 +30,28 @@ func NewWebsiteController() *WebsiteController {
}
}
// List 网站列表
func (c *WebsiteController) List(ctx http.Context) http.Response {
limit := ctx.Request().QueryInt("limit", 10)
page := ctx.Request().QueryInt("page", 1)
// List
//
// @Summary 获取网站列表
// @Description 获取网站管理的网站列表
// @Tags 网站管理
// @Produce json
// @Security BearerToken
// @Param data body commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites [get]
func (r *WebsiteController) List(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := Sanitize(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
total, websites, err := c.website.List(page, limit)
total, websites, err := r.website.List(paginateRequest.Page, paginateRequest.Limit)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 获取网站列表失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("获取网站列表失败")
return ErrorSystem(ctx)
}
@@ -45,74 +61,102 @@ func (c *WebsiteController) List(ctx http.Context) http.Response {
})
}
// Add 添加网站
func (c *WebsiteController) Add(ctx http.Context) http.Response {
check := Check(ctx, "openresty")
if check != nil {
return check
}
validator, err := ctx.Request().Validate(map[string]string{
"name": "required|regex:^[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*$|not_exists:websites,name",
"domains": "required|slice",
"ports": "required|slice",
"php": "required",
"db": "bool",
"db_type": "required_if:db,true",
"db_name": "required_if:db,true",
"db_user": "required_if:db,true",
"db_password": "required_if:db,true",
})
if err != nil {
return Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if validator.Fails() {
return Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
// Add
//
// @Summary 添加网站
// @Description 添加网站到网站管理
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.Add true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites [post]
func (r *WebsiteController) Add(ctx http.Context) http.Response {
var addRequest requests.Add
sanitize := Sanitize(ctx, &addRequest)
if sanitize != nil {
return sanitize
}
var website services.PanelWebsite
website.Name = ctx.Request().Input("name")
website.Domains = ctx.Request().InputArray("domains")
website.Ports = ctx.Request().InputArray("ports")
website.Php = ctx.Request().InputInt("php")
website.Db = ctx.Request().InputBool("db")
website.DbType = ctx.Request().Input("db_type")
website.DbName = ctx.Request().Input("db_name")
website.DbUser = ctx.Request().Input("db_user")
website.DbPassword = ctx.Request().Input("db_password")
if len(addRequest.Path) == 0 {
addRequest.Path = r.setting.Get(models.SettingKeyWebsitePath) + "/" + addRequest.Name
}
newSite, err := c.website.Add(website)
website := internal.PanelWebsite{
Name: addRequest.Name,
Status: true,
Domains: addRequest.Domains,
Ports: addRequest.Ports,
Path: addRequest.Path,
Php: addRequest.Php,
Ssl: false,
Db: addRequest.Db,
DbType: addRequest.DbType,
DbName: addRequest.DbName,
DbUser: addRequest.DbUser,
DbPassword: addRequest.DbPassword,
}
_, err := r.website.Add(website)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 添加网站失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("添加网站失败")
return ErrorSystem(ctx)
}
return Success(ctx, newSite)
return Success(ctx, nil)
}
// Delete 删除网站
func (c *WebsiteController) Delete(ctx http.Context) http.Response {
check := Check(ctx, "openresty")
if check != nil {
return check
// Delete
//
// @Summary 删除网站
// @Description 删除网站管理的网站
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id} [delete]
func (r *WebsiteController) Delete(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
id := ctx.Request().InputInt("id")
err := c.website.Delete(id)
err := r.website.Delete(idRequest.ID)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 删除网站失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"id": idRequest.ID,
"error": err.Error(),
}).Info("删除网站失败")
return Error(ctx, http.StatusInternalServerError, "删除网站失败: "+err.Error())
}
return Success(ctx, nil)
}
// GetDefaultConfig 获取默认配置
func (c *WebsiteController) GetDefaultConfig(ctx http.Context) http.Response {
check := Check(ctx, "openresty")
if check != nil {
return check
// GetDefaultConfig
//
// @Summary 获取默认配置
// @Description 获取默认首页和停止页配置
// @Tags 网站管理
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse{data=map[string]string}
// @Router /panel/website/defaultConfig [get]
func (r *WebsiteController) GetDefaultConfig(ctx http.Context) http.Response {
index, err := tools.Read("/www/server/openresty/html/index.html")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
stop, err := tools.Read("/www/server/openresty/html/stop.html")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
index := tools.Read("/www/server/openresty/html/index.html")
stop := tools.Read("/www/server/openresty/html/stop.html")
return Success(ctx, http.Json{
"index": index,
@@ -120,443 +164,380 @@ func (c *WebsiteController) GetDefaultConfig(ctx http.Context) http.Response {
})
}
// SaveDefaultConfig 保存默认配置
func (c *WebsiteController) SaveDefaultConfig(ctx http.Context) http.Response {
check := Check(ctx, "openresty")
if check != nil {
return check
}
// SaveDefaultConfig
//
// @Summary 保存默认配置
// @Description 保存默认首页和停止页配置
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body map[string]string true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/website/defaultConfig [post]
func (r *WebsiteController) SaveDefaultConfig(ctx http.Context) http.Response {
index := ctx.Request().Input("index")
stop := ctx.Request().Input("stop")
if !tools.Write("/www/server/openresty/html/index.html", index, 0644) {
facades.Log().Error("[面板][WebsiteController] 保存默认配置失败")
if err := tools.Write("/www/server/openresty/html/index.html", index, 0644); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("保存默认首页配置失败")
return ErrorSystem(ctx)
}
if !tools.Write("/www/server/openresty/html/stop.html", stop, 0644) {
facades.Log().Error("[面板][WebsiteController] 保存默认配置失败")
if err := tools.Write("/www/server/openresty/html/stop.html", stop, 0644); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("保存默认停止页配置失败")
return ErrorSystem(ctx)
}
return Success(ctx, nil)
}
// GetConfig 获取配置
func (c *WebsiteController) GetConfig(ctx http.Context) http.Response {
check := Check(ctx, "openresty")
if check != nil {
return check
}
id := ctx.Request().InputInt("id")
if id == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
// GetConfig
//
// @Summary 获取配置
// @Description 获取网站的配置
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse{data=internal.PanelWebsite}
// @Router /panel/websites/{id}/config [get]
func (r *WebsiteController) GetConfig(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
config, err := c.website.GetConfig(id)
config, err := r.website.GetConfig(idRequest.ID)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 获取网站配置失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"id": idRequest.ID,
"error": err.Error(),
}).Info("获取网站配置失败")
return ErrorSystem(ctx)
}
return Success(ctx, config)
}
// SaveConfig 保存配置
func (c *WebsiteController) SaveConfig(ctx http.Context) http.Response {
check := Check(ctx, "openresty")
if check != nil {
return check
// SaveConfig
//
// @Summary 保存配置
// @Description 保存网站的配置
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Param data body requests.SaveConfig true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/config [post]
func (r *WebsiteController) SaveConfig(ctx http.Context) http.Response {
var saveConfigRequest requests.SaveConfig
sanitize := Sanitize(ctx, &saveConfigRequest)
if sanitize != nil {
return sanitize
}
validator, err := ctx.Request().Validate(map[string]string{
"domains": "required|slice",
"ports": "required|slice",
"hsts": "bool",
"ssl": "bool",
"http_redirect": "bool",
"open_basedir": "bool",
"waf": "bool",
"waf_cache": "required",
"waf_mode": "required",
"waf_cc_deny": "required",
"index": "required",
"path": "required",
"root": "required",
"raw": "required",
"php": "required",
"ssl_certificate": "required_if:ssl,true",
"ssl_certificate_key": "required_if:ssl,true",
})
err := r.website.SaveConfig(saveConfigRequest)
if err != nil {
return Error(ctx, http.StatusUnprocessableEntity, err.Error())
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if validator.Fails() {
return Error(ctx, http.StatusUnprocessableEntity, validator.Errors().One())
}
var website models.Website
if facades.Orm().Query().Where("id", ctx.Request().Input("id")).FirstOrFail(&website) != nil {
return Error(ctx, http.StatusUnprocessableEntity, "网站不存在")
}
if !website.Status {
return Error(ctx, http.StatusUnprocessableEntity, "网站已停用,请先启用")
}
// 原文
raw := tools.Read("/www/server/vhost/" + website.Name + ".conf")
if strings.TrimSpace(raw) != strings.TrimSpace(ctx.Request().Input("raw")) {
tools.Write("/www/server/vhost/"+website.Name+".conf", ctx.Request().Input("raw"), 0644)
tools.Exec("systemctl reload openresty")
return Success(ctx, nil)
}
// 目录
path := ctx.Request().Input("path")
if !tools.Exists(path) {
return Error(ctx, http.StatusUnprocessableEntity, "网站目录不存在")
}
website.Path = path
// 域名
domain := "server_name"
domains := ctx.Request().InputArray("domains")
if len(domains) == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "域名不能为空")
}
for _, v := range domains {
if v == "" {
continue
}
domain += " " + v
}
domain += ";"
domainConfigOld := tools.Cut(raw, "# server_name标记位开始", "# server_name标记位结束")
if len(strings.TrimSpace(domainConfigOld)) == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "配置文件中缺少server_name标记位")
}
raw = strings.Replace(raw, domainConfigOld, "\n "+domain+"\n ", -1)
// 端口
var port strings.Builder
ports := ctx.Request().InputArray("ports")
if len(ports) == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "端口不能为空")
}
for i, v := range ports {
if _, err := strconv.Atoi(v); err != nil && v != "443 ssl http2" {
return Error(ctx, http.StatusUnprocessableEntity, "端口格式错误")
}
if v == "443" && ctx.Request().InputBool("ssl") {
v = "443 ssl http2"
}
if i != len(ports)-1 {
port.WriteString(" listen " + v + ";\n")
} else {
port.WriteString(" listen " + v + ";")
}
}
portConfigOld := tools.Cut(raw, "# port标记位开始", "# port标记位结束")
if len(strings.TrimSpace(portConfigOld)) == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "配置文件中缺少port标记位")
}
raw = strings.Replace(raw, portConfigOld, "\n"+port.String()+"\n ", -1)
// 运行目录
root := tools.Cut(raw, "# root标记位开始", "# root标记位结束")
if len(strings.TrimSpace(root)) == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "配置文件中缺少root标记位")
}
match := regexp.MustCompile(`root\s+(.+);`).FindStringSubmatch(root)
if len(match) != 2 {
return Error(ctx, http.StatusUnprocessableEntity, "配置文件中root标记位格式错误")
}
rootNew := strings.Replace(root, match[1], ctx.Request().Input("root"), -1)
raw = strings.Replace(raw, root, rootNew, -1)
// 默认文件
index := tools.Cut(raw, "# index标记位开始", "# index标记位结束")
if len(strings.TrimSpace(index)) == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "配置文件中缺少index标记位")
}
match = regexp.MustCompile(`index\s+(.+);`).FindStringSubmatch(index)
if len(match) != 2 {
return Error(ctx, http.StatusUnprocessableEntity, "配置文件中index标记位格式错误")
}
indexNew := strings.Replace(index, match[1], ctx.Request().Input("index"), -1)
raw = strings.Replace(raw, index, indexNew, -1)
// 防跨站
root = ctx.Request().Input("root")
if !strings.HasSuffix(root, "/") {
root += "/"
}
if ctx.Request().InputBool("open_basedir") {
tools.Write(root+".user.ini", "open_basedir="+path+":/tmp/", 0644)
} else {
if tools.Exists(root + ".user.ini") {
tools.Remove(root + ".user.ini")
}
}
// WAF
waf := ctx.Request().InputBool("waf")
wafStr := "off"
if waf {
wafStr = "on"
}
wafMode := ctx.Request().Input("waf_mode", "DYNAMIC")
wafCcDeny := ctx.Request().Input("waf_cc_deny", "rate=1000r/m duration=60m")
wafCache := ctx.Request().Input("waf_cache", "capacity=50")
wafConfig := `# waf标记位开始
waf ` + wafStr + `;
waf_rule_path /www/server/openresty/ngx_waf/assets/rules/;
waf_mode ` + wafMode + `;
waf_cc_deny ` + wafCcDeny + `;
waf_cache ` + wafCache + `;
`
wafConfigOld := tools.Cut(raw, "# waf标记位开始", "# waf标记位结束")
if len(strings.TrimSpace(wafConfigOld)) != 0 {
raw = strings.Replace(raw, wafConfigOld, "", -1)
}
raw = strings.Replace(raw, "# waf标记位开始", wafConfig, -1)
// SSL
ssl := ctx.Request().InputBool("ssl")
website.Ssl = ssl
tools.Write("/www/server/vhost/ssl/"+website.Name+".pem", ctx.Request().Input("ssl_certificate"), 0644)
tools.Write("/www/server/vhost/ssl/"+website.Name+".key", ctx.Request().Input("ssl_certificate_key"), 0644)
if ssl {
sslConfig := `# ssl标记位开始
ssl_certificate /www/server/vhost/ssl/` + website.Name + `.pem;
ssl_certificate_key /www/server/vhost/ssl/` + website.Name + `.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
`
if ctx.Request().InputBool("http_redirect") {
sslConfig += `# http重定向标记位开始
if ($server_port !~ 443){
return 301 https://$host$request_uri;
}
error_page 497 https://$host$request_uri;
# http重定向标记位结束
`
}
if ctx.Request().InputBool("hsts") {
sslConfig += `# hsts标记位开始
add_header Strict-Transport-Security "max-age=63072000" always;
# hsts标记位结束
`
}
sslConfigOld := tools.Cut(raw, "# ssl标记位开始", "# ssl标记位结束")
if len(strings.TrimSpace(sslConfigOld)) != 0 {
raw = strings.Replace(raw, sslConfigOld, "", -1)
}
raw = strings.Replace(raw, "# ssl标记位开始", sslConfig, -1)
} else {
sslConfigOld := tools.Cut(raw, "# ssl标记位开始", "# ssl标记位结束")
if len(strings.TrimSpace(sslConfigOld)) != 0 {
raw = strings.Replace(raw, sslConfigOld, "\n ", -1)
}
}
if website.Php != ctx.Request().InputInt("php") {
website.Php = ctx.Request().InputInt("php")
phpConfigOld := tools.Cut(raw, "# php标记位开始", "# php标记位结束")
phpConfig := `
include enable-php-` + strconv.Itoa(website.Php) + `.conf;
`
if len(strings.TrimSpace(phpConfigOld)) != 0 {
raw = strings.Replace(raw, phpConfigOld, phpConfig, -1)
}
}
err = facades.Orm().Query().Save(&website)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 保存网站配置失败 ", err)
return ErrorSystem(ctx)
}
tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644)
tools.Write("/www/server/vhost/rewrite/"+website.Name+".conf", ctx.Request().Input("rewrite"), 0644)
tools.Exec("systemctl reload openresty")
return Success(ctx, nil)
}
// ClearLog 清空日志
func (c *WebsiteController) ClearLog(ctx http.Context) http.Response {
check := Check(ctx, "openresty")
if check != nil {
return check
}
id := ctx.Request().InputInt("id")
if id == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
// ClearLog
//
// @Summary 清空日志
// @Description 清空网站的日志
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/log [delete]
func (r *WebsiteController) ClearLog(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
website := models.Website{}
err := facades.Orm().Query().Where("id", id).Get(&website)
err := facades.Orm().Query().Where("id", idRequest.ID).Get(&website)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 获取网站信息失败 ", err)
return ErrorSystem(ctx)
}
tools.Remove("/www/wwwlogs/" + website.Name + ".log")
if err := tools.Remove("/www/wwwlogs/" + website.Name + ".log"); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
// UpdateRemark 更新备注
func (c *WebsiteController) UpdateRemark(ctx http.Context) http.Response {
id := ctx.Request().InputInt("id")
if id == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
// UpdateRemark
//
// @Summary 更新备注
// @Description 更新网站的备注
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/updateRemark [post]
func (r *WebsiteController) UpdateRemark(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
website := models.Website{}
err := facades.Orm().Query().Where("id", id).Get(&website)
err := facades.Orm().Query().Where("id", idRequest.ID).Get(&website)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 获取网站信息失败 ", err)
return ErrorSystem(ctx)
}
website.Remark = ctx.Request().Input("remark")
err = facades.Orm().Query().Save(&website)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 保存网站备注失败 ", err)
if err = facades.Orm().Query().Save(&website); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"id": idRequest.ID,
"error": err.Error(),
}).Info("更新网站备注失败")
return ErrorSystem(ctx)
}
return Success(ctx, nil)
}
// BackupList 备份列表
func (c *WebsiteController) BackupList(ctx http.Context) http.Response {
backupList, err := c.backup.WebsiteList()
// BackupList
//
// @Summary 获取备份列表
// @Description 获取网站的备份列表
// @Tags 网站管理
// @Produce json
// @Security BearerToken
// @Param data body commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse{data=[]internal.BackupFile}
// @Router /panel/website/backupList [get]
func (r *WebsiteController) BackupList(ctx http.Context) http.Response {
var paginateRequest commonrequests.Paginate
sanitize := Sanitize(ctx, &paginateRequest)
if sanitize != nil {
return sanitize
}
backupList, err := r.backup.WebsiteList()
if err != nil {
facades.Log().Error("[面板][WebsiteController] 获取网站备份列表失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("获取备份列表失败")
return ErrorSystem(ctx)
}
return Success(ctx, backupList)
startIndex := (paginateRequest.Page - 1) * paginateRequest.Limit
endIndex := paginateRequest.Page * paginateRequest.Limit
if startIndex > len(backupList) {
return 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 Success(ctx, http.Json{
"total": len(backupList),
"items": pagedBackupList,
})
}
// CreateBackup 创建备份
func (c *WebsiteController) CreateBackup(ctx http.Context) http.Response {
id := ctx.Request().InputInt("id")
if id == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
// CreateBackup
//
// @Summary 创建备份
// @Description 创建网站的备份
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/createBackup [post]
func (r *WebsiteController) CreateBackup(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
website := models.Website{}
err := facades.Orm().Query().Where("id", id).Get(&website)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 获取网站信息失败 ", err)
return Error(ctx, http.StatusInternalServerError, "获取网站信息失败: "+err.Error())
if err := facades.Orm().Query().Where("id", idRequest.ID).Get(&website); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"id": idRequest.ID,
"error": err.Error(),
}).Info("获取网站信息失败")
return ErrorSystem(ctx)
}
err = c.backup.WebSiteBackup(website)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 备份网站失败 ", err)
if err := r.backup.WebSiteBackup(website); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"id": idRequest.ID,
"error": err.Error(),
}).Info("备份网站失败")
return Error(ctx, http.StatusInternalServerError, "备份网站失败: "+err.Error())
}
return Success(ctx, nil)
}
// UploadBackup 上传备份
func (c *WebsiteController) UploadBackup(ctx http.Context) http.Response {
// UploadBackup
//
// @Summary 上传备份
// @Description 上传网站的备份
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param file formData file true "备份文件"
// @Success 200 {object} SuccessResponse
// @Router /panel/website/uploadBackup [put]
func (r *WebsiteController) UploadBackup(ctx http.Context) http.Response {
file, err := ctx.Request().File("file")
if err != nil {
return Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
return Error(ctx, http.StatusInternalServerError, "上传文件失败")
}
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/website"
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/website"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
if err = tools.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
name := file.GetClientOriginalName()
_, err = file.StoreAs(backupPath, name)
if err != nil {
return Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"error": err.Error(),
}).Info("上传备份失败")
return ErrorSystem(ctx)
}
return Success(ctx, "上传文件成功")
return Success(ctx, nil)
}
// RestoreBackup 还原备份
func (c *WebsiteController) RestoreBackup(ctx http.Context) http.Response {
id := ctx.Request().InputInt("id")
if id == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
fileName := ctx.Request().Input("name")
if len(fileName) == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
// RestoreBackup
//
// @Summary 还原备份
// @Description 还原网站的备份
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/restoreBackup [post]
func (r *WebsiteController) RestoreBackup(ctx http.Context) http.Response {
var restoreBackupRequest requests.RestoreBackup
sanitize := Sanitize(ctx, &restoreBackupRequest)
if sanitize != nil {
return sanitize
}
website := models.Website{}
err := facades.Orm().Query().Where("id", id).Get(&website)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 获取网站信息失败 ", err)
return Error(ctx, http.StatusInternalServerError, "获取网站信息失败: "+err.Error())
if err := facades.Orm().Query().Where("id", restoreBackupRequest.ID).Get(&website); err != nil {
return ErrorSystem(ctx)
}
err = c.backup.WebsiteRestore(website, fileName)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 还原网站失败 ", err)
if err := r.backup.WebsiteRestore(website, restoreBackupRequest.Name); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"id": restoreBackupRequest.ID,
"file": restoreBackupRequest.Name,
"error": err.Error(),
}).Info("还原网站失败")
return Error(ctx, http.StatusInternalServerError, "还原网站失败: "+err.Error())
}
return Success(ctx, nil)
}
// DeleteBackup 删除备份
func (c *WebsiteController) DeleteBackup(ctx http.Context) http.Response {
fileName := ctx.Request().Input("name")
if len(fileName) == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
// DeleteBackup
//
// @Summary 删除备份
// @Description 删除网站的备份
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.DeleteBackup true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/website/deleteBackup [delete]
func (r *WebsiteController) DeleteBackup(ctx http.Context) http.Response {
var deleteBackupRequest requests.DeleteBackup
sanitize := Sanitize(ctx, &deleteBackupRequest)
if sanitize != nil {
return sanitize
}
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/website"
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/website"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
if err := tools.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
if !tools.Remove(backupPath + "/" + fileName) {
return Error(ctx, http.StatusInternalServerError, "删除备份失败")
if err := tools.Remove(backupPath + "/" + deleteBackupRequest.Name); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
// ResetConfig 重置配置
func (c *WebsiteController) ResetConfig(ctx http.Context) http.Response {
check := Check(ctx, "openresty")
if check != nil {
return check
}
id := ctx.Request().InputInt("id")
if id == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
// ResetConfig
//
// @Summary 重置配置
// @Description 重置网站的配置
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/resetConfig [post]
func (r *WebsiteController) ResetConfig(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
website := models.Website{}
if err := facades.Orm().Query().Where("id", id).Get(&website); err != nil {
facades.Log().Error("[面板][WebsiteController] 获取网站信息失败 ", err)
if err := facades.Orm().Query().Where("id", idRequest.ID).Get(&website); err != nil {
return ErrorSystem(ctx)
}
website.Status = true
website.Ssl = false
if err := facades.Orm().Query().Save(&website); err != nil {
facades.Log().Error("[面板][WebsiteController] 保存网站配置失败 ", err)
facades.Log().Request(ctx.Request()).Tags("面板", "网站管理").With(map[string]any{
"id": idRequest.ID,
"error": err.Error(),
}).Info("保存网站配置失败")
return ErrorSystem(ctx)
}
@@ -618,38 +599,51 @@ server
}
`, website.Path, website.Php, website.Name, website.Name, website.Name)
tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644)
tools.Write("/www/server/vhost/rewrite"+website.Name+".conf", "", 0644)
tools.Exec("systemctl reload openresty")
if err := tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
return nil
}
if err := tools.Write("/www/server/vhost/rewrite"+website.Name+".conf", "", 0644); err != nil {
return nil
}
if exec, err := tools.Exec("systemctl reload openresty"); err != nil {
return Error(ctx, http.StatusInternalServerError, exec)
}
return Success(ctx, nil)
}
// Status 网站状态
func (c *WebsiteController) Status(ctx http.Context) http.Response {
check := Check(ctx, "openresty")
if check != nil {
return check
}
id := ctx.Request().InputInt("id")
if id == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "参数错误")
// Status
//
// @Summary 状态
// @Description 启用或停用网站
// @Tags 网站管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse
// @Router /panel/websites/{id}/status [post]
func (r *WebsiteController) Status(ctx http.Context) http.Response {
var idRequest requests.ID
sanitize := Sanitize(ctx, &idRequest)
if sanitize != nil {
return sanitize
}
website := models.Website{}
if err := facades.Orm().Query().Where("id", id).Get(&website); err != nil {
facades.Log().Error("[面板][WebsiteController] 获取网站信息失败 ", err)
if err := facades.Orm().Query().Where("id", idRequest.ID).Get(&website); err != nil {
return ErrorSystem(ctx)
}
website.Status = ctx.Request().InputBool("status")
if err := facades.Orm().Query().Save(&website); err != nil {
facades.Log().Error("[面板][WebsiteController] 保存网站配置失败 ", err)
return ErrorSystem(ctx)
}
raw := tools.Read("/www/server/vhost/" + website.Name + ".conf")
raw, err := tools.Read("/www/server/vhost/" + website.Name + ".conf")
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
// 运行目录
rootConfig := tools.Cut(raw, "# root标记位开始\n", "# root标记位结束")
@@ -675,8 +669,12 @@ func (c *WebsiteController) Status(ctx http.Context) http.Response {
}
}
tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644)
tools.Exec("systemctl reload openresty")
if err := tools.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
return ErrorSystem(ctx)
}
if exec, err := tools.Exec("systemctl reload openresty"); err != nil {
return Error(ctx, http.StatusInternalServerError, exec)
}
return Success(ctx, nil)
}
+2 -2
View File
@@ -21,9 +21,9 @@ func Jwt() http.Middleware {
}
// JWT 鉴权
if _, err := facades.Auth().Parse(ctx, token); err != nil {
if _, err := facades.Auth(ctx).Parse(token); err != nil {
if errors.Is(err, auth.ErrorTokenExpired) {
token, err = facades.Auth().Refresh(ctx)
token, err = facades.Auth(ctx).Refresh()
if err != nil {
// 到达刷新时间上限
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
+79
View File
@@ -0,0 +1,79 @@
package middleware
import (
"strings"
"github.com/goravel/framework/contracts/http"
"panel/internal/services"
)
// MustInstall 确保已安装插件
func MustInstall() http.Middleware {
return func(ctx http.Context) {
path := ctx.Request().Path()
var slug string
if strings.HasPrefix(path, "/api/panel/website") {
slug = "openresty"
} else {
pathArr := strings.Split(path, "/")
if len(pathArr) < 4 {
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
"code": http.StatusForbidden,
"message": "插件不存在",
})
return
}
slug = pathArr[3]
}
plugin := services.NewPluginImpl().GetBySlug(slug)
installedPlugin := services.NewPluginImpl().GetInstalledBySlug(slug)
installedPlugins, err := services.NewPluginImpl().AllInstalled()
if err != nil {
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
"code": http.StatusInternalServerError,
"message": "系统内部错误",
})
return
}
if installedPlugin.Version != plugin.Version || installedPlugin.Slug != plugin.Slug {
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
"code": http.StatusForbidden,
"message": "插件 " + slug + " 需要更新至 " + plugin.Version + " 版本",
})
return
}
pluginsMap := make(map[string]bool)
for _, p := range installedPlugins {
pluginsMap[p.Slug] = true
}
for _, require := range plugin.Requires {
_, requireFound := pluginsMap[require]
if !requireFound {
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
"code": http.StatusForbidden,
"message": "插件 " + slug + " 需要依赖 " + require + " 插件",
})
return
}
}
for _, exclude := range plugin.Excludes {
_, excludeFound := pluginsMap[exclude]
if excludeFound {
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
"code": http.StatusForbidden,
"message": "插件 " + slug + " 不兼容 " + exclude + " 插件",
})
return
}
}
ctx.Request().Next()
}
}
+5 -12
View File
@@ -6,8 +6,8 @@ import (
)
type Paginate struct {
Page int `form:"page" json:"page"`
Limit int `form:"limit" json:"limit"`
Page int `form:"page" json:"page" filter:"int"`
Limit int `form:"limit" json:"limit" filter:"int"`
}
func (r *Paginate) Authorize(ctx http.Context) error {
@@ -16,20 +16,13 @@ func (r *Paginate) Authorize(ctx http.Context) error {
func (r *Paginate) Rules(ctx http.Context) map[string]string {
return map[string]string{
"page": "required|uint|min:1",
"limit": "required|uint|min:1",
"page": "required|int|min:1",
"limit": "required|int|min:1",
}
}
func (r *Paginate) Messages(ctx http.Context) map[string]string {
return map[string]string{
"page.required": "分页参数 page 不能为空",
"page.uint": "分页参数 page 必须是一个整数",
"page.min": "分页参数 page 必须大于等于 1",
"limit.required": "分页参数 limit 不能为空",
"limit.uint": "分页参数 limit 必须是一个整数",
"limit.min": "分页参数 limit 必须大于等于 1",
}
return map[string]string{}
}
func (r *Paginate) Attributes(ctx http.Context) map[string]string {
+35
View File
@@ -0,0 +1,35 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Archive struct {
Paths []string `form:"paths" json:"paths"`
File string `form:"file" json:"file"`
}
func (r *Archive) Authorize(ctx http.Context) error {
return nil
}
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",
}
}
func (r *Archive) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Archive) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Archive) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+34
View File
@@ -0,0 +1,34 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Copy struct {
Old string `form:"old" json:"old"`
New string `form:"new" json:"new"`
}
func (r *Copy) Authorize(ctx http.Context) error {
return nil
}
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_-]+)*$",
}
}
func (r *Copy) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Copy) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Copy) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+32
View File
@@ -0,0 +1,32 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Exist struct {
Path string `form:"path" json:"path"`
}
func (r *Exist) Authorize(ctx http.Context) error {
return nil
}
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",
}
}
func (r *Exist) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Exist) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Exist) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+34
View File
@@ -0,0 +1,34 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Move struct {
Old string `form:"old" json:"old"`
New string `form:"new" json:"new"`
}
func (r *Move) Authorize(ctx http.Context) error {
return nil
}
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_-]+)*$|path_not_exists",
}
}
func (r *Move) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Move) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Move) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+32
View File
@@ -0,0 +1,32 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type NotExist struct {
Path string `form:"path" json:"path"`
}
func (r *NotExist) Authorize(ctx http.Context) error {
return nil
}
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",
}
}
func (r *NotExist) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *NotExist) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *NotExist) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+38
View File
@@ -0,0 +1,38 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Permission struct {
Path string `form:"path" json:"path"`
Mode uint `form:"mode" json:"mode" filter:"uint"`
Owner string `form:"owner" json:"owner"`
Group string `form:"group" json:"group"`
}
func (r *Permission) Authorize(ctx http.Context) error {
return nil
}
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",
"mode": "regex:^[0-7]{3}$|uint",
"owner": "regex:^[a-zA-Z0-9_-]+$",
"group": "regex:^[a-zA-Z0-9_-]+$",
}
}
func (r *Permission) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Permission) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Permission) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+34
View File
@@ -0,0 +1,34 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Search struct {
Path string `form:"path" json:"path"`
KeyWord string `form:"keyword" json:"keyword"`
}
func (r *Search) Authorize(ctx http.Context) error {
return nil
}
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",
"keyword": "required|string",
}
}
func (r *Search) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Search) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Search) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+34
View File
@@ -0,0 +1,34 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type UnArchive struct {
File string `form:"file" json:"file"`
Path string `form:"path" json:"path"`
}
func (r *UnArchive) Authorize(ctx http.Context) error {
return nil
}
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",
}
}
func (r *UnArchive) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UnArchive) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UnArchive) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+42
View File
@@ -0,0 +1,42 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Create struct {
Name string `form:"name" json:"name"`
Path string `form:"path" json:"path"`
Comment string `form:"comment" json:"comment"`
AuthUser string `form:"auth_user" json:"auth_user"`
Secret string `form:"secret" json:"secret"`
HostsAllow string `form:"hosts_allow" json:"hosts_allow"`
}
func (r *Create) Authorize(ctx http.Context) error {
return nil
}
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_-]+)*$",
"comment": "string",
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
"secret": "required|min_len:8",
"hosts_allow": "string",
}
}
func (r *Create) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Create) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Create) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+42
View File
@@ -0,0 +1,42 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Update struct {
Name string `form:"name" json:"name"`
Path string `form:"path" json:"path"`
Comment string `form:"comment" json:"comment"`
AuthUser string `form:"auth_user" json:"auth_user"`
Secret string `form:"secret" json:"secret"`
HostsAllow string `form:"hosts_allow" json:"hosts_allow"`
}
func (r *Update) Authorize(ctx http.Context) error {
return nil
}
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_-]+)*$",
"comment": "string",
"auth_user": "required|regex:^[a-zA-Z0-9-_]+$",
"secret": "required|min_len:8",
"hosts_allow": "string",
}
}
func (r *Update) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Update) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Update) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
@@ -0,0 +1,32 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type UpdateConfig struct {
Config string `form:"config" json:"config"`
}
func (r *UpdateConfig) Authorize(ctx http.Context) error {
return nil
}
func (r *UpdateConfig) Rules(ctx http.Context) map[string]string {
return map[string]string{
"config": "required|string",
}
}
func (r *UpdateConfig) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UpdateConfig) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *UpdateConfig) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+2
View File
@@ -11,6 +11,7 @@ type Update struct {
BackupPath string `form:"backup_path" json:"backup_path"`
WebsitePath string `form:"website_path" json:"website_path"`
Entrance string `form:"entrance" json:"entrance"`
SSL bool `form:"ssl" json:"ssl"`
UserName string `form:"username" json:"username"`
Email string `form:"email" json:"email"`
Password string `form:"password" json:"password"`
@@ -27,6 +28,7 @@ func (r *Update) Rules(ctx http.Context) map[string]string {
"backup_path": "required|string:2,255",
"website_path": "required|string:2,255",
"entrance": `required|regex:^/(\w+)?$|not_in:/api`,
"ssl": "bool",
"username": "required|string:2,20",
"email": "required|email",
"password": "string:8,255",
+50
View File
@@ -0,0 +1,50 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Add struct {
Name string `form:"name" json:"name"`
Domains []string `form:"domains" json:"domains"`
Ports []uint `form:"ports" json:"ports"`
Path string `form:"path" json:"path"`
Php int `form:"php" json:"php"`
Db bool `form:"db" json:"db"`
DbType string `form:"db_type" json:"db_type"`
DbName string `form:"db_name" json:"db_name"`
DbUser string `form:"db_user" json:"db_user"`
DbPassword string `form:"db_password" json:"db_password"`
}
func (r *Add) Authorize(ctx http.Context) error {
return nil
}
func (r *Add) Rules(ctx http.Context) map[string]string {
return 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_-]+)*$",
"php": "required",
"db": "bool",
"db_type": "required_if:db,true|in:0,mysql,postgresql",
"db_name": "required_if:db,true|regex:^[a-zA-Z0-9_-]+$",
"db_user": "required_if:db,true|regex:^[a-zA-Z0-9_-]+$",
"db_password": "required_if:db,true|min_len:8",
}
}
func (r *Add) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Add) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Add) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
@@ -0,0 +1,32 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type DeleteBackup struct {
Name string `form:"name" json:"name"`
}
func (r *DeleteBackup) Authorize(ctx http.Context) error {
return nil
}
func (r *DeleteBackup) Rules(ctx http.Context) map[string]string {
return map[string]string{
"name": "required|string",
}
}
func (r *DeleteBackup) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *DeleteBackup) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *DeleteBackup) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+32
View File
@@ -0,0 +1,32 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type ID struct {
ID uint `form:"id" json:"id" filter:"uint"`
}
func (r *ID) Authorize(ctx http.Context) error {
return nil
}
func (r *ID) Rules(ctx http.Context) map[string]string {
return map[string]string{
"id": "required|exists:websites,id",
}
}
func (r *ID) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ID) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ID) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
@@ -0,0 +1,34 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type RestoreBackup struct {
ID uint `form:"id" json:"id" filter:"uint"`
Name string `form:"name" json:"name"`
}
func (r *RestoreBackup) Authorize(ctx http.Context) error {
return nil
}
func (r *RestoreBackup) Rules(ctx http.Context) map[string]string {
return map[string]string{
"id": "required|exists:websites,id",
"name": "required|string",
}
}
func (r *RestoreBackup) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *RestoreBackup) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *RestoreBackup) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+87
View File
@@ -0,0 +1,87 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type SaveConfig struct {
ID uint `form:"id" json:"id" filter:"uint"`
Domains []string `form:"domains" json:"domains"`
Ports []uint `form:"ports" json:"ports"`
Hsts bool `form:"hsts" json:"hsts"`
Ssl bool `form:"ssl" json:"ssl"`
HttpRedirect bool `form:"http_redirect" json:"http_redirect"`
OpenBasedir bool `form:"open_basedir" json:"open_basedir"`
Waf bool `form:"waf" json:"waf"`
WafCache string `form:"waf_cache" json:"waf_cache"`
WafMode string `form:"waf_mode" json:"waf_mode"`
WafCcDeny string `form:"waf_cc_deny" json:"waf_cc_deny"`
Index string `form:"index" json:"index"`
Path string `form:"path" json:"path"`
Root string `form:"root" json:"root"`
Raw string `form:"raw" json:"raw"`
Rewrite string `form:"rewrite" json:"rewrite"`
Php int `form:"php" json:"php" filter:"int"`
SslCertificate string `form:"ssl_certificate" json:"ssl_certificate"`
SslCertificateKey string `form:"ssl_certificate_key" json:"ssl_certificate_key"`
}
func (r *SaveConfig) Authorize(ctx http.Context) error {
return nil
}
func (r *SaveConfig) Rules(ctx http.Context) map[string]string {
return map[string]string{
"id": "required|exists:websites,id",
"domains": "required|slice",
"ports": "required|slice",
"hsts": "bool",
"ssl": "bool",
"http_redirect": "bool",
"open_basedir": "bool",
"waf": "bool",
"waf_cache": "required|string",
"waf_mode": "required|string",
"waf_cc_deny": "required|string",
"index": "required|string",
"path": "required|string",
"root": "required|string",
"raw": "required|string",
"rewrite": "string",
"php": "int",
"ssl_certificate": "required_if:ssl,true",
"ssl_certificate_key": "required_if:ssl,true",
}
}
func (r *SaveConfig) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *SaveConfig) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *SaveConfig) PrepareForValidation(ctx http.Context, data validation.Data) error {
_, exist := data.Get("waf_mode")
if !exist {
if err := data.Set("waf_mode", "DYNAMIC"); err != nil {
return err
}
}
_, exist = data.Get("waf_cc_deny")
if !exist {
if err := data.Set("waf_cc_deny", "rate=1000r/m duration=60m"); err != nil {
return err
}
}
_, exist = data.Get("waf_cache")
if !exist {
if err := data.Set("waf_cache", "capacity=50"); err != nil {
return err
}
}
return nil
}
-8
View File
@@ -1,8 +0,0 @@
package responses
import "panel/app/models"
type CertList struct {
Total int64 `json:"total"`
Items []models.Cert `json:"items"`
}
-8
View File
@@ -1,8 +0,0 @@
package responses
import "panel/app/models"
type DNSList struct {
Total int64 `json:"total"`
Items []models.CertDNS `json:"items"`
}
-8
View File
@@ -1,8 +0,0 @@
package responses
import "panel/app/models"
type UserList struct {
Total int64 `json:"total"`
Items []models.CertUser `json:"items"`
}
-12
View File
@@ -1,12 +0,0 @@
package responses
type Settings struct {
Name string `json:"name"`
Username string `json:"username"`
Password string `json:"password"`
Email string `json:"email"`
Port string `json:"port"`
Entrance string `json:"entrance"`
WebsitePath string `json:"website_path"`
BackupPath string `json:"backup_path"`
}
-8
View File
@@ -1,8 +0,0 @@
package responses
type Info struct {
ID uint `json:"id"`
Role []string `json:"role"`
Username string `json:"username"`
Email string `json:"email"`
}
+7 -7
View File
@@ -22,7 +22,7 @@ func (receiver *ProcessTask) Signature() string {
func (receiver *ProcessTask) Handle(args ...any) error {
taskID, ok := args[0].(uint)
if !ok {
facades.Log().Error("[面板][ProcessTask] 任务ID参数错误")
facades.Log().Info("[面板][ProcessTask] 任务ID参数错误")
return nil
}
@@ -35,13 +35,13 @@ func (receiver *ProcessTask) Handle(args ...any) error {
var task models.Task
if err := facades.Orm().Query().Where("id = ?", taskID).Get(&task); err != nil {
facades.Log().Errorf("[面板][ProcessTask] 获取任务%d失败: %s", taskID, err.Error())
facades.Log().Infof("[面板][ProcessTask] 获取任务%d失败: %s", taskID, err.Error())
return nil
}
task.Status = models.TaskStatusRunning
if err := facades.Orm().Query().Save(&task); err != nil {
facades.Log().Errorf("[面板][ProcessTask] 更新任务%d失败: %s", taskID, err.Error())
facades.Log().Infof("[面板][ProcessTask] 更新任务%d失败: %s", taskID, err.Error())
return nil
}
@@ -51,16 +51,16 @@ func (receiver *ProcessTask) Handle(args ...any) error {
if err != nil {
task.Status = models.TaskStatusFailed
if err := facades.Orm().Query().Save(&task); err != nil {
facades.Log().Errorf("[面板][ProcessTask] 更新任务%d失败: %s", taskID, err.Error())
facades.Log().Infof("[面板][ProcessTask] 更新任务%d失败: %s", taskID, err.Error())
return nil
}
facades.Log().Errorf("[面板][ProcessTask] 任务%d执行失败: %s", taskID, err.Error())
facades.Log().Infof("[面板][ProcessTask] 任务%d执行失败: %s", taskID, err.Error())
return nil
}
task.Status = models.TaskStatusSuccess
if err := facades.Orm().Query().Save(&task); err != nil {
facades.Log().Errorf("[面板][ProcessTask] 更新任务%d失败: %s", taskID, err.Error())
facades.Log().Infof("[面板][ProcessTask] 更新任务%d失败: %s", taskID, err.Error())
return nil
}
@@ -72,7 +72,7 @@ func (receiver *ProcessTask) Handle(args ...any) error {
func haveRunningTask() bool {
var task models.Task
if err := facades.Orm().Query().Where("status = ?", models.TaskStatusRunning).Get(&task); err != nil {
facades.Log().Error("[面板][ProcessTask] 获取任务失败: " + err.Error())
facades.Log().Info("[面板][ProcessTask] 获取任务失败: " + err.Error())
return true
}
+3 -1
View File
@@ -17,7 +17,7 @@ func (receiver *ValidationServiceProvider) Register(app foundation.Application)
func (receiver *ValidationServiceProvider) Boot(app foundation.Application) {
if err := facades.Validation().AddRules(receiver.rules()); err != nil {
facades.Log().Errorf("add rules error: %+v", err)
facades.Log().Infof("add rules error: %+v", err)
}
}
@@ -26,5 +26,7 @@ func (receiver *ValidationServiceProvider) rules() []validation.Rule {
&rules.Exists{},
&rules.NotExists{},
&rules.Captcha{},
&rules.PathExists{},
&rules.PathNotExists{},
}
}
+36
View File
@@ -0,0 +1,36 @@
package rules
import (
"github.com/goravel/framework/contracts/validation"
"github.com/spf13/cast"
"panel/pkg/tools"
)
type PathExists struct {
}
// Signature The name of the rule.
func (receiver *PathExists) Signature() string {
return "path_exists"
}
// Passes Determine if the validation rule passes.
func (receiver *PathExists) Passes(_ validation.Data, val any, options ...any) bool {
// 用户请求过来的数据
requestValue, err := cast.ToStringE(val)
if err != nil {
return false
}
// 判断是否为空
if len(requestValue) == 0 {
return false
}
return tools.Exists(requestValue)
}
// Message Get the validation error message.
func (receiver *PathExists) Message() string {
return "路径 %v 不存在"
}
+36
View File
@@ -0,0 +1,36 @@
package rules
import (
"github.com/goravel/framework/contracts/validation"
"github.com/spf13/cast"
"panel/pkg/tools"
)
type PathNotExists struct {
}
// Signature The name of the rule.
func (receiver *PathNotExists) Signature() string {
return "path_exists"
}
// Passes Determine if the validation rule passes.
func (receiver *PathNotExists) Passes(_ validation.Data, val any, options ...any) bool {
// 用户请求过来的数据
requestValue, err := cast.ToStringE(val)
if err != nil {
return false
}
// 判断是否为空
if len(requestValue) == 0 {
return false
}
return !tools.Exists(requestValue)
}
// Message Get the validation error message.
func (receiver *PathNotExists) Message() string {
return "路径 %v 已存在"
}
-306
View File
@@ -1,306 +0,0 @@
// Package services 备份服务
package services
import (
"errors"
"os"
"path/filepath"
"strings"
"github.com/goravel/framework/support/carbon"
"panel/app/models"
"panel/pkg/tools"
)
type Backup interface {
WebsiteList() ([]BackupFile, error)
WebSiteBackup(website models.Website) error
WebsiteRestore(website models.Website, backupFile string) error
MysqlList() ([]BackupFile, error)
MysqlBackup(database string) error
MysqlRestore(database string, backupFile string) error
PostgresqlList() ([]BackupFile, error)
PostgresqlBackup(database string) error
PostgresqlRestore(database string, backupFile string) error
}
type BackupFile struct {
Name string `json:"name"`
Size string `json:"size"`
}
type BackupImpl struct {
setting Setting
}
func NewBackupImpl() *BackupImpl {
return &BackupImpl{
setting: NewSettingImpl(),
}
}
// WebsiteList 网站备份列表
func (s *BackupImpl) WebsiteList() ([]BackupFile, error) {
backupPath := s.setting.Get(models.SettingKeyBackupPath)
if len(backupPath) == 0 {
return []BackupFile{}, nil
}
backupPath += "/website"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
files, err := os.ReadDir(backupPath)
if err != nil {
return []BackupFile{}, err
}
var backupList []BackupFile
for _, file := range files {
info, err := file.Info()
if err != nil {
continue
}
backupList = append(backupList, BackupFile{
Name: file.Name(),
Size: tools.FormatBytes(float64(info.Size())),
})
}
return backupList, nil
}
// WebSiteBackup 网站备份
func (s *BackupImpl) WebSiteBackup(website models.Website) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath)
if len(backupPath) == 0 {
return errors.New("未正确配置备份路径")
}
backupPath += "/website"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
backupFile := backupPath + "/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".zip"
tools.Exec(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`)
return nil
}
// WebsiteRestore 网站恢复
func (s *BackupImpl) WebsiteRestore(website models.Website, backupFile string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath)
if len(backupPath) == 0 {
return errors.New("未正确配置备份路径")
}
backupPath += "/website"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
backupFile = backupPath + "/" + backupFile
if !tools.Exists(backupFile) {
return errors.New("备份文件不存在")
}
tools.Exec(`rm -rf '` + website.Path + `/*'`)
tools.Exec(`unzip -o '` + backupFile + `' -d '` + website.Path + `' 2>&1`)
tools.Chmod(website.Path, 0755)
tools.Chown(website.Path, "www", "www")
return nil
}
// MysqlList MySQL备份列表
func (s *BackupImpl) MysqlList() ([]BackupFile, error) {
backupPath := s.setting.Get(models.SettingKeyBackupPath)
if len(backupPath) == 0 {
return []BackupFile{}, nil
}
backupPath += "/mysql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
files, err := os.ReadDir(backupPath)
if err != nil {
return []BackupFile{}, err
}
var backupList []BackupFile
for _, file := range files {
info, err := file.Info()
if err != nil {
continue
}
backupList = append(backupList, BackupFile{
Name: file.Name(),
Size: tools.FormatBytes(float64(info.Size())),
})
}
return backupList, nil
}
// MysqlBackup MySQL备份
func (s *BackupImpl) MysqlBackup(database string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/mysql"
rootPassword := s.setting.Get(models.SettingKeyMysqlRootPassword)
backupFile := database + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
err := os.Setenv("MYSQL_PWD", rootPassword)
if err != nil {
return err
}
tools.Exec("/www/server/mysql/bin/mysqldump -uroot " + database + " > " + backupPath + "/" + backupFile)
tools.Exec("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile)
tools.Remove(backupPath + "/" + backupFile)
_ = os.Unsetenv("MYSQL_PWD")
return nil
}
// MysqlRestore MySQL恢复
func (s *BackupImpl) MysqlRestore(database string, backupFile string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/mysql"
rootPassword := s.setting.Get(models.SettingKeyMysqlRootPassword)
ext := filepath.Ext(backupFile)
backupFile = backupPath + "/" + backupFile
if !tools.Exists(backupFile) {
return errors.New("备份文件不存在")
}
err := os.Setenv("MYSQL_PWD", rootPassword)
if err != nil {
return err
}
switch ext {
case ".zip":
tools.Exec("unzip -o " + backupFile + " -d " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ext)
case ".gz":
if strings.HasSuffix(backupFile, ".tar.gz") {
// 解压.tar.gz文件
tools.Exec("tar -zxvf " + backupFile + " -C " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ".tar.gz")
} else {
// 解压.gz文件
tools.Exec("gzip -d " + backupFile)
backupFile = strings.TrimSuffix(backupFile, ext)
}
case ".bz2":
tools.Exec("bzip2 -d " + backupFile)
backupFile = strings.TrimSuffix(backupFile, ext)
case ".tar":
tools.Exec("tar -xvf " + backupFile + " -C " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ext)
case ".rar":
tools.Exec("unrar x " + backupFile + " " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ext)
}
if !tools.Exists(backupFile) {
return errors.New("自动解压失败,请手动解压")
}
tools.Exec("/www/server/mysql/bin/mysql -uroot " + database + " < " + backupFile)
_ = os.Unsetenv("MYSQL_PWD")
return nil
}
// PostgresqlList PostgreSQL备份列表
func (s *BackupImpl) PostgresqlList() ([]BackupFile, error) {
backupPath := s.setting.Get(models.SettingKeyBackupPath)
if len(backupPath) == 0 {
return []BackupFile{}, nil
}
backupPath += "/postgresql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
files, err := os.ReadDir(backupPath)
if err != nil {
return []BackupFile{}, err
}
var backupList []BackupFile
for _, file := range files {
info, err := file.Info()
if err != nil {
continue
}
backupList = append(backupList, BackupFile{
Name: file.Name(),
Size: tools.FormatBytes(float64(info.Size())),
})
}
return backupList, nil
}
// PostgresqlBackup PostgreSQL备份
func (s *BackupImpl) PostgresqlBackup(database string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
backupFile := database + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
tools.Exec(`su - postgres -c "pg_dump ` + database + `" > ` + backupPath + "/" + backupFile)
tools.Exec("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile)
tools.Remove(backupPath + "/" + backupFile)
return nil
}
// PostgresqlRestore PostgreSQL恢复
func (s *BackupImpl) PostgresqlRestore(database string, backupFile string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/postgresql"
ext := filepath.Ext(backupFile)
backupFile = backupPath + "/" + backupFile
if !tools.Exists(backupFile) {
return errors.New("备份文件不存在")
}
switch ext {
case ".zip":
tools.Exec("unzip -o " + backupFile + " -d " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ext)
case ".gz":
if strings.HasSuffix(backupFile, ".tar.gz") {
// 解压.tar.gz文件
tools.Exec("tar -zxvf " + backupFile + " -C " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ".tar.gz")
} else {
// 解压.gz文件
tools.Exec("gzip -d " + backupFile)
backupFile = strings.TrimSuffix(backupFile, ext)
}
case ".bz2":
tools.Exec("bzip2 -d " + backupFile)
backupFile = strings.TrimSuffix(backupFile, ext)
case ".tar":
tools.Exec("tar -xvf " + backupFile + " -C " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ext)
case ".rar":
tools.Exec("unrar x " + backupFile + " " + backupPath)
backupFile = strings.TrimSuffix(backupFile, ext)
}
if !tools.Exists(backupFile) {
return errors.New("自动解压失败,请手动解压")
}
tools.Exec(`su - postgres -c "psql ` + database + `" < ` + backupFile)
return nil
}
-44
View File
@@ -1,44 +0,0 @@
package services
import (
"strings"
"panel/app/models"
"panel/pkg/tools"
)
type Cron interface {
AddToSystem(cron models.Cron)
DeleteFromSystem(cron models.Cron)
}
type CronImpl struct {
}
func NewCronImpl() *CronImpl {
return &CronImpl{}
}
// AddToSystem 添加到系统
func (r *CronImpl) AddToSystem(cron models.Cron) {
if tools.IsRHEL() {
tools.Exec("echo \"" + cron.Time + " " + cron.Shell + " >> " + cron.Log + " 2>&1\" >> /var/spool/cron/root")
tools.Exec("systemctl restart crond")
} else {
tools.Exec("echo \"" + cron.Time + " " + cron.Shell + " >> " + cron.Log + " 2>&1\" >> /var/spool/cron/crontabs/root")
tools.Exec("systemctl restart cron")
}
}
// DeleteFromSystem 从系统中删除
func (r *CronImpl) DeleteFromSystem(cron models.Cron) {
// 需要转义Shell路径的/为\/
cron.Shell = strings.ReplaceAll(cron.Shell, "/", "\\/")
if tools.IsRHEL() {
tools.Exec("sed -i '/" + cron.Shell + "/d' /var/spool/cron/root")
tools.Exec("systemctl restart crond")
} else {
tools.Exec("sed -i '/" + cron.Shell + "/d' /var/spool/cron/crontabs/root")
tools.Exec("systemctl restart cron")
}
}
-41
View File
@@ -1,41 +0,0 @@
package services
import (
"testing"
"github.com/stretchr/testify/suite"
)
type SettingTestSuite struct {
suite.Suite
setting Setting
}
func TestSettingTestSuite(t *testing.T) {
suite.Run(t, &SettingTestSuite{
setting: NewSettingImpl(),
})
}
func (s *SettingTestSuite) SetupTest() {
}
func (s *SettingTestSuite) TestGet() {
/*mockOrm, mockDb, _, _ := mock.Orm()
mockOrm.On("Query").Return(mockDb).Twice()
mockDb.On("Where", "key", "test").Return(mockDb).Twice()
mockDb.On("FirstOrFail", &models.Setting{}).Return(nil).Twice()
a := s.setting.Get("test")
b := s.setting.Get("test", "test")
s.Equal("", a)
s.Equal("test", b)*/
}
func (s *SettingTestSuite) TestSet() {
/*mockOrm, mockDb, _, _ := mock.Orm()
mockOrm.On("Query").Return(mockDb).Once()
mockDb.On("UpdateOrCreate", &models.Setting{}, models.Setting{Key: "test"}, models.Setting{Value: "test"}).Return(nil).Once()
err := s.setting.Set("test", "test")
s.Nil(err)*/
}
-34
View File
@@ -1,34 +0,0 @@
package services
import (
"testing"
"github.com/stretchr/testify/suite"
)
type UserTestSuite struct {
suite.Suite
user User
}
func TestUserTestSuite(t *testing.T) {
suite.Run(t, &UserTestSuite{
user: NewUserImpl(),
})
}
func (s *UserTestSuite) SetupTest() {
}
func (s *UserTestSuite) TestCreate() {
/*mockOrm, mockDb, _, _ := mock.Orm()
mockOrm.On("Query").Return(mockDb).Once()
mockDb.On("Create", &models.User{
Username: "haozi",
Password: "123456",
}).Return(nil).Once()
user, err := s.user.Create("haozi", "123456")
s.Nil(err)
s.Equal("haozi", user.Username)*/
}
-425
View File
@@ -1,425 +0,0 @@
// Package services 网站服务
package services
import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/goravel/framework/facades"
"golang.org/x/exp/slices"
"panel/app/models"
"panel/pkg/tools"
)
type Website interface {
List(page int, limit int) (int64, []models.Website, error)
Add(website PanelWebsite) (models.Website, error)
Delete(id int) error
GetConfig(id int) (WebsiteSetting, error)
GetConfigByName(name string) (WebsiteSetting, error)
}
type PanelWebsite struct {
Name string `json:"name"`
Status bool `json:"status"`
Domains []string `json:"domains"`
Ports []string `json:"ports"`
Path string `json:"path"`
Php int `json:"php"`
Ssl bool `json:"ssl"`
Remark string `json:"remark"`
Db bool `json:"db"`
DbType string `json:"db_type"`
DbName string `json:"db_name"`
DbUser string `json:"db_user"`
DbPassword string `json:"db_password"`
}
// WebsiteSetting 网站设置
type WebsiteSetting struct {
Name string `json:"name"`
Domains []string `json:"domains"`
Ports []string `json:"ports"`
Root string `json:"root"`
Path string `json:"path"`
Index string `json:"index"`
Php string `json:"php"`
OpenBasedir bool `json:"open_basedir"`
Ssl bool `json:"ssl"`
SslCertificate string `json:"ssl_certificate"`
SslCertificateKey string `json:"ssl_certificate_key"`
SslNotBefore string `json:"ssl_not_before"`
SslNotAfter string `json:"ssl_not_after"`
SSlDNSNames []string `json:"ssl_dns_names"`
SslIssuer string `json:"ssl_issuer"`
SslOCSPServer []string `json:"ssl_ocsp_server"`
HttpRedirect bool `json:"http_redirect"`
Hsts bool `json:"hsts"`
Waf bool `json:"waf"`
WafMode string `json:"waf_mode"`
WafCcDeny string `json:"waf_cc_deny"`
WafCache string `json:"waf_cache"`
Rewrite string `json:"rewrite"`
Raw string `json:"raw"`
Log string `json:"log"`
}
type WebsiteImpl struct {
setting Setting
}
func NewWebsiteImpl() *WebsiteImpl {
return &WebsiteImpl{
setting: NewSettingImpl(),
}
}
// List 列出网站
func (r *WebsiteImpl) List(page, limit int) (int64, []models.Website, error) {
var websites []models.Website
var total int64
if err := facades.Orm().Query().Paginate(page, limit, &websites, &total); err != nil {
return total, websites, err
}
return total, websites, nil
}
// Add 添加网站
func (r *WebsiteImpl) Add(website PanelWebsite) (models.Website, error) {
// 禁止部分保留名称
nameSlices := []string{"phpmyadmin", "mysql", "panel", "ssh"}
if slices.Contains(nameSlices, website.Name) {
return models.Website{}, errors.New("网站名称" + website.Name + "为保留名称,请更换")
}
// path为空时,设置默认值
if len(website.Path) == 0 {
website.Path = r.setting.Get(models.SettingKeyWebsitePath) + "/" + website.Name
}
// path不为/开头时,返回错误
if website.Path[0] != '/' {
return models.Website{}, errors.New("网站路径" + website.Path + "必须以/开头")
}
website.Ssl = false
website.Status = true
w := models.Website{
Name: website.Name,
Status: website.Status,
Path: website.Path,
Php: website.Php,
Ssl: website.Ssl,
Remark: website.Remark,
}
if err := facades.Orm().Query().Create(&w); err != nil {
return w, err
}
tools.Mkdir(website.Path, 0755)
index := `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>耗子Linux面板</title>
<style>
body {
background-color: #f9f9f9;
margin: 0;
padding: 0;
}
.container {
max-width: 800px;
margin: 2em auto;
background-color: #ffffff;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 2.5em;
margin-top: 0;
margin-bottom: 20px;
text-align: center;
color: #333;
border-bottom: 2px solid #ddd;
padding-bottom: 0.5em;
}
p {
color: #555;
line-height: 1.8;
}
@media screen and (max-width: 768px) {
.container {
padding: 15px;
margin: 2em 15px;
}
h1 {
font-size: 1.8em;
}
}
</style>
</head>
<body>
<div class="container">
<h1>耗子Linux面板</h1>
<p>这是耗子Linux面板的网站默认页面!</p>
<p>当您看到此页面,说明您的网站已创建成功。</p>
</div>
</body>
</html>
`
tools.Write(website.Path+"/index.html", index, 0644)
portList := ""
domainList := ""
portUsed := make(map[string]bool)
domainUsed := make(map[string]bool)
for i, port := range website.Ports {
if _, ok := portUsed[port]; !ok {
if i == len(website.Ports)-1 {
portList += " listen " + port + ";"
} else {
portList += " listen " + port + ";\n"
}
portUsed[port] = true
}
}
if len(website.Ports) == 0 {
portList += " listen 80;\n"
}
for _, domain := range website.Domains {
if _, ok := domainUsed[domain]; !ok {
domainList += " " + domain
domainUsed[domain] = true
}
}
nginxConf := fmt.Sprintf(`# 配置文件中的标记位请勿随意修改,改错将导致面板无法识别!
# 有自定义配置需求的,请将自定义的配置写在各标记位下方。
server
{
# port标记位开始
%s
# port标记位结束
# server_name标记位开始
server_name%s;
# server_name标记位结束
# index标记位开始
index index.php index.html;
# index标记位结束
# root标记位开始
root %s;
# root标记位结束
# ssl标记位开始
# ssl标记位结束
# php标记位开始
include enable-php-%d.conf;
# php标记位结束
# waf标记位开始
waf on;
waf_rule_path /www/server/openresty/ngx_waf/assets/rules/;
waf_mode DYNAMIC;
waf_cc_deny rate=1000r/m duration=60m;
waf_cache capacity=50;
# waf标记位结束
# 错误页配置,可自行设置
#error_page 404 /404.html;
#error_page 502 /502.html;
# 伪静态规则引入,修改后将导致面板设置的伪静态规则失效
include /www/server/vhost/rewrite/%s.conf;
# 面板默认禁止访问部分敏感目录,可自行修改
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn)
{
return 404;
}
# 面板默认不记录静态资源的访问日志并开启1小时浏览器缓存,可自行修改
location ~ .*\.(js|css)$
{
expires 1h;
error_log /dev/null;
access_log /dev/null;
}
access_log /www/wwwlogs/%s.log;
error_log /www/wwwlogs/%s.log;
}
`, portList, domainList, website.Path, website.Php, website.Name, website.Name, website.Name)
tools.Write("/www/server/vhost/"+website.Name+".conf", nginxConf, 0644)
tools.Write("/www/server/vhost/rewrite/"+website.Name+".conf", "", 0644)
tools.Write("/www/server/vhost/ssl/"+website.Name+".pem", "", 0644)
tools.Write("/www/server/vhost/ssl/"+website.Name+".key", "", 0644)
tools.Chmod(r.setting.Get(models.SettingKeyWebsitePath), 0755)
tools.Chmod(website.Path, 0755)
tools.Chown(r.setting.Get(models.SettingKeyWebsitePath), "www", "www")
tools.Chown(website.Path, "www", "www")
tools.Exec("systemctl reload openresty")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if website.Db && website.DbType == "mysql" {
tools.Exec(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "CREATE DATABASE IF NOT EXISTS ` + website.DbName + ` DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;"`)
tools.Exec(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "CREATE USER '` + website.DbUser + `'@'localhost' IDENTIFIED BY '` + website.DbPassword + `';"`)
tools.Exec(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "GRANT ALL PRIVILEGES ON ` + website.DbName + `.* TO '` + website.DbUser + `'@'localhost';"`)
tools.Exec(`/www/server/mysql/bin/mysql -uroot -p` + rootPassword + ` -e "FLUSH PRIVILEGES;"`)
}
if website.Db && website.DbType == "postgresql" {
tools.Exec(`echo "CREATE DATABASE ` + website.DbName + `;" | su - postgres -c "psql"`)
tools.Exec(`echo "CREATE USER ` + website.DbUser + ` WITH PASSWORD '` + website.DbPassword + `';" | su - postgres -c "psql"`)
tools.Exec(`echo "ALTER DATABASE ` + website.DbName + ` OWNER TO ` + website.DbUser + `;" | su - postgres -c "psql"`)
tools.Exec(`echo "GRANT ALL PRIVILEGES ON DATABASE ` + website.DbName + ` TO ` + website.DbUser + `;" | su - postgres -c "psql"`)
}
return w, nil
}
// Delete 删除网站
func (r *WebsiteImpl) Delete(id int) error {
var website models.Website
if err := facades.Orm().Query().With("Cert").Where("id", id).FirstOrFail(&website); err != nil {
return err
}
if website.Cert != nil {
return errors.New("网站" + website.Name + "已绑定SSL证书,请先删除证书")
}
if _, err := facades.Orm().Query().Delete(&website); err != nil {
return err
}
tools.Remove("/www/server/vhost/" + website.Name + ".conf")
tools.Remove("/www/server/vhost/rewrite/" + website.Name + ".conf")
tools.Remove("/www/server/vhost/ssl/" + website.Name + ".pem")
tools.Remove("/www/server/vhost/ssl/" + website.Name + ".key")
tools.Remove(website.Path)
tools.Exec("systemctl reload openresty")
return nil
}
// GetConfig 获取网站配置
func (r *WebsiteImpl) GetConfig(id int) (WebsiteSetting, error) {
var website models.Website
if err := facades.Orm().Query().Where("id", id).First(&website); err != nil {
return WebsiteSetting{}, err
}
config := tools.Read("/www/server/vhost/" + website.Name + ".conf")
var setting WebsiteSetting
setting.Name = website.Name
setting.Path = website.Path
setting.Ssl = website.Ssl
setting.Php = strconv.Itoa(website.Php)
setting.Raw = config
ports := tools.Cut(config, "# port标记位开始", "# port标记位结束")
matches := regexp.MustCompile(`listen\s+(.*);`).FindAllStringSubmatch(ports, -1)
for _, match := range matches {
if len(match) < 2 {
continue
}
setting.Ports = append(setting.Ports, match[1])
}
serverName := tools.Cut(config, "# server_name标记位开始", "# server_name标记位结束")
match := regexp.MustCompile(`server_name\s+(.*);`).FindStringSubmatch(serverName)
if len(match) > 1 {
setting.Domains = strings.Split(match[1], " ")
}
root := tools.Cut(config, "# root标记位开始", "# root标记位结束")
match = regexp.MustCompile(`root\s+(.*);`).FindStringSubmatch(root)
if len(match) > 1 {
setting.Root = match[1]
}
index := tools.Cut(config, "# index标记位开始", "# index标记位结束")
match = regexp.MustCompile(`index\s+(.*);`).FindStringSubmatch(index)
if len(match) > 1 {
setting.Index = match[1]
}
if tools.Exists(setting.Root + "/.user.ini") {
userIni := tools.Read(setting.Path + "/.user.ini")
if strings.Contains(userIni, "open_basedir") {
setting.OpenBasedir = true
} else {
setting.OpenBasedir = false
}
} else {
setting.OpenBasedir = false
}
setting.SslCertificate = tools.Read("/www/server/vhost/ssl/" + website.Name + ".pem")
setting.SslCertificateKey = tools.Read("/www/server/vhost/ssl/" + website.Name + ".key")
if setting.Ssl {
ssl := tools.Cut(config, "# ssl标记位开始", "# ssl标记位结束")
setting.HttpRedirect = strings.Contains(ssl, "# http重定向标记位")
setting.Hsts = strings.Contains(ssl, "# hsts标记位")
certData := tools.Read("/www/server/vhost/ssl/" + website.Name + ".pem")
block, _ := pem.Decode([]byte(certData))
if block != nil {
cert, err := x509.ParseCertificate(block.Bytes)
if err == nil {
setting.SslNotBefore = cert.NotBefore.Format("2006-01-02 15:04:05")
setting.SslNotAfter = cert.NotAfter.Format("2006-01-02 15:04:05")
setting.SslIssuer = cert.Issuer.CommonName
setting.SslOCSPServer = cert.OCSPServer
setting.SSlDNSNames = cert.DNSNames
}
}
} else {
setting.HttpRedirect = false
setting.Hsts = false
}
waf := tools.Cut(config, "# waf标记位开始", "# waf标记位结束")
setting.Waf = strings.Contains(waf, "waf on;")
match = regexp.MustCompile(`waf_mode\s+(.+);`).FindStringSubmatch(waf)
if len(match) > 1 {
setting.WafMode = match[1]
}
match = regexp.MustCompile(`waf_cc_deny\s+(.+);`).FindStringSubmatch(waf)
if len(match) > 1 {
setting.WafCcDeny = match[1]
}
match = regexp.MustCompile(`waf_cache\s+(.+);`).FindStringSubmatch(waf)
if len(match) > 1 {
setting.WafCache = match[1]
}
setting.Rewrite = tools.Read("/www/server/vhost/rewrite/" + website.Name + ".conf")
setting.Log = tools.Escape(tools.Exec(`tail -n 100 '/www/wwwlogs/` + website.Name + `.log'`))
return setting, nil
}
// GetConfigByName 根据网站名称获取网站配置
func (r *WebsiteImpl) GetConfigByName(name string) (WebsiteSetting, error) {
var website models.Website
if err := facades.Orm().Query().Where("name", name).First(&website); err != nil {
return WebsiteSetting{}, err
}
return r.GetConfig(int(website.ID))
}
+1 -1
View File
@@ -14,7 +14,7 @@ func init() {
"connections": map[string]any{
"panel": map[string]any{
"driver": "sqlite",
"database": config.Env("DB_FILE", "/www/panel/database/panel.db"),
"database": config.Env("DB_FILE", "/www/panel/storage/panel.db"),
"prefix": "",
"singular": false, // Table name is singular
},
+6 -4
View File
@@ -16,6 +16,8 @@ func init() {
"fiber": map[string]any{
// prefork mode, see https://docs.gofiber.io/api/fiber/#config
"prefork": false,
// Optional, default is 4096 KB
"body_limit": 1024 * 1024 * 4,
"route": func() (route.Route, error) {
return fiberfacades.Route("fiber"), nil
},
@@ -24,7 +26,7 @@ func init() {
// HTTP URL
"url": "http://localhost",
// HTTP Host
"host": config.Env("APP_HOST", "0.0.0.0"),
"host": "",
// HTTP Port
"port": config.Env("APP_PORT", "8888"),
// HTTP Entrance
@@ -32,15 +34,15 @@ func init() {
// HTTPS Configuration
"tls": map[string]any{
// HTTPS Host
"host": config.Env("APP_HOST", "0.0.0.0"),
"host": "",
// HTTPS Port
"port": config.Env("APP_PORT", "8888"),
// SSL Certificate
"ssl": map[string]any{
// ca.pem
"cert": "",
"cert": config.Env("APP_SSL_CERT", "/www/panel/storage/ssl.crt"),
// ca.key
"key": "",
"key": config.Env("APP_SSL_KEY", "/www/panel/storage/ssl.key"),
},
},
})
+2 -2
View File
@@ -26,13 +26,13 @@ func init() {
},
"single": map[string]any{
"driver": "single",
"path": "storage/logs/goravel.log",
"path": "storage/logs/panel.log",
"level": "info",
"print": true,
},
"daily": map[string]any{
"driver": "daily",
"path": "storage/logs/goravel.log",
"path": "storage/logs/panel.log",
"level": "info",
"days": 7,
"print": true,
+2 -1
View File
@@ -8,6 +8,7 @@ func init() {
config := facades.Config()
config.Add("panel", map[string]any{
"name": "耗子Linux面板",
"version": "v2.1.9",
"version": "v2.1.34",
"ssl": config.Env("APP_SSL", false),
})
}
-1
View File
@@ -1 +0,0 @@
panel.db
+1909 -435
View File
File diff suppressed because it is too large Load Diff
+1909 -435
View File
File diff suppressed because it is too large Load Diff
+1197 -286
View File
File diff suppressed because it is too large Load Diff
+54 -68
View File
@@ -6,43 +6,40 @@ require (
github.com/fasthttp/websocket v1.5.6
github.com/gertd/go-pluralize v0.2.1
github.com/go-acme/lego/v4 v4.14.2
github.com/gofiber/swagger v0.1.14
github.com/gookit/color v1.5.4
github.com/gookit/validate v1.5.1
github.com/goravel/fiber v1.1.11-0.20231108081345-36e967f101d0
github.com/goravel/framework v1.13.1-0.20231109083801-f2c86a7c0796
github.com/goravel/fiber v1.1.11-0.20231222082732-ab5faaaee0d2
github.com/goravel/framework v1.13.1-0.20231222074402-8955968c9670
github.com/iancoleman/strcase v0.3.0
github.com/imroc/req/v3 v3.42.1
github.com/mojocn/base64Captcha v1.3.5
github.com/mholt/archiver/v3 v3.5.1
github.com/mojocn/base64Captcha v1.3.6
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spf13/cast v1.5.1
github.com/spf13/cast v1.6.0
github.com/stretchr/testify v1.8.4
github.com/swaggo/files/v2 v2.0.0
github.com/swaggo/swag v1.16.2
github.com/valyala/fasthttp v1.50.0
golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
github.com/valyala/fasthttp v1.51.0
golang.org/x/crypto v0.17.0
)
require (
cloud.google.com/go v0.110.9 // indirect
cloud.google.com/go/compute v1.23.2 // indirect
cloud.google.com/go v0.110.10 // indirect
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.4 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
cloud.google.com/go/pubsub v1.33.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae // indirect
github.com/RichardKnop/machinery/v2 v2.0.11 // 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/andybalholm/brotli v1.0.6 // indirect
github.com/aws/aws-sdk-go v1.37.16 // indirect
github.com/aws/aws-sdk-go v1.49.6 // indirect
github.com/bytedance/sonic v1.10.2 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
@@ -50,40 +47,34 @@ require (
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/cloudflare/cloudflare-go v0.70.0 // indirect
github.com/containerd/continuity v0.4.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // 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/docker/cli v24.0.7+incompatible // indirect
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/go-connections v0.4.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/fsnotify/fsnotify v1.6.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/glebarez/sqlite v1.9.0 // indirect
github.com/glebarez/sqlite v1.10.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-redsync/redsync/v4 v4.0.4 // indirect
github.com/go-redsync/redsync/v4 v4.8.1 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gofiber/fiber/v2 v2.50.0 // indirect
github.com/gofiber/fiber/v2 v2.51.0 // indirect
github.com/gofiber/template v1.8.2 // indirect
github.com/gofiber/template/html/v2 v2.0.5 // indirect
github.com/gofiber/utils v1.1.0 // 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.0.0 // indirect
github.com/golang-migrate/migrate/v4 v4.16.2 // indirect
github.com/golang-module/carbon/v2 v2.2.12 // 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.2.14 // 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
@@ -95,36 +86,32 @@ require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20230907193218-d3ddc7976beb // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/google/wire v0.5.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gookit/filter v1.2.0 // indirect
github.com/gookit/goutil v0.6.14 // indirect
github.com/goravel/file-rotatelogs v0.0.0-20211215053220-2ab31dd9575c // indirect
github.com/goravel/file-rotatelogs/v2 v2.4.1 // indirect
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.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.16 // 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.3.1 // indirect
github.com/jackc/pgx/v5 v5.4.3 // 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
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/klauspost/compress v1.17.3 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/lestrrat-go/strftime v1.0.5 // indirect
github.com/lib/pq v1.10.2 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
@@ -133,23 +120,21 @@ require (
github.com/microsoft/go-mssqldb v1.6.0 // indirect
github.com/miekg/dns v1.1.55 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nrdcg/dnspod-go v0.4.0 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/onsi/ginkgo/v2 v2.12.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.1.10 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/ory/dockertest/v3 v3.10.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
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/qtls-go1-20 v0.3.4 // indirect
github.com/quic-go/quic-go v0.38.1 // indirect
github.com/redis/go-redis/v9 v9.3.0 // indirect
github.com/refraction-networking/utls v1.5.3 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
@@ -157,30 +142,30 @@ require (
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rotisserie/eris v0.5.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/savioxavier/termlink v1.3.0 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.10.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.17.0 // indirect
github.com/spf13/viper v1.18.2 // indirect
github.com/streadway/amqp v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/urfave/cli/v2 v2.25.7 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/urfave/cli/v2 v2.26.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/tcplisten v1.0.0 // 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/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // 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/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
@@ -190,30 +175,31 @@ require (
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.6.0 // indirect
golang.org/x/image v0.11.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.12.0 // indirect
golang.org/x/sync v0.4.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/oauth2 v0.15.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.14.0 // indirect
google.golang.org/api v0.143.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/grpc v1.59.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.15.0 // indirect
google.golang.org/api v0.153.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/grpc v1.60.1 // indirect
google.golang.org/protobuf v1.31.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.2 // indirect
gorm.io/driver/postgres v1.5.2 // indirect
gorm.io/driver/postgres v1.5.4 // indirect
gorm.io/driver/sqlserver v1.5.2 // indirect
gorm.io/gorm v1.25.5 // indirect
gorm.io/plugin/dbresolver v1.4.7 // indirect
gorm.io/plugin/dbresolver v1.5.0 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
+166 -169
View File
@@ -3,7 +3,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
@@ -17,23 +16,23 @@ 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.110.9 h1:e7ITSqGFFk4rbz/JFIqZh3G4VEHguhAL4BQcFlWtU68=
cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM=
cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic=
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.2 h1:nWEMDhgbBkBJjfpVySqU4jgWdc22PLR0o4vEexZHers=
cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4hFDcR04Rns=
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/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.4 h1:K6n/GZHFTtEoKT5aUG3l9diPi0VduZNQ1PfdnpkkIFk=
cloud.google.com/go/iam v1.1.4/go.mod h1:l/rg8l1AaA+VFMho/HYx2Vv6xinPSLMF8qfhRPIZ0L8=
cloud.google.com/go/kms v1.15.4 h1:gEZzC54ZBI+aeW8/jg9tgz9KR4Aa+WEDPbdGIV3iJ7A=
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/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=
@@ -46,7 +45,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
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=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
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=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
@@ -65,7 +63,6 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
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.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE=
@@ -87,23 +84,31 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
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.11 h1:BTfLGOmOju3W/OtlZmLX26OjYNZsU4PJo04pQReycdc=
github.com/RichardKnop/machinery/v2 v2.0.11/go.mod h1:b5Q6cT/w7YLlIl4Vi+jpdEoyYiqhTgx+0USoKb1wzqU=
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/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
github.com/aws/aws-sdk-go v1.37.16 h1:Q4YOP2s00NpB9wfmTDZArdcLRuG9ijbnoAwTW3ivleI=
github.com/aws/aws-sdk-go v1.37.16/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.49.6 h1:yNldzF5kzLBRvKlKz1S0bkvc2+04R1kt13KfBWQBfFA=
github.com/aws/aws-sdk-go v1.49.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/brianvoe/gofakeit/v6 v6.24.0 h1:74yq7RRz/noddscZHRS2T84oHZisW9muwbb8sRnU52A=
github.com/brianvoe/gofakeit/v6 v6.26.3 h1:3ljYrjPwsUNAUFdUIr2jVg5EhKdcke/ZLop7uVg1Er8=
github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk=
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
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.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
@@ -111,7 +116,7 @@ github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf5
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=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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=
@@ -132,31 +137,26 @@ github.com/cloudflare/cloudflare-go v0.70.0/go.mod h1:VW6GuazkaZ4xEDkFt24lkXQUsE
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=
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
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.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw=
github.com/dhui/dktest v0.4.0 h1:z05UmuXZHO/bgj/ds2bGMBu8FI4WA+Ag/m3ghL+om7M=
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/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg=
github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -167,13 +167,12 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fasthttp/websocket v1.5.6 h1:4WtWgRJ0Gzj1Ou+xGKy66Ji+a0mUfgAj9ZdPqHiUwQE=
github.com/fasthttp/websocket v1.5.6/go.mod h1:yiKhNx2zFOv65YYtCJNhtl5VjdCFew3W+gt8U/9aFkI=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
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/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
@@ -182,8 +181,8 @@ github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlL
github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs=
github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw=
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.14.2 h1:/D/jqRgLi8Cbk33sLGtu2pX2jEg3bGJWHyV8kFuUHGM=
github.com/go-acme/lego/v4 v4.14.2/go.mod h1:kBXxbeTg0x9AgaOYjPSwIeJy3Y33zTz+tMD16O4MO6c=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -201,9 +200,11 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
@@ -215,12 +216,10 @@ github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGK
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4=
github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-redis/redis/v8 v8.1.1/go.mod h1:ysgGY09J/QeDYbu3HikWEIPCwaeOkuNoTgKayTEaEOw=
github.com/go-redis/redis/v8 v8.6.0/go.mod h1:DQ9q4Rk2HtwkrwVrdgmphoOQDMfpvcd/nHEwRsicg8s=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-redsync/redsync/v4 v4.0.4 h1:ru0qG+VCefaZSx3a5ADmlKZXkNdgeeYWIuymDu/tzV8=
github.com/go-redsync/redsync/v4 v4.0.4/go.mod h1:QBOJAs1k8O6Eyrre4a++pxQgHe5eQ+HF56KuTVv+8Bs=
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-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=
@@ -228,6 +227,7 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
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=
@@ -254,8 +254,11 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/gofiber/fiber/v2 v2.50.0 h1:ia0JaB+uw3GpNSCR5nvC5dsaxXjRU5OEu36aytx+zGw=
github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw=
github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ=
github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U=
github.com/gofiber/swagger v0.1.14 h1:o524wh4QaS4eKhUCpj7M0Qhn8hvtzcyxDsfZLXuQcRI=
github.com/gofiber/swagger v0.1.14/go.mod h1:DCk1fUPsj+P07CKaZttBbV1WzTZSQcSxfub8y9/BFr8=
github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk=
github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
github.com/gofiber/template/html/v2 v2.0.5 h1:BKLJ6Qr940NjntbGmpO3zVa4nFNGDCi/IfUiDB9OC20=
@@ -269,12 +272,13 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw
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 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA=
github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o=
github.com/golang-module/carbon/v2 v2.2.12 h1:qydcu64b5+29dOnMNVS27V3JaphyA2eLrUvKv8CNPMI=
github.com/golang-module/carbon/v2 v2.2.12/go.mod h1:XDALX7KgqmHk95xyLeaqX9/LJGbfLATyruTziq68SZ8=
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.2.14 h1:mT2hpNoCQVnkboZ6iyRf7WCbXtZTRXFBvXXWMp0PaMc=
github.com/golang-module/carbon/v2 v2.2.14/go.mod h1:XDALX7KgqmHk95xyLeaqX9/LJGbfLATyruTziq68SZ8=
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=
@@ -311,9 +315,11 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
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/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=
@@ -332,7 +338,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
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-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=
@@ -354,23 +361,21 @@ github.com/google/pprof v0.0.0-20230907193218-d3ddc7976beb/go.mod h1:czg5+yv1E0Z
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gookit/filter v1.2.0 h1:r7E01dHVkysb5WgzooiGsfblHGShEZCeGcyYM+5IpYU=
@@ -379,14 +384,12 @@ github.com/gookit/goutil v0.6.14 h1:96elyOG4BvVoDaiT7vx1vHPrVyEtFfYlPPBODR0/FGQ=
github.com/gookit/goutil v0.6.14/go.mod h1:YyDBddefmjS+mU2PDPgCcjVzTDM5WgExiDv5ZA/b8I8=
github.com/gookit/validate v1.5.1 h1:rPp64QZQJM+fysGFAhKpvekQAav4Ok6sjfTs9ZtxcpA=
github.com/gookit/validate v1.5.1/go.mod h1:SskOHUQokzMNt6T3r7N+N/4me/6fxDx+tmoXf/3ZQog=
github.com/goravel/fiber v1.1.11-0.20231108081345-36e967f101d0 h1:mEuJdUWmNXIrg5njcbOS+GucmOM2QrqAHL+ODTgDHPo=
github.com/goravel/fiber v1.1.11-0.20231108081345-36e967f101d0/go.mod h1:IOvZ0G1bojUU/Kv9DZl+/8cwGhcNr0YRhwElz0hpjTg=
github.com/goravel/file-rotatelogs v0.0.0-20211215053220-2ab31dd9575c h1:obhFK91JAhcf7s6h5sggZishm1VyGW/gBCreo+7/SwQ=
github.com/goravel/file-rotatelogs v0.0.0-20211215053220-2ab31dd9575c/go.mod h1:YSWsLXlG16u5CWFaXNZHhEQD10+NwF3xfgDV816OwLE=
github.com/goravel/file-rotatelogs/v2 v2.4.1 h1:ogkeIFcTHSBRUBpZYiyJbpul8hkVXxHPuDbOaP78O1M=
github.com/goravel/file-rotatelogs/v2 v2.4.1/go.mod h1:euk9qr52WrzM8ICs1hecFcR4CZ/ZZOPdacHfvHgbOf0=
github.com/goravel/framework v1.13.1-0.20231109083801-f2c86a7c0796 h1:4ZqUXMfPP8M10joipSdP9PGgDoVD51vZIcCPds/EyGk=
github.com/goravel/framework v1.13.1-0.20231109083801-f2c86a7c0796/go.mod h1:5jKFbJzfqhaQTP3HCgbyrpnrCyoJjxN5JWSXO96H0iQ=
github.com/goravel/fiber v1.1.11-0.20231222082732-ab5faaaee0d2 h1:qEg6rlQx6QMPevK/USi2lluMG41JpBnOOojT9r2PWY4=
github.com/goravel/fiber v1.1.11-0.20231222082732-ab5faaaee0d2/go.mod h1:xYYb8zeglHFaJAw2g6ShNTXLXIksyYe6281Aqq6wwbc=
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.20231222074402-8955968c9670 h1:NdTwBawgRq2/VihgkU3dPM4aNWW8wqIiKkI7oxfjrYo=
github.com/goravel/framework v1.13.1-0.20231222074402-8955968c9670/go.mod h1:qsNWyGlIyatBorwo+7yrHsPDe1w4WKCEQZU+hEzTiQU=
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/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
@@ -398,7 +401,6 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
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-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
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.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
@@ -414,8 +416,6 @@ github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSAS
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/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/imroc/req/v3 v3.42.1 h1:g82SouLvX7pqwqJjpQJYrVvuI+LOycWhyuwxtLlyQJk=
github.com/imroc/req/v3 v3.42.1/go.mod h1:W7dOrfQORA9nFoj+CafIZ6P5iyk+rWdbp2sffOAvABU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@@ -423,15 +423,14 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
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.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
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=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
@@ -443,10 +442,6 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
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=
@@ -460,17 +455,23 @@ github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dv
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA=
github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
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/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -481,12 +482,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/strftime v0.0.0-20180821113735-8b31f9c59b0f/go.mod h1:RMlXygAD3c48Psmr06d2G75L4E4xxzxkIe/+ppX9eAU=
github.com/lestrrat-go/strftime v1.0.5 h1:A7H3tT8DhTz8u65w+JRpiBxM4dINQhUXAZnhBa2xeOE=
github.com/lestrrat-go/strftime v1.0.5/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -499,10 +496,13 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN
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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
@@ -510,7 +510,6 @@ github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60
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/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=
@@ -518,53 +517,49 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
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=
github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew03H+Tw=
github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U=
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
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.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
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.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
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.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v1.1.10 h1:EaL5WeO9lv9wmS6SASjszOeQdSctvpbu0DdBQBizE40=
github.com/opencontainers/runc v1.1.10/go.mod h1:+/R6+KmDlh+hOO8NkjmgkG9Qzvypzk0yXxAPYYR65+M=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.16 h1:kQPfno+wyx6C5572ABwV+Uo3pDFzQ7yhyGchSyRda0c=
github.com/pierrec/lz4/v4 v4.1.16/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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=
@@ -575,6 +570,10 @@ github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNV
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE=
github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4=
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.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0=
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/refraction-networking/utls v1.5.3 h1:Ds5Ocg1+MC1ahNx5iBEcHe0jHeLaA/fLey61EENm7ro=
github.com/refraction-networking/utls v1.5.3/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -596,8 +595,8 @@ github.com/rotisserie/eris v0.5.4/go.mod h1:Z/kgYTJiJtocxCbFfvRmO+QejApzG6zpyky9
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=
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/savioxavier/termlink v1.3.0 h1:3Gl4FzQjUyiHzmoEDfmWEhgIwDiJY4poOQHP+k8ReA4=
@@ -614,16 +613,16 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
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.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
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=
@@ -651,22 +650,28 @@ github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
@@ -679,13 +684,8 @@ github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCO
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
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=
@@ -711,11 +711,6 @@ 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/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0=
go.opentelemetry.io/otel v0.17.0/go.mod h1:Oqtdxmf7UtEvL037ohlgnaYa1h7GtMh0NcSd9eqkC9s=
go.opentelemetry.io/otel/metric v0.17.0/go.mod h1:hUz9lH1rNXyEwWAhIWCMFWKhYtpASgSnObJFnU26dJ0=
go.opentelemetry.io/otel/oteltest v0.17.0/go.mod h1:JT/LGFxPwpN+nlsTiinSYjdIx3hZIGqHCpChcIZmdoE=
go.opentelemetry.io/otel/trace v0.17.0/go.mod h1:bIujpqg6ZL6xUTubIUgziI1jSaUPthmabA/ygf/6Cfg=
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=
@@ -737,15 +732,13 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
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=
@@ -756,13 +749,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -786,7 +778,9 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -822,20 +816,21 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
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=
@@ -845,9 +840,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.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
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=
@@ -862,8 +856,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -898,9 +892,7 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -910,34 +902,34 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.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.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
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.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
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=
@@ -947,18 +939,21 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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/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=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -1017,9 +1012,11 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1043,17 +1040,17 @@ 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.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.143.0 h1:o8cekTkqhywkbZT6p1UHJPZ9+9uuCAJs/KYomxZB8fA=
google.golang.org/api v0.143.0/go.mod h1:FoX9DO9hT7DLNn97OuoZAGSDuNAXdJRuGK98rSUgurk=
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/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=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -1091,13 +1088,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-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 h1:I6WNifs6pF9tNdSob2W24JtyxIYjzFB9qDlpUC76q+U=
google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4=
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k=
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
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=
@@ -1114,8 +1110,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
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=
@@ -1141,10 +1137,10 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
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 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1153,13 +1149,14 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
gorm.io/driver/sqlserver v1.5.2 h1:+o4RQ8w1ohPbADhFqDxeeZnSWjwOcBnxBckjTbcP4wk=
gorm.io/driver/sqlserver v1.5.2/go.mod h1:gaKF0MO0cfTq9Q3/XhkowSw4g6nIwHPGAs4hzKCmvBo=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
@@ -1168,9 +1165,8 @@ gorm.io/gorm v1.25.2-0.20230610234218-206613868439/go.mod h1:L4uxeKpfBml98NYqVqw
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/plugin/dbresolver v1.4.7 h1:ZwtwmJQxTx9us7o6zEHFvH1q4OeEo1pooU7efmnunJA=
gorm.io/plugin/dbresolver v1.4.7/go.mod h1:l4Cn87EHLEYuqUncpEeTC2tTJQkjngPSD+lo8hIvcT0=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
gorm.io/plugin/dbresolver v1.5.0 h1:XVHLxh775eP0CqVh3vcfJtYqja3uFl5Wr3cKlY8jgDY=
gorm.io/plugin/dbresolver v1.5.0/go.mod h1:l4Cn87EHLEYuqUncpEeTC2tTJQkjngPSD+lo8hIvcT0=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1191,3 +1187,4 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
+20
View File
@@ -0,0 +1,20 @@
package internal
import "panel/app/models"
type Backup interface {
WebsiteList() ([]BackupFile, error)
WebSiteBackup(website models.Website) error
WebsiteRestore(website models.Website, backupFile string) error
MysqlList() ([]BackupFile, error)
MysqlBackup(database string) error
MysqlRestore(database string, backupFile string) error
PostgresqlList() ([]BackupFile, error)
PostgresqlBackup(database string) error
PostgresqlRestore(database string, backupFile string) error
}
type BackupFile struct {
Name string `json:"name"`
Size string `json:"size"`
}
+28
View File
@@ -0,0 +1,28 @@
package internal
import (
"github.com/go-acme/lego/v4/certificate"
requests "panel/app/http/requests/cert"
"panel/app/models"
"panel/pkg/acme"
)
type Cert interface {
UserStore(request requests.UserStore) error
UserUpdate(request requests.UserUpdate) error
UserShow(ID uint) (models.CertUser, error)
UserDestroy(ID uint) error
DNSStore(request requests.DNSStore) error
DNSUpdate(request requests.DNSUpdate) error
DNSShow(ID uint) (models.CertDNS, error)
DNSDestroy(ID uint) error
CertStore(request requests.CertStore) error
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)
}
+8
View File
@@ -0,0 +1,8 @@
package internal
import "panel/app/models"
type Cron interface {
AddToSystem(cron models.Cron) error
DeleteFromSystem(cron models.Cron) error
}
+23
View File
@@ -0,0 +1,23 @@
package internal
import "panel/app/models"
// PanelPlugin 插件元数据结构
type PanelPlugin struct {
Name string
Description string
Slug string
Version string
Requires []string
Excludes []string
Install string
Uninstall string
Update string
}
type Plugin interface {
AllInstalled() ([]models.Plugin, error)
All() []PanelPlugin
GetBySlug(slug string) PanelPlugin
GetInstalledBySlug(slug string) models.Plugin
}
@@ -2,9 +2,9 @@ package mysql57
var (
Name = "MySQL-5.7"
Description = "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。"
Description = "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。(已停止维护,不建议使用!预计 2025 年 12 月移除)"
Slug = "mysql57"
Version = "5.7.43"
Version = "5.7.44"
Requires = []string{}
Excludes = []string{"mysql80"}
Install = `bash /www/panel/scripts/mysql/install.sh 57`
@@ -2,9 +2,9 @@ package mysql80
var (
Name = "MySQL-8.0"
Description = "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。(内存 < 4G 无法安装)"
Description = "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。(建议内存 > 2G 安装"
Slug = "mysql80"
Version = "8.0.34"
Version = "8.0.35"
Requires = []string{}
Excludes = []string{"mysql57"}
Install = `bash /www/panel/scripts/mysql/install.sh 80`
@@ -4,7 +4,7 @@ var (
Name = "OpenResty"
Description = "OpenResty® 是一款基于 NGINX 和 LuaJIT 的 Web 平台。"
Slug = "openresty"
Version = "1.21.4.2"
Version = "1.25.3.1rc1"
Requires = []string{}
Excludes = []string{}
Install = "bash /www/panel/scripts/openresty/install.sh"

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