Compare commits

...

196 Commits

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

See merge request opensource/panel!50
2024-03-22 22:18:27 +00:00
机器人 e0a401f688 chore(deps): Update module go.uber.org/zap to v1.27.0 2024-03-22 22:10:00 +00:00
耗子 f50f2f7f2b fix: lint 2024-03-23 01:11:36 +08:00
耗子 3df1388326 fix: lint 2024-03-23 01:05:56 +08:00
耗子 5390ef3140 refactor: 移除 lego 2024-03-23 00:50:59 +08:00
耗子 7cb5a3db4b feat: 发布 v2.1.43 2024-03-21 01:05:33 +08:00
耗子 082b0d2755 feat: 发布 v2.1.42 2024-03-21 00:42:23 +08:00
机器人 10ba87b63a Merge branch 'renovate/github.com-docker-go-connections-0.x' into 'main'
chore(deps): Update module github.com/docker/go-connections to v0.5.0

See merge request opensource/panel!48
2024-03-20 04:16:27 +00:00
机器人 ec4c187a11 chore(deps): Update module github.com/docker/go-connections to v0.5.0 2024-03-20 04:10:45 +00:00
机器人 2e250dbb39 Merge branch 'renovate/github.com-docker-docker-25.x' into 'main'
chore(deps): Update module github.com/docker/docker to v25.0.5+incompatible

See merge request opensource/panel!47
2024-03-19 22:16:47 +00:00
机器人 69938cf9bc chore(deps): Update module github.com/docker/docker to v25.0.5+incompatible 2024-03-19 22:09:20 +00:00
耗子 3bc3efe59c fix(容器): 正确解析卷创建时间 2024-03-20 01:41:37 +08:00
耗子 9b6c9484e4 fix(容器): 获取卷列表 panic 2024-03-20 01:33:03 +08:00
耗子 e127572300 feat(容器): 格式化返回 2024-03-17 22:12:43 +08:00
耗子 c757dd9555 fix: lint 2024-03-16 23:10:59 +08:00
耗子 97c3f4c7c7 fix: lint 2024-03-16 23:04:18 +08:00
耗子 1e8c4945d5 refactor: 统一类型结构体至 types 目录 2024-03-16 22:42:36 +08:00
耗子 90ef29acc4 feat: 完善容器管理 2024-03-16 22:37:52 +08:00
耗子 89a6c3733b fix(容器): 镜像拉取 2024-03-16 16:24:43 +08:00
耗子 26d8aacabd feat: 完善容器接口 2024-03-16 15:29:00 +08:00
耗子 e7e5d4b283 fix: panic 2024-03-16 13:02:33 +08:00
耗子 db0e722ea3 feat: 容器部分 2024-03-16 00:15:37 +08:00
耗子 ccd0aa3505 docs: 移除 codecov 2024-03-15 19:48:37 +08:00
耗子 43f19c802f fix: Swow 拓展名称问题 2024-03-15 18:00:51 +08:00
耗子 a86d8a7490 feat: 补全 PHP 拓展 2024-03-15 17:49:06 +08:00
耗子 08d906e27c feat: 添加 PHP FPM 配置的操作接口 2024-03-15 17:22:52 +08:00
耗子 70d65b2b2c feat: 不再强制更新插件 2024-03-15 17:07:55 +08:00
耗子 3de92c9603 refactor: 重构插件结构 2024-03-15 17:02:17 +08:00
耗子 deb9d9fbd2 fix(文件管理): 一堆小问题 2024-03-15 00:39:01 +08:00
耗子 71903c1100 docs: 更新赞助商信息 2024-03-14 18:57:41 +08:00
机器人 9853c1792b Merge branch 'renovate/github.com-imroc-req-v3-3.x' into 'main'
chore(deps): Update module github.com/imroc/req/v3 to v3.43.1

See merge request opensource/panel!46
2024-03-12 10:20:27 +00:00
机器人 18f0257ff2 chore(deps): Update module github.com/imroc/req/v3 to v3.43.1 2024-03-12 10:08:20 +00:00
机器人 0754fcaf6a Merge branch 'renovate/github.com-go-acme-lego-v4-4.x' into 'main'
chore(deps): Update module github.com/go-acme/lego/v4 to v4.16.1

See merge request opensource/panel!45
2024-03-11 04:14:59 +00:00
机器人 ea7bb34be7 chore(deps): Update module github.com/go-acme/lego/v4 to v4.16.1 2024-03-11 04:07:24 +00:00
机器人 383b1652cf Merge branch 'renovate/github.com-go-acme-lego-v4-4.x' into 'main'
chore(deps): Update module github.com/go-acme/lego/v4 to v4.16.0

See merge request opensource/panel!44
2024-03-10 04:15:14 +00:00
机器人 ca36c7c62a chore(deps): Update module github.com/go-acme/lego/v4 to v4.16.0 2024-03-10 04:07:27 +00:00
机器人 c42fc0c70d Merge branch 'renovate/github.com-docker-docker-25.x' into 'main'
chore(deps): Update module github.com/docker/docker to v25.0.4+incompatible

See merge request opensource/panel!43
2024-03-07 16:17:58 +00:00
机器人 a89f5e3ca3 chore(deps): Update module github.com/docker/docker to v25.0.4+incompatible 2024-03-07 16:11:50 +00:00
机器人 64537e1c75 Merge branch 'renovate/github.com-imroc-req-v3-3.x' into 'main'
chore(deps): Update module github.com/imroc/req/v3 to v3.43.0

See merge request opensource/panel!42
2024-03-06 16:21:17 +00:00
机器人 19a57f9951 chore(deps): Update module github.com/imroc/req/v3 to v3.43.0 2024-03-06 16:10:05 +00:00
耗子 95a90db230 feat: 发布 v2.0.41 2024-03-06 03:42:41 +08:00
耗子 5dcfe4126c fix: 修正部分接口未跟随更新状态码 2024-03-06 03:42:15 +08:00
耗子 d3e758f61a fix: 修正 PostgreSQL CLI 下备份提示数据库不存在 2024-03-06 03:38:17 +08:00
耗子 d921939186 feat: 发布 v2.1.40 2024-03-06 03:26:55 +08:00
耗子 b0a5221aed fix: PHP 版本字符串 2024-03-06 03:17:32 +08:00
耗子 dd4db4899d feat: 发布 v2.1.39 2024-03-06 02:38:32 +08:00
耗子 7f0b7b349a feat: 发布 v2.1.39 2024-03-06 02:22:12 +08:00
耗子 2a483bc9cd Merge remote-tracking branch 'origin/main' 2024-03-06 01:27:40 +08:00
耗子 731a1f13a9 feat: 更新一批软件 2024-03-06 01:27:30 +08:00
耗子 95025267aa feat: PHP 8.2 支持 ionCube 2024-03-06 01:04:16 +08:00
机器人 4dd1bd3d46 Merge branch 'renovate/golang.org-x-crypto-0.x' into 'main'
chore(deps): Update module golang.org/x/crypto to v0.21.0

See merge request opensource/panel!41
2024-03-04 22:22:36 +00:00
机器人 633fa61738 chore(deps): Update module golang.org/x/crypto to v0.21.0 2024-03-04 22:11:00 +00:00
耗子 35c998f402 fix: 签发证书不强制 OCSP 装订 2024-03-04 18:34:23 +08:00
机器人 1a82279304 Merge branch 'renovate/github.com-stretchr-testify-1.x' into 'main'
chore(deps): Update module github.com/stretchr/testify to v1.9.0

See merge request opensource/panel!40
2024-03-01 16:21:47 +00:00
机器人 ac4380ef14 chore(deps): Update module github.com/stretchr/testify to v1.9.0 2024-03-01 16:07:42 +00:00
机器人 6b8228a048 Merge branch 'renovate/golang.org-x-crypto-0.x' into 'main'
chore(deps): Update module golang.org/x/crypto to v0.20.0

See merge request opensource/panel!39
2024-02-26 22:17:50 +00:00
机器人 67927eef06 chore(deps): Update module golang.org/x/crypto to v0.20.0 2024-02-26 22:09:26 +00:00
耗子 429a027c79 fix: 磁盘统计信息顺序蹦迪的问题 2024-02-26 16:30:25 +08:00
耗子 284f6fb953 fix: 软件安装日志兼容各种奇葩情况 2024-02-24 00:54:59 +08:00
耗子 8194028a76 fix: lint 2024-02-23 01:57:01 +08:00
耗子 c173f57c6d fix: 在不同的文件系统中移动文件,os.Rename 可能会失败 2024-02-23 01:56:37 +08:00
耗子 639159f150 fix: 修复添加数据库备份数据库名称未正确填充 2024-02-23 01:46:46 +08:00
耗子 89bd5d4b36 feat: 更新 Go 1.22 2024-02-19 23:20:43 +08:00
机器人 165424b4b2 Merge branch 'renovate/golang-1.x' into 'main'
chore(deps): Update golang Docker tag to v1.22

See merge request opensource/panel!38
2024-02-17 10:15:07 +00:00
机器人 d519ede9e9 chore(deps): Update golang Docker tag to v1.22 2024-02-17 10:04:52 +00:00
耗子 ebe2700cfd feat: 同步框架更新 2024-02-17 17:08:17 +08:00
耗子 bf56151db5 fix: PHP 错误日志及慢日志文件不存在的时候会报错 2024-02-17 15:58:11 +08:00
耗子 53e13d1257 feat: 暂时固定 Go 1.21 2024-02-17 15:53:04 +08:00
耗子 2732a476fd feat: 移除验证码测试 2024-02-17 15:47:31 +08:00
耗子 e8b7128f7a feat: 规范错误返回 2024-02-17 15:34:35 +08:00
耗子 ec9bd0ebb3 Merge branch 'renovate/golangci-golangci-lint-action-4.x' into 'main'
chore(deps): Update golangci/golangci-lint-action action to v4

See merge request opensource/panel!37
2024-02-14 20:50:08 +00:00
耗子 e1da619460 Merge branch 'renovate/codecov-codecov-action-4.x' into 'main'
chore(deps): Update codecov/codecov-action action to v4

See merge request opensource/panel!34
2024-02-14 20:50:01 +00:00
机器人 a90b800ede chore(deps): Update golangci/golangci-lint-action action to v4 2024-02-09 22:09:35 +00:00
机器人 d64403f49a Merge branch 'renovate/golang.org-x-crypto-0.x' into 'main'
chore(deps): Update module golang.org/x/crypto to v0.19.0

See merge request opensource/panel!36
2024-02-07 22:19:31 +00:00
机器人 527ef39a92 chore(deps): Update module golang.org/x/crypto to v0.19.0 2024-02-07 22:11:49 +00:00
机器人 82d2fab895 Merge branch 'renovate/github.com-docker-docker-25.x' into 'main'
chore(deps): Update module github.com/docker/docker to v25.0.3+incompatible

See merge request opensource/panel!35
2024-02-07 04:18:07 +00:00
机器人 bdf33163f0 chore(deps): Update module github.com/docker/docker to v25.0.3+incompatible 2024-02-07 04:10:36 +00:00
机器人 705620ab13 chore(deps): Update codecov/codecov-action action to v4 2024-02-02 22:08:46 +00:00
机器人 6e2d3eed06 Merge branch 'renovate/github.com-swaggo-swag-1.x' into 'main'
chore(deps): Update module github.com/swaggo/swag to v1.16.3

See merge request opensource/panel!33
2024-02-02 18:15:14 +00:00
机器人 053de1b848 Merge branch 'renovate/github.com-docker-docker-25.x' into 'main'
chore(deps): Update module github.com/docker/docker to v25.0.2+incompatible

See merge request opensource/panel!32
2024-02-02 18:09:52 +00:00
机器人 721d579512 chore(deps): Update module github.com/swaggo/swag to v1.16.3 2024-02-02 17:57:06 +00:00
机器人 8b8fbaa3ec chore(deps): Update module github.com/docker/docker to v25.0.2+incompatible 2024-02-02 17:55:40 +00:00
机器人 4a8a4dbcde Merge branch 'renovate/github.com-go-acme-lego-v4-4.x' into 'main'
chore(deps): Update module github.com/go-acme/lego/v4 to v4.15.0

See merge request opensource/panel!31
2024-01-28 22:21:18 +00:00
机器人 cd241f88af chore(deps): Update module github.com/go-acme/lego/v4 to v4.15.0 2024-01-28 22:09:27 +00:00
机器人 064bf7e1ed Merge branch 'renovate/github.com-gookit-validate-1.x' into 'main'
chore(deps): Update module github.com/gookit/validate to v1.5.2

See merge request opensource/panel!30
2024-01-24 16:20:39 +00:00
机器人 c1aa6e2e17 Merge branch 'renovate/github.com-docker-docker-25.x' into 'main'
chore(deps): Update module github.com/docker/docker to v25.0.1+incompatible

See merge request opensource/panel!29
2024-01-24 16:19:35 +00:00
机器人 246e53c519 chore(deps): Update module github.com/gookit/validate to v1.5.2 2024-01-24 16:09:20 +00:00
机器人 0810eee37a chore(deps): Update module github.com/docker/docker to v25.0.1+incompatible 2024-01-24 16:08:37 +00:00
耗子 c003b11ff3 chore: 更新依赖 2024-01-24 13:34:53 +08:00
耗子 d39743cdb9 chore: 调整依赖机器人配置 2024-01-24 13:18:10 +08:00
耗子 94751d474f feat(容器): 支持自定义 sock 2024-01-24 02:54:50 +08:00
耗子 63033dfa15 fix: 文档错误 2024-01-24 02:50:15 +08:00
耗子 8dc41d4222 feat: podman 初步支持 2024-01-24 02:48:56 +08:00
耗子 396fb1207e chore: update dependence 2024-01-16 16:25:51 +08:00
耗子 0ce8b15aca refactor: fallback to gin 2024-01-16 16:07:41 +08:00
耗子 55fc516049 feat(系统监控): 优化代码 2024-01-13 02:27:51 +08:00
耗子 a9c19115d4 feat(系统监控): 去除部分数据 2024-01-13 02:19:33 +08:00
耗子 9375860203 feat: 发布 v2.1.38 2024-01-13 00:19:15 +08:00
耗子 52aaadb2f9 feat: 为 TheTNB 做准备 2024-01-12 23:39:43 +08:00
耗子 c7ad1a1435 feat: 自动开启 BBR 2024-01-12 22:45:20 +08:00
耗子 c0db1cf993 feat: 添加面板每日任务 2024-01-12 22:14:10 +08:00
耗子 bad23197c1 feat: 发布 v2.1.37 2024-01-12 00:48:29 +08:00
耗子 f6dc38e61b docs: 更新 README 2024-01-12 00:48:03 +08:00
耗子 40ce8a2aad feat: 更新 OpenResty 1.25.3.1 2024-01-12 00:33:45 +08:00
耗子 999fda846d feat: 优化文案 2024-01-11 22:30:35 +08:00
耗子 958369b5ce feat: 优化文案 2024-01-11 22:29:45 +08:00
耗子 dc86e2efa3 feat: x86 下阻止在不支持 v2 指令集的 CPU 上安装 2024-01-11 22:29:09 +08:00
耗子 bb23be565a feat: 发布 v2.1.36 2024-01-10 13:57:00 +08:00
耗子 2a40cb11f7 fix: 优化部分软件 systemd 配置 2024-01-06 20:02:56 +08:00
耗子 4da6969636 fix: 安装面板生成 SSL 证书 2024-01-06 11:17:52 +08:00
耗子 bb5dd8a835 ci: optimize 2024-01-06 01:39:58 +08:00
耗子 7af5ab3c30 feat: 规范缩进 2023-12-30 18:21:37 +08:00
耗子 6a328c3a42 feat: 默认不开启 WAF 2023-12-24 15:11:04 +08:00
耗子 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
197 changed files with 14652 additions and 4653 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# These are supported funding model platforms
open_collective: haozi-panel
custom: ['https://afdian.net/a/haozi-team']
custom: ['https://afdian.net/a/TheTNB']
+4 -4
View File
@@ -13,10 +13,10 @@ 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'
go-version: '1.22'
- name: Install dependencies
run: go mod tidy
- name: Build ${{ matrix.goarch }}
@@ -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 }}
+3 -3
View File
@@ -9,15 +9,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: 'stable'
go-version: '1.22'
- name: Install dependencies
run: go mod tidy
- name: Run tests with coverage
run: go test -v -coverprofile="coverage.out" ./...
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
file: ./coverage.out
token: ${{ secrets.CODECOV }}
+3 -3
View File
@@ -14,13 +14,13 @@ jobs:
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: 'stable'
go-version: '1.22'
- name: Fetch Frontend
run: |
sudo apt-get install -y curl jq unzip zip
curl -sSL https://api.github.com/repos/haozi-team/panel-frontend/releases/latest | jq -r ".assets[] | select(.name | contains(\"dist\")) | .browser_download_url" | xargs curl -L -o frontend.zip
curl -sSL https://api.github.com/repos/TheTNB/panel-frontend/releases/latest | jq -r ".assets[] | select(.name | contains(\"dist\")) | .browser_download_url" | xargs curl -L -o frontend.zip
rm -rf public
unzip frontend.zip
mv dist public
+2 -2
View File
@@ -27,7 +27,7 @@ jobs:
我们认为您的建议非常有价值!欢迎提交 PR,请包含相应的测试用例、文档等,并确保 CI 通过,感谢和期待您的贡献!
We think your suggestion is very valuable! Welcome to submit a PR, please include test cases, documentation, etc., and ensure that the CI is passed, thank you and look forward to your contribution!
![aoligei](https://github.com/haozi-team/panel/assets/115467771/fb04debf-3f4c-4fac-a0b8-c3455f8e57a0)
![aoligei](https://github.com/TheTNB/panel/assets/115467771/fb04debf-3f4c-4fac-a0b8-c3455f8e57a0)
- name: ☢️ Bug
if: github.event.label.name == '☢️ Bug'
uses: actions-cool/issues-helper@v3
@@ -41,4 +41,4 @@ jobs:
我们认为您的反馈非常有价值!欢迎提交 PR,请包含相应的测试用例、文档等,并确保 CI 通过,感谢和期待您的贡献!
We think your feedback is very valuable! Welcome to submit a PR, please include test cases, documentation, etc., and ensure that the CI is passed, thank you and look forward to your contribution!
![aoligei](https://github.com/haozi-team/panel/assets/115467771/fb04debf-3f4c-4fac-a0b8-c3455f8e57a0)
![aoligei](https://github.com/TheTNB/panel/assets/115467771/fb04debf-3f4c-4fac-a0b8-c3455f8e57a0)
+3 -3
View File
@@ -12,12 +12,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: 'stable'
go-version: '1.22'
cache: false
- name: Lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v4
with:
skip-cache: true
skip-pkg-cache: true
+3 -3
View File
@@ -9,15 +9,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: 'stable'
go-version: '1.22'
- name: Install dependencies
run: sudo apt-get install -y curl jq
- name: Set up environment
run: |
cp panel-example.conf .env
echo "DB_FILE=$(pwd)/database/panel.db" >> .env
echo "DB_FILE=$(pwd)/storage/panel.db" >> .env
go run . artisan key:generate
go run . artisan migrate
- name: Run tests
+8 -14
View File
@@ -1,4 +1,4 @@
image: golang:bookworm
image: golang:1.22-bookworm
# 在每个任务执行前运行
before_script:
@@ -14,12 +14,6 @@ before_script:
paths:
- .go/pkg/mod/
# 全局变量
variables:
OUTPUT_NAME: "panel"
GO111MODULE: "on"
GOPROXY: "https://goproxy.cn,direct"
stages:
- prepare
- build
@@ -44,7 +38,7 @@ unit_test:
- apt-get update
- apt-get install -y curl jq
- cp panel-example.conf .env
- echo "DB_FILE=$(pwd)/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,21 +48,21 @@ 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 panel
artifacts:
name: "$OUTPUT_NAME"
name: "panel"
paths:
- $OUTPUT_NAME
- panel
expire_in: 3 days
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
@@ -84,7 +78,7 @@ release:
- build
- fetch
image:
name: goreleaser/goreleaser
name: goreleaser/goreleaser:nightly
entrypoint: [ '' ]
only:
- tags
+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
+1 -1
View File
@@ -1,6 +1,6 @@
## 行为准则
耗子Linux面板遵守业界通用的行为准则。任何违反行为准则的行为都可以报告给我们:
耗子 Linux 面板遵守业界通用的行为准则。任何违反行为准则的行为都可以报告给我们:
- 参与者将容忍反对意见。
- 参与者必须确保他们的语言和行为没有人身攻击和贬低个人言论。
+28 -37
View File
@@ -5,12 +5,11 @@
<h1 align="center">耗子 Linux 面板</h1>
<p align="center">
<a href="https://github.com/haozi-team/panel/releases"><img src="https://img.shields.io/github/release/haozi-team/panel.svg"></a>
<a href="https://github.com/haozi-team/panel/actions"><img src="https://github.com/haozi-team/panel/actions/workflows/test.yml/badge.svg"></a>
<a href="https://goreportcard.com/report/github.com/haozi-team/panel"><img src="https://goreportcard.com/badge/github.com/haozi-team/panel"></a>
<a href="https://codecov.io/gh/haozi-team/panel"><img src="https://codecov.io/gh/haozi-team/panel/branch/main/graph/badge.svg?token=XFT5NGNSRG"></a>
<a href="https://img.shields.io/github/license/haozi-team/panel"><img src="https://img.shields.io/github/license/haozi-team/panel"></a>
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2Fhaozi-team%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2Fhaozi-team%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
<a href="https://github.com/TheTNB/panel/actions"><img src="https://github.com/TheTNB/panel/actions/workflows/test.yml/badge.svg"></a>
<a href="https://goreportcard.com/report/github.com/TheTNB/panel"><img src="https://goreportcard.com/badge/github.com/TheTNB/panel"></a>
<a href="https://img.shields.io/github/license/TheTNB/panel"><img src="https://img.shields.io/github/license/TheTNB/panel"></a>
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FTheTNB%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FTheTNB%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
</p>
耗子 Linux 面板是使用 Golang + Vue 开发的轻量 Linux 服务器运维管理面板,以 GNU Affero General Public License v3.0 开源。
@@ -20,10 +19,12 @@
## 优势
1. **极低占用:** 在 Debian 12 下部署面板 + LNMP 环境,内存占用不到 500 MB,遥遥领先于使用容器化的其他面板。
2. **高效运维:** 面板 UI 界面简洁,操作简单,无需繁琐的配置,即可快速部署各类环境、调整应用设置
3. **离线运行:** 面板运行可不依赖任何外部服务,你甚至可以在部署完成后停止面板进程,不会对已部署服务造成任何影响
4. **久经考验:** 我们生产环境自 2022 年即开始使用,已稳定运行 1 年无事故
5. **开源开放:** 面板开源,你可以自由修改、审计面板源码,安全性有保障
2. **低破坏性:** 面板的设计理念是尽可能减少对系统的额外修改,在同类面板中,我们对系统的修改最少
3. **追随时代:** 面板所有组件均走在时代前沿,更新快,功能强大,安全性有保障
4. **高效运维:** 面板 UI 界面简洁,操作简单,无需繁琐的配置,即可快速部署各类环境、调整应用设置
5. **离线运行:** 面板运行可不依赖任何外部服务,你甚至可以在部署完成后停止面板进程,不会对已部署服务造成任何影响
6. **久经考验:** 我们生产环境自 2022 年即开始使用,已稳定运行 1 年以上无事故。
7. **开源开放:** 面板开源,你可以自由修改、审计面板源码,安全性有保障。
## UI 截图
@@ -31,13 +32,13 @@
## 运行环境
耗子Linux面板仅支持 `amd64` | `arm64` 架构下的主流系统的最新版本,不支持 `Ubuntu`,因为其发版太过频繁,难以维护。
耗子 Linux 面板仅支持 `amd64` | `arm64` 架构下的主流系统的最新版本,不支持 `Ubuntu`,因为其发版太过频繁,难以维护。
低配机器建议使用 `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)
CentOS Stream 可使用迁移脚本迁移至支持的系统: [CentOS 8/9 迁移脚本](https://github.com/TheTNB/byecentos)
| 系统 | 版本 |
|------------|----|
@@ -51,12 +52,12 @@ CentOS Stream 可使用迁移脚本迁移至支持的系统: [CentOS 8/9 迁移
## 安装面板
> **Warning**
> 安装面板前,你需要了解LNMP环境的基本知识,以及如何处理常见的LNMP环境问题,不建议0基础的用户安装和使用耗子Linux面板。
> 安装面板前,你需要了解 LNMP 环境的基本知识,以及如何处理常见的 LNMP 环境问题,不建议 0 基础的用户安装和使用耗子 Linux 面板。
如果你决定继续,请以`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 +67,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 验证失败,文件可能被篡改,已终止操作"
```
卸载面板前请务必备份好所有数据,提前卸载面板全部插件。卸载后数据将**无法恢复**!
@@ -79,38 +80,28 @@ HAOZI_DL_URL="https://jihulab.com/haozi-team/download/-/raw/main/panel"; curl -s
panel
```
在 [Wiki](https://github.com/haozi-team/panel/wiki) 中查看更多使用方法和技巧。
在 [Wiki](https://github.com/TheTNB/panel/wiki) 中查看更多使用方法和技巧。
## 问题反馈
使用类问题,可在 [WePublish 社区论坛](https://wepublish.cn/forums) 提问或QQ群`@坤坤`寻求 AI 帮助,亦可在群里寻求付费支持。
使用类问题,可在 [鼠片社区](https://forum.haozi.net) 提问或QQ群`@坤坤`寻求 AI 帮助,亦可在群里寻求付费支持。
面板自身问题,可在 GitHub 的`Issues`页面提交问题反馈,注意[提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)。
## 贡献代码
### 寻找/创建 Issue
您可以在 [Issue 列表](https://github.com/haozi-team/panel/issues) 中寻找或创建一个 Issue,留言表达想要处理该 Issue 的意愿,得到维护者的确认后,即可开始处理。
### 创建 PR
- 在开发过程中,如果遇到问题可以随时在 Issue 中详尽描述该问题,以进一步沟通,但在此之前请确保自己已通过 Google 等方式尽可能的尝试解决问题;
- PR 须提交至我们的极狐 GitLab 仓库[https://jihulab.com/haozi-team/panel](https://jihulab.com/haozi-team/panel),勿在 GitHub 上提交;
- 当 PR 开发完毕后,请为其添加 `🚀 Review Ready` 标签,维护者将及时进行评审;
- 我们非常欢迎您的贡献,将在下次发版时将您添加到首页贡献者中;❤️
## 赞助商
### 资金
- [微晓朵](https://www.weixiaoduo.com/)
### CDN
- [无畏云加速](https://su.sctes.com/register?code=8st689ujpmm2p)
- [盾云CDN](http://cdn.ddunyun.com/)
### DevOps
- [极狐 GitLab](https://www.jihulab.com/)
- [JetBrains](https://www.jetbrains.com/)
**接受云资源和资金赞助,可通过QQ群咨询联系**
@@ -122,11 +113,11 @@ panel
## Star 历史
<a href="https://star-history.com/#haozi-team/panel&Date">
<a href="https://star-history.com/#TheTNB/panel&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=haozi-team/panel&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=haozi-team/panel&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=haozi-team/panel&type=Date" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
</picture>
</a>
+26 -35
View File
@@ -5,12 +5,11 @@
<h1 align="center">HaoZi Linux Panel</h1>
<p align="center">
<a href="https://github.com/haozi-team/panel/releases"><img src="https://img.shields.io/github/release/haozi-team/panel.svg"></a>
<a href="https://github.com/haozi-team/panel/actions"><img src="https://github.com/haozi-team/panel/actions/workflows/test.yml/badge.svg"></a>
<a href="https://goreportcard.com/report/github.com/haozi-team/panel"><img src="https://goreportcard.com/badge/github.com/haozi-team/panel"></a>
<a href="https://codecov.io/gh/haozi-team/panel"><img src="https://codecov.io/gh/haozi-team/panel/branch/main/graph/badge.svg?token=XFT5NGNSRG"></a>
<a href="https://img.shields.io/github/license/haozi-team/panel"><img src="https://img.shields.io/github/license/haozi-team/panel"></a>
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2Fhaozi-team%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2Fhaozi-team%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
<a href="https://github.com/TheTNB/panel/releases"><img src="https://img.shields.io/github/release/TheTNB/panel.svg"></a>
<a href="https://github.com/TheTNB/panel/actions"><img src="https://github.com/TheTNB/panel/actions/workflows/test.yml/badge.svg"></a>
<a href="https://goreportcard.com/report/github.com/TheTNB/panel"><img src="https://goreportcard.com/badge/github.com/TheTNB/panel"></a>
<a href="https://img.shields.io/github/license/TheTNB/panel"><img src="https://img.shields.io/github/license/TheTNB/panel"></a>
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2FTheTNB%2Fpanel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2FTheTNB%2Fpanel.svg?type=shield" alt="FOSSA Status"></a>
</p>
The HaoZi Linux Panel is a lightweight Linux server operation and maintenance management panel developed using Golang and Vue. It is open source under the GNU Affero General Public License v3.0 protocol.
@@ -20,10 +19,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 more than 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,9 +36,9 @@ 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)
CentOS Stream can be migrated to a supported system using the migration script: [CentOS 8/9 Migration Script](https://github.com/TheTNB/byecentos)
| OS | Version |
|------------|---------|
@@ -56,7 +57,7 @@ As system versions are constantly updated, we may also terminate support for som
If you decide to continue, please log in to the server as `root` user and execute the following command to install the panel:
```shell
HAOZI_DL_URL="https://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 +67,7 @@ Recommended to back up data and reinstall the system first, so that the system c
If you are unable to reinstall the system, log in to the server as the `root` user and execute the following command to uninstall the panel:
```shell
HAOZI_DL_URL="https://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!
@@ -79,38 +80,28 @@ Use `panel` command for daily maintenance:
panel
```
See more usage methods and tips in [Wiki](https://github.com/haozi-team/panel/wiki).
See more usage methods and tips in [Wiki](https://github.com/TheTNB/panel/wiki).
## Feedback
For usage issues, you can ask questions in the [WePublish Community Forum](https://wepublish.cn/forums) or seek AI help in the QQ group `@坤坤`. You can also seek paid support in the group.
For usage issues, you can ask questions in the [HaoZi Community Forum](https://forum.haozi.net) or seek AI help in the QQ group `@坤坤`. You can also seek paid support in the group.
For issues with the panel itself, you can submit feedback on the `Issues` page of GitHub. Please note [the wisdom of asking questions](http://www.catb.org/~esr/faqs/smart-questions.html).
## Contribute code
### Find/Create Issue
You can find or create an Issue in [Issue List](https://github.com/haozi-team/panel/issues), leave a message to express your willingness to deal with the Issue, and get confirmation from the maintainer. Start processing.
### Create PR
- During the development process, if you encounter a problem, you can describe the problem in detail in the Issue at any time for further communication, but before doing so, please make sure that you have tried your best to solve the problem through Google and other methods;
- PRs must be submitted to our JiHu GitLab repository [https://jihulab.com/haozi-team/panel](https://jihulab.com/haozi-team/panel). Do not submit on GitHub;
- When the PR is completed, please add the `🚀 Review Ready` tag to it, and the maintainer will review it in time;
- We very much welcome your contributions and will add you to the homepage contributors in the next release; ❤️
## Sponsor
### Funding
- [微晓朵](https://www.weixiaoduo.com/)
### CDN
- [无畏云加速](https://su.sctes.com/register?code=8st689ujpmm2p)
- [盾云CDN](http://cdn.ddunyun.com/)
### DevOps
- [JiHu GitLab](https://www.jihulab.com/)
- [JetBrains](https://www.jetbrains.com/)
**Accept cloud resources and financial sponsorship, you can contact us through QQ group**
@@ -122,11 +113,11 @@ This project owes its existence to all those who have contributed. To contribute
## Star History
<a href="https://star-history.com/#haozi-team/panel&Date">
<a href="https://star-history.com/#TheTNB/panel&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=haozi-team/panel&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=haozi-team/panel&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=haozi-team/panel&type=Date" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=TheTNB/panel&type=Date" />
</picture>
</a>
+2 -2
View File
@@ -1,8 +1,8 @@
## 安全说明
安全性是我们最关心的问题之一,我们已在多个不同应用的生产环境广泛应用耗子Linux面板,至今无一例安全事故。
安全性是我们最关心的问题之一,我们已在多个不同应用的生产环境广泛应用耗子 Linux 面板,至今无一例安全事故。
耗子Linux面板采用业界多种方案尽可能保证面板的安全性,但是我们不能保证面板的绝对安全性,**因此我们不对面板的安全性做任何保证**。
耗子 Linux 面板采用业界多种方案尽可能保证面板的安全性,但是我们不能保证面板的绝对安全性,**因此我们不对面板的安全性做任何保证**。
如果您在使用面板的过程中发现任何安全问题,请勿提交 Issue,可通过以下方式直接联系我们:
+7 -1
View File
@@ -10,9 +10,11 @@ import (
"github.com/goravel/framework/support/carbon"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
)
// CertRenew 证书续签
type CertRenew struct {
}
@@ -35,6 +37,10 @@ func (receiver *CertRenew) Extend() command.Extend {
// Handle Execute the console command.
func (receiver *CertRenew) Handle(ctx console.Context) error {
if internal.Status != internal.StatusNormal {
return nil
}
var certs []models.Cert
err := facades.Orm().Query().With("Website").With("User").With("DNS").Find(&certs)
if err != nil {
+27 -11
View File
@@ -11,10 +11,12 @@ import (
"github.com/spf13/cast"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
// Monitoring 系统监控
type Monitoring struct {
}
@@ -37,14 +39,32 @@ func (receiver *Monitoring) Extend() command.Extend {
// Handle Execute the console command.
func (receiver *Monitoring) Handle(ctx console.Context) error {
var setting models.Setting
monitor := services.NewSettingImpl().Get(models.SettingKeyMonitor)
if internal.Status != internal.StatusNormal {
return nil
}
setting := services.NewSettingImpl()
monitor := setting.Get(models.SettingKeyMonitor)
if !cast.ToBool(monitor) {
return nil
}
info := tools.GetMonitoringInfo()
// 去除部分数据以减少数据库存储
info.Disk = nil
for _, cpu := range info.Cpus {
cpu.VendorID = ""
cpu.Family = ""
cpu.Model = ""
cpu.PhysicalID = ""
cpu.ModelName = ""
cpu.Flags = nil
}
if internal.Status != internal.StatusNormal {
return nil
}
err := facades.Orm().Query().Create(&models.Monitor{
Info: info,
})
@@ -55,20 +75,16 @@ func (receiver *Monitoring) Handle(ctx console.Context) error {
}
// 删除过期数据
err = facades.Orm().Query().Where("key", "monitor_days").First(&setting)
monitorDays := setting.Get(models.SettingKeyMonitorDays)
days, err := strconv.Atoi(monitorDays)
if err != nil {
return nil
}
if setting.Value == "0" || len(setting.Value) == 0 {
return nil
}
days, err := strconv.Atoi(setting.Value)
if err != nil {
if days <= 0 || internal.Status != internal.StatusNormal {
return nil
}
_, err = facades.Orm().Query().Where("created_at < ?", carbon.Now().SubDays(days).ToDateTimeString()).Delete(&models.Monitor{})
if err != nil {
if _, err = facades.Orm().Query().Where("created_at < ?", carbon.Now().SubDays(days).ToDateTimeString()).Delete(&models.Monitor{}); err != nil {
facades.Log().Infof("[面板] 系统监控删除过期数据失败: %s", err.Error())
return nil
}
+150 -8
View File
@@ -15,10 +15,12 @@ import (
"github.com/spf13/cast"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
)
// Panel 面板命令行
type Panel struct {
}
@@ -46,6 +48,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
arg2 := ctx.Argument(2)
arg3 := ctx.Argument(3)
arg4 := ctx.Argument(4)
arg5 := ctx.Argument(5)
switch action {
case "init":
@@ -56,7 +59,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
settings := []models.Setting{{Key: models.SettingKeyName, Value: "耗子Linux面板"}, {Key: models.SettingKeyMonitor, Value: "1"}, {Key: models.SettingKeyMonitorDays, Value: "30"}, {Key: models.SettingKeyBackupPath, Value: "/www/backup"}, {Key: models.SettingKeyWebsitePath, Value: "/www/wwwroot"}, {Key: models.SettingKeyVersion, Value: facades.Config().GetString("panel.version")}}
settings := []models.Setting{{Key: models.SettingKeyName, Value: "耗子 Linux 面板"}, {Key: models.SettingKeyMonitor, Value: "1"}, {Key: models.SettingKeyMonitorDays, Value: "30"}, {Key: models.SettingKeyBackupPath, Value: "/www/backup"}, {Key: models.SettingKeyWebsitePath, Value: "/www/wwwroot"}, {Key: models.SettingKeyVersion, Value: facades.Config().GetString("panel.version")}}
err = facades.Orm().Query().Create(&settings)
if err != nil {
color.Redln("初始化失败")
@@ -92,12 +95,14 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return err
}
err = tools.UpdatePanel(panel)
if err != nil {
internal.Status = internal.StatusUpgrade
if err = tools.UpdatePanel(panel); err != nil {
internal.Status = internal.StatusFailed
color.Redln("更新失败: " + err.Error())
return nil
}
internal.Status = internal.StatusNormal
color.Greenln("更新成功")
tools.RestartPanel()
@@ -115,9 +120,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 {
@@ -130,11 +137,20 @@ func (receiver *Panel) Handle(ctx console.Context) error {
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, err := tools.Exec(`cat /www/panel/panel.conf | grep APP_PORT | awk -F '=' '{print $2}' | tr -d '\n'`)
@@ -312,7 +328,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Greenln(hr)
return nil
}
if strings.Contains(check, name) {
if !strings.Contains(check, name) {
color.Redln("|-数据库不存在")
color.Greenln(hr)
return nil
@@ -450,8 +466,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("参数错误")
@@ -549,6 +565,127 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Greenln("删除设置成功")
case "addSite":
name := arg1
domain := arg2
port := arg3
path := arg4
php := arg5
if len(name) == 0 || len(domain) == 0 || len(port) == 0 || len(path) == 0 {
color.Redln("参数错误")
return nil
}
domains := strings.Split(domain, ",")
ports := strings.Split(port, ",")
if len(domains) == 0 || len(ports) == 0 {
color.Redln("参数错误")
return nil
}
var uintPorts []uint
for _, p := range ports {
uintPorts = append(uintPorts, cast.ToUint(p))
}
website := services.NewWebsiteImpl()
id, err := website.GetIDByName(name)
if err != nil {
color.Redln(err.Error())
return nil
}
if id != 0 {
color.Redln("网站名已存在")
return nil
}
_, err = website.Add(internal.PanelWebsite{
Name: name,
Status: true,
Domains: domains,
Ports: uintPorts,
Path: path,
Php: php,
Ssl: false,
Db: false,
})
if err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln("网站添加成功")
case "removeSite":
name := arg1
if len(name) == 0 {
color.Redln("参数错误")
return nil
}
website := services.NewWebsiteImpl()
id, err := website.GetIDByName(name)
if err != nil {
color.Redln(err.Error())
return nil
}
if id == 0 {
color.Redln("网站名不存在")
return nil
}
if err = website.Delete(id); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln("网站删除成功")
case "installPlugin":
slug := arg1
if len(slug) == 0 {
color.Redln("参数错误")
return nil
}
plugin := services.NewPluginImpl()
if err := plugin.Install(slug); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln("任务已提交")
case "uninstallPlugin":
slug := arg1
if len(slug) == 0 {
color.Redln("参数错误")
return nil
}
plugin := services.NewPluginImpl()
if err := plugin.Uninstall(slug); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln("任务已提交")
case "updatePlugin":
slug := arg1
if len(slug) == 0 {
color.Redln("参数错误")
return nil
}
plugin := services.NewPluginImpl()
if err := plugin.Update(slug); err != nil {
color.Redln(err.Error())
return nil
}
color.Greenln("任务已提交")
default:
color.Yellowln(facades.Config().GetString("panel.name") + "命令行工具 - " + facades.Config().GetString("panel.version"))
color.Greenln("请使用以下命令:")
@@ -560,6 +697,11 @@ func (receiver *Panel) Handle(ctx console.Context) error {
color.Greenln("panel cleanTask 清理面板运行中和等待中的任务[任务卡住时使用]")
color.Greenln("panel backup {website/mysql/postgresql} {name} {path} {save_copies} 备份网站 / MySQL数据库 / PostgreSQL数据库到指定目录并保留指定数量")
color.Greenln("panel cutoff {website_name} {save_copies} 切割网站日志并保留指定数量")
color.Greenln("panel installPlugin {slug} 安装插件")
color.Greenln("panel uninstallPlugin {slug} 卸载插件")
color.Greenln("panel updatePlugin {slug} 更新插件")
color.Greenln("panel addSite {name} {domain} {port} {path} {php} 添加网站[域名和端口用英文逗号分隔]")
color.Greenln("panel removeSite {name} 删除网站")
color.Redln("以下命令请在开发者指导下使用:")
color.Yellowln("panel init 初始化面板")
color.Yellowln("panel writePlugin {slug} {version} 写入插件安装状态")
+67
View File
@@ -0,0 +1,67 @@
package commands
import (
"github.com/goravel/framework/contracts/console"
"github.com/goravel/framework/contracts/console/command"
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/carbon"
"panel/internal"
"panel/pkg/tools"
)
// PanelTask 面板每日任务
type PanelTask struct {
}
// Signature The name and signature of the console command.
func (receiver *PanelTask) Signature() string {
return "panel:task"
}
// Description The console command description.
func (receiver *PanelTask) Description() string {
return "[面板] 每日任务"
}
// Extend The console command extend.
func (receiver *PanelTask) Extend() command.Extend {
return command.Extend{
Category: "panel",
}
}
// Handle Execute the console command.
func (receiver *PanelTask) Handle(ctx console.Context) error {
internal.Status = internal.StatusMaintain
// 优化数据库
if _, err := facades.Orm().Query().Exec("VACUUM"); err != nil {
facades.Log().Tags("面板", "每日任务").
With(map[string]any{
"error": err.Error(),
}).Error("优化面板数据库失败")
return err
}
// 备份面板
if err := tools.Archive([]string{"/www/panel"}, "/www/backup/panel/panel-"+carbon.Now().ToShortDateTimeString()+".zip"); err != nil {
facades.Log().Tags("面板", "每日任务").
With(map[string]any{
"error": err.Error(),
}).Error("备份面板失败")
return err
}
// 清理 7 天前的备份
if _, err := tools.Exec(`find /www/backup/panel -mtime +7 -name "*.zip" -exec rm -rf {} \;`); err != nil {
facades.Log().Tags("面板", "每日任务").
With(map[string]any{
"error": err.Error(),
}).Error("清理面板备份失败")
return err
}
internal.Status = internal.StatusNormal
return nil
}
+3 -1
View File
@@ -14,7 +14,8 @@ type Kernel struct {
func (kernel *Kernel) Schedule() []schedule.Event {
return []schedule.Event{
facades.Schedule().Command("panel:monitoring").EveryMinute().SkipIfStillRunning(),
facades.Schedule().Command("panel:cert-renew").Daily().SkipIfStillRunning(),
facades.Schedule().Command("panel:cert-renew").DailyAt("04:00").SkipIfStillRunning(),
facades.Schedule().Command("panel:task").Daily().SkipIfStillRunning(),
}
}
@@ -23,5 +24,6 @@ func (kernel *Kernel) Commands() []console.Command {
&commands.Panel{},
&commands.Monitoring{},
&commands.CertRenew{},
&commands.PanelTask{},
}
}
+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))
}
+50 -20
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 {
@@ -121,8 +121,8 @@ func (r *CertController) Algorithms(ctx http.Context) http.Response {
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param data body commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse{data=responses.CertList}
// @Param data query 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
@@ -141,9 +141,9 @@ func (r *CertController) UserList(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
return Success(ctx, responses.UserList{
Total: total,
Items: users,
return Success(ctx, http.Json{
"total": total,
"items": users,
})
}
@@ -273,8 +273,8 @@ func (r *CertController) UserDestroy(ctx http.Context) http.Response {
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param data body commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse{data=responses.DNSList}
// @Param data query 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
@@ -293,9 +293,9 @@ func (r *CertController) DNSList(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
return Success(ctx, responses.DNSList{
Total: total,
Items: dns,
return Success(ctx, http.Json{
"total": total,
"items": dns,
})
}
@@ -425,8 +425,8 @@ func (r *CertController) DNSDestroy(ctx http.Context) http.Response {
// @Tags 证书管理
// @Produce json
// @Security BearerToken
// @Param data body commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse{data=responses.CertList}
// @Param data query 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
@@ -445,9 +445,9 @@ func (r *CertController) CertList(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
return Success(ctx, responses.CertList{
Total: total,
Items: certs,
return Success(ctx, http.Json{
"total": total,
"items": certs,
})
}
@@ -650,7 +650,7 @@ func (r *CertController) Renew(ctx http.Context) http.Response {
// @Produce json
// @Security BearerToken
// @Param data body requests.Obtain true "request"
// @Success 200 {object} SuccessResponse{data=map[string]acme.Resolve}
// @Success 200 {object} SuccessResponse{data=[]acme.DNSRecord}
// @Router /panel/cert/manualDNS [post]
func (r *CertController) ManualDNS(ctx http.Context) http.Response {
var obtainRequest requests.Obtain
@@ -669,3 +669,33 @@ func (r *CertController) ManualDNS(ctx http.Context) http.Response {
return Success(ctx, resolves)
}
// Deploy
//
// @Summary 部署证书
// @Description 部署面板证书管理的证书
// @Tags 证书管理
// @Accept json
// @Produce json
// @Security BearerToken
// @Param data body requests.CertDeploy true "request"
// @Success 200 {object} SuccessResponse
// @Router /panel/cert/deploy [post]
func (r *CertController) Deploy(ctx http.Context) http.Response {
var deployRequest requests.CertDeploy
sanitize := Sanitize(ctx, &deployRequest)
if sanitize != nil {
return sanitize
}
err := r.cert.Deploy(deployRequest.ID, deployRequest.WebsiteID)
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{
"certID": deployRequest.ID,
"error": err.Error(),
}).Info("部署证书失败")
return Error(ctx, http.StatusInternalServerError, err.Error())
}
return Success(ctx, nil)
}
File diff suppressed because it is too large Load Diff
+2 -7
View File
@@ -6,21 +6,18 @@ import (
// SuccessResponse 通用成功响应
type SuccessResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data"`
}
// ErrorResponse 通用错误响应
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
// Success 响应成功
func Success(ctx http.Context, data any) http.Response {
return ctx.Response().Success().Json(&SuccessResponse{
Code: 0,
Message: "success",
Data: data,
})
@@ -28,16 +25,14 @@ func Success(ctx http.Context, data any) http.Response {
// Error 响应错误
func Error(ctx http.Context, code int, message string) http.Response {
return ctx.Response().Json(http.StatusOK, &ErrorResponse{
Code: code,
return ctx.Response().Json(code, &ErrorResponse{
Message: "错误: " + message,
})
}
// ErrorSystem 响应系统错误
func ErrorSystem(ctx http.Context) http.Response {
return ctx.Response().Json(http.StatusOK, &ErrorResponse{
Code: http.StatusInternalServerError,
return ctx.Response().Json(http.StatusInternalServerError, &ErrorResponse{
Message: "系统内部错误",
})
}
+8 -6
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 {
@@ -71,7 +73,7 @@ func (r *CronController) Add(ctx http.Context) http.Response {
cronType := ctx.Request().Input("type")
if cronType == "backup" {
backupType := ctx.Request().Input("backup_type")
backupName := ctx.Request().Input("backup_database")
backupName := ctx.Request().Input("database")
if backupType == "website" {
backupName = ctx.Request().Input("website")
}
@@ -83,7 +85,7 @@ func (r *CronController) Add(ctx http.Context) http.Response {
shell = `#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
# 耗子Linux面板 - 数据备份脚本
# 耗子 Linux 面板 - 数据备份脚本
type=` + backupType + `
path=` + backupPath + `
@@ -100,7 +102,7 @@ panel backup ${type} ${name} ${path} ${save} 2>&1
shell = `#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
# 耗子Linux面板 - 日志切割脚本
# 耗子 Linux 面板 - 日志切割脚本
name=` + website + `
save=` + cast.ToString(save) + `
+485 -4
View File
@@ -1,11 +1,492 @@
package controllers
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/carbon"
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())
}
}
r.setPermission(request.Path, 0755, "www", "www")
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
}
fileInfo, err := tools.FileInfo(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if fileInfo.IsDir() {
return Error(ctx, http.StatusInternalServerError, "目标路径不是文件")
}
if fileInfo.Size() > 10*1024*1024 {
return Error(ctx, http.StatusInternalServerError, "文件大小超过 10M")
}
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.Save 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.Save
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())
}
if err = tools.Write(request.Path, request.Content, fileInfo.Mode()); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.Path, 0755, "www", "www")
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 file formData file true "file"
// @Param path formData string true "path"
// @Success 200 {object} SuccessResponse
// @Router /panel/file/upload [post]
func (r *FileController) Upload(ctx http.Context) http.Response {
var request requests.Upload
sanitize := Sanitize(ctx, &request)
if sanitize != nil {
return sanitize
}
src, err := request.File.Open()
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
defer src.Close()
if tools.Exists(request.Path) && !ctx.Request().InputBool("force") {
return Error(ctx, http.StatusForbidden, "目标路径已存在,是否覆盖?")
}
data, err := io.ReadAll(src)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = tools.Write(request.Path, string(data), 0755); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.Path, 0755, "www", "www")
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.StatusForbidden, "目标路径已存在,是否覆盖?")
}
if err := tools.Mv(request.Old, request.New); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.New, 0755, "www", "www")
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.StatusForbidden, "目标路径已存在,是否覆盖?")
}
if err := tools.Cp(request.Old, request.New); err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
r.setPermission(request.New, 0755, "www", "www")
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.Exist
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, http.Json{
"name": fileInfo.Name(),
"size": tools.FormatBytes(float64(fileInfo.Size())),
"mode_str": fileInfo.Mode().String(),
"mode": fmt.Sprintf("%04o", fileInfo.Mode().Perm()),
"dir": fileInfo.IsDir(),
"modify": carbon.FromStdTime(fileInfo.ModTime()).ToDateTimeString(),
})
}
// 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())
}
if err := tools.Chown(request.Path, request.Owner, request.Group); 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())
}
r.setPermission(request.File, 0755, "www", "www")
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())
}
r.setPermission(request.Path, 0755, "www", "www")
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
}
fileInfoList, err := os.ReadDir(request.Path)
if err != nil {
return Error(ctx, http.StatusInternalServerError, err.Error())
}
var paths []any
for _, fileInfo := range fileInfoList {
info, _ := fileInfo.Info()
paths = append(paths, map[string]any{
"name": info.Name(),
"size": tools.FormatBytes(float64(info.Size())),
"mode_str": info.Mode().String(),
"mode": fmt.Sprintf("%04o", info.Mode().Perm()),
"dir": info.IsDir(),
"modify": carbon.FromStdTime(info.ModTime()).ToDateTimeString(),
})
}
return Success(ctx, paths)
}
// setPermission
func (r *FileController) setPermission(path string, mode uint, owner, group string) {
_ = tools.Chmod(path, os.FileMode(mode))
_ = tools.Chown(path, owner, group)
}
+9 -6
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 {
@@ -60,7 +61,7 @@ func (r *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,
})
}
@@ -308,14 +309,16 @@ func (r *InfoController) Update(ctx http.Context) http.Response {
return Error(ctx, http.StatusInternalServerError, "获取最新版本失败")
}
err = tools.UpdatePanel(panel)
if err != nil {
internal.Status = internal.StatusUpgrade
if err = tools.UpdatePanel(panel); err != nil {
internal.Status = internal.StatusFailed
facades.Log().Request(ctx.Request()).Tags("面板", "基础信息").With(map[string]any{
"error": err.Error(),
}).Info("更新面板失败")
return Error(ctx, http.StatusInternalServerError, err.Error())
}
internal.Status = internal.StatusNormal
tools.RestartPanel()
return Success(ctx, nil)
}
+3 -2
View File
@@ -9,11 +9,12 @@ import (
"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 {
+10 -164
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 {
@@ -96,183 +92,33 @@ func (r *PluginController) List(ctx http.Context) http.Response {
// Install 安装插件
func (r *PluginController) Install(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
plugin := r.plugin.GetBySlug(slug)
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
if err := r.plugin.Install(slug); err != nil {
return ErrorSystem(ctx)
}
if installedPlugin.ID != 0 {
return Error(ctx, http.StatusUnprocessableEntity, "插件已安装")
}
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+" 插件")
}
}
var task models.Task
task.Name = "安装插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Install + " >> /tmp/" + plugin.Slug + ".log 2>&1"
task.Log = "/tmp/" + plugin.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
r.task.Process(task.ID)
return Success(ctx, "任务已提交")
}
// Uninstall 卸载插件
func (r *PluginController) Uninstall(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
plugin := r.plugin.GetBySlug(slug)
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
if err := r.plugin.Uninstall(slug); err != nil {
return ErrorSystem(ctx)
}
if installedPlugin.ID == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
}
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+" 插件")
}
}
var task models.Task
task.Name = "卸载插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Uninstall + " >> /tmp/" + plugin.Slug + ".log 2>&1"
task.Log = "/tmp/" + plugin.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
r.task.Process(task.ID)
return Success(ctx, "任务已提交")
}
// Update 更新插件
func (r *PluginController) Update(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
plugin := r.plugin.GetBySlug(slug)
installedPlugin := r.plugin.GetInstalledBySlug(slug)
installedPlugins, err := r.plugin.AllInstalled()
if err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
}).Info("检查插件安装状态失败")
if err := r.plugin.Update(slug); err != nil {
return ErrorSystem(ctx)
}
if installedPlugin.ID == 0 {
return Error(ctx, http.StatusUnprocessableEntity, "插件未安装")
}
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+" 插件")
}
}
var task models.Task
task.Name = "更新插件 " + plugin.Name
task.Status = models.TaskStatusWaiting
task.Shell = plugin.Update + " >> /tmp/" + plugin.Slug + ".log 2>&1"
task.Log = "/tmp/" + plugin.Slug + ".log"
if err := facades.Orm().Query().Create(&task); err != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "插件中心").With(map[string]any{
"slug": slug,
"err": err.Error(),
}).Info("创建任务失败")
return ErrorSystem(ctx)
}
r.task.Process(task.ID)
return Success(ctx, "任务已提交")
}
-29
View File
@@ -1,29 +0,0 @@
package plugins
type PHPExtension struct {
Name string `json:"name"`
Slug string `json:"slug"`
Description string `json:"description"`
Installed bool `json:"installed"`
}
type LoadInfo struct {
Name string `json:"name"`
Value string `json:"value"`
}
type Fail2banJail struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
LogPath string `json:"log_path"`
MaxRetry int `json:"max_retry"`
FindTime int `json:"find_time"`
BanTime int `json:"ban_time"`
}
type S3fsMount struct {
ID int64 `json:"id"`
Path string `json:"path"`
Bucket string `json:"bucket"`
Url string `json:"url"`
}
@@ -10,12 +10,14 @@ import (
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
"panel/types"
)
type Fail2banController struct {
website services.Website
website internal.Website
}
func NewFail2banController() *Fail2banController {
@@ -88,7 +90,7 @@ func (r *Fail2banController) List(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "Fail2ban 规则为空")
}
var jails []Fail2banJail
var jails []types.Fail2banJail
for i, jail := range jailList {
if i == 0 {
continue
@@ -105,7 +107,7 @@ func (r *Fail2banController) List(ctx http.Context) http.Response {
jailFindTime := regexp.MustCompile(`findtime = (.*)`).FindStringSubmatch(jailRaw)
jailBanTime := regexp.MustCompile(`bantime = (.*)`).FindStringSubmatch(jailRaw)
jails = append(jails, Fail2banJail{
jails = append(jails, types.Fail2banJail{
Name: jailName,
Enabled: jailEnabled,
LogPath: jailLogPath[1],
@@ -120,7 +122,7 @@ func (r *Fail2banController) List(ctx http.Context) http.Response {
if startIndex > len(jails) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []Fail2banJail{},
"items": []types.Fail2banJail{},
})
}
if endIndex > len(jails) {
@@ -128,7 +130,7 @@ func (r *Fail2banController) List(ctx http.Context) http.Response {
}
pagedJails := jails[startIndex:endIndex]
if pagedJails == nil {
pagedJails = []Fail2banJail{}
pagedJails = []types.Fail2banJail{}
}
return controllers.Success(ctx, http.Json{
@@ -10,13 +10,14 @@ import (
"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 {
@@ -376,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) {
@@ -384,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{
@@ -10,13 +10,14 @@ import (
"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 {
@@ -376,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) {
@@ -384,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{
@@ -98,7 +98,7 @@ func (r *OpenRestyController) ErrorLog(ctx http.Context) http.Response {
return controllers.Success(ctx, "")
}
out, err := 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)
}
@@ -108,7 +108,7 @@ func (r *OpenRestyController) ErrorLog(ctx http.Context) http.Response {
// ClearErrorLog 清空错误日志
func (r *OpenRestyController) ClearErrorLog(ctx http.Context) http.Response {
if out, err := tools.Exec("echo '' > /www/wwwlogs/nginx_error.log"); err != nil {
if out, err := tools.Exec("echo '' > /www/wwwlogs/openresty_error.log"); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
@@ -1,280 +0,0 @@
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/app/services"
"panel/pkg/tools"
)
type Php74Controller struct {
setting services.Setting
task services.Task
version string
}
func NewPhp74Controller() *Php74Controller {
return &Php74Controller{
setting: services.NewSettingImpl(),
task: services.NewTaskImpl(),
version: "74",
}
}
func (r *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) GetExtensionList(ctx http.Context) http.Response {
extensions := r.GetExtensions()
return controllers.Success(ctx, extensions)
}
func (r *Php74Controller) 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 *Php74Controller) 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 *Php74Controller) 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,
})
extensions = append(extensions, PHPExtension{
Name: "ionCube",
Slug: "ionCube Loader",
Description: "ionCube 是一个专业级的PHP加密解密工具。",
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
}
@@ -1,280 +0,0 @@
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/app/services"
"panel/pkg/tools"
)
type Php80Controller struct {
setting services.Setting
task services.Task
version string
}
func NewPhp80Controller() *Php80Controller {
return &Php80Controller{
setting: services.NewSettingImpl(),
task: services.NewTaskImpl(),
version: "80",
}
}
func (r *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) GetExtensionList(ctx http.Context) http.Response {
extensions := r.GetExtensions()
return controllers.Success(ctx, extensions)
}
func (r *Php80Controller) 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 *Php80Controller) 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 *Php80Controller) 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,
})
extensions = append(extensions, PHPExtension{
Name: "ionCube",
Slug: "ionCube Loader",
Description: "ionCube 是一个专业级的PHP加密解密工具。",
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
}
@@ -1,280 +0,0 @@
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/app/services"
"panel/pkg/tools"
)
type Php81Controller struct {
setting services.Setting
task services.Task
version string
}
func NewPhp81Controller() *Php81Controller {
return &Php81Controller{
setting: services.NewSettingImpl(),
task: services.NewTaskImpl(),
version: "81",
}
}
func (r *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) GetExtensionList(ctx http.Context) http.Response {
extensions := r.GetExtensions()
return controllers.Success(ctx, extensions)
}
func (r *Php81Controller) 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 *Php81Controller) 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 *Php81Controller) 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,
})
extensions = append(extensions, PHPExtension{
Name: "ionCube",
Slug: "ionCube Loader",
Description: "ionCube 是一个专业级的PHP加密解密工具。",
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
}
@@ -1,274 +0,0 @@
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/app/services"
"panel/pkg/tools"
)
type Php82Controller struct {
setting services.Setting
task services.Task
version string
}
func NewPhp82Controller() *Php82Controller {
return &Php82Controller{
setting: services.NewSettingImpl(),
task: services.NewTaskImpl(),
version: "82",
}
}
func (r *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) GetExtensionList(ctx http.Context) http.Response {
extensions := r.GetExtensions()
return controllers.Success(ctx, extensions)
}
func (r *Php82Controller) 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 *Php82Controller) 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 *Php82Controller) 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
}
@@ -0,0 +1,167 @@
package plugins
import (
"github.com/goravel/framework/contracts/http"
"panel/app/http/controllers"
"panel/internal"
"panel/internal/services"
)
type PHPController struct {
service internal.PHP
}
func NewPHPController(version uint) *PHPController {
return &PHPController{
service: services.NewPHPImpl(version),
}
}
func (r *PHPController) Status(ctx http.Context) http.Response {
status, err := r.service.Status()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, status)
}
func (r *PHPController) Reload(ctx http.Context) http.Response {
if err := r.service.Reload(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) Start(ctx http.Context) http.Response {
if err := r.service.Start(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) Stop(ctx http.Context) http.Response {
if err := r.service.Stop(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) Restart(ctx http.Context) http.Response {
if err := r.service.Restart(); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) GetConfig(ctx http.Context) http.Response {
config, err := r.service.GetConfig()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, config)
}
func (r *PHPController) SaveConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
if err := r.service.SaveConfig(config); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) GetFPMConfig(ctx http.Context) http.Response {
config, err := r.service.GetFPMConfig()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, config)
}
func (r *PHPController) SaveFPMConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
if err := r.service.SaveFPMConfig(config); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) Load(ctx http.Context) http.Response {
load, err := r.service.Load()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, load)
}
func (r *PHPController) ErrorLog(ctx http.Context) http.Response {
log, _ := r.service.GetErrorLog()
return controllers.Success(ctx, log)
}
func (r *PHPController) SlowLog(ctx http.Context) http.Response {
log, _ := r.service.GetSlowLog()
return controllers.Success(ctx, log)
}
func (r *PHPController) ClearErrorLog(ctx http.Context) http.Response {
err := r.service.ClearErrorLog()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) ClearSlowLog(ctx http.Context) http.Response {
err := r.service.ClearSlowLog()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) GetExtensionList(ctx http.Context) http.Response {
extensions, err := r.service.GetExtensions()
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, extensions)
}
func (r *PHPController) InstallExtension(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
if err := r.service.InstallExtension(slug); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
func (r *PHPController) UninstallExtension(ctx http.Context) http.Response {
slug := ctx.Request().Input("slug")
if len(slug) == 0 {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "参数错误")
}
if err := r.service.UninstallExtension(slug); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
return controllers.Success(ctx, nil)
}
@@ -8,13 +8,15 @@ import (
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
"panel/types"
)
type Postgresql15Controller struct {
setting services.Setting
backup services.Backup
setting internal.Setting
backup internal.Backup
}
func NewPostgresql15Controller() *Postgresql15Controller {
@@ -151,12 +153,12 @@ func (r *Postgresql15Controller) Load(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL空间占用失败")
}
data := []LoadInfo{
{"启动时间", carbon.Parse(time).ToDateTimeString()},
{"进程 PID", pid},
{"进程数", process},
{"总连接数", connections},
{"空间占用", storage},
data := []types.NV{
{Name: "启动时间", Value: carbon.Parse(time).ToDateTimeString()},
{Name: "进程 PID", Value: pid},
{Name: "进程数", Value: process},
{Name: "总连接数", Value: connections},
{Name: "空间占用", Value: storage},
}
return controllers.Success(ctx, data)
@@ -319,7 +321,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) {
@@ -327,7 +329,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{
@@ -8,13 +8,15 @@ import (
"panel/app/http/controllers"
"panel/app/models"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
"panel/types"
)
type Postgresql16Controller struct {
setting services.Setting
backup services.Backup
setting internal.Setting
backup internal.Backup
}
func NewPostgresql16Controller() *Postgresql16Controller {
@@ -151,12 +153,12 @@ func (r *Postgresql16Controller) Load(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "获取PostgreSQL空间占用失败")
}
data := []LoadInfo{
{"启动时间", carbon.Parse(time).ToDateTimeString()},
{"进程 PID", pid},
{"进程数", process},
{"总连接数", connections},
{"空间占用", storage},
data := []types.NV{
{Name: "启动时间", Value: carbon.Parse(time).ToDateTimeString()},
{Name: "进程 PID", Value: pid},
{Name: "进程数", Value: process},
{Name: "总连接数", Value: connections},
{Name: "空间占用", Value: storage},
}
return controllers.Success(ctx, data)
@@ -319,7 +321,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) {
@@ -327,7 +329,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{
@@ -7,6 +7,7 @@ import (
"panel/app/http/controllers"
"panel/pkg/tools"
"panel/types"
)
type RedisController struct {
@@ -103,20 +104,20 @@ func (r *RedisController) Load(ctx http.Context) http.Response {
}
}
data := []LoadInfo{
{"TCP 端口", dataRaw["tcp_port"]},
{"已运行天数", dataRaw["uptime_in_days"]},
{"连接的客户端数", dataRaw["connected_clients"]},
{"已分配的内存总量", dataRaw["used_memory_human"]},
{"占用内存总量", dataRaw["used_memory_rss_human"]},
{"占用内存峰值", dataRaw["used_memory_peak_human"]},
{"内存碎片比率", dataRaw["mem_fragmentation_ratio"]},
{"运行以来连接过的客户端的总数", dataRaw["total_connections_received"]},
{"运行以来执行过的命令的总数", dataRaw["total_commands_processed"]},
{"每秒执行的命令数", dataRaw["instantaneous_ops_per_sec"]},
{"查找数据库键成功次数", dataRaw["keyspace_hits"]},
{"查找数据库键失败次数", dataRaw["keyspace_misses"]},
{"最近一次 fork() 操作耗费的毫秒数", dataRaw["latest_fork_usec"]},
data := []types.NV{
{Name: "TCP 端口", Value: dataRaw["tcp_port"]},
{Name: "已运行天数", Value: dataRaw["uptime_in_days"]},
{Name: "连接的客户端数", Value: dataRaw["connected_clients"]},
{Name: "已分配的内存总量", Value: dataRaw["used_memory_human"]},
{Name: "占用内存总量", Value: dataRaw["used_memory_rss_human"]},
{Name: "占用内存峰值", Value: dataRaw["used_memory_peak_human"]},
{Name: "内存碎片比率", Value: dataRaw["mem_fragmentation_ratio"]},
{Name: "运行以来连接过的客户端的总数", Value: dataRaw["total_connections_received"]},
{Name: "运行以来执行过的命令的总数", Value: dataRaw["total_commands_processed"]},
{Name: "每秒执行的命令数", Value: dataRaw["instantaneous_ops_per_sec"]},
{Name: "查找数据库键成功次数", Value: dataRaw["keyspace_hits"]},
{Name: "查找数据库键失败次数", Value: dataRaw["keyspace_misses"]},
{Name: "最近一次 fork() 操作耗费的毫秒数", Value: dataRaw["latest_fork_usec"]},
}
return controllers.Success(ctx, data)
@@ -0,0 +1,395 @@
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"
"panel/types"
)
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 query 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 []types.RsyncModule
lines := strings.Split(config, "\n")
var currentModule *types.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 = &types.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": []types.RsyncModule{},
})
}
if endIndex > len(modules) {
endIndex = len(modules)
}
pagedModules := modules[startIndex:endIndex]
if pagedModules == nil {
pagedModules = []types.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)
}
+12 -10
View File
@@ -9,12 +9,14 @@ import (
"github.com/spf13/cast"
"panel/app/http/controllers"
"panel/app/services"
"panel/internal"
"panel/internal/services"
"panel/pkg/tools"
"panel/types"
)
type S3fsController struct {
setting services.Setting
setting internal.Setting
}
func NewS3fsController() *S3fsController {
@@ -28,7 +30,7 @@ func (r *S3fsController) List(ctx http.Context) http.Response {
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
var s3fsList []S3fsMount
var s3fsList []types.S3fsMount
err := json.UnmarshalString(r.setting.Get("s3fs", "[]"), &s3fsList)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取 S3fs 挂载失败")
@@ -39,7 +41,7 @@ func (r *S3fsController) List(ctx http.Context) http.Response {
if startIndex > len(s3fsList) {
return controllers.Success(ctx, http.Json{
"total": 0,
"items": []S3fsMount{},
"items": []types.S3fsMount{},
})
}
if endIndex > len(s3fsList) {
@@ -47,7 +49,7 @@ func (r *S3fsController) List(ctx http.Context) http.Response {
}
pagedS3fsList := s3fsList[startIndex:endIndex]
if pagedS3fsList == nil {
pagedS3fsList = []S3fsMount{}
pagedS3fsList = []types.S3fsMount{}
}
return controllers.Success(ctx, http.Json{
@@ -93,7 +95,7 @@ func (r *S3fsController) Add(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载目录必须为空")
}
var s3fsList []S3fsMount
var s3fsList []types.S3fsMount
err = json.UnmarshalString(r.setting.Get("s3fs", "[]"), &s3fsList)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取 S3fs 挂载失败")
@@ -123,7 +125,7 @@ func (r *S3fsController) Add(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, "挂载失败,请检查配置是否正确")
}
s3fsList = append(s3fsList, S3fsMount{
s3fsList = append(s3fsList, types.S3fsMount{
ID: id,
Path: path,
Bucket: bucket,
@@ -148,13 +150,13 @@ func (r *S3fsController) Delete(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusUnprocessableEntity, "挂载ID不能为空")
}
var s3fsList []S3fsMount
var s3fsList []types.S3fsMount
err := json.UnmarshalString(r.setting.Get("s3fs", "[]"), &s3fsList)
if err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, "获取 S3fs 挂载失败")
}
var mount S3fsMount
var mount types.S3fsMount
for _, s := range s3fsList {
if cast.ToString(s.ID) == id {
mount = s
@@ -181,7 +183,7 @@ func (r *S3fsController) Delete(ctx http.Context) http.Response {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
var newS3fsList []S3fsMount
var newS3fsList []types.S3fsMount
for _, s := range s3fsList {
if s.ID != mount.ID {
newS3fsList = append(newS3fsList, s)
@@ -296,15 +296,9 @@ func (r *SupervisorController) SaveProcessConfig(ctx http.Context) http.Response
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if out, err := tools.Exec(`supervisorctl reread`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`supervisorctl update`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`supervisorctl start ` + process); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
_, _ = tools.Exec(`supervisorctl restart ` + process)
return controllers.Success(ctx, nil)
}
@@ -352,15 +346,9 @@ stdout_logfile_maxbytes=2MB
return controllers.Error(ctx, http.StatusUnprocessableEntity, err.Error())
}
if out, err := tools.Exec(`supervisorctl reread`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`supervisorctl update`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`supervisorctl start ` + name); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
_, _ = tools.Exec(`supervisorctl start ` + name)
return controllers.Success(ctx, nil)
}
@@ -393,12 +381,8 @@ func (r *SupervisorController) DeleteProcess(ctx http.Context) http.Response {
if err := tools.Remove(logPath); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
}
if out, err := tools.Exec(`supervisorctl reread`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
if out, err := tools.Exec(`supervisorctl update`); err != nil {
return controllers.Error(ctx, http.StatusInternalServerError, out)
}
_, _ = tools.Exec(`supervisorctl reread`)
_, _ = tools.Exec(`supervisorctl update`)
return controllers.Success(ctx, nil)
}
+29 -17
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 {
@@ -29,7 +29,7 @@ func NewSettingController() *SettingController {
// @Tags 面板设置
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse{data=responses.Settings}
// @Success 200 {object} SuccessResponse
// @Router /panel/setting/list [get]
func (r *SettingController) List(ctx http.Context) http.Response {
var settings []models.Setting
@@ -41,24 +41,16 @@ func (r *SettingController) List(ctx http.Context) http.Response {
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().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, err = 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(),
@@ -66,7 +58,17 @@ func (r *SettingController) List(ctx http.Context) http.Response {
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
@@ -124,7 +126,7 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
}
var user models.User
err = facades.Auth().User(ctx, &user)
err = facades.Auth(ctx).User(&user)
if err != nil {
return ErrorSystem(ctx)
}
@@ -195,7 +197,17 @@ func (r *SettingController) Update(ctx http.Context) http.Response {
}
}
if oldPort != port || oldEntrance != entrance {
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()
}
+58 -57
View File
@@ -3,24 +3,24 @@ package controllers
import (
"bytes"
"context"
nethttp "net/http"
"sync"
"time"
"github.com/fasthttp/websocket"
"github.com/goravel/fiber"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"github.com/gorilla/websocket"
"github.com/spf13/cast"
"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 {
@@ -85,15 +85,24 @@ func (r *SshController) UpdateInfo(ctx http.Context) http.Response {
// Session SSH 会话
func (r *SshController) Session(ctx http.Context) http.Response {
upGrader := websocket.FastHTTPUpgrader{
upGrader := websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
CheckOrigin: func(ctx *fasthttp.RequestCtx) bool {
CheckOrigin: func(r *nethttp.Request) bool {
return true
},
Subprotocols: []string{ctx.Request().Header("Sec-WebSocket-Protocol")},
}
ws, err := upGrader.Upgrade(ctx.Response().Writer(), ctx.Request().Origin(), nil)
if err != nil {
facades.Log().Tags("面板", "SSH").With(map[string]any{
"error": err.Error(),
}).Infof("建立连接失败")
return ErrorSystem(ctx)
}
defer ws.Close()
config := ssh.SSHClientConfigPassword(
r.setting.Get(models.SettingKeySshHost)+":"+r.setting.Get(models.SettingKeySshPort),
r.setting.Get(models.SettingKeySshUser),
@@ -101,59 +110,51 @@ func (r *SshController) Session(ctx http.Context) http.Response {
)
client, err := ssh.NewSSHClient(config)
err = upGrader.Upgrade(ctx.(*fiber.Context).Instance().Context(), func(conn *websocket.Conn) {
if err != nil {
_ = conn.WriteControl(websocket.CloseMessage,
[]byte(err.Error()), time.Now().Add(time.Second))
return
}
defer client.Close()
turn, err := ssh.NewTurn(conn, client)
if err != nil {
_ = conn.WriteControl(websocket.CloseMessage,
[]byte(err.Error()), time.Now().Add(time.Second))
return
}
defer turn.Close()
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
var logBuff = bufPool.Get().(*bytes.Buffer)
logBuff.Reset()
defer bufPool.Put(logBuff)
ctx2, cancel := context.WithCancel(context.Background())
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
err := turn.LoopRead(logBuff, ctx2)
if err != nil {
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().Request(ctx.Request()).Tags("面板", "SSH").With(map[string]any{
"error": err.Error(),
}).Info("SSH 会话错误")
}
cancel()
}()
wg.Wait()
})
if err != nil {
_ = ws.WriteControl(websocket.CloseMessage,
[]byte(err.Error()), time.Now().Add(time.Second))
return ErrorSystem(ctx)
}
defer client.Close()
turn, err := ssh.NewTurn(ws, client)
if err != nil {
_ = ws.WriteControl(websocket.CloseMessage,
[]byte(err.Error()), time.Now().Add(time.Second))
return ErrorSystem(ctx)
}
defer turn.Close()
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
var logBuff = bufPool.Get().(*bytes.Buffer)
logBuff.Reset()
defer bufPool.Put(logBuff)
sshCtx, cancel := context.WithCancel(context.Background())
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
if err = turn.LoopRead(logBuff, sshCtx); err != nil {
facades.Log().Tags("面板", "SSH").With(map[string]any{
"error": err.Error(),
}).Infof("读取数据失败")
}
}()
go func() {
defer wg.Done()
if err = turn.SessionWait(); err != nil {
facades.Log().Tags("面板", "SSH").With(map[string]any{
"error": err.Error(),
}).Infof("会话错误")
}
cancel()
}()
wg.Wait()
return nil
}
+10 -163
View File
@@ -1,35 +1,19 @@
package controllers
import (
"html/template"
"io"
"path/filepath"
"regexp"
"strings"
"github.com/goravel/framework/contracts/http"
swaggerFiles "github.com/swaggo/files/v2"
"github.com/swaggo/swag"
"github.com/goravel/framework/facades"
"github.com/swaggo/http-swagger/v2"
_ "panel/docs"
)
type SwaggerController struct {
// Dependent services
}
// Config stores fiberSwagger configuration variables.
type Config struct {
URL string
InstanceName string
DocExpansion string
DomID string
DeepLinking bool
PersistAuthorization bool
}
func NewSwaggerController() *SwaggerController {
return &SwaggerController{
// Inject services
}
return &SwaggerController{}
}
// Index
@@ -41,149 +25,12 @@ func NewSwaggerController() *SwaggerController {
// @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)
handler := httpSwagger.Handler()
handler(ctx.Response().Writer(), ctx.Request().Origin())
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)
if err != nil {
return ctx.Response().Status(http.StatusInternalServerError).String(http.StatusText(http.StatusInternalServerError))
}
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>
`
+3 -2
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"
)
@@ -62,9 +63,9 @@ func (r *TaskController) Log(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
log, err := tools.Exec("tail -n 1000 " + task.Log)
log, err := tools.Exec(`tail -n 500 '` + task.Log + `'`)
if err != nil {
return Error(ctx, http.StatusInternalServerError, log)
return Error(ctx, http.StatusInternalServerError, "日志已被清理")
}
return Success(ctx, log)
+8 -9
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"
)
@@ -61,7 +60,7 @@ func (r *UserController) Login(ctx http.Context) http.Response {
}
}
token, loginErr := facades.Auth().LoginUsingID(ctx, user.ID)
token, loginErr := facades.Auth(ctx).LoginUsingID(user.ID)
if loginErr != nil {
facades.Log().Request(ctx.Request()).Tags("面板", "用户").With(map[string]any{
"error": err.Error(),
@@ -81,11 +80,11 @@ func (r *UserController) Login(ctx http.Context) http.Response {
// @Tags 用户鉴权
// @Produce json
// @Security BearerToken
// @Success 200 {object} SuccessResponse{data=responses.Info}
// @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()).Tags("面板", "用户").With(map[string]any{
"error": err.Error(),
@@ -93,10 +92,10 @@ func (r *UserController) Info(ctx http.Context) http.Response {
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,
})
}
+17 -17
View File
@@ -10,16 +10,16 @@ import (
commonrequests "panel/app/http/requests/common"
requests "panel/app/http/requests/website"
responses "panel/app/http/responses/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 {
@@ -37,8 +37,8 @@ func NewWebsiteController() *WebsiteController {
// @Tags 网站管理
// @Produce json
// @Security BearerToken
// @Param data body commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse{data=responses.List}
// @Param data query 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
@@ -55,9 +55,9 @@ func (r *WebsiteController) List(ctx http.Context) http.Response {
return ErrorSystem(ctx)
}
return Success(ctx, responses.List{
Total: total,
Items: websites,
return Success(ctx, http.Json{
"total": total,
"items": websites,
})
}
@@ -83,7 +83,7 @@ func (r *WebsiteController) Add(ctx http.Context) http.Response {
addRequest.Path = r.setting.Get(models.SettingKeyWebsitePath) + "/" + addRequest.Name
}
website := services.PanelWebsite{
website := internal.PanelWebsite{
Name: addRequest.Name,
Status: true,
Domains: addRequest.Domains,
@@ -205,7 +205,7 @@ func (r *WebsiteController) SaveDefaultConfig(ctx http.Context) http.Response {
// @Produce json
// @Security BearerToken
// @Param id path int true "网站 ID"
// @Success 200 {object} SuccessResponse{data=services.PanelWebsite}
// @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
@@ -327,8 +327,8 @@ func (r *WebsiteController) UpdateRemark(ctx http.Context) http.Response {
// @Tags 网站管理
// @Produce json
// @Security BearerToken
// @Param data body commonrequests.Paginate true "request"
// @Success 200 {object} SuccessResponse{data=[]services.BackupFile}
// @Param data query 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
@@ -350,7 +350,7 @@ func (r *WebsiteController) BackupList(ctx http.Context) http.Response {
if startIndex > len(backupList) {
return Success(ctx, http.Json{
"total": 0,
"items": []services.BackupFile{},
"items": []internal.BackupFile{},
})
}
if endIndex > len(backupList) {
@@ -358,7 +358,7 @@ func (r *WebsiteController) BackupList(ctx http.Context) http.Response {
}
pagedBackupList := backupList[startIndex:endIndex]
if pagedBackupList == nil {
pagedBackupList = []services.BackupFile{}
pagedBackupList = []internal.BackupFile{}
}
return Success(ctx, http.Json{
@@ -567,7 +567,7 @@ server
# php标记位结束
# waf标记位开始
waf on;
waf off;
waf_rule_path /www/server/openresty/ngx_waf/assets/rules/;
waf_mode DYNAMIC;
waf_cc_deny rate=1000r/m duration=60m;
+5 -1
View File
@@ -2,6 +2,8 @@ package http
import (
"github.com/goravel/framework/contracts/http"
"panel/app/http/middleware"
)
type Kernel struct {
@@ -10,5 +12,7 @@ type Kernel struct {
// The application's global HTTP middleware stack.
// These middleware are run during every request to your application.
func (kernel Kernel) Middleware() []http.Middleware {
return []http.Middleware{}
return []http.Middleware{
middleware.Status(),
}
}
+5 -8
View File
@@ -13,21 +13,19 @@ func Jwt() http.Middleware {
return func(ctx http.Context) {
token := ctx.Request().Header("Authorization", ctx.Request().Header("Sec-WebSocket-Protocol"))
if len(token) == 0 {
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
"code": 401,
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": "未登录",
})
return
}
// 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{
"code": 401,
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": "登录已过期",
})
return
@@ -35,8 +33,7 @@ func Jwt() http.Middleware {
token = "Bearer " + token
} else {
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
"code": 401,
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
"message": "登录已过期",
})
return
+8 -21
View File
@@ -2,11 +2,10 @@ package middleware
import (
"strings"
"sync"
"github.com/goravel/framework/contracts/http"
"panel/app/services"
"panel/internal/services"
)
// MustInstall 确保已安装插件
@@ -19,8 +18,7 @@ func MustInstall() http.Middleware {
} else {
pathArr := strings.Split(path, "/")
if len(pathArr) < 4 {
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
"code": http.StatusForbidden,
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
"message": "插件不存在",
})
return
@@ -32,37 +30,29 @@ func MustInstall() http.Middleware {
installedPlugin := services.NewPluginImpl().GetInstalledBySlug(slug)
installedPlugins, err := services.NewPluginImpl().AllInstalled()
if err != nil {
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
"code": http.StatusInternalServerError,
ctx.Request().AbortWithStatusJson(http.StatusInternalServerError, http.Json{
"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 + " 版本",
if installedPlugin.Slug != plugin.Slug {
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
"message": "插件 " + slug + " 未安装",
})
return
}
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 {
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
"code": http.StatusForbidden,
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
"message": "插件 " + slug + " 需要依赖 " + require + " 插件",
})
return
@@ -70,12 +60,9 @@ func MustInstall() http.Middleware {
}
for _, exclude := range plugin.Excludes {
lock.RLock()
_, excludeFound := pluginsMap[exclude]
lock.RUnlock()
if excludeFound {
ctx.Request().AbortWithStatusJson(http.StatusOK, http.Json{
"code": http.StatusForbidden,
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
"message": "插件 " + slug + " 不兼容 " + exclude + " 插件",
})
return
+38
View File
@@ -0,0 +1,38 @@
package middleware
import (
"github.com/goravel/framework/contracts/http"
"panel/internal"
)
// Status 检查程序状态
func Status() http.Middleware {
return func(ctx http.Context) {
switch internal.Status {
case internal.StatusUpgrade:
ctx.Request().AbortWithStatusJson(http.StatusServiceUnavailable, http.Json{
"message": "面板升级中,请稍后",
})
return
case internal.StatusMaintain:
ctx.Request().AbortWithStatusJson(http.StatusServiceUnavailable, http.Json{
"message": "面板正在运行维护,请稍后",
})
return
case internal.StatusClosed:
ctx.Request().AbortWithStatusJson(http.StatusForbidden, http.Json{
"message": "面板已关闭",
})
return
case internal.StatusFailed:
ctx.Request().AbortWithStatusJson(http.StatusInternalServerError, http.Json{
"message": "面板运行出错,请检查排除或联系支持",
})
return
default:
ctx.Request().Next()
return
}
}
}
+2 -2
View File
@@ -6,8 +6,8 @@ import (
)
type CertDeploy struct {
ID uint `form:"id" json:"id"`
WebsiteID uint `form:"website_id" json:"website_id"`
ID uint `form:"id" json:"id" filter:"uint"`
WebsiteID uint `form:"website_id" json:"website_id" filter:"uint"`
}
func (r *CertDeploy) Authorize(ctx http.Context) error {
-1
View File
@@ -26,7 +26,6 @@ func (r *DNSStore) Rules(ctx http.Context) map[string]string {
"data.token": "required_if:type,dnspod",
"data.access_key": "required_if:type,aliyun",
"data.secret_key": "required_if:type,aliyun",
"data.email": "required_if:type,cloudflare",
"data.api_key": "required_if:type,cloudflare",
}
}
+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 {
@@ -0,0 +1,77 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"panel/types"
)
type ContainerCreate struct {
Name string `form:"name" json:"name"`
Image string `form:"image" json:"image"`
Ports []types.ContainerPort `form:"ports" json:"ports"`
Network string `form:"network" json:"network"`
Volumes []types.ContainerVolume `form:"volumes" json:"volumes"`
Labels []types.KV `form:"labels" json:"labels"`
Env []types.KV `form:"env" json:"env"`
Entrypoint []string `form:"entrypoint" json:"entrypoint"`
Command []string `form:"command" json:"command"`
RestartPolicy string `form:"restart_policy" json:"restart_policy"`
AutoRemove bool `form:"auto_remove" json:"auto_remove"`
Privileged bool `form:"privileged" json:"privileged"`
OpenStdin bool `form:"openStdin" json:"open_stdin"`
PublishAllPorts bool `form:"publish_all_ports" json:"publish_all_ports"`
Tty bool `form:"tty" json:"tty"`
CPUShares int64 `form:"cpu_shares" json:"cpu_shares" filter:"int"`
CPUs int64 `form:"cpus" json:"cpus" filter:"int"`
Memory int64 `form:"memory" json:"memory" filter:"int"`
}
func (r *ContainerCreate) Authorize(ctx http.Context) error {
return nil
}
func (r *ContainerCreate) Rules(ctx http.Context) map[string]string {
return map[string]string{
"name": "required|string",
"image": "required|string",
"ports": "slice",
/*"ports.*.host": "string",
"ports.*.host_start": "int",
"ports.*.host_end": "int",
"ports.*.container_start": "int",
"ports.*.container_end": "int",
"ports.*.protocol": "string|in:tcp,udp",*/
"network": "string",
"volumes": "slice",
/*"volumes.*.host": "string",
"volumes.*.container": "string",
"volumes.*.mode": "string|in:ro,rw",*/
"labels": "slice",
"env": "slice",
"entrypoint": "slice",
"command": "slice",
"restart_policy": "string|in:always,on-failure,unless-stopped,no",
"auto_remove": "bool",
"privileged": "bool",
"open_stdin": "bool",
"publish_all_ports": "bool",
"tty": "bool",
"cpu_shares": "int",
"cpus": "int",
"memory": "int",
}
}
func (r *ContainerCreate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ContainerCreate) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ContainerCreate) 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 ContainerRename struct {
ID string `form:"id" json:"id"`
Name string `form:"name" json:"name"`
}
func (r *ContainerRename) Authorize(ctx http.Context) error {
return nil
}
func (r *ContainerRename) Rules(ctx http.Context) map[string]string {
return map[string]string{
"id": "required|string",
"name": "required|string",
}
}
func (r *ContainerRename) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ContainerRename) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ContainerRename) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
@@ -0,0 +1,70 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"panel/types"
)
type ContainerUpdate struct {
ID string `form:"id" json:"id"`
Name string `form:"name" json:"name"`
Image string `form:"image" json:"image"`
Ports []types.ContainerPort `form:"ports" json:"ports"`
Network string `form:"network" json:"network"`
Volumes []types.ContainerVolume `form:"volumes" json:"volumes"`
Labels []string `form:"labels" json:"labels"`
Env []string `form:"env" json:"env"`
Entrypoint []string `form:"entrypoint" json:"entrypoint"`
Command []string `form:"command" json:"command"`
RestartPolicy string `form:"restart_policy" json:"restart_policy"`
AutoRemove bool `form:"auto_remove" json:"auto_remove"`
Privileged bool `form:"privileged" json:"privileged"`
OpenStdin bool `form:"openStdin" json:"open_stdin"`
PublishAllPorts bool `form:"publish_all_ports" json:"publish_all_ports"`
Tty bool `form:"tty" json:"tty"`
CPUShares int64 `form:"cpu_shares" json:"cpu_shares"`
CPUs int64 `form:"cpus" json:"cpus"`
Memory int64 `form:"memory" json:"memory"`
}
func (r *ContainerUpdate) Authorize(ctx http.Context) error {
return nil
}
func (r *ContainerUpdate) Rules(ctx http.Context) map[string]string {
return map[string]string{
"id": "required|string",
"name": "required|string",
"image": "required|string",
"ports": "slice",
"network": "string",
"volumes": "slice",
"labels": "slice",
"env": "slice",
"entrypoint": "slice",
"command": "slice",
"restart_policy": "string|in:always,on-failure,unless-stopped,no",
"auto_remove": "bool",
"privileged": "bool",
"open_stdin": "bool",
"publish_all_ports": "bool",
"tty": "bool",
"cpu_shares": "int",
"cpus": "int",
"memory": "int",
}
}
func (r *ContainerUpdate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ContainerUpdate) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ContainerUpdate) 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 string `form:"id" json:"id"`
}
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|string",
}
}
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
}
+38
View File
@@ -0,0 +1,38 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type ImagePull struct {
Name string `form:"name" json:"name"`
Auth bool `form:"auth" json:"auth"`
Username string `form:"username" json:"username"`
Password string `form:"password" json:"password"`
}
func (r *ImagePull) Authorize(ctx http.Context) error {
return nil
}
func (r *ImagePull) Rules(ctx http.Context) map[string]string {
return map[string]string{
"name": "required|string",
"auth": "bool",
"username": "string",
"password": "string",
}
}
func (r *ImagePull) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ImagePull) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *ImagePull) 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 NetworkConnectDisConnect struct {
Network string `form:"network" json:"network"`
Container string `form:"container" json:"container"`
}
func (r *NetworkConnectDisConnect) Authorize(ctx http.Context) error {
return nil
}
func (r *NetworkConnectDisConnect) Rules(ctx http.Context) map[string]string {
return map[string]string{
"network": "required|string",
"container": "required|string",
}
}
func (r *NetworkConnectDisConnect) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *NetworkConnectDisConnect) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *NetworkConnectDisConnect) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
@@ -0,0 +1,44 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"panel/types"
)
type NetworkCreate struct {
Name string `form:"name" json:"name"`
Driver string `form:"driver" json:"driver"`
Ipv4 types.ContainerNetwork `form:"ipv4" json:"ipv4"`
Ipv6 types.ContainerNetwork `form:"ipv6" json:"ipv6"`
Labels []types.KV `form:"labels" json:"labels"`
Options []types.KV `form:"options" json:"options"`
}
func (r *NetworkCreate) Authorize(ctx http.Context) error {
return nil
}
func (r *NetworkCreate) Rules(ctx http.Context) map[string]string {
return map[string]string{
"name": "required|string",
"driver": "required|string|in:bridge,overlay,macvlan,ipvlan",
"ipv4": "required",
"ipv6": "required",
"labels": "slice",
"options": "slice",
}
}
func (r *NetworkCreate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *NetworkCreate) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *NetworkCreate) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
@@ -0,0 +1,40 @@
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
"panel/types"
)
type VolumeCreate struct {
Name string `form:"name" json:"name"`
Driver string `form:"driver" json:"driver"`
Labels []types.KV `form:"labels" json:"labels"`
Options []types.KV `form:"options" json:"options"`
}
func (r *VolumeCreate) Authorize(ctx http.Context) error {
return nil
}
func (r *VolumeCreate) Rules(ctx http.Context) map[string]string {
return map[string]string{
"name": "required|string",
"driver": "required|string|in:local",
"labels": "slice",
"options": "slice",
}
}
func (r *VolumeCreate) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *VolumeCreate) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *VolumeCreate) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
+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_.@#$%-]+)*$",
}
}
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 Save struct {
Path string `form:"path" json:"path"`
Content string `form:"content" json:"content"`
}
func (r *Save) Authorize(ctx http.Context) error {
return nil
}
func (r *Save) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$|path_exists",
"content": "required|string",
}
}
func (r *Save) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Save) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Save) 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
}
+36
View File
@@ -0,0 +1,36 @@
package requests
import (
"mime/multipart"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type Upload struct {
Path string `form:"path" json:"path"`
File *multipart.FileHeader `form:"file" json:"file"`
}
func (r *Upload) Authorize(ctx http.Context) error {
return nil
}
func (r *Upload) Rules(ctx http.Context) map[string]string {
return map[string]string{
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"file": "required",
}
}
func (r *Upload) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Upload) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *Upload) 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",
+2 -2
View File
@@ -10,7 +10,7 @@ type Add struct {
Domains []string `form:"domains" json:"domains"`
Ports []uint `form:"ports" json:"ports"`
Path string `form:"path" json:"path"`
Php int `form:"php" json:"php"`
Php string `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"`
@@ -27,7 +27,7 @@ func (r *Add) Rules(ctx http.Context) map[string]string {
"name": "required|regex:^[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*$|not_exists:websites,name|not_in:phpmyadmin,mysql,panel,ssh",
"domains": "required|slice",
"ports": "required|slice",
"path": "regex:^/[a-zA-Z0-9_-]+(\\/[a-zA-Z0-9_-]+)*$",
"path": "regex:^/[a-zA-Z0-9_.@#$%-]+(\\/[a-zA-Z0-9_.@#$%-]+)*$",
"php": "required",
"db": "bool",
"db_type": "required_if:db,true|in:0,mysql,postgresql",
-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"`
}
-8
View File
@@ -1,8 +0,0 @@
package responses
import "panel/app/models"
type List struct {
Total int64 `json:"total"`
Items []models.Website `json:"items"`
}
+3 -3
View File
@@ -6,9 +6,9 @@ import (
type Website struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"unique;not null" json:"name"`
Status bool `gorm:"default:true;not null;index" json:"status"`
Path string `gorm:"not null" json:"path"`
Name string `json:"name"`
Status bool `gorm:"default:true" json:"status"`
Path string `json:"path"`
Php int `gorm:"default:0;not null;index" json:"php"`
Ssl bool `gorm:"default:false;not null;index" json:"ssl"`
Remark string `gorm:"default:''" json:"remark"`
-13
View File
@@ -1,13 +0,0 @@
package fail2ban
var (
Name = "Fail2ban"
Description = "Fail2ban 扫描系统日志文件并从中找出多次尝试失败的IP地址,将该IP地址加入防火墙的拒绝访问列表中。"
Slug = "fail2ban"
Version = "1.0.0"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/fail2ban/install.sh`
Uninstall = `bash /www/panel/scripts/fail2ban/uninstall.sh`
Update = `bash /www/panel/scripts/fail2ban/update.sh`
)
-13
View File
@@ -1,13 +0,0 @@
package mysql57
var (
Name = "MySQL-5.7"
Description = "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。"
Slug = "mysql57"
Version = "5.7.44"
Requires = []string{}
Excludes = []string{"mysql80"}
Install = `bash /www/panel/scripts/mysql/install.sh 57`
Uninstall = `bash /www/panel/scripts/mysql/uninstall.sh 57`
Update = `bash /www/panel/scripts/mysql/update.sh 57`
)
-13
View File
@@ -1,13 +0,0 @@
package mysql80
var (
Name = "MySQL-8.0"
Description = "MySQL 是最流行的关系型数据库管理系统之一,Oracle 旗下产品。(内存 < 4G 无法安装)"
Slug = "mysql80"
Version = "8.0.35"
Requires = []string{}
Excludes = []string{"mysql57"}
Install = `bash /www/panel/scripts/mysql/install.sh 80`
Uninstall = `bash /www/panel/scripts/mysql/uninstall.sh 80`
Update = `bash /www/panel/scripts/mysql/update.sh 80`
)
-13
View File
@@ -1,13 +0,0 @@
package openresty
var (
Name = "OpenResty"
Description = "OpenResty® 是一款基于 NGINX 和 LuaJIT 的 Web 平台。"
Slug = "openresty"
Version = "1.21.4.3"
Requires = []string{}
Excludes = []string{}
Install = "bash /www/panel/scripts/openresty/install.sh"
Uninstall = "bash /www/panel/scripts/openresty/uninstall.sh"
Update = "bash /www/panel/scripts/openresty/install.sh"
)
-13
View File
@@ -1,13 +0,0 @@
package php74
var (
Name = "PHP-7.4"
Description = "PHP 是世界上最好的语言!"
Slug = "php74"
Version = "7.4.33"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/php/install.sh 74`
Uninstall = `bash /www/panel/scripts/php/uninstall.sh 74`
Update = `bash /www/panel/scripts/php/install.sh 74`
)
-13
View File
@@ -1,13 +0,0 @@
package php80
var (
Name = "PHP-8.0"
Description = "PHP 是世界上最好的语言!"
Slug = "php80"
Version = "8.0.30"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/php/install.sh 80`
Uninstall = `bash /www/panel/scripts/php/uninstall.sh 80`
Update = `bash /www/panel/scripts/php/install.sh 80`
)
-13
View File
@@ -1,13 +0,0 @@
package php81
var (
Name = "PHP-8.1"
Description = "PHP 是世界上最好的语言!"
Slug = "php81"
Version = "8.1.25"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/php/install.sh 81`
Uninstall = `bash /www/panel/scripts/php/uninstall.sh 81`
Update = `bash /www/panel/scripts/php/install.sh 81`
)
-13
View File
@@ -1,13 +0,0 @@
package php82
var (
Name = "PHP-8.2"
Description = "PHP 是世界上最好的语言!"
Slug = "php82"
Version = "8.2.12"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/php/install.sh 82`
Uninstall = `bash /www/panel/scripts/php/uninstall.sh 82`
Update = `bash /www/panel/scripts/php/install.sh 82`
)
-13
View File
@@ -1,13 +0,0 @@
package phpmyadmin
var (
Name = "phpMyAdmin"
Description = "phpMyAdmin 是一个以 PHP 为基础,以 Web-Base 方式架构在网站主机上的 MySQL 数据库管理工具。"
Slug = "phpmyadmin"
Version = "5.2.1"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/phpmyadmin/install.sh`
Uninstall = `bash /www/panel/scripts/phpmyadmin/uninstall.sh`
Update = `bash /www/panel/scripts/phpmyadmin/uninstall.sh && bash /www/panel/scripts/phpmyadmin/install.sh`
)
-13
View File
@@ -1,13 +0,0 @@
package postgresql15
var (
Name = "PostgreSQL-15"
Description = "PostgreSQL 是世界上最先进的开源关系数据库,在类似 BSD 与 MIT 许可的 PostgreSQL 许可下发行。"
Slug = "postgresql15"
Version = "15.5"
Requires = []string{}
Excludes = []string{"postgresql16"}
Install = `bash /www/panel/scripts/postgresql/install.sh 15`
Uninstall = `bash /www/panel/scripts/postgresql/uninstall.sh 15`
Update = `bash /www/panel/scripts/postgresql/update.sh 15`
)
-13
View File
@@ -1,13 +0,0 @@
package postgresql16
var (
Name = "PostgreSQL-16"
Description = "PostgreSQL 是世界上最先进的开源关系数据库,在类似 BSD 与 MIT 许可的 PostgreSQL 许可下发行。"
Slug = "postgresql16"
Version = "16.1"
Requires = []string{}
Excludes = []string{"postgresql15"}
Install = `bash /www/panel/scripts/postgresql/install.sh 16`
Uninstall = `bash /www/panel/scripts/postgresql/uninstall.sh 16`
Update = `bash /www/panel/scripts/postgresql/update.sh 16`
)
-13
View File
@@ -1,13 +0,0 @@
package pureftpd
var (
Name = "Pure-FTPd"
Description = "Pure-Ftpd 是一个快速、高效、轻便、安全的 FTP 服务器,它以安全和配置简单为设计目标,支持虚拟主机,IPV6,PAM 等功能。"
Slug = "pureftpd"
Version = "1.0.50"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/pureftpd/install.sh`
Uninstall = `bash /www/panel/scripts/pureftpd/uninstall.sh`
Update = `bash /www/panel/scripts/pureftpd/update.sh`
)
-13
View File
@@ -1,13 +0,0 @@
package redis
var (
Name = "Redis"
Description = "Redis 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。"
Slug = "redis"
Version = "7.2.3"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/redis/install.sh`
Uninstall = `bash /www/panel/scripts/redis/uninstall.sh`
Update = `bash /www/panel/scripts/redis/update.sh`
)
-13
View File
@@ -1,13 +0,0 @@
package s3fs
var (
Name = "S3fs"
Description = "S3fs 通过 FUSE 挂载兼容 S3 标准的存储桶,例如Amazon S3、阿里云OSS、腾讯云COS、七牛云Kodo等。"
Slug = "s3fs"
Version = "1.9"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/s3fs/install.sh`
Uninstall = `bash /www/panel/scripts/s3fs/uninstall.sh`
Update = `bash /www/panel/scripts/s3fs/install.sh`
)

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