mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 01:12:17 +08:00
Compare commits
288 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 841b1d7d1b | |||
| 6f9d5370e0 | |||
| c8f12b3a89 | |||
| daa3407505 | |||
| f5d2d2204f | |||
| 04ef3ce442 | |||
| aff68bd1c1 | |||
| fca7065dca | |||
| 29330ec457 | |||
| cc15852944 | |||
| ecc44b2e92 | |||
| 2b1e21ba4b | |||
| 7a22469fb1 | |||
| 31c063ba68 | |||
| dad868b64c | |||
| 16de31c838 | |||
| 3c1062a006 | |||
| 7017aff841 | |||
| 334c4bc4c1 | |||
| e07a69ab67 | |||
| f4e31c4902 | |||
| b2d382c2af | |||
| 5d0ad333c9 | |||
| ccf05b8c42 | |||
| 684c1b7140 | |||
| 15e12d083d | |||
| f6937bb4cb | |||
| 8683a85640 | |||
| 7ee6dd4d0e | |||
| 1c7c6074a1 | |||
| af865b62a5 | |||
| 69b430d7cb | |||
| 0956385284 | |||
| 46cf1fa7da | |||
| ac9b42ca4d | |||
| 264335429a | |||
| 323e2bf95a | |||
| 6b413d2962 | |||
| 83e0e33207 | |||
| 827df91241 | |||
| f78f414c77 | |||
| dfb73d1730 | |||
| 92c1d136c2 | |||
| 45428f3e0b | |||
| 139679ed0f | |||
| eed834bd5f | |||
| 422c92cca5 | |||
| 28d699c886 | |||
| f4533c9662 | |||
| ad71a8075b | |||
| 7d39015d97 | |||
| c3498aa5c5 | |||
| 2c3bb5bf91 | |||
| 3c2b6e6488 | |||
| dff0a61182 | |||
| d081f1e312 | |||
| 46809c8d5b | |||
| f1246ee779 | |||
| 03168a5652 | |||
| 0e43d25430 | |||
| 46a9446007 | |||
| 3761223230 | |||
| 836b34e59d | |||
| 3dc1106012 | |||
| 3eb8bf793f | |||
| cd823c4506 | |||
| 36d45ee66f | |||
| 068d36de3d | |||
| e3607b9bc7 | |||
| 76a2ff8e43 | |||
| b3e3da6833 | |||
| fa6c574743 | |||
| e7625a77a8 | |||
| e1b3c80771 | |||
| 3e9228cb65 | |||
| e8b5bb266f | |||
| a6d27f261b | |||
| 1a85571fca | |||
| 9b4f308367 | |||
| aabf49d041 | |||
| 0d22b53b36 | |||
| 8d9c25c195 | |||
| bfd4fe41d3 | |||
| 8400705053 | |||
| b3e72acad8 | |||
| 33884c4c67 | |||
| 45a939571d | |||
| ac4f2a8531 | |||
| 7c09b017c9 | |||
| 02c207d0b8 | |||
| 6d23f59d6a | |||
| 4c55552dec | |||
| 5ba24d11f6 | |||
| bcf557c47e | |||
| cac55a4f79 | |||
| 0d26edf6f0 | |||
| 7a39ef706c | |||
| f5c37c0402 | |||
| 16d571e4ea | |||
| b4b75aa9c5 | |||
| 6ed7784a89 | |||
| d2c7c1109d | |||
| 079a7f7c07 | |||
| 923c6a478c | |||
| 9e0f65cf52 | |||
| eab8184b6b | |||
| 3972a17bd1 | |||
| 34b7b8a10a | |||
| a4da49d8ae | |||
| f8a989fbfc | |||
| 4497b8fa82 | |||
| 0bb7ccd121 | |||
| b90227017c | |||
| 75d941060f | |||
| b37be8aae9 | |||
| f5a471768b | |||
| f6ad6b23cb | |||
| f5b9d3e912 | |||
| 9619c3f55b | |||
| 8c08ebb041 | |||
| 0567319671 | |||
| 760c3be9c7 | |||
| 1084968e99 | |||
| 188c2ffce0 | |||
| f6296e7704 | |||
| bd149f6524 | |||
| 989beb3642 | |||
| 5d4d94a5d8 | |||
| 41eca93f12 | |||
| 946def76c5 | |||
| 24768a4078 | |||
| 7751117dc6 | |||
| f9f286335d | |||
| 1d50da79bb | |||
| 7e78b6eaa3 | |||
| a098c988a7 | |||
| 0b04f32aed | |||
| e14c1db060 | |||
| e109e16610 | |||
| cd3f2b5951 | |||
| fe28e84bf7 | |||
| fe4e3850c9 | |||
| 2a070e14c0 | |||
| 3984f85d57 | |||
| dcd4eb1e49 | |||
| bc62fbadbb | |||
| d17ef0bfda | |||
| 622d08f6f4 | |||
| 2a2fbbf287 | |||
| 77fa6ec58f | |||
| fae4894cea | |||
| b4195459c5 | |||
| 5a40cd3910 | |||
| c6971004f5 | |||
| e6ff46ccac | |||
| a91d0b79a7 | |||
| f0a9aceb13 | |||
| c251f73895 | |||
| 79b8e58412 | |||
| 73be25dcf7 | |||
| fff4d3eae2 | |||
| e614e97312 | |||
| 7ac448d2c4 | |||
| 08f5c88cee | |||
| 779d734dad | |||
| c3ca61a665 | |||
| 832367eb2a | |||
| 929a799f68 | |||
| f1cdb19a59 | |||
| 6f588efea7 | |||
| 333e8ae39f | |||
| 12dc49968a | |||
| 960fc78518 | |||
| 447c393b46 | |||
| a72eb2ba43 | |||
| 6cff72de98 | |||
| 89be5e077a | |||
| 3e1caa4f2e | |||
| 459b71e8cb | |||
| 8c8a957df1 | |||
| 9648b24d58 | |||
| b5752ac9e5 | |||
| 6e8cb1d513 | |||
| 191c3d8b3f | |||
| 657382f929 | |||
| a64ec36744 | |||
| c527af97ca | |||
| 2293646c4c | |||
| 23e215b009 | |||
| a07c50b3f5 | |||
| 9956824d54 | |||
| 6ac5e63684 | |||
| 23b13ff79d | |||
| 4a7742b991 | |||
| 377b904459 | |||
| b30d476a59 | |||
| 3e7629b5b1 | |||
| 32bda21196 | |||
| d27a29151e | |||
| 2b74b35701 | |||
| 30b1cf28cb | |||
| 2d17de5266 | |||
| 01eb10bda3 | |||
| 86376503ac | |||
| 39fd752825 | |||
| 0b6eed5fed | |||
| 9a81c77167 | |||
| 03c375e7d9 | |||
| 645e6d663f | |||
| 2b112d735f | |||
| 4632cf7d58 | |||
| b15262b5ce | |||
| b25456e2fa | |||
| 398bdce676 | |||
| d25c035fc0 | |||
| 486cbe047b | |||
| 06d620ac06 | |||
| aa661972cc | |||
| 74261f4045 | |||
| 50ec26ef03 | |||
| dff6d27e04 | |||
| e3703975ee | |||
| 0cd49a3b18 | |||
| 1ad51e0fa0 | |||
| 405d8390bb | |||
| c4727dca27 | |||
| 19d1169491 | |||
| e95fb840f7 | |||
| 2e0def62ba | |||
| a4d12f8590 | |||
| 8a2c2411af | |||
| e79d4b43cc | |||
| 169b8c74f7 | |||
| 68eb239619 | |||
| 93f4797d44 | |||
| 170453e5ec | |||
| 6e44504378 | |||
| 27c851ba8e | |||
| 03c71a3fe6 | |||
| 95d88cc9a8 | |||
| db909f1235 | |||
| 27c0451d00 | |||
| 6b66552350 | |||
| c873f850bb | |||
| 61b93c81e6 | |||
| 7181390d94 | |||
| 2a9a6301ac | |||
| 22222f0fd7 | |||
| 74380a5d37 | |||
| 43764cf364 | |||
| 6cec736467 | |||
| d9138ddf89 | |||
| 73c2c31ce6 | |||
| f6bd27a0cb | |||
| 47df0ab396 | |||
| 35d7049bc1 | |||
| 438f7fd458 | |||
| 5cc2ae1a32 | |||
| f4af1319c9 | |||
| c966920e29 | |||
| a80b0ff8d4 | |||
| c1ed51767a | |||
| 7b8575b6dd | |||
| 48c41c7fd5 | |||
| 16791f3acb | |||
| 34e00731b5 | |||
| c54bff44cb | |||
| 3b738406c6 | |||
| 3f83119b39 | |||
| 0eae148737 | |||
| 7a648097d2 | |||
| 6de5e799ea | |||
| 36d7575f0b | |||
| 48a49db67b | |||
| a573a6e44f | |||
| d6b016d5e8 | |||
| 645a6d2b98 | |||
| 7dc98096a5 | |||
| 4f5961d7f7 | |||
| 4beb040b3d | |||
| 092da898b8 | |||
| 818c5b466b | |||
| 5dc14ef1bb | |||
| 813a8f42cf | |||
| 47fbbe7134 | |||
| 50d988a62b | |||
| 8e3d929def | |||
| 3022c91eb3 |
@@ -1,7 +1,6 @@
|
||||
name: 🐛 报告问题 (Bug Report)
|
||||
name: ☢️ 报告问题 (Bug Report)
|
||||
description: 创建一个报告以帮助我们改进 (Create a report to help us improve)
|
||||
title: "🐛 [Bug] "
|
||||
labels: [ "☢️ Bug", "bug" ]
|
||||
type: ☢️ Bug
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
@@ -42,7 +41,7 @@ body:
|
||||
description: |
|
||||
请提供面板的版本号。
|
||||
Please provide the version number of the panel.
|
||||
placeholder: "2.3.0"
|
||||
placeholder: "2.4.0"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
name: ✨ 功能请求 (Feature Request)
|
||||
description: 为这个项目提出一个想法 (Suggest an idea for this project)
|
||||
title: "✨ [Feature] "
|
||||
labels: [ "✏️ Feature", "enhancement" ]
|
||||
type: ✨ Feature
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -6,14 +6,13 @@ on:
|
||||
pull_request:
|
||||
jobs:
|
||||
frontend:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
@@ -35,7 +34,7 @@ jobs:
|
||||
path: web/dist/
|
||||
backend:
|
||||
needs: frontend
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goarch: [ amd64, arm64 ]
|
||||
|
||||
@@ -7,7 +7,7 @@ permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -21,7 +21,6 @@ jobs:
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
@@ -40,6 +39,8 @@ jobs:
|
||||
run: |
|
||||
echo "GOVERSION=$(go version | cut -d' ' -f3)" >> $GITHUB_ENV
|
||||
echo "HOSTNAME=$(hostname)" >> $GITHUB_ENV
|
||||
- name: Clear node_modules
|
||||
run: rm -rf node_modules
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ☢️ Bug
|
||||
if: github.event.label.name == '☢️ Bug'
|
||||
|
||||
@@ -9,7 +9,7 @@ permissions:
|
||||
jobs:
|
||||
golangci:
|
||||
name: golanci-lint
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
version: latest
|
||||
args: --timeout=30m ./...
|
||||
govulncheck:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -39,14 +39,13 @@ jobs:
|
||||
- name: Run Govulncheck
|
||||
run: govulncheck ./...
|
||||
frontend:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
|
||||
@@ -10,6 +10,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install Mockery
|
||||
run: |
|
||||
go install github.com/vektra/mockery/v2@latest
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
pull_request:
|
||||
jobs:
|
||||
unit:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ archives:
|
||||
builds:
|
||||
- web
|
||||
- cli
|
||||
format: zip
|
||||
formats: ["zip"]
|
||||
wrap_in_directory: false
|
||||
strip_binary_directory: true
|
||||
files:
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
企业级全能服务器运维管理面板。简单轻量,高效运维。
|
||||
新一代全能服务器运维管理面板。简单轻量,高效运维。
|
||||
|
||||
官网:[panel.haozi.net](https://panel.haozi.net) | QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制此链接](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
|
||||
|
||||
@@ -59,6 +59,9 @@
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="80" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://hongtoutong.com">
|
||||
<img height="80" src=".github/assets/htt.png" alt="红透通">
|
||||
</a>
|
||||
<a href="https://1ms.run">
|
||||
<img height="80" src=".github/assets/1ms.svg" alt="毫秒镜像提供经过审核的 Docker 镜像加速服务">
|
||||
</a>
|
||||
|
||||
+4
-1
@@ -15,7 +15,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
Enterprise-level all-in-one server operation and maintenance management panel. Simple and lightweight, efficient operation and maintenance.
|
||||
A new generation of all-in-one server operation and maintenance management panel. Simple and lightweight, efficient operation and maintenance.
|
||||
|
||||
Website: [panel.haozi.net](https://panel.haozi.net) | QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | WeChat group: [Copy this link](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
|
||||
|
||||
@@ -59,6 +59,9 @@ If the Rat Panel is helpful to you, welcome to sponsor us, also thanks to the fo
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="80" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://hongtoutong.com">
|
||||
<img height="80" src=".github/assets/htt.png" alt="红透通">
|
||||
</a>
|
||||
<a href="https://1ms.run">
|
||||
<img height="80" src=".github/assets/1ms.svg" alt="毫秒镜像提供经过审核的 Docker 镜像加速服务">
|
||||
</a>
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/apps"
|
||||
"github.com/tnb-labs/panel/internal/bootstrap"
|
||||
"github.com/tnb-labs/panel/internal/data"
|
||||
"github.com/tnb-labs/panel/internal/route"
|
||||
@@ -14,5 +15,5 @@ import (
|
||||
|
||||
// initCli init command line.
|
||||
func initCli() (*app.Cli, error) {
|
||||
panic(wire.Build(bootstrap.ProviderSet, route.ProviderSet, service.ProviderSet, data.ProviderSet, app.NewCli))
|
||||
panic(wire.Build(bootstrap.ProviderSet, route.ProviderSet, service.ProviderSet, data.ProviderSet, apps.ProviderSet, app.NewCli))
|
||||
}
|
||||
|
||||
+52
-3
@@ -8,6 +8,30 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/apps/benchmark"
|
||||
"github.com/tnb-labs/panel/internal/apps/docker"
|
||||
"github.com/tnb-labs/panel/internal/apps/fail2ban"
|
||||
"github.com/tnb-labs/panel/internal/apps/frp"
|
||||
"github.com/tnb-labs/panel/internal/apps/gitea"
|
||||
"github.com/tnb-labs/panel/internal/apps/memcached"
|
||||
"github.com/tnb-labs/panel/internal/apps/minio"
|
||||
"github.com/tnb-labs/panel/internal/apps/mysql"
|
||||
"github.com/tnb-labs/panel/internal/apps/nginx"
|
||||
"github.com/tnb-labs/panel/internal/apps/php74"
|
||||
"github.com/tnb-labs/panel/internal/apps/php80"
|
||||
"github.com/tnb-labs/panel/internal/apps/php81"
|
||||
"github.com/tnb-labs/panel/internal/apps/php82"
|
||||
"github.com/tnb-labs/panel/internal/apps/php83"
|
||||
"github.com/tnb-labs/panel/internal/apps/php84"
|
||||
"github.com/tnb-labs/panel/internal/apps/phpmyadmin"
|
||||
"github.com/tnb-labs/panel/internal/apps/podman"
|
||||
"github.com/tnb-labs/panel/internal/apps/postgresql"
|
||||
"github.com/tnb-labs/panel/internal/apps/pureftpd"
|
||||
"github.com/tnb-labs/panel/internal/apps/redis"
|
||||
"github.com/tnb-labs/panel/internal/apps/rsync"
|
||||
"github.com/tnb-labs/panel/internal/apps/s3fs"
|
||||
"github.com/tnb-labs/panel/internal/apps/supervisor"
|
||||
"github.com/tnb-labs/panel/internal/apps/toolbox"
|
||||
"github.com/tnb-labs/panel/internal/bootstrap"
|
||||
"github.com/tnb-labs/panel/internal/data"
|
||||
"github.com/tnb-labs/panel/internal/route"
|
||||
@@ -40,14 +64,39 @@ func initCli() (*app.Cli, error) {
|
||||
databaseServerRepo := data.NewDatabaseServerRepo(db, logger)
|
||||
databaseUserRepo := data.NewDatabaseUserRepo(db, databaseServerRepo)
|
||||
databaseRepo := data.NewDatabaseRepo(db, databaseServerRepo, databaseUserRepo)
|
||||
certRepo := data.NewCertRepo(db)
|
||||
certAccountRepo := data.NewCertAccountRepo(db, userRepo)
|
||||
certRepo := data.NewCertRepo(db, logger)
|
||||
certAccountRepo := data.NewCertAccountRepo(db, userRepo, logger)
|
||||
websiteRepo := data.NewWebsiteRepo(db, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo)
|
||||
backupRepo := data.NewBackupRepo(db, settingRepo, websiteRepo)
|
||||
cliService := service.NewCliService(koanf, db, appRepo, cacheRepo, userRepo, settingRepo, backupRepo, websiteRepo, databaseServerRepo)
|
||||
cli := route.NewCli(cliService)
|
||||
command := bootstrap.NewCli(cli)
|
||||
gormigrate := bootstrap.NewMigrate(db)
|
||||
appCli := app.NewCli(command, gormigrate)
|
||||
benchmarkApp := benchmark.NewApp()
|
||||
dockerApp := docker.NewApp()
|
||||
fail2banApp := fail2ban.NewApp(websiteRepo)
|
||||
frpApp := frp.NewApp()
|
||||
giteaApp := gitea.NewApp()
|
||||
memcachedApp := memcached.NewApp()
|
||||
minioApp := minio.NewApp()
|
||||
mysqlApp := mysql.NewApp(settingRepo)
|
||||
nginxApp := nginx.NewApp()
|
||||
php74App := php74.NewApp(taskRepo)
|
||||
php80App := php80.NewApp(taskRepo)
|
||||
php81App := php81.NewApp(taskRepo)
|
||||
php82App := php82.NewApp(taskRepo)
|
||||
php83App := php83.NewApp(taskRepo)
|
||||
php84App := php84.NewApp(taskRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp()
|
||||
podmanApp := podman.NewApp()
|
||||
postgresqlApp := postgresql.NewApp()
|
||||
pureftpdApp := pureftpd.NewApp()
|
||||
redisApp := redis.NewApp()
|
||||
rsyncApp := rsync.NewApp()
|
||||
s3fsApp := s3fs.NewApp(settingRepo)
|
||||
supervisorApp := supervisor.NewApp()
|
||||
toolboxApp := toolbox.NewApp()
|
||||
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
|
||||
appCli := app.NewCli(command, gormigrate, loader)
|
||||
return appCli, nil
|
||||
}
|
||||
|
||||
+21
-8
@@ -8,16 +8,21 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/apps"
|
||||
"github.com/tnb-labs/panel/internal/apps/benchmark"
|
||||
"github.com/tnb-labs/panel/internal/apps/docker"
|
||||
"github.com/tnb-labs/panel/internal/apps/fail2ban"
|
||||
"github.com/tnb-labs/panel/internal/apps/frp"
|
||||
"github.com/tnb-labs/panel/internal/apps/gitea"
|
||||
"github.com/tnb-labs/panel/internal/apps/memcached"
|
||||
"github.com/tnb-labs/panel/internal/apps/minio"
|
||||
"github.com/tnb-labs/panel/internal/apps/mysql"
|
||||
"github.com/tnb-labs/panel/internal/apps/nginx"
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/apps/php74"
|
||||
"github.com/tnb-labs/panel/internal/apps/php80"
|
||||
"github.com/tnb-labs/panel/internal/apps/php81"
|
||||
"github.com/tnb-labs/panel/internal/apps/php82"
|
||||
"github.com/tnb-labs/panel/internal/apps/php83"
|
||||
"github.com/tnb-labs/panel/internal/apps/php84"
|
||||
"github.com/tnb-labs/panel/internal/apps/phpmyadmin"
|
||||
"github.com/tnb-labs/panel/internal/apps/podman"
|
||||
"github.com/tnb-labs/panel/internal/apps/postgresql"
|
||||
@@ -66,8 +71,8 @@ func initWeb() (*app.Web, error) {
|
||||
databaseServerRepo := data.NewDatabaseServerRepo(db, logger)
|
||||
databaseUserRepo := data.NewDatabaseUserRepo(db, databaseServerRepo)
|
||||
databaseRepo := data.NewDatabaseRepo(db, databaseServerRepo, databaseUserRepo)
|
||||
certRepo := data.NewCertRepo(db)
|
||||
certAccountRepo := data.NewCertAccountRepo(db, userRepo)
|
||||
certRepo := data.NewCertRepo(db, logger)
|
||||
certAccountRepo := data.NewCertAccountRepo(db, userRepo, logger)
|
||||
websiteRepo := data.NewWebsiteRepo(db, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo)
|
||||
settingRepo := data.NewSettingRepo(db, koanf, taskRepo)
|
||||
cronRepo := data.NewCronRepo(db)
|
||||
@@ -93,6 +98,8 @@ func initWeb() (*app.Web, error) {
|
||||
sshService := service.NewSSHService(sshRepo)
|
||||
containerRepo := data.NewContainerRepo()
|
||||
containerService := service.NewContainerService(containerRepo)
|
||||
containerComposeRepo := data.NewContainerComposeRepo()
|
||||
containerComposeService := service.NewContainerComposeService(containerComposeRepo)
|
||||
containerNetworkRepo := data.NewContainerNetworkRepo()
|
||||
containerNetworkService := service.NewContainerNetworkService(containerNetworkRepo)
|
||||
containerImageRepo := data.NewContainerImageRepo()
|
||||
@@ -110,9 +117,15 @@ func initWeb() (*app.Web, error) {
|
||||
frpApp := frp.NewApp()
|
||||
giteaApp := gitea.NewApp()
|
||||
memcachedApp := memcached.NewApp()
|
||||
minioApp := minio.NewApp()
|
||||
mysqlApp := mysql.NewApp(settingRepo)
|
||||
nginxApp := nginx.NewApp()
|
||||
phpApp := php.NewApp(taskRepo)
|
||||
php74App := php74.NewApp(taskRepo)
|
||||
php80App := php80.NewApp(taskRepo)
|
||||
php81App := php81.NewApp(taskRepo)
|
||||
php82App := php82.NewApp(taskRepo)
|
||||
php83App := php83.NewApp(taskRepo)
|
||||
php84App := php84.NewApp(taskRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp()
|
||||
podmanApp := podman.NewApp()
|
||||
postgresqlApp := postgresql.NewApp()
|
||||
@@ -122,8 +135,8 @@ func initWeb() (*app.Web, error) {
|
||||
s3fsApp := s3fs.NewApp(settingRepo)
|
||||
supervisorApp := supervisor.NewApp()
|
||||
toolboxApp := toolbox.NewApp()
|
||||
loader := apps.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, mysqlApp, nginxApp, phpApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
|
||||
http := route.NewHttp(userService, dashboardService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader)
|
||||
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
|
||||
http := route.NewHttp(userService, dashboardService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader)
|
||||
wsService := service.NewWsService(koanf, sshRepo)
|
||||
ws := route.NewWs(wsService)
|
||||
mux, err := bootstrap.NewRouter(middlewares, http, ws)
|
||||
@@ -135,7 +148,7 @@ func initWeb() (*app.Web, error) {
|
||||
return nil, err
|
||||
}
|
||||
gormigrate := bootstrap.NewMigrate(db)
|
||||
jobs := job.NewJobs(db, logger, settingRepo, certRepo, backupRepo, cacheRepo)
|
||||
jobs := job.NewJobs(db, logger, settingRepo, certRepo, backupRepo, cacheRepo, taskRepo)
|
||||
cron, err := bootstrap.NewCron(koanf, logger, jobs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
module github.com/tnb-labs/panel
|
||||
|
||||
go 1.23
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/bddjr/hlfhr v1.3.6
|
||||
github.com/bddjr/hlfhr v1.3.8
|
||||
github.com/beevik/ntp v1.4.3
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/expr-lang/expr v1.16.9
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-chi/chi/v5 v5.2.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3
|
||||
github.com/go-rat/chix v1.1.4
|
||||
github.com/devhaozi/westcn v0.0.0-20250329192208-199d82100bff
|
||||
github.com/expr-lang/expr v1.17.2
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.4
|
||||
github.com/go-rat/chix v1.1.5
|
||||
github.com/go-rat/gormstore v1.0.6
|
||||
github.com/go-rat/sessions v1.0.11
|
||||
github.com/go-rat/utils v1.0.7
|
||||
github.com/go-resty/resty/v2 v2.16.2
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/go-rat/sessions v1.1.0
|
||||
github.com/go-rat/utils v1.1.4
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/go-sql-driver/mysql v1.9.1
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a
|
||||
github.com/gomodule/redigo v1.9.2
|
||||
github.com/google/wire v0.6.0
|
||||
@@ -27,22 +27,35 @@ require (
|
||||
github.com/knadh/koanf/v2 v2.1.2
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/libdns/alidns v1.0.3
|
||||
github.com/libdns/cloudflare v0.1.1
|
||||
github.com/libdns/huaweicloud v0.3.1
|
||||
github.com/libdns/libdns v0.2.2
|
||||
github.com/libdns/tencentcloud v1.1.0
|
||||
github.com/mholt/acmez/v3 v3.0.0
|
||||
github.com/libdns/cloudflare v0.1.3
|
||||
github.com/libdns/cloudns v1.0.0
|
||||
github.com/libdns/duckdns v0.2.0
|
||||
github.com/libdns/gcore v0.0.0-20250127070537-4a9d185c9d20
|
||||
github.com/libdns/godaddy v1.0.3
|
||||
github.com/libdns/hetzner v0.0.1
|
||||
github.com/libdns/huaweicloud v0.3.4
|
||||
github.com/libdns/libdns v0.2.3
|
||||
github.com/libdns/linode v0.4.1
|
||||
github.com/libdns/namecheap v0.0.0-20250228022813-d8b4b66c5072
|
||||
github.com/libdns/namedotcom v0.3.3
|
||||
github.com/libdns/namesilo v0.1.1
|
||||
github.com/libdns/porkbun v0.2.0
|
||||
github.com/libdns/tencentcloud v1.2.0
|
||||
github.com/libdns/vercel v0.0.2
|
||||
github.com/mholt/acmez/v3 v3.1.1
|
||||
github.com/ncruces/go-sqlite3 v0.24.1
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.24.0
|
||||
github.com/orandin/slog-gorm v1.4.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samber/lo v1.47.0
|
||||
github.com/samber/lo v1.49.1
|
||||
github.com/sethvargo/go-limiter v1.0.0
|
||||
github.com/shirou/gopsutil v2.21.11+incompatible
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/cast v1.7.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241225173012-5cc15aab65bd
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250225174229-c03497ddaef6
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/net v0.33.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/net v0.38.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.25.12
|
||||
@@ -50,45 +63,42 @@ require (
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.2.9 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-rat/securecookie v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-rat/securecookie v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/gofiber/schema v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gofiber/schema v1.3.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/gookit/filter v1.2.2 // indirect
|
||||
github.com/gookit/goutil v0.6.18 // indirect
|
||||
github.com/jaevor/go-nanoid v1.4.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/linode/linodego v1.23.0 // indirect
|
||||
github.com/miekg/dns v1.1.40 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1051 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
modernc.org/libc v1.60.1 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.32.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/libdns/tencentcloud => github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504
|
||||
github.com/mholt/acmez/v3 => github.com/tnb-labs/acmez/v3 v3.0.0-20241217024017-a2f2c48efdcc
|
||||
github.com/libdns/alidns => github.com/devhaozi/alidns v0.0.0-20250330073315-5c0067dc1fbb
|
||||
github.com/mholt/acmez/v3 => github.com/tnb-labs/acmez/v3 v3.0.0-20250329064837-dd8e7d30835a
|
||||
)
|
||||
|
||||
@@ -1,67 +1,65 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/bddjr/hlfhr v1.3.6 h1:XautNR6Vomud8SGh1V1Yjoj1L4YblsNM0ZUwZ7r3q4U=
|
||||
github.com/bddjr/hlfhr v1.3.6/go.mod h1:oyIv4Q9JpCgZFdtH3KyTNWp7YYRWl4zl8k4ozrMAB4g=
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.2.9 h1:LMMZIRX8y3aJJuAviNSpFmLbovZUw+6Om+8VElp1F90=
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.2.9/go.mod h1:35t795gOfzfVanhzkFyUXEzaBuMXwETmJldPpP28MN4=
|
||||
github.com/bddjr/hlfhr v1.3.8 h1:QQ6KYgtnBbvYvCWuu/tOnBZamKAPtJzesj2qbjgyn7o=
|
||||
github.com/bddjr/hlfhr v1.3.8/go.mod h1:oyIv4Q9JpCgZFdtH3KyTNWp7YYRWl4zl8k4ozrMAB4g=
|
||||
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
|
||||
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504 h1:Wk7RQyamGDfAz+5hZIT/GIm6ay5cORztgYVXOJTobKY=
|
||||
github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504/go.mod h1:o0+WCxQ7LGLtyjnjYU4HbGW9uVjN44SdUDhxdUYLGPw=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI=
|
||||
github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/devhaozi/alidns v0.0.0-20250330073315-5c0067dc1fbb h1:cXsR1VR8dGBq6LjndT/Wc228t2Uf6K4vRKis7CVO5w8=
|
||||
github.com/devhaozi/alidns v0.0.0-20250330073315-5c0067dc1fbb/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||
github.com/devhaozi/westcn v0.0.0-20250329192208-199d82100bff h1:SLHnJyRcp6OxbMjHr7FvPvT84IoXuofu+1EeMU+Tr1M=
|
||||
github.com/devhaozi/westcn v0.0.0-20250329192208-199d82100bff/go.mod h1:dUJZQSurBuEWKixGyCYCyiVIkSFxrgqN3B036tHMPtU=
|
||||
github.com/expr-lang/expr v1.17.2 h1:o0A99O/Px+/DTjEnQiodAgOIK9PPxL8DtXhBRKC+Iso=
|
||||
github.com/expr-lang/expr v1.17.2/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3 h1:ei3Vq/rpPI/jCJY9mRHJAKg5vU+EhZyWhBAkaAomQuw=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3/go.mod h1:VJ9FIOBAur+NmQ8c4tDVwOuiJcgupTG105FexPFrXzA=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.4 h1:KOPEt27qy1cNzHfMZbp9YTmEuzkY4F4wrdsJW9WFk1U=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.4/go.mod h1:y/6gPAH6QGAgP1UfHMiXcqGeJ88/GRQbfCReE1JJD5Y=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-rat/chix v1.1.4 h1:YVDwF4vrlkm5hmlDGWKoiWPgP98pJooBvhsto8UWq+o=
|
||||
github.com/go-rat/chix v1.1.4/go.mod h1:eKUTG7GXqMS278PH0QZi8yMWZddvMfxqWu7mverM6kQ=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-rat/chix v1.1.5 h1:nv+NHODd9o3AwK9JhEaUsuMCAc7cvw4bh39yTrVuo1M=
|
||||
github.com/go-rat/chix v1.1.5/go.mod h1:YQP0YT0x/U3Xx5uQe2jfyMiS/bA732Nunnii6MG0wos=
|
||||
github.com/go-rat/gormstore v1.0.6 h1:YxmaY6n3JoTk6o9RXoZ1+cQ6HQL+ayf/6d8D76ydCAc=
|
||||
github.com/go-rat/gormstore v1.0.6/go.mod h1:xG/n+Du8baWf7ptiafiHLWBZWVXrYIPC42CsoEbfr4M=
|
||||
github.com/go-rat/securecookie v1.0.1 h1:HW0fpKmB+FjJzXTw8ABOwBJ+XrPmRBSZqHhmrv86lBo=
|
||||
github.com/go-rat/securecookie v1.0.1/go.mod h1:tP/ObWYyjmcpabQ7WTon/i2lBSip/Aolliw2llXuPDU=
|
||||
github.com/go-rat/sessions v1.0.11 h1:2eTUvhEBtymrAtwv71LlGHNylz+Ad2SgY3jg/sSUboA=
|
||||
github.com/go-rat/sessions v1.0.11/go.mod h1:Ray/GCbuhm4U9xpjFFSCfOTCEn91puEhAXX5creHE9g=
|
||||
github.com/go-rat/utils v1.0.7 h1:hZUe+n1W/tvF3j+oP/TeAHX3AF98SjVB1FuF11zkCAI=
|
||||
github.com/go-rat/utils v1.0.7/go.mod h1:b0S/vd2bXWvPUQpw8z6RmJrUG7G7BkMuNSrsz6JRBHs=
|
||||
github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
|
||||
github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-rat/securecookie v1.1.0 h1:bvwE9n6X3a4eazOIevEvZghkvZ3OpY4BX1cnH/1/x0s=
|
||||
github.com/go-rat/securecookie v1.1.0/go.mod h1:Z870TcL7ktAjRuhPeQXYuQcA6/DdzKwwqSwDQuPmufI=
|
||||
github.com/go-rat/sessions v1.1.0 h1:5zCySVVxNE6xLymf5F2u4TQPKIM7kz1MuD4TwIb/7Do=
|
||||
github.com/go-rat/sessions v1.1.0/go.mod h1:ofnsWh7JoAApzCfIGd60xEyT5HlJY+Nx8zpWcwUXSHc=
|
||||
github.com/go-rat/utils v1.1.4 h1:tjlX6IWHhA/VvEBVbLGjzA96dcFSdMU5quetUHY2uqs=
|
||||
github.com/go-rat/utils v1.1.4/go.mod h1:SUy4Xw44VkZvo2FaURgiIiJlL6Kukll85fVKMp/50+8=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
|
||||
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gofiber/schema v1.2.0 h1:j+ZRrNnUa/0ZuWrn/6kAtAufEr4jCJ+JuTURAMxNSZg=
|
||||
github.com/gofiber/schema v1.2.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
|
||||
github.com/gofiber/schema v1.3.0 h1:K3F3wYzAY+aivfCCEHPufCthu5/13r/lzp1nuk6mr3Q=
|
||||
github.com/gofiber/schema v1.3.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a h1:BAyyIK6rc6Tq9vT2KVlPJXD8EVv/LpaFwn4fgfLwtHg=
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a/go.mod h1:bgk4Ij/0OQ89UeoFFAQrSNhbbr4rKJ0fwWfo7wc+TCc=
|
||||
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
|
||||
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
||||
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
|
||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||
@@ -82,6 +80,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
||||
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
|
||||
@@ -92,63 +92,93 @@ github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
|
||||
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
|
||||
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
||||
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
|
||||
github.com/libdns/huaweicloud v0.3.1 h1:wro0zpG86JKL3QVEaV/xfHW/59rmqwMYhcbl16w9EYg=
|
||||
github.com/libdns/huaweicloud v0.3.1/go.mod h1:6s5ZIwLUr2qKsQz2SRNo7uh8X5uJohxoBR95rtSpNcI=
|
||||
github.com/libdns/cloudflare v0.1.3 h1:XPFa2f3Mm/3FDNwl9Ki2bfAQJ0Cm5GQB0e8PQVy25Us=
|
||||
github.com/libdns/cloudflare v0.1.3/go.mod h1:XbvSCSMcxspwpSialM3bq0LsS3/Houy9WYxW8Ok8b6M=
|
||||
github.com/libdns/cloudns v1.0.0 h1:Qrf8TepFnXVhQJi+IwCa/TXhaDy0RXZIXf+DGD/bcE4=
|
||||
github.com/libdns/cloudns v1.0.0/go.mod h1:3bx4a2XxtgXcaDzTHktQqPNC4rghXAcSDd+GNPkhvjs=
|
||||
github.com/libdns/duckdns v0.2.0 h1:vd3pE09G2qTx1Zh1o3LmrivWSByD3Z5FbL7csX5vDgE=
|
||||
github.com/libdns/duckdns v0.2.0/go.mod h1:jCQ/7+qvhLK39+28qXvKEYGBBvmHBCmIwNqdJTCUmVs=
|
||||
github.com/libdns/gcore v0.0.0-20250127070537-4a9d185c9d20 h1:bQwFw+C9sX/zYZlV53ey0KnNkxrfWYIFpvptuAVhJ1Y=
|
||||
github.com/libdns/gcore v0.0.0-20250127070537-4a9d185c9d20/go.mod h1:JGoT1mbmqQwtYQqN5F/vGc9j4TTTMKw/hDm5vXADHUI=
|
||||
github.com/libdns/godaddy v1.0.3 h1:PX1FOYDQ1HGQzz8mVOmtwm3aa6Sv5MwCkNzivUUTA44=
|
||||
github.com/libdns/godaddy v1.0.3/go.mod h1:vuKWUXnvblDvcaiRwutOoLl7DuB21x8tI06owsF/JTM=
|
||||
github.com/libdns/hetzner v0.0.1 h1:WsmcsOKnfpKmzwhfyqhGQEIlEeEaEUvb7ezoJgBKaqU=
|
||||
github.com/libdns/hetzner v0.0.1/go.mod h1:Jj12aJipO9Ir7OGaXueJ5J1RnerFMD0auGa6k9kujG4=
|
||||
github.com/libdns/huaweicloud v0.3.4 h1:vEVf9oWNkvQuoxTKBSlocCxs3KTQpcGRfuiRJX0GUWo=
|
||||
github.com/libdns/huaweicloud v0.3.4/go.mod h1:z7M2b0aUu/qFyhRHeDqXzci3yHl9nh7GrobRlbPkJEE=
|
||||
github.com/libdns/libdns v0.1.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/libdns/libdns v0.2.2-0.20230227175549-2dc480633939/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
|
||||
github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/libdns/linode v0.4.1 h1:Fr22y3aWBzaj3AlFbCQ743eC+5Hss+sJ+OEjtlK61AA=
|
||||
github.com/libdns/linode v0.4.1/go.mod h1:BqAJnQxzpnQgy4GDVYf3hQvhLGDc3n8BRwMZ0/9m2y0=
|
||||
github.com/libdns/namecheap v0.0.0-20250228022813-d8b4b66c5072 h1:hCPOoLd9Mr5kAWLofhSCuGYZxAVVy0CV9zgbahzIeSg=
|
||||
github.com/libdns/namecheap v0.0.0-20250228022813-d8b4b66c5072/go.mod h1:jo2LWZSD/g/scevxNSp2kWtdMIXfsgWxrtSc3Z0Yy/I=
|
||||
github.com/libdns/namedotcom v0.3.3 h1:R10C7+IqQGVeC4opHHMiFNBxdNBg1bi65ZwqLESl+jE=
|
||||
github.com/libdns/namedotcom v0.3.3/go.mod h1:GbYzsAF2yRUpI0WgIK5fs5UX+kDVUPaYCFLpTnKQm0s=
|
||||
github.com/libdns/namesilo v0.1.1 h1:G6ECxNXpphWDVhSXyAdQBLA4KpHU0Az+TB9YMZMMH/4=
|
||||
github.com/libdns/namesilo v0.1.1/go.mod h1:JSyG04w+33JDbA2fYWaD9A4zufcxwK7ATNQ1RDuZbps=
|
||||
github.com/libdns/porkbun v0.2.0 h1:oa2F0doE93RiJkauVVbM+P2AZ7jovDSiH6u3aaQezvQ=
|
||||
github.com/libdns/porkbun v0.2.0/go.mod h1:mwrhwXpsSA2Xw23t9+qmgFnz+erkn25Sxuh7IBA+x2I=
|
||||
github.com/libdns/tencentcloud v1.2.0 h1:SBbZ9gUZ+ba/p2d8NpiOcj+WZTdYqB5Fz7zypdbK3YU=
|
||||
github.com/libdns/tencentcloud v1.2.0/go.mod h1:o0+WCxQ7LGLtyjnjYU4HbGW9uVjN44SdUDhxdUYLGPw=
|
||||
github.com/libdns/vercel v0.0.2 h1:slfQWxgUdYFgKfPbK1vLE7k1CZSspHFlwC+nEWlm79w=
|
||||
github.com/libdns/vercel v0.0.2/go.mod h1:z2NBq/OQ8G0JPglZmit0YnqHm/k+eOz8s4XyLvYxhQo=
|
||||
github.com/linode/linodego v1.23.0 h1:s0ReCZtuN9Z1IoUN9w1RLeYO1dMZUGPwOQ/IBFsBHtU=
|
||||
github.com/linode/linodego v1.23.0/go.mod h1:0U7wj/UQOqBNbKv1FYTXiBUXueR8DY4HvIotwE0ENgg=
|
||||
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/go-sqlite3 v0.24.1 h1:qHlIz+dlH3Y0wCUErFZXon5hCvw1Kc9eEkZVYYi8p14=
|
||||
github.com/ncruces/go-sqlite3 v0.24.1/go.mod h1:n6Z7036yFilJx04yV0mi5JWaF66rUmXn1It9Ux8dx68=
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.24.0 h1:81sHeq3CCdhjoqAB650n5wEdRlLO9VBvosArskcN3+c=
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.24.0/go.mod h1:vXfVWdBfg7qOgqQqHpzUWl9LLswD0h+8mK4oouaV2oc=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/orandin/slog-gorm v1.4.0 h1:FgA8hJufF9/jeNSYoEXmHPPBwET2gwlF3B85JdpsTUU=
|
||||
github.com/orandin/slog-gorm v1.4.0/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/sethvargo/go-limiter v1.0.0 h1:JqW13eWEMn0VFv86OKn8wiYJY/m250WoXdrjRV0kLe4=
|
||||
github.com/sethvargo/go-limiter v1.0.0/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II=
|
||||
github.com/shirou/gopsutil v2.21.11+incompatible h1:lOGOyCG67a5dv2hq5Z1BLDUqqKp3HkbjPcz5j6XMS0U=
|
||||
github.com/shirou/gopsutil v2.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1051 h1:3mg0L9vv9eO8UN4Oa7vNawe6yUIuXf9D0Q79rUmnblo=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1051/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096 h1:DMokC7T0UF8wMfT1kD+mX3M+hc2C06gmFvQ9gsfRPmI=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20241217024017-a2f2c48efdcc h1:DjqfrS/10NQOqx8yFtPcNfbAN5wrjnYgaub+ZQXPHEY=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20241217024017-a2f2c48efdcc/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241225173012-5cc15aab65bd h1:PYNV7LTzEzZ4lousHUM2yaDO2zI3FNt9NA0iuGrbK7w=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20241225173012-5cc15aab65bd/go.mod h1:hdMWBc1+TyB6G5ZZBBgPWQ8cjRZ6zpYdhal0uu6E9QM=
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250329064837-dd8e7d30835a h1:Qs7oWoFZO1CR9ui4NnwRVFtK3uNcLMYIlOHsikIBmUo=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250329064837-dd8e7d30835a/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250225174229-c03497ddaef6 h1:HmtcQ7w07RI2SdTKkPf+NM8R33B1oR9MjIZYzlBizwA=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250225174229-c03497ddaef6/go.mod h1:hdMWBc1+TyB6G5ZZBBgPWQ8cjRZ6zpYdhal0uu6E9QM=
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
@@ -157,56 +187,60 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
@@ -214,22 +248,25 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
@@ -238,27 +275,3 @@ gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
|
||||
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
|
||||
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s=
|
||||
modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
|
||||
modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
||||
+3
-1
@@ -7,6 +7,8 @@ import (
|
||||
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/tnb-labs/panel/pkg/apploader"
|
||||
)
|
||||
|
||||
type Cli struct {
|
||||
@@ -14,7 +16,7 @@ type Cli struct {
|
||||
migrator *gormigrate.Gormigrate
|
||||
}
|
||||
|
||||
func NewCli(cmd *cli.Command, migrator *gormigrate.Gormigrate) *Cli {
|
||||
func NewCli(cmd *cli.Command, migrator *gormigrate.Gormigrate, _ *apploader.Loader) *Cli {
|
||||
IsCli = true
|
||||
return &Cli{
|
||||
cmd: cmd,
|
||||
|
||||
@@ -11,11 +11,11 @@ const (
|
||||
|
||||
// 面板全局变量
|
||||
var (
|
||||
Key string
|
||||
Root string
|
||||
Locale string
|
||||
IsCli bool
|
||||
Status = StatusNormal
|
||||
Key string // 密钥
|
||||
Root string // 根目录
|
||||
Locale string // 语言
|
||||
IsCli bool // 是否命令行
|
||||
Status = StatusNormal // 面板状态
|
||||
)
|
||||
|
||||
// 自动注入
|
||||
|
||||
+14
-127
@@ -1,11 +1,6 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/benchmark"
|
||||
@@ -14,9 +9,15 @@ import (
|
||||
"github.com/tnb-labs/panel/internal/apps/frp"
|
||||
"github.com/tnb-labs/panel/internal/apps/gitea"
|
||||
"github.com/tnb-labs/panel/internal/apps/memcached"
|
||||
"github.com/tnb-labs/panel/internal/apps/minio"
|
||||
"github.com/tnb-labs/panel/internal/apps/mysql"
|
||||
"github.com/tnb-labs/panel/internal/apps/nginx"
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/apps/php74"
|
||||
"github.com/tnb-labs/panel/internal/apps/php80"
|
||||
"github.com/tnb-labs/panel/internal/apps/php81"
|
||||
"github.com/tnb-labs/panel/internal/apps/php82"
|
||||
"github.com/tnb-labs/panel/internal/apps/php83"
|
||||
"github.com/tnb-labs/panel/internal/apps/php84"
|
||||
"github.com/tnb-labs/panel/internal/apps/phpmyadmin"
|
||||
"github.com/tnb-labs/panel/internal/apps/podman"
|
||||
"github.com/tnb-labs/panel/internal/apps/postgresql"
|
||||
@@ -29,16 +30,21 @@ import (
|
||||
)
|
||||
|
||||
var ProviderSet = wire.NewSet(
|
||||
NewLoader,
|
||||
benchmark.NewApp,
|
||||
docker.NewApp,
|
||||
fail2ban.NewApp,
|
||||
frp.NewApp,
|
||||
gitea.NewApp,
|
||||
memcached.NewApp,
|
||||
minio.NewApp,
|
||||
mysql.NewApp,
|
||||
nginx.NewApp,
|
||||
php.NewApp,
|
||||
php74.NewApp,
|
||||
php80.NewApp,
|
||||
php81.NewApp,
|
||||
php82.NewApp,
|
||||
php83.NewApp,
|
||||
php84.NewApp,
|
||||
phpmyadmin.NewApp,
|
||||
podman.NewApp,
|
||||
postgresql.NewApp,
|
||||
@@ -49,122 +55,3 @@ var ProviderSet = wire.NewSet(
|
||||
supervisor.NewApp,
|
||||
toolbox.NewApp,
|
||||
)
|
||||
|
||||
var slugs []string
|
||||
|
||||
type Loader struct {
|
||||
benchmark *benchmark.App
|
||||
docker *docker.App
|
||||
fail2ban *fail2ban.App
|
||||
frp *frp.App
|
||||
gitea *gitea.App
|
||||
memcached *memcached.App
|
||||
mysql *mysql.App
|
||||
nginx *nginx.App
|
||||
php *php.App
|
||||
phpmyadmin *phpmyadmin.App
|
||||
podman *podman.App
|
||||
postgresql *postgresql.App
|
||||
pureftpd *pureftpd.App
|
||||
redis *redis.App
|
||||
rsync *rsync.App
|
||||
s3fs *s3fs.App
|
||||
supervisor *supervisor.App
|
||||
toolbox *toolbox.App
|
||||
}
|
||||
|
||||
func NewLoader(
|
||||
benchmark *benchmark.App,
|
||||
docker *docker.App,
|
||||
fail2ban *fail2ban.App,
|
||||
frp *frp.App,
|
||||
gitea *gitea.App,
|
||||
memcached *memcached.App,
|
||||
mysql *mysql.App,
|
||||
nginx *nginx.App,
|
||||
php *php.App,
|
||||
phpmyadmin *phpmyadmin.App,
|
||||
podman *podman.App,
|
||||
postgresql *postgresql.App,
|
||||
pureftpd *pureftpd.App,
|
||||
redis *redis.App,
|
||||
rsync *rsync.App,
|
||||
s3fs *s3fs.App,
|
||||
supervisor *supervisor.App,
|
||||
toolbox *toolbox.App,
|
||||
) *Loader {
|
||||
loader := &Loader{
|
||||
benchmark: benchmark,
|
||||
docker: docker,
|
||||
fail2ban: fail2ban,
|
||||
frp: frp,
|
||||
gitea: gitea,
|
||||
memcached: memcached,
|
||||
mysql: mysql,
|
||||
nginx: nginx,
|
||||
php: php,
|
||||
phpmyadmin: phpmyadmin,
|
||||
podman: podman,
|
||||
postgresql: postgresql,
|
||||
pureftpd: pureftpd,
|
||||
redis: redis,
|
||||
rsync: rsync,
|
||||
s3fs: s3fs,
|
||||
supervisor: supervisor,
|
||||
toolbox: toolbox,
|
||||
}
|
||||
|
||||
loader.initSlugs()
|
||||
return loader
|
||||
}
|
||||
|
||||
func (r *Loader) Register(mux chi.Router) {
|
||||
mux.Route("/benchmark", r.benchmark.Route)
|
||||
mux.Route("/docker", r.docker.Route)
|
||||
mux.Route("/fail2ban", r.fail2ban.Route)
|
||||
mux.Route("/frp", r.frp.Route)
|
||||
mux.Route("/gitea", r.gitea.Route)
|
||||
mux.Route("/memcached", r.memcached.Route)
|
||||
mux.Route("/mysql", r.mysql.Route)
|
||||
mux.Route("/nginx", r.nginx.Route)
|
||||
mux.Route("/php74", r.php.Route(74))
|
||||
mux.Route("/php80", r.php.Route(80))
|
||||
mux.Route("/php81", r.php.Route(81))
|
||||
mux.Route("/php82", r.php.Route(82))
|
||||
mux.Route("/php83", r.php.Route(83))
|
||||
mux.Route("/php84", r.php.Route(84))
|
||||
mux.Route("/phpmyadmin", r.phpmyadmin.Route)
|
||||
mux.Route("/podman", r.podman.Route)
|
||||
mux.Route("/postgresql", r.postgresql.Route)
|
||||
mux.Route("/pureftpd", r.pureftpd.Route)
|
||||
mux.Route("/redis", r.redis.Route)
|
||||
mux.Route("/rsync", r.rsync.Route)
|
||||
mux.Route("/s3fs", r.s3fs.Route)
|
||||
mux.Route("/supervisor", r.supervisor.Route)
|
||||
mux.Route("/toolbox", r.toolbox.Route)
|
||||
}
|
||||
|
||||
func (r *Loader) initSlugs() []string {
|
||||
if len(slugs) == 0 {
|
||||
v := reflect.Indirect(reflect.ValueOf(r))
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
slug := strings.ToLower(v.Type().Field(i).Name)
|
||||
if !field.IsNil() {
|
||||
slugs = append(slugs, slug)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理php
|
||||
slugs = slices.DeleteFunc(slugs, func(slug string) bool {
|
||||
return slug == "php"
|
||||
})
|
||||
slugs = append(slugs, "php74", "php80", "php81", "php82", "php83", "php84")
|
||||
}
|
||||
|
||||
return slugs
|
||||
}
|
||||
|
||||
func Slugs() []string {
|
||||
return slugs
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package minio
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/env", s.GetEnv)
|
||||
r.Post("/env", s.UpdateEnv)
|
||||
}
|
||||
|
||||
func (s *App) GetEnv(w http.ResponseWriter, r *http.Request) {
|
||||
env, _ := io.Read("/etc/default/minio")
|
||||
service.Success(w, env)
|
||||
}
|
||||
|
||||
func (s *App) UpdateEnv(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateEnv](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/etc/default/minio", req.Env, 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Restart("minio"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package minio
|
||||
|
||||
type UpdateEnv struct {
|
||||
Env string `form:"env" json:"env" validate:"required"`
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func (s *App) SaveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
service.Error(w, http.StatusInternalServerError, "重载服务失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package php74
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(74)(r)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package php80
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(80)(r)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package php81
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(81)(r)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package php82
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(82)(r)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package php83
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(83)(r)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package php84
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(84)(r)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package phpmyadmin
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -32,7 +33,7 @@ func (s *App) Route(r chi.Router) {
|
||||
}
|
||||
|
||||
func (s *App) Info(w http.ResponseWriter, r *http.Request) {
|
||||
files, err := io.ReadDir(fmt.Sprintf("%s/server/phpmyadmin", app.Root))
|
||||
files, err := os.ReadDir(fmt.Sprintf("%s/server/phpmyadmin", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
|
||||
return
|
||||
|
||||
@@ -51,13 +51,13 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := io.Write(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root), req.Config, 0644); err != nil {
|
||||
if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("postgresql"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载服务失败")
|
||||
if err = systemctl.Reload("postgresql"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -84,13 +84,13 @@ func (s *App) UpdateUserConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := io.Write(fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), req.Config, 0644); err != nil {
|
||||
if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("postgresql"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载服务失败")
|
||||
if err = systemctl.Reload("postgresql"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package redis
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@@ -38,7 +39,20 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := shell.Execf("redis-cli info")
|
||||
// 检查 Redis 密码
|
||||
withPassword := ""
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/redis/redis.conf", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
re := regexp.MustCompile(`^requirepass\s+(.+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) == 2 {
|
||||
withPassword = " -a " + matches[1]
|
||||
}
|
||||
|
||||
raw, err := shell.Execf("redis-cli%s info", withPassword)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取 Redis 负载失败")
|
||||
return
|
||||
|
||||
@@ -3,6 +3,7 @@ package s3fs
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -70,7 +71,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 检查挂载目录是否存在且为空
|
||||
if !io.Exists(req.Path) {
|
||||
if err = io.Mkdir(req.Path, 0755); err != nil {
|
||||
if err = os.MkdirAll(req.Path, 0755); err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "failed to create mount path: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = systemctl.Restart(s.name); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重启 %s 服务失败", s.name)
|
||||
service.Error(w, http.StatusInternalServerError, "重启 %s 服务失败: %v", s.name, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -8,11 +8,11 @@ import (
|
||||
|
||||
type App struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Slug string `gorm:"not null;unique" json:"slug"`
|
||||
Channel string `gorm:"not null" json:"channel"`
|
||||
Version string `gorm:"not null" json:"version"`
|
||||
Show bool `gorm:"not null" json:"show"`
|
||||
ShowOrder int `gorm:"not null" json:"show_order"`
|
||||
Slug string `gorm:"not null;default:'';unique" json:"slug"`
|
||||
Channel string `gorm:"not null;default:''" json:"channel"`
|
||||
Version string `gorm:"not null;default:''" json:"version"`
|
||||
Show bool `gorm:"not null;default:false" json:"show"`
|
||||
ShowOrder int `gorm:"not null;default:0" json:"show_order"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const (
|
||||
|
||||
type Cache struct {
|
||||
Key CacheKey `gorm:"primaryKey" json:"key"`
|
||||
Value string `gorm:"not null" json:"value"`
|
||||
Value string `gorm:"not null;default:''" json:"value"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
+10
-9
@@ -10,15 +10,16 @@ import (
|
||||
|
||||
type Cert struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
AccountID uint `gorm:"not null" json:"account_id"` // 关联的 ACME 账户 ID
|
||||
WebsiteID uint `gorm:"not null" json:"website_id"` // 关联的网站 ID
|
||||
DNSID uint `gorm:"not null" json:"dns_id"` // 关联的 DNS ID
|
||||
Type string `gorm:"not null" json:"type"` // 证书类型 (P256, P384, 2048, 3072, 4096)
|
||||
Domains []string `gorm:"not null;serializer:json" json:"domains"`
|
||||
AutoRenew bool `gorm:"not null" json:"auto_renew"` // 自动续签
|
||||
CertURL string `gorm:"not null" json:"cert_url"` // 证书 URL (续签时使用)
|
||||
Cert string `gorm:"not null" json:"cert"` // 证书内容
|
||||
Key string `gorm:"not null" json:"key"` // 私钥内容
|
||||
AccountID uint `gorm:"not null;default:0" json:"account_id"` // 关联的 ACME 账户 ID
|
||||
WebsiteID uint `gorm:"not null;default:0" json:"website_id"` // 关联的网站 ID
|
||||
DNSID uint `gorm:"not null;default:0" json:"dns_id"` // 关联的 DNS ID
|
||||
Type string `gorm:"not null;default:''" json:"type"` // 证书类型 (P256, P384, 2048, 3072, 4096)
|
||||
Domains []string `gorm:"not null;default:'[]';serializer:json" json:"domains"`
|
||||
AutoRenew bool `gorm:"not null;default:false" json:"auto_renew"` // 自动续签
|
||||
CertURL string `gorm:"not null;default:''" json:"cert_url"` // 证书 URL (续签时使用)
|
||||
Cert string `gorm:"not null;default:''" json:"cert"` // 证书内容
|
||||
Key string `gorm:"not null;default:''" json:"key"` // 私钥内容
|
||||
Script string `gorm:"not null;default:''" json:"script"` // 部署脚本
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
|
||||
type CertAccount struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Email string `gorm:"not null" json:"email"`
|
||||
CA string `gorm:"not null" json:"ca"` // CA 提供商 (letsencrypt, zerossl, sslcom, google, buypass)
|
||||
Kid string `gorm:"not null" json:"kid"`
|
||||
HmacEncoded string `gorm:"not null" json:"hmac_encoded"`
|
||||
PrivateKey string `gorm:"not null" json:"private_key"`
|
||||
KeyType string `gorm:"not null" json:"key_type"` // 密钥类型 (P256, P384, 2048, 3072, 4096)
|
||||
Email string `gorm:"not null;default:''" json:"email"`
|
||||
CA string `gorm:"not null;default:'letsencrypt'" json:"ca"` // CA 提供商 (letsencrypt, zerossl, sslcom, google, buypass)
|
||||
Kid string `gorm:"not null;default:''" json:"kid"`
|
||||
HmacEncoded string `gorm:"not null;default:''" json:"hmac_encoded"`
|
||||
PrivateKey string `gorm:"not null;default:''" json:"private_key"`
|
||||
KeyType string `gorm:"not null;default:'P256'" json:"key_type"` // 密钥类型 (P256, P384, 2048, 3072, 4096)
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
type CertDNS struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null" json:"name"` // 备注名称
|
||||
Type string `gorm:"not null" json:"type"` // DNS 提供商 (tencent, aliyun, cloudflare)
|
||||
Name string `gorm:"not null;default:''" json:"name"` // 备注名称
|
||||
Type acme.DnsType `gorm:"not null;default:'aliyun'" json:"type"` // DNS 提供商
|
||||
Data acme.DNSParam `gorm:"not null;serializer:json" json:"dns_param"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package biz
|
||||
|
||||
import "github.com/tnb-labs/panel/pkg/types"
|
||||
|
||||
type ContainerComposeRepo interface {
|
||||
List() ([]types.ContainerCompose, error)
|
||||
Get(name string) (string, []types.KV, error)
|
||||
Create(name, compose string, envs []types.KV) error
|
||||
Update(name, compose string, envs []types.KV) error
|
||||
Up(name string, force bool) error
|
||||
Down(name string) error
|
||||
Remove(name string) error
|
||||
}
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
|
||||
type Cron struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Status bool `gorm:"not null" json:"status"`
|
||||
Type string `gorm:"not null" json:"type"`
|
||||
Time string `gorm:"not null" json:"time"`
|
||||
Shell string `gorm:"not null" json:"shell"`
|
||||
Log string `gorm:"not null" json:"log"`
|
||||
Name string `gorm:"not null;default:'';unique" json:"name"`
|
||||
Status bool `gorm:"not null;default:false" json:"status"`
|
||||
Type string `gorm:"not null;default:''" json:"type"`
|
||||
Time string `gorm:"not null;default:''" json:"time"`
|
||||
Shell string `gorm:"not null;default:''" json:"shell"`
|
||||
Log string `gorm:"not null;default:''" json:"log"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@ const (
|
||||
|
||||
type DatabaseServer struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Type DatabaseType `gorm:"not null" json:"type"`
|
||||
Host string `gorm:"not null" json:"host"`
|
||||
Port uint `gorm:"not null" json:"port"`
|
||||
Username string `gorm:"not null" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Name string `gorm:"not null;default:'';unique" json:"name"`
|
||||
Type DatabaseType `gorm:"not null;default:''" json:"type"`
|
||||
Host string `gorm:"not null;default:''" json:"host"`
|
||||
Port uint `gorm:"not null;default:0" json:"port"`
|
||||
Username string `gorm:"not null;default:''" json:"username"`
|
||||
Password string `gorm:"not null;default:''" json:"password"`
|
||||
Status DatabaseServerStatus `gorm:"-:all" json:"status"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
Remark string `gorm:"not null;default:''" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ const (
|
||||
|
||||
type DatabaseUser struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
ServerID uint `gorm:"not null" json:"server_id"`
|
||||
Username string `gorm:"not null" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Host string `gorm:"not null" json:"host"` // 仅 mysql
|
||||
Status DatabaseUserStatus `gorm:"-:all" json:"status"` // 仅显示
|
||||
Privileges []string `gorm:"-:all" json:"privileges"` // 仅显示
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
ServerID uint `gorm:"not null;default:0" json:"server_id"`
|
||||
Username string `gorm:"not null;default:''" json:"username"`
|
||||
Password string `gorm:"not null;default:''" json:"password"`
|
||||
Host string `gorm:"not null;default:''" json:"host"` // 仅 mysql
|
||||
Status DatabaseUserStatus `gorm:"-:all" json:"status"` // 仅显示
|
||||
Privileges []string `gorm:"-:all" json:"privileges"` // 仅显示
|
||||
Remark string `gorm:"not null;default:''" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
type Monitor struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Info types.CurrentInfo `gorm:"not null;serializer:json" json:"info"`
|
||||
Info types.CurrentInfo `gorm:"not null;default:'{}';serializer:json" json:"info"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
@@ -18,12 +18,13 @@ const (
|
||||
SettingKeyWebsitePath SettingKey = "website_path"
|
||||
SettingKeyMySQLRootPassword SettingKey = "mysql_root_password"
|
||||
SettingKeyOfflineMode SettingKey = "offline_mode"
|
||||
SettingKeyAutoUpdate SettingKey = "auto_update"
|
||||
)
|
||||
|
||||
type Setting struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Key SettingKey `gorm:"not null;unique" json:"key"`
|
||||
Value string `gorm:"not null" json:"value"`
|
||||
Key SettingKey `gorm:"not null;default:'';unique" json:"key"`
|
||||
Value string `gorm:"not null;default:''" json:"value"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
+5
-5
@@ -13,11 +13,11 @@ import (
|
||||
|
||||
type SSH struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Host string `gorm:"not null" json:"host"`
|
||||
Port uint `gorm:"not null" json:"port"`
|
||||
Config ssh.ClientConfig `gorm:"not null;serializer:json" json:"config"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
Name string `gorm:"not null;default:''" json:"name"`
|
||||
Host string `gorm:"not null;default:''" json:"host"`
|
||||
Port uint `gorm:"not null;default:0" json:"port"`
|
||||
Config ssh.ClientConfig `gorm:"not null;default:'{}';serializer:json" json:"config"`
|
||||
Remark string `gorm:"not null;default:''" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ const (
|
||||
|
||||
type Task struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null;index" json:"name"`
|
||||
Name string `gorm:"not null;default:'';index" json:"name"`
|
||||
Status TaskStatus `gorm:"not null;default:'waiting'" json:"status"`
|
||||
Shell string `gorm:"not null" json:"-"`
|
||||
Log string `gorm:"not null" json:"log"`
|
||||
Shell string `gorm:"not null;default:''" json:"-"`
|
||||
Log string `gorm:"not null;default:''" json:"log"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Username string `gorm:"not null;unique" json:"username"`
|
||||
Password string `gorm:"not null" json:"password"`
|
||||
Email string `gorm:"not null" json:"email"`
|
||||
Username string `gorm:"not null;default:'';unique" json:"username"`
|
||||
Password string `gorm:"not null;default:''" json:"password"`
|
||||
Email string `gorm:"not null;default:''" json:"email"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
|
||||
|
||||
@@ -10,11 +10,11 @@ import (
|
||||
|
||||
type Website struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null;unique" json:"name"`
|
||||
Name string `gorm:"not null;default:'';unique" json:"name"`
|
||||
Status bool `gorm:"not null;default:true" json:"status"`
|
||||
Path string `gorm:"not null" json:"path"`
|
||||
Https bool `gorm:"not null" json:"https"`
|
||||
Remark string `gorm:"not null" json:"remark"`
|
||||
Path string `gorm:"not null;default:''" json:"path"`
|
||||
Https bool `gorm:"not null;default:false" json:"https"`
|
||||
Remark string `gorm:"not null;default:''" json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/tnb-labs/panel/internal/apps/benchmark"
|
||||
"github.com/tnb-labs/panel/internal/apps/docker"
|
||||
"github.com/tnb-labs/panel/internal/apps/fail2ban"
|
||||
"github.com/tnb-labs/panel/internal/apps/frp"
|
||||
"github.com/tnb-labs/panel/internal/apps/gitea"
|
||||
"github.com/tnb-labs/panel/internal/apps/memcached"
|
||||
"github.com/tnb-labs/panel/internal/apps/minio"
|
||||
"github.com/tnb-labs/panel/internal/apps/mysql"
|
||||
"github.com/tnb-labs/panel/internal/apps/nginx"
|
||||
"github.com/tnb-labs/panel/internal/apps/php74"
|
||||
"github.com/tnb-labs/panel/internal/apps/php80"
|
||||
"github.com/tnb-labs/panel/internal/apps/php81"
|
||||
"github.com/tnb-labs/panel/internal/apps/php82"
|
||||
"github.com/tnb-labs/panel/internal/apps/php83"
|
||||
"github.com/tnb-labs/panel/internal/apps/php84"
|
||||
"github.com/tnb-labs/panel/internal/apps/phpmyadmin"
|
||||
"github.com/tnb-labs/panel/internal/apps/podman"
|
||||
"github.com/tnb-labs/panel/internal/apps/postgresql"
|
||||
"github.com/tnb-labs/panel/internal/apps/pureftpd"
|
||||
"github.com/tnb-labs/panel/internal/apps/redis"
|
||||
"github.com/tnb-labs/panel/internal/apps/rsync"
|
||||
"github.com/tnb-labs/panel/internal/apps/s3fs"
|
||||
"github.com/tnb-labs/panel/internal/apps/supervisor"
|
||||
"github.com/tnb-labs/panel/internal/apps/toolbox"
|
||||
"github.com/tnb-labs/panel/pkg/apploader"
|
||||
)
|
||||
|
||||
func NewLoader(
|
||||
benchmark *benchmark.App,
|
||||
docker *docker.App,
|
||||
fail2ban *fail2ban.App,
|
||||
frp *frp.App,
|
||||
gitea *gitea.App,
|
||||
memcached *memcached.App,
|
||||
minio *minio.App,
|
||||
mysql *mysql.App,
|
||||
nginx *nginx.App,
|
||||
php74 *php74.App,
|
||||
php80 *php80.App,
|
||||
php81 *php81.App,
|
||||
php82 *php82.App,
|
||||
php83 *php83.App,
|
||||
php84 *php84.App,
|
||||
phpmyadmin *phpmyadmin.App,
|
||||
podman *podman.App,
|
||||
postgresql *postgresql.App,
|
||||
pureftpd *pureftpd.App,
|
||||
redis *redis.App,
|
||||
rsync *rsync.App,
|
||||
s3fs *s3fs.App,
|
||||
supervisor *supervisor.App,
|
||||
toolbox *toolbox.App,
|
||||
) *apploader.Loader {
|
||||
loader := new(apploader.Loader)
|
||||
loader.Add(benchmark, docker, fail2ban, frp, gitea, memcached, minio, mysql, nginx, php74, php80, php81, php82, php83, php84, phpmyadmin, podman, postgresql, pureftpd, redis, rsync, s3fs, supervisor, toolbox)
|
||||
return loader
|
||||
}
|
||||
@@ -3,4 +3,4 @@ package bootstrap
|
||||
import "github.com/google/wire"
|
||||
|
||||
// ProviderSet is bootstrap providers.
|
||||
var ProviderSet = wire.NewSet(NewConf, NewLog, NewCli, NewValidator, NewRouter, NewHttp, NewDB, NewMigrate, NewSession, NewCron, NewQueue)
|
||||
var ProviderSet = wire.NewSet(NewConf, NewLog, NewCli, NewValidator, NewRouter, NewHttp, NewDB, NewMigrate, NewLoader, NewSession, NewCron, NewQueue)
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"github.com/knadh/koanf/v2"
|
||||
slogGorm "github.com/orandin/slog-gorm"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/gormlite"
|
||||
sloggorm "github.com/orandin/slog-gorm"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -16,8 +17,8 @@ import (
|
||||
|
||||
func NewDB(conf *koanf.Koanf, log *slog.Logger) (*gorm.DB, error) {
|
||||
// You can use any other database, like MySQL or PostgreSQL.
|
||||
return gorm.Open(sqlite.Open(filepath.Join(app.Root, "panel/storage/app.db")), &gorm.Config{
|
||||
Logger: slogGorm.New(slogGorm.WithHandler(log.Handler())),
|
||||
return gorm.Open(gormlite.Open(filepath.Join(app.Root, "panel/storage/app.db")), &gorm.Config{
|
||||
Logger: sloggorm.New(sloggorm.WithHandler(log.Handler())),
|
||||
SkipDefaultTransaction: true,
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@ func NewHttp(conf *koanf.Koanf, r *chi.Mux) (*hlfhr.Server, error) {
|
||||
|
||||
if conf.Bool("http.tls") {
|
||||
srv.TLSConfig = &tls.Config{
|
||||
MinVersion: tls.VersionTLS13,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ func NewSession(conf *koanf.Koanf, db *gorm.DB) (*sessions.Manager, error) {
|
||||
manager, err := sessions.NewManager(&sessions.ManagerOptions{
|
||||
Key: conf.MustString("app.key"),
|
||||
Lifetime: 120,
|
||||
GcInterval: 30,
|
||||
GcInterval: 5,
|
||||
DisableDefaultDriver: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
+23
-11
@@ -42,7 +42,7 @@ func (r *backupRepo) List(typ biz.BackupType) ([]*types.BackupFile, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files, err := io.ReadDir(path)
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func (r *backupRepo) CutoffLog(path, target string) error {
|
||||
// prefix 目标文件前缀
|
||||
// save 保存份数
|
||||
func (r *backupRepo) ClearExpired(path, prefix string, save int) error {
|
||||
files, err := io.ReadDir(path)
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func (r *backupRepo) GetPath(typ biz.BackupType) (string, error) {
|
||||
|
||||
backupPath = filepath.Join(backupPath, string(typ))
|
||||
if !io.Exists(backupPath) {
|
||||
if err = io.Mkdir(backupPath, 0644); err != nil {
|
||||
if err = os.MkdirAll(backupPath, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@@ -338,7 +338,7 @@ func (r *backupRepo) createPanel(to string) error {
|
||||
|
||||
start := time.Now()
|
||||
|
||||
temp, err := io.TempDir("panel-backup")
|
||||
temp, err := os.MkdirTemp("", "panel-backup")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -418,11 +418,13 @@ func (r *backupRepo) restoreMySQL(backup, target string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
clean := false
|
||||
if !strings.HasSuffix(backup, ".sql") {
|
||||
backup, err = r.autoUnCompressSQL(backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clean = true
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`mysql -u root '%s' < '%s'`, target, backup); err != nil {
|
||||
@@ -431,8 +433,10 @@ func (r *backupRepo) restoreMySQL(backup, target string) error {
|
||||
if err = os.Unsetenv("MYSQL_PWD"); err != nil {
|
||||
return err
|
||||
}
|
||||
if clean {
|
||||
_ = io.Remove(filepath.Dir(backup))
|
||||
}
|
||||
|
||||
_ = io.Remove(filepath.Dir(backup))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -451,18 +455,22 @@ func (r *backupRepo) restorePostgres(backup, target string) error {
|
||||
return fmt.Errorf("数据库不存在:%s", target)
|
||||
}
|
||||
|
||||
clean := false
|
||||
if !strings.HasSuffix(backup, ".sql") {
|
||||
backup, err = r.autoUnCompressSQL(backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clean = true
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`su - postgres -c "psql '%s'" < '%s'`, target, backup); err != nil {
|
||||
return err
|
||||
}
|
||||
if clean {
|
||||
_ = io.Remove(filepath.Dir(backup))
|
||||
}
|
||||
|
||||
_ = io.Remove(filepath.Dir(backup))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -494,9 +502,10 @@ func (r *backupRepo) preCheckPath(to, path string) error {
|
||||
if uint64(size) > usage.Free {
|
||||
return errors.New("备份目录空间不足")
|
||||
}
|
||||
if uint64(files) > usage.InodesFree {
|
||||
// 对于 fuse 等文件系统,可能没有 inode 的概念
|
||||
/*if uint64(files) > usage.InodesFree {
|
||||
return errors.New("备份目录Inode不足")
|
||||
}
|
||||
}*/
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -525,7 +534,7 @@ func (r *backupRepo) preCheckDB(to string, size int64) error {
|
||||
|
||||
// autoUnCompressSQL 自动处理压缩文件
|
||||
func (r *backupRepo) autoUnCompressSQL(backup string) (string, error) {
|
||||
temp, err := io.TempDir(backup)
|
||||
temp, err := os.MkdirTemp("", "sql-uncompress")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -674,7 +683,7 @@ func (r *backupRepo) FixPanel() error {
|
||||
if err = io.Chmod("/usr/local/etc/panel/config.yml", 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Chmod("/etc/systemd/system/panel.service", 0700); err != nil {
|
||||
if err = io.Chmod("/etc/systemd/system/panel.service", 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = io.Chmod("/usr/local/sbin/panel-cli", 0700); err != nil {
|
||||
@@ -815,7 +824,7 @@ func (r *backupRepo) UpdatePanel(version, url, checksum string) error {
|
||||
fmt.Println("|-设置关键文件权限...")
|
||||
}
|
||||
_ = io.Chmod("/usr/local/sbin/panel-cli", 0700)
|
||||
_ = io.Chmod("/etc/systemd/system/panel.service", 0700)
|
||||
_ = io.Chmod("/etc/systemd/system/panel.service", 0644)
|
||||
_ = io.Chmod(filepath.Join(app.Root, "panel"), 0700)
|
||||
|
||||
if app.IsCli {
|
||||
@@ -825,6 +834,9 @@ func (r *backupRepo) UpdatePanel(version, url, checksum string) error {
|
||||
_, _ = shell.Execf("systemctl daemon-reload")
|
||||
_ = io.Remove("/tmp/panel-storage.zip")
|
||||
_ = io.Remove(filepath.Join(app.Root, "panel/config.example.yml"))
|
||||
if sqlDB, err := r.db.DB(); err == nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
tools.RestartPanel()
|
||||
|
||||
return nil
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/apps"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/pkg/api"
|
||||
"github.com/tnb-labs/panel/pkg/apploader"
|
||||
)
|
||||
|
||||
type cacheRepo struct {
|
||||
@@ -57,8 +57,8 @@ func (r *cacheRepo) UpdateApps() error {
|
||||
}
|
||||
|
||||
// 去除本地不存在的应用
|
||||
*remote = slices.Clip(slices.DeleteFunc(*remote, func(app *api.App) bool {
|
||||
return !slices.Contains(apps.Slugs(), app.Slug)
|
||||
*remote = slices.Clip(slices.DeleteFunc(*remote, func(item *api.App) bool {
|
||||
return !slices.Contains(apploader.Slugs(), item.Slug)
|
||||
}))
|
||||
|
||||
encoded, err := json.Marshal(remote)
|
||||
|
||||
+61
-6
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -23,12 +25,14 @@ import (
|
||||
|
||||
type certRepo struct {
|
||||
db *gorm.DB
|
||||
log *slog.Logger
|
||||
client *acme.Client
|
||||
}
|
||||
|
||||
func NewCertRepo(db *gorm.DB) biz.CertRepo {
|
||||
func NewCertRepo(db *gorm.DB, log *slog.Logger) biz.CertRepo {
|
||||
return &certRepo{
|
||||
db: db,
|
||||
db: db,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +53,8 @@ func (r *certRepo) List(page, limit uint) ([]*types.CertList, int64, error) {
|
||||
AutoRenew: cert.AutoRenew,
|
||||
Cert: cert.Cert,
|
||||
Key: cert.Key,
|
||||
CertURL: cert.CertURL,
|
||||
Script: cert.Script,
|
||||
CreatedAt: cert.CreatedAt,
|
||||
UpdatedAt: cert.UpdatedAt,
|
||||
}
|
||||
@@ -119,6 +125,9 @@ func (r *certRepo) Update(req *request.CertUpdate) error {
|
||||
if err == nil && req.Type == "upload" {
|
||||
req.Domains = info.DNSNames
|
||||
}
|
||||
if req.Type == "upload" && req.AutoRenew {
|
||||
return errors.New("upload certificate cannot be set to auto renew")
|
||||
}
|
||||
|
||||
return r.db.Model(&biz.Cert{}).Where("id = ?", req.ID).Select("*").Updates(&biz.Cert{
|
||||
ID: req.ID,
|
||||
@@ -128,6 +137,7 @@ func (r *certRepo) Update(req *request.CertUpdate) error {
|
||||
Type: req.Type,
|
||||
Cert: req.Cert,
|
||||
Key: req.Key,
|
||||
Script: req.Script,
|
||||
Domains: req.Domains,
|
||||
AutoRenew: req.AutoRenew,
|
||||
}).Error
|
||||
@@ -149,7 +159,7 @@ func (r *certRepo) ObtainAuto(id uint) (*acme.Certificate, error) {
|
||||
}
|
||||
|
||||
if cert.DNS != nil {
|
||||
client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data)
|
||||
client.UseDns(cert.DNS.Type, cert.DNS.Data)
|
||||
} else {
|
||||
if cert.Website == nil {
|
||||
return nil, errors.New("this certificate is not associated with a website and cannot be signed. You can try to sign it manually")
|
||||
@@ -180,6 +190,10 @@ func (r *certRepo) ObtainAuto(id uint) (*acme.Certificate, error) {
|
||||
return &ssl, r.Deploy(cert.ID, cert.WebsiteID)
|
||||
}
|
||||
|
||||
if err = r.runScript(cert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ssl, nil
|
||||
}
|
||||
|
||||
@@ -209,6 +223,10 @@ func (r *certRepo) ObtainManual(id uint) (*acme.Certificate, error) {
|
||||
return &ssl, r.Deploy(cert.ID, cert.WebsiteID)
|
||||
}
|
||||
|
||||
if err = r.runScript(cert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ssl, nil
|
||||
}
|
||||
|
||||
@@ -233,6 +251,10 @@ func (r *certRepo) ObtainSelfSigned(id uint) error {
|
||||
return r.Deploy(cert.ID, cert.WebsiteID)
|
||||
}
|
||||
|
||||
if err = r.runScript(cert); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -252,7 +274,7 @@ func (r *certRepo) Renew(id uint) (*acme.Certificate, error) {
|
||||
}
|
||||
|
||||
if cert.DNS != nil {
|
||||
client.UseDns(acme.DnsType(cert.DNS.Type), cert.DNS.Data)
|
||||
client.UseDns(cert.DNS.Type, cert.DNS.Data)
|
||||
} else {
|
||||
if cert.Website == nil {
|
||||
return nil, errors.New("this certificate is not associated with a website and cannot be signed. You can try to sign it manually")
|
||||
@@ -269,7 +291,11 @@ func (r *certRepo) Renew(id uint) (*acme.Certificate, error) {
|
||||
|
||||
ssl, err := client.RenewCertificate(context.Background(), cert.CertURL, cert.Domains, acme.KeyType(cert.Type))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// 续签失败,尝试重签
|
||||
ssl, err = client.ObtainCertificate(context.Background(), cert.Domains, acme.KeyType(cert.Type))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cert.CertURL = ssl.URL
|
||||
@@ -341,6 +367,35 @@ func (r *certRepo) Deploy(ID, WebsiteID uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *certRepo) runScript(cert *biz.Cert) error {
|
||||
if cert.Script == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp("", "cert-deploy-*.sh")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 替换变量
|
||||
cert.Script = strings.ReplaceAll(cert.Script, "{cert}", cert.Cert)
|
||||
cert.Script = strings.ReplaceAll(cert.Script, "{key}", cert.Key)
|
||||
|
||||
if _, err = f.WriteString(cert.Script); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.Chmod(0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
_, err = shell.Execf("bash " + f.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *certRepo) getClient(cert *biz.Cert) (*acme.Client, error) {
|
||||
if cert.Account == nil {
|
||||
return nil, errors.New("this certificate is not associated with an ACME account and cannot be signed")
|
||||
@@ -367,5 +422,5 @@ func (r *certRepo) getClient(cert *biz.Cert) (*acme.Client, error) {
|
||||
eab = &acme.EAB{KeyID: cert.Account.Kid, MACKey: cert.Account.HmacEncoded}
|
||||
}
|
||||
|
||||
return acme.NewPrivateKeyAccount(cert.Account.Email, cert.Account.PrivateKey, ca, eab)
|
||||
return acme.NewPrivateKeyAccount(cert.Account.Email, cert.Account.PrivateKey, ca, eab, r.log)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
@@ -17,12 +18,14 @@ import (
|
||||
|
||||
type certAccountRepo struct {
|
||||
db *gorm.DB
|
||||
log *slog.Logger
|
||||
user biz.UserRepo
|
||||
}
|
||||
|
||||
func NewCertAccountRepo(db *gorm.DB, user biz.UserRepo) biz.CertAccountRepo {
|
||||
func NewCertAccountRepo(db *gorm.DB, user biz.UserRepo, log *slog.Logger) biz.CertAccountRepo {
|
||||
return &certAccountRepo{
|
||||
db: db,
|
||||
log: log,
|
||||
user: user,
|
||||
}
|
||||
}
|
||||
@@ -78,13 +81,13 @@ func (r certAccountRepo) Create(req *request.CertAccountCreate) (*biz.CertAccoun
|
||||
}
|
||||
account.Kid = eab.KeyID
|
||||
account.HmacEncoded = eab.MACKey
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogleCN, eab, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogleCN, eab, acme.KeyType(account.KeyType), r.log)
|
||||
case "google":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogle, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogle, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType), r.log)
|
||||
case "letsencrypt":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CALetsEncrypt, nil, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CALetsEncrypt, nil, acme.KeyType(account.KeyType), r.log)
|
||||
case "buypass":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CABuypass, nil, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CABuypass, nil, acme.KeyType(account.KeyType), r.log)
|
||||
case "zerossl":
|
||||
eab, eabErr := r.getZeroSSLEAB(account.Email)
|
||||
if eabErr != nil {
|
||||
@@ -92,9 +95,9 @@ func (r certAccountRepo) Create(req *request.CertAccountCreate) (*biz.CertAccoun
|
||||
}
|
||||
account.Kid = eab.KeyID
|
||||
account.HmacEncoded = eab.MACKey
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAZeroSSL, eab, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAZeroSSL, eab, acme.KeyType(account.KeyType), r.log)
|
||||
case "sslcom":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CASSLcom, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CASSLcom, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType), r.log)
|
||||
default:
|
||||
return nil, errors.New("unsupported CA")
|
||||
}
|
||||
@@ -137,13 +140,13 @@ func (r certAccountRepo) Update(req *request.CertAccountUpdate) error {
|
||||
}
|
||||
account.Kid = eab.KeyID
|
||||
account.HmacEncoded = eab.MACKey
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogleCN, eab, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogleCN, eab, acme.KeyType(account.KeyType), r.log)
|
||||
case "google":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogle, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAGoogle, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType), r.log)
|
||||
case "letsencrypt":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CALetsEncrypt, nil, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CALetsEncrypt, nil, acme.KeyType(account.KeyType), r.log)
|
||||
case "buypass":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CABuypass, nil, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CABuypass, nil, acme.KeyType(account.KeyType), r.log)
|
||||
case "zerossl":
|
||||
eab, eabErr := r.getZeroSSLEAB(account.Email)
|
||||
if eabErr != nil {
|
||||
@@ -151,9 +154,9 @@ func (r certAccountRepo) Update(req *request.CertAccountUpdate) error {
|
||||
}
|
||||
account.Kid = eab.KeyID
|
||||
account.HmacEncoded = eab.MACKey
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAZeroSSL, eab, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CAZeroSSL, eab, acme.KeyType(account.KeyType), r.log)
|
||||
case "sslcom":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CASSLcom, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType))
|
||||
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CASSLcom, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType), r.log)
|
||||
default:
|
||||
return errors.New("unsupported CA")
|
||||
}
|
||||
|
||||
+11
-11
@@ -39,7 +39,7 @@ func (r *containerRepo) ListAll() ([]types.Container, error) {
|
||||
for _, port := range item.Ports {
|
||||
ports = append(ports, types.ContainerPort{
|
||||
ContainerStart: uint(port.PrivatePort),
|
||||
ContainerEnd: uint(port.PublicPort),
|
||||
ContainerEnd: uint(port.PrivatePort),
|
||||
HostStart: uint(port.PublicPort),
|
||||
HostEnd: uint(port.PublicPort),
|
||||
Protocol: port.Type,
|
||||
@@ -152,59 +152,59 @@ func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) {
|
||||
|
||||
// Remove 移除容器
|
||||
func (r *containerRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rm -f %s", id)
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Start 启动容器
|
||||
func (r *containerRepo) Start(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker start %s", id)
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker start %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop 停止容器
|
||||
func (r *containerRepo) Stop(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker stop %s", id)
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker stop %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart 重启容器
|
||||
func (r *containerRepo) Restart(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker restart %s", id)
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker restart %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Pause 暂停容器
|
||||
func (r *containerRepo) Pause(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker pause %s", id)
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker pause %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Unpause 恢复容器
|
||||
func (r *containerRepo) Unpause(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker unpause %s", id)
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker unpause %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Kill 杀死容器
|
||||
func (r *containerRepo) Kill(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker kill %s", id)
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker kill %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename 重命名容器
|
||||
func (r *containerRepo) Rename(id string, newName string) error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rename %s %s", id, newName)
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker rename %s %s", id, newName)
|
||||
return err
|
||||
}
|
||||
|
||||
// Logs 查看容器日志
|
||||
func (r *containerRepo) Logs(id string) (string, error) {
|
||||
return shell.ExecfWithTimeout(120*time.Second, "docker logs %s", id)
|
||||
return shell.ExecfWithTimeout(2*time.Minute, "docker logs %s", id)
|
||||
}
|
||||
|
||||
// Prune 清理未使用的容器
|
||||
func (r *containerRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker container prune -f")
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker container prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
type containerComposeRepo struct{}
|
||||
|
||||
func NewContainerComposeRepo() biz.ContainerComposeRepo {
|
||||
return &containerComposeRepo{}
|
||||
}
|
||||
|
||||
// List 列出所有编排文件名
|
||||
func (r *containerComposeRepo) List() ([]types.ContainerCompose, error) {
|
||||
raw, err := shell.Execf("docker compose ls -a --format json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var composeRaws []types.ContainerComposeRaw
|
||||
if err = json.Unmarshal([]byte(raw), &composeRaws); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
composeDir := filepath.Join(app.Root, "server", "compose")
|
||||
entries, err := os.ReadDir(composeDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var composes []types.ContainerCompose
|
||||
index := make(map[string]int)
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
path := filepath.Join(composeDir, entry.Name())
|
||||
var createdAt time.Time
|
||||
if info, err := entry.Info(); err == nil {
|
||||
createdAt = info.ModTime()
|
||||
}
|
||||
|
||||
composes = append(composes, types.ContainerCompose{
|
||||
Name: entry.Name(),
|
||||
Path: path,
|
||||
Status: "unknown",
|
||||
CreatedAt: createdAt,
|
||||
})
|
||||
index[entry.Name()] = len(composes) - 1
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
for _, item := range composeRaws {
|
||||
if i, ok := index[item.Name]; ok {
|
||||
composes[i].Status = item.Status
|
||||
}
|
||||
}
|
||||
|
||||
return composes, nil
|
||||
}
|
||||
|
||||
// Get 获取编排文件和环境变量内容
|
||||
func (r *containerComposeRepo) Get(name string) (string, []types.KV, error) {
|
||||
content, _ := os.ReadFile(filepath.Join(app.Root, "server", "compose", name, "docker-compose.yml"))
|
||||
env, _ := os.ReadFile(filepath.Join(app.Root, "server", "compose", name, ".env"))
|
||||
|
||||
var envs []types.KV
|
||||
for _, line := range strings.Split(string(env), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
kv := strings.SplitN(line, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
envs = append(envs, types.KV{Key: kv[0], Value: kv[1]})
|
||||
}
|
||||
|
||||
return string(content), envs, nil // 有意忽略错误,这样可以允许新建文件
|
||||
}
|
||||
|
||||
// Create 创建编排文件
|
||||
func (r *containerComposeRepo) Create(name, compose string, envs []types.KV) error {
|
||||
dir := filepath.Join(app.Root, "server", "compose", name)
|
||||
if err := os.MkdirAll(dir, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "docker-compose.yml"), []byte(compose), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for _, kv := range envs {
|
||||
sb.WriteString(kv.Key)
|
||||
sb.WriteString("=")
|
||||
sb.WriteString(kv.Value)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, ".env"), []byte(sb.String()), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update 更新编排文件
|
||||
func (r *containerComposeRepo) Update(name, compose string, envs []types.KV) error {
|
||||
dir := filepath.Join(app.Root, "server", "compose", name)
|
||||
if err := os.WriteFile(filepath.Join(dir, "docker-compose.yml"), []byte(compose), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for _, kv := range envs {
|
||||
sb.WriteString(kv.Key)
|
||||
sb.WriteString("=")
|
||||
sb.WriteString(kv.Value)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, ".env"), []byte(sb.String()), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Up 启动编排
|
||||
func (r *containerComposeRepo) Up(name string, force bool) error {
|
||||
file := filepath.Join(app.Root, "server", "compose", name, "docker-compose.yml")
|
||||
cmd := "docker compose -f %s up -d"
|
||||
if force {
|
||||
cmd += " --pull always" // 强制拉取镜像
|
||||
}
|
||||
_, err := shell.Execf(cmd, file)
|
||||
return err
|
||||
}
|
||||
|
||||
// Down 停止编排
|
||||
func (r *containerComposeRepo) Down(name string) error {
|
||||
file := filepath.Join(app.Root, "server", "compose", name, "docker-compose.yml")
|
||||
_, err := shell.Execf("docker compose -f %s down", file)
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove 删除编排
|
||||
func (r *containerComposeRepo) Remove(name string) error {
|
||||
dir := filepath.Join(app.Root, "server", "compose", name)
|
||||
return os.RemoveAll(dir)
|
||||
}
|
||||
@@ -77,12 +77,12 @@ func (r *containerImageRepo) Pull(req *request.ContainerImagePull) error {
|
||||
|
||||
// Remove 删除镜像
|
||||
func (r *containerImageRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rmi %s", id)
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker rmi %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的镜像
|
||||
func (r *containerImageRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker image prune -f")
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker image prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -104,12 +104,12 @@ func (r *containerNetworkRepo) Create(req *request.ContainerNetworkCreate) (stri
|
||||
|
||||
// Remove 删除网络
|
||||
func (r *containerNetworkRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker network rm -f %s", id)
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker network rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的网络
|
||||
func (r *containerNetworkRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker network prune -f")
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker network prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -78,12 +78,12 @@ func (r *containerVolumeRepo) Create(req *request.ContainerVolumeCreate) (string
|
||||
|
||||
// Remove 删除存储卷
|
||||
func (r *containerVolumeRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker volume rm -f %s", id)
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker volume rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的存储卷
|
||||
func (r *containerVolumeRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker volume prune -f")
|
||||
_, err := shell.ExecfWithTimeout(2*time.Minute, "docker volume prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
panel-cli backup website -n '%s' -p '%s'
|
||||
panel-cli backup clear -t website -f '%s' -s '%d' -p '%s'
|
||||
systemctl reload nginx
|
||||
`, req.Target, req.BackupPath, req.Target, req.Save, req.BackupPath)
|
||||
}
|
||||
if req.BackupType == "mysql" || req.BackupType == "postgres" {
|
||||
@@ -85,8 +86,8 @@ export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
# 耗子面板 - 日志切割脚本
|
||||
|
||||
# 执行切割
|
||||
panel-cli cutoff website -n %s -p %s
|
||||
panel-cli cutoff clear -t website -f %s -s %d -p %s
|
||||
panel-cli cutoff website -n '%s' -p '%s'
|
||||
panel-cli cutoff clear -t website -f '%s' -s '%d' -p '%s'
|
||||
`, req.Target, req.BackupPath, req.Target, req.Save, req.BackupPath)
|
||||
}
|
||||
if req.Type == "shell" {
|
||||
|
||||
@@ -11,6 +11,7 @@ var ProviderSet = wire.NewSet(
|
||||
NewCertAccountRepo,
|
||||
NewCertDNSRepo,
|
||||
NewContainerRepo,
|
||||
NewContainerComposeRepo,
|
||||
NewContainerImageRepo,
|
||||
NewContainerNetworkRepo,
|
||||
NewContainerVolumeRepo,
|
||||
|
||||
@@ -93,7 +93,11 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offlineMode, err := r.Get(biz.SettingKeyOfflineMode)
|
||||
offlineMode, err := r.GetBool(biz.SettingKeyOfflineMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
autoUpdate, err := r.GetBool(biz.SettingKeyAutoUpdate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -125,7 +129,8 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin
|
||||
Name: name,
|
||||
Locale: r.conf.String("app.locale"),
|
||||
Entrance: r.conf.String("http.entrance"),
|
||||
OfflineMode: cast.ToBool(offlineMode),
|
||||
OfflineMode: offlineMode,
|
||||
AutoUpdate: autoUpdate,
|
||||
WebsitePath: websitePath,
|
||||
BackupPath: backupPath,
|
||||
Username: user.Username,
|
||||
@@ -144,6 +149,9 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
|
||||
if err := r.Set(biz.SettingKeyOfflineMode, cast.ToString(setting.OfflineMode)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := r.Set(biz.SettingKeyAutoUpdate, cast.ToString(setting.AutoUpdate)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := r.Set(biz.SettingKeyWebsitePath, setting.WebsitePath); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -185,7 +186,13 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||||
rewrite, _ := io.Read(filepath.Join(app.Root, "server/vhost/rewrite", website.Name+".conf"))
|
||||
setting.Rewrite = rewrite
|
||||
// 访问日志
|
||||
setting.Log = fmt.Sprintf("%s/wwwlogs/%s.log", app.Root, website.Name)
|
||||
if setting.Log, err = p.GetAccessLog(); err != nil {
|
||||
setting.Log = fmt.Sprintf("%s/wwwlogs/%s.log", app.Root, website.Name)
|
||||
}
|
||||
// 错误日志
|
||||
if setting.ErrorLog, err = p.GetErrorLog(); err != nil {
|
||||
setting.ErrorLog = fmt.Sprintf("%s/wwwlogs/%s.error.log", app.Root, website.Name)
|
||||
}
|
||||
|
||||
return setting, err
|
||||
}
|
||||
@@ -266,7 +273,7 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
}
|
||||
|
||||
// 初始化网站目录
|
||||
if err = io.Mkdir(req.Path, 0755); err != nil {
|
||||
if err = os.MkdirAll(req.Path, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index, err := embed.WebsiteFS.ReadFile(filepath.Join("website", "index.html"))
|
||||
|
||||
@@ -2,6 +2,8 @@ package embed
|
||||
|
||||
import "embed"
|
||||
|
||||
// TODO 移到pkg目录下
|
||||
|
||||
//go:embed all:frontend/*
|
||||
var PublicFS embed.FS
|
||||
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
package request
|
||||
|
||||
import "mime/multipart"
|
||||
|
||||
type BackupList struct {
|
||||
Type string `json:"type" form:"type" validate:"required|in:path,website,mysql,postgres,redis,panel"`
|
||||
Type string `uri:"type" form:"type" validate:"required|in:path,website,mysql,postgres,redis,panel"`
|
||||
}
|
||||
|
||||
type BackupCreate struct {
|
||||
Type string `json:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
Type string `uri:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
Target string `json:"target" form:"target" validate:"required"`
|
||||
Path string `json:"path" form:"path"`
|
||||
}
|
||||
|
||||
type BackupUpload struct {
|
||||
Type string `uri:"type" form:"type"` // 校验没有必要,因为根本没经过验证器
|
||||
File *multipart.FileHeader `form:"file"`
|
||||
}
|
||||
|
||||
type BackupFile struct {
|
||||
Type string `json:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
Type string `uri:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
File string `json:"file" form:"file" validate:"required"`
|
||||
}
|
||||
|
||||
type BackupRestore struct {
|
||||
Type string `json:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
Type string `uri:"type" form:"type" validate:"required|in:website,mysql,postgres,redis,panel"`
|
||||
File string `json:"file" form:"file" validate:"required"`
|
||||
Target string `json:"target" form:"target" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type CertUpdate struct {
|
||||
Domains []string `form:"domains" json:"domains" validate:"required|isSlice"`
|
||||
Cert string `form:"cert" json:"cert"`
|
||||
Key string `form:"key" json:"key"`
|
||||
Script string `form:"script" json:"script"`
|
||||
AutoRenew bool `form:"auto_renew" json:"auto_renew"`
|
||||
AccountID uint `form:"account_id" json:"account_id"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id"`
|
||||
|
||||
@@ -3,14 +3,14 @@ package request
|
||||
import "github.com/tnb-labs/panel/pkg/acme"
|
||||
|
||||
type CertDNSCreate struct {
|
||||
Type string `form:"type" json:"type" validate:"required"`
|
||||
Type acme.DnsType `form:"type" json:"type" validate:"required|in:aliyun,tencent,huawei,westcn,cloudflare,godaddy,gcore,porkbun,namecheap,namesilo,namecom,cloudns,duckdns,hetzner,linode,vercel"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Data acme.DNSParam `form:"data" json:"data" validate:"required"`
|
||||
}
|
||||
|
||||
type CertDNSUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:cert_dns,id"`
|
||||
Type string `form:"type" json:"type" validate:"required"`
|
||||
Type acme.DnsType `form:"type" json:"type" validate:"required|in:aliyun,tencent,huawei,westcn,cloudflare,godaddy,gcore,porkbun,namecheap,namesilo,namecom,cloudns,duckdns,hetzner,linode,vercel"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Data acme.DNSParam `form:"data" json:"data" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package request
|
||||
|
||||
import "github.com/tnb-labs/panel/pkg/types"
|
||||
|
||||
type ContainerComposeGet struct {
|
||||
Name string `uri:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type ContainerComposeCreate struct {
|
||||
Name string `json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Compose string `json:"compose" validate:"required"`
|
||||
Envs []types.KV `json:"envs"`
|
||||
}
|
||||
|
||||
type ContainerComposeUpdate struct {
|
||||
Name string `uri:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Compose string `json:"compose" validate:"required"`
|
||||
Envs []types.KV `json:"envs"`
|
||||
}
|
||||
|
||||
type ContainerComposeUp struct {
|
||||
Name string `uri:"name" validate:"required"`
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
|
||||
type ContainerComposeDown struct {
|
||||
Name string `uri:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type ContainerComposeRemove struct {
|
||||
Name string `uri:"name" validate:"required"`
|
||||
}
|
||||
@@ -7,7 +7,7 @@ type CronCreate struct {
|
||||
Script string `form:"script" json:"script"`
|
||||
BackupType string `form:"backup_type" json:"backup_type" validate:"requiredIf:Type,backup"`
|
||||
BackupPath string `form:"backup_path" json:"backup_path"`
|
||||
Target string `form:"target" json:"target" validate:"required"`
|
||||
Target string `form:"target" json:"target" validate:"requiredIf:Type,backup,cutoff"`
|
||||
Save int `form:"save" json:"save" validate:"required"`
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ type PanelSetting struct {
|
||||
Locale string `json:"locale" validate:"required"`
|
||||
Entrance string `json:"entrance" validate:"required"`
|
||||
OfflineMode bool `json:"offline_mode"`
|
||||
AutoUpdate bool `json:"auto_update"`
|
||||
WebsitePath string `json:"website_path" validate:"required"`
|
||||
BackupPath string `json:"backup_path" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
|
||||
@@ -13,11 +13,11 @@ import (
|
||||
// 例子:exists:users,phone,email
|
||||
// Example: exists:users,phone,email
|
||||
type Exists struct {
|
||||
DB *gorm.DB
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewExists(db *gorm.DB) *Exists {
|
||||
return &Exists{DB: db}
|
||||
return &Exists{db: db}
|
||||
}
|
||||
|
||||
func (r *Exists) Passes(val any, options ...any) bool {
|
||||
@@ -28,7 +28,7 @@ func (r *Exists) Passes(val any, options ...any) bool {
|
||||
tableName := options[0].(string)
|
||||
fieldNames := options[1:]
|
||||
|
||||
query := r.DB.Table(tableName).Where(fmt.Sprintf("%s = ?", fieldNames[0]), val)
|
||||
query := r.db.Table(tableName).Where(fmt.Sprintf("%s = ?", fieldNames[0]), val)
|
||||
for _, fieldName := range fieldNames[1:] {
|
||||
query = query.Or(fmt.Sprintf("%s = ?", fieldName), val)
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ import (
|
||||
// 例子:notExists:users,phone,email
|
||||
// Example: notExists:users,phone,email
|
||||
type NotExists struct {
|
||||
DB *gorm.DB
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewNotExists(db *gorm.DB) *NotExists {
|
||||
return &NotExists{DB: db}
|
||||
return &NotExists{db: db}
|
||||
}
|
||||
|
||||
func (r *NotExists) Passes(val any, options ...any) bool {
|
||||
@@ -28,7 +28,7 @@ func (r *NotExists) Passes(val any, options ...any) bool {
|
||||
tableName := options[0].(string)
|
||||
fieldNames := options[1:]
|
||||
|
||||
query := r.DB.Table(tableName).Where(fmt.Sprintf("%s = ?", fieldNames[0]), val)
|
||||
query := r.db.Table(tableName).Where(fmt.Sprintf("%s = ?", fieldNames[0]), val)
|
||||
for _, fieldName := range fieldNames[1:] {
|
||||
query = query.Or(fmt.Sprintf("%s = ?", fieldName), val)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (r *CertRenew) Run() {
|
||||
|
||||
var certs []biz.Cert
|
||||
if err := r.db.Preload("Website").Preload("Account").Preload("DNS").Find(&certs).Error; err != nil {
|
||||
r.log.Warn("获取证书失败", slog.Any("err", err))
|
||||
r.log.Warn("[Cert Renew] failed to get certs", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -48,14 +48,13 @@ func (r *CertRenew) Run() {
|
||||
}
|
||||
|
||||
// 结束时间大于 7 天的证书不续签
|
||||
now := time.Now()
|
||||
if decode.NotAfter.Sub(now).Hours() > 24*7 {
|
||||
if time.Until(decode.NotAfter) > 24*7*time.Hour {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = r.certRepo.Renew(cert.ID)
|
||||
if err != nil {
|
||||
r.log.Warn("续签证书失败", slog.Any("err", err))
|
||||
r.log.Warn("[Cert Renew] failed to renew cert", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -19,9 +19,10 @@ type Jobs struct {
|
||||
cert biz.CertRepo
|
||||
backup biz.BackupRepo
|
||||
cache biz.CacheRepo
|
||||
task biz.TaskRepo
|
||||
}
|
||||
|
||||
func NewJobs(db *gorm.DB, log *slog.Logger, setting biz.SettingRepo, cert biz.CertRepo, backup biz.BackupRepo, cache biz.CacheRepo) *Jobs {
|
||||
func NewJobs(db *gorm.DB, log *slog.Logger, setting biz.SettingRepo, cert biz.CertRepo, backup biz.BackupRepo, cache biz.CacheRepo, task biz.TaskRepo) *Jobs {
|
||||
return &Jobs{
|
||||
db: db,
|
||||
log: log,
|
||||
@@ -29,6 +30,7 @@ func NewJobs(db *gorm.DB, log *slog.Logger, setting biz.SettingRepo, cert biz.Ce
|
||||
cert: cert,
|
||||
backup: backup,
|
||||
cache: cache,
|
||||
task: task,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +42,7 @@ func (r *Jobs) Register(c *cron.Cron) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := c.AddJob("0 2 * * *", NewPanelTask(r.db, r.log, r.backup, r.cache, r.setting)); err != nil {
|
||||
if _, err := c.AddJob("0 2 * * *", NewPanelTask(r.db, r.log, r.backup, r.cache, r.task, r.setting)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ func (r *Monitoring) Run() {
|
||||
}
|
||||
|
||||
if err = r.db.Create(&biz.Monitor{Info: info}).Error; err != nil {
|
||||
r.log.Warn("记录系统监控失败", slog.Any("err", err))
|
||||
r.log.Warn("[Monitor] failed to create monitor record", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func (r *Monitoring) Run() {
|
||||
return
|
||||
}
|
||||
if err = r.db.Where("created_at < ?", time.Now().AddDate(0, 0, -day).Format(time.DateTime)).Delete(&biz.Monitor{}).Error; err != nil {
|
||||
r.log.Warn("删除过期系统监控失败", slog.Any("err", err))
|
||||
r.log.Warn("[Monitor] failed to delete monitor record", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
+76
-22
@@ -7,27 +7,34 @@ import (
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/go-rat/utils/collect"
|
||||
"github.com/hashicorp/go-version"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/pkg/api"
|
||||
)
|
||||
|
||||
// PanelTask 面板每日任务
|
||||
type PanelTask struct {
|
||||
api *api.API
|
||||
db *gorm.DB
|
||||
log *slog.Logger
|
||||
backupRepo biz.BackupRepo
|
||||
cacheRepo biz.CacheRepo
|
||||
taskRepo biz.TaskRepo
|
||||
settingRepo biz.SettingRepo
|
||||
}
|
||||
|
||||
func NewPanelTask(db *gorm.DB, log *slog.Logger, backup biz.BackupRepo, cache biz.CacheRepo, setting biz.SettingRepo) *PanelTask {
|
||||
func NewPanelTask(db *gorm.DB, log *slog.Logger, backup biz.BackupRepo, cache biz.CacheRepo, task biz.TaskRepo, setting biz.SettingRepo) *PanelTask {
|
||||
return &PanelTask{
|
||||
api: api.NewAPI(app.Version),
|
||||
db: db,
|
||||
log: log,
|
||||
backupRepo: backup,
|
||||
cacheRepo: cache,
|
||||
taskRepo: task,
|
||||
settingRepo: setting,
|
||||
}
|
||||
}
|
||||
@@ -38,11 +45,18 @@ func (r *PanelTask) Run() {
|
||||
// 优化数据库
|
||||
if err := r.db.Exec("VACUUM").Error; err != nil {
|
||||
app.Status = app.StatusFailed
|
||||
r.log.Warn("优化面板数据库失败", slog.Any("err", err))
|
||||
r.log.Warn("[Panel Task] failed to vacuum database", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
if err := r.db.Exec("PRAGMA journal_mode=WAL;").Error; err != nil {
|
||||
app.Status = app.StatusFailed
|
||||
r.log.Warn("[Panel Task] failed to set database journal_mode to WAL", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
if err := r.db.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error; err != nil {
|
||||
app.Status = app.StatusFailed
|
||||
r.log.Warn("优化面板数据库失败", slog.Any("err", err))
|
||||
r.log.Warn("[Panel Task] failed to wal checkpoint database", slog.Any("err", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 备份面板
|
||||
@@ -51,30 +65,20 @@ func (r *PanelTask) Run() {
|
||||
}
|
||||
|
||||
// 清理备份
|
||||
path, err := r.backupRepo.GetPath("panel")
|
||||
if err == nil {
|
||||
if path, err := r.backupRepo.GetPath("panel"); err == nil {
|
||||
if err = r.backupRepo.ClearExpired(path, "panel_", 10); err != nil {
|
||||
r.log.Warn("清理面板备份失败", slog.Any("err", err))
|
||||
r.log.Warn("[Panel Task] failed to clear backup", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
|
||||
// 更新商店缓存
|
||||
time.AfterFunc(time.Duration(rand.IntN(300))*time.Second, func() {
|
||||
if offline, err := r.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline {
|
||||
if err = r.cacheRepo.UpdateApps(); err != nil {
|
||||
r.log.Warn("更新商店缓存失败", slog.Any("err", err))
|
||||
}
|
||||
// 非离线模式下任务
|
||||
if offline, err := r.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline {
|
||||
r.updateApps()
|
||||
r.updateRewrites()
|
||||
if autoUpdate, err := r.settingRepo.GetBool(biz.SettingKeyAutoUpdate); err == nil && autoUpdate {
|
||||
r.updatePanel()
|
||||
}
|
||||
})
|
||||
|
||||
// 更新伪静态缓存
|
||||
time.AfterFunc(time.Duration(rand.IntN(300))*time.Second, func() {
|
||||
if offline, err := r.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline {
|
||||
if err = r.cacheRepo.UpdateRewrites(); err != nil {
|
||||
r.log.Warn("更新伪静态缓存失败", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 回收内存
|
||||
runtime.GC()
|
||||
@@ -82,3 +86,53 @@ func (r *PanelTask) Run() {
|
||||
|
||||
app.Status = app.StatusNormal
|
||||
}
|
||||
|
||||
// 更新商店缓存
|
||||
func (r *PanelTask) updateApps() {
|
||||
time.AfterFunc(time.Duration(rand.IntN(300))*time.Second, func() {
|
||||
if err := r.cacheRepo.UpdateApps(); err != nil {
|
||||
r.log.Warn("[Panel Task] failed to update apps cache", slog.Any("err", err))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更新伪静态缓存
|
||||
func (r *PanelTask) updateRewrites() {
|
||||
time.AfterFunc(time.Duration(rand.IntN(300))*time.Second, func() {
|
||||
if err := r.cacheRepo.UpdateRewrites(); err != nil {
|
||||
r.log.Warn("[Panel Task] failed to update rewrites cache", slog.Any("err", err))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更新面板
|
||||
func (r *PanelTask) updatePanel() {
|
||||
if r.taskRepo.HasRunningTask() {
|
||||
return
|
||||
}
|
||||
|
||||
// 加 300 秒确保在缓存更新后才更新面板
|
||||
time.AfterFunc(time.Duration(rand.IntN(300))*time.Second+300*time.Second, func() {
|
||||
panel, err := r.api.LatestVersion()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
current, err := version.NewVersion(app.Version)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
latest, err := version.NewVersion(panel.Version)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if current.GreaterThanOrEqual(latest) {
|
||||
return
|
||||
}
|
||||
if download := collect.First(panel.Downloads); download != nil {
|
||||
if err = r.backupRepo.UpdatePanel(panel.Version, download.URL, download.Checksum); err != nil {
|
||||
r.log.Warn("[Panel Task] failed to update panel", slog.Any("err", err))
|
||||
_ = r.backupRepo.FixPanel()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -69,4 +69,28 @@ func init() {
|
||||
)
|
||||
},
|
||||
})
|
||||
Migrations = append(Migrations, &gormigrate.Migration{
|
||||
ID: "20250318-cert-script",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(
|
||||
&biz.Cache{},
|
||||
&biz.Cert{},
|
||||
&biz.CertDNS{},
|
||||
&biz.CertAccount{},
|
||||
&biz.Cron{},
|
||||
&biz.Monitor{},
|
||||
&biz.App{},
|
||||
&biz.Setting{},
|
||||
&biz.Task{},
|
||||
&biz.User{},
|
||||
&biz.Website{},
|
||||
&biz.SSH{},
|
||||
&biz.DatabaseServer{},
|
||||
&biz.DatabaseUser{},
|
||||
)
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return tx.Migrator().DropColumn(&biz.Cert{}, "script")
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
+15
-3
@@ -8,10 +8,10 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps"
|
||||
"github.com/tnb-labs/panel/internal/embed"
|
||||
"github.com/tnb-labs/panel/internal/http/middleware"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/apploader"
|
||||
)
|
||||
|
||||
type Http struct {
|
||||
@@ -33,6 +33,7 @@ type Http struct {
|
||||
firewall *service.FirewallService
|
||||
ssh *service.SSHService
|
||||
container *service.ContainerService
|
||||
containerCompose *service.ContainerComposeService
|
||||
containerNetwork *service.ContainerNetworkService
|
||||
containerImage *service.ContainerImageService
|
||||
containerVolume *service.ContainerVolumeService
|
||||
@@ -40,7 +41,7 @@ type Http struct {
|
||||
monitor *service.MonitorService
|
||||
setting *service.SettingService
|
||||
systemctl *service.SystemctlService
|
||||
apps *apps.Loader
|
||||
apps *apploader.Loader
|
||||
}
|
||||
|
||||
func NewHttp(
|
||||
@@ -62,6 +63,7 @@ func NewHttp(
|
||||
firewall *service.FirewallService,
|
||||
ssh *service.SSHService,
|
||||
container *service.ContainerService,
|
||||
containerCompose *service.ContainerComposeService,
|
||||
containerNetwork *service.ContainerNetworkService,
|
||||
containerImage *service.ContainerImageService,
|
||||
containerVolume *service.ContainerVolumeService,
|
||||
@@ -69,7 +71,7 @@ func NewHttp(
|
||||
monitor *service.MonitorService,
|
||||
setting *service.SettingService,
|
||||
systemctl *service.SystemctlService,
|
||||
apps *apps.Loader,
|
||||
apps *apploader.Loader,
|
||||
) *Http {
|
||||
return &Http{
|
||||
user: user,
|
||||
@@ -90,6 +92,7 @@ func NewHttp(
|
||||
firewall: firewall,
|
||||
ssh: ssh,
|
||||
container: container,
|
||||
containerCompose: containerCompose,
|
||||
containerNetwork: containerNetwork,
|
||||
containerImage: containerImage,
|
||||
containerVolume: containerVolume,
|
||||
@@ -284,6 +287,15 @@ func (route *Http) Register(r *chi.Mux) {
|
||||
r.Get("/{id}/logs", route.container.Logs)
|
||||
r.Post("/prune", route.container.Prune)
|
||||
})
|
||||
r.Route("/compose", func(r chi.Router) {
|
||||
r.Get("/", route.containerCompose.List)
|
||||
r.Get("/{name}", route.containerCompose.Get)
|
||||
r.Post("/", route.containerCompose.Create)
|
||||
r.Put("/{name}", route.containerCompose.Update)
|
||||
r.Post("/{name}/up", route.containerCompose.Up)
|
||||
r.Post("/{name}/down", route.containerCompose.Down)
|
||||
r.Delete("/{name}", route.containerCompose.Remove)
|
||||
})
|
||||
r.Route("/network", func(r chi.Router) {
|
||||
r.Get("/", route.containerNetwork.List)
|
||||
r.Post("/", route.containerNetwork.Create)
|
||||
|
||||
+22
-15
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
@@ -55,38 +56,44 @@ func (s *BackupService) Create(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *BackupService) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseMultipartForm(2 << 30); err != nil {
|
||||
binder := chix.NewBind(r)
|
||||
defer binder.Release()
|
||||
|
||||
req := new(request.BackupUpload)
|
||||
if err := binder.MultipartForm(req, 2<<30); err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if err := binder.URI(req); err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, handler, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "上传文件失败:%v", err)
|
||||
// 只允许上传 .sql .zip .tar .gz .tgz .bz2 .xz .7z
|
||||
if !slices.Contains([]string{".sql", ".zip", ".tar", ".gz", ".tgz", ".bz2", ".xz", ".7z"}, filepath.Ext(req.File.Filename)) {
|
||||
Error(w, http.StatusForbidden, "unsupported file type")
|
||||
return
|
||||
}
|
||||
path, err := s.backupRepo.GetPath(biz.BackupType(r.FormValue("type")))
|
||||
|
||||
path, err := s.backupRepo.GetPath(biz.BackupType(req.Type))
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !io.Exists(filepath.Dir(path)) {
|
||||
if err = io.Mkdir(filepath.Dir(path), 0755); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "创建文件夹失败:%v", err)
|
||||
return
|
||||
}
|
||||
if io.Exists(filepath.Join(path, req.File.Filename)) {
|
||||
Error(w, http.StatusForbidden, "target backup %s already exists", path)
|
||||
return
|
||||
}
|
||||
|
||||
src, _ := handler.Open()
|
||||
out, err := os.OpenFile(filepath.Join(path, handler.Filename), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
|
||||
src, _ := req.File.Open()
|
||||
out, err := os.OpenFile(filepath.Join(path, req.File.Filename), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "打开文件失败:%v", err)
|
||||
Error(w, http.StatusInternalServerError, "open file error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = stdio.Copy(out, src); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "写入文件失败:%v", err)
|
||||
Error(w, http.StatusInternalServerError, "write file error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -65,12 +65,59 @@ func (s *CertService) DNSProviders(w http.ResponseWriter, r *http.Request) {
|
||||
Label: "华为云",
|
||||
Value: string(acme.Huawei),
|
||||
},
|
||||
{
|
||||
Label: "西部数码",
|
||||
Value: string(acme.Westcn),
|
||||
},
|
||||
{
|
||||
Label: "CloudFlare",
|
||||
Value: string(acme.CloudFlare),
|
||||
},
|
||||
{
|
||||
Label: "Godaddy",
|
||||
Value: string(acme.Godaddy),
|
||||
},
|
||||
{
|
||||
Label: "Gcore",
|
||||
Value: string(acme.Gcore),
|
||||
},
|
||||
{
|
||||
Label: "Porkbun",
|
||||
Value: string(acme.Porkbun),
|
||||
},
|
||||
{
|
||||
Label: "Namecheap",
|
||||
Value: string(acme.Namecheap),
|
||||
},
|
||||
{
|
||||
Label: "NameSilo",
|
||||
Value: string(acme.NameSilo),
|
||||
},
|
||||
{
|
||||
Label: "Name.com",
|
||||
Value: string(acme.Namecom),
|
||||
},
|
||||
{
|
||||
Label: "ClouDNS",
|
||||
Value: string(acme.ClouDNS),
|
||||
},
|
||||
{
|
||||
Label: "Duck DNS",
|
||||
Value: string(acme.DuckDNS),
|
||||
},
|
||||
{
|
||||
Label: "Hetzner",
|
||||
Value: string(acme.Hetzner),
|
||||
},
|
||||
{
|
||||
Label: "Linode",
|
||||
Value: string(acme.Linode),
|
||||
},
|
||||
{
|
||||
Label: "Vercel",
|
||||
Value: string(acme.Vercel),
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (s *CertService) Algorithms(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -99,9 +99,8 @@ func (s *CliService) Update(ctx context.Context, cmd *cli.Command) error {
|
||||
if download == nil {
|
||||
return fmt.Errorf("下载地址为空")
|
||||
}
|
||||
ver, url, checksum := panel.Version, download.URL, download.Checksum
|
||||
|
||||
return s.backupRepo.UpdatePanel(ver, url, checksum)
|
||||
return s.backupRepo.UpdatePanel(panel.Version, download.URL, download.Checksum)
|
||||
}
|
||||
|
||||
func (s *CliService) Sync(ctx context.Context, cmd *cli.Command) error {
|
||||
@@ -536,7 +535,7 @@ func (s *CliService) BackupWebsite(ctx context.Context, cmd *cli.Command) error
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("★ 开始备份 [%s]\n", time.Now().Format(time.DateTime))
|
||||
fmt.Println(s.hr)
|
||||
fmt.Println("|-备份类型:网站")
|
||||
fmt.Println("|-备份类型:website")
|
||||
fmt.Printf("|-备份目标:%s\n", cmd.String("name"))
|
||||
if err := s.backupRepo.Create(biz.BackupTypeWebsite, cmd.String("name"), cmd.String("path")); err != nil {
|
||||
return fmt.Errorf("备份失败:%v", err)
|
||||
@@ -551,7 +550,7 @@ func (s *CliService) BackupDatabase(ctx context.Context, cmd *cli.Command) error
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("★ 开始备份 [%s]\n", time.Now().Format(time.DateTime))
|
||||
fmt.Println(s.hr)
|
||||
fmt.Println("|-备份类型:数据库")
|
||||
fmt.Println("|-备份类型:database")
|
||||
fmt.Printf("|-数据库:%s\n", cmd.String("type"))
|
||||
fmt.Printf("|-备份目标:%s\n", cmd.String("name"))
|
||||
if err := s.backupRepo.Create(biz.BackupType(cmd.String("type")), cmd.String("name"), cmd.String("path")); err != nil {
|
||||
@@ -567,7 +566,7 @@ func (s *CliService) BackupPanel(ctx context.Context, cmd *cli.Command) error {
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("★ 开始备份 [%s]\n", time.Now().Format(time.DateTime))
|
||||
fmt.Println(s.hr)
|
||||
fmt.Println("|-备份类型:面板")
|
||||
fmt.Println("|-备份类型:panel")
|
||||
if err := s.backupRepo.Create(biz.BackupTypePanel, "", cmd.String("path")); err != nil {
|
||||
return fmt.Errorf("备份失败:%v", err)
|
||||
}
|
||||
@@ -614,7 +613,7 @@ func (s *CliService) CutoffWebsite(ctx context.Context, cmd *cli.Command) error
|
||||
fmt.Println(s.hr)
|
||||
fmt.Printf("★ 开始切割日志 [%s]\n", time.Now().Format(time.DateTime))
|
||||
fmt.Println(s.hr)
|
||||
fmt.Println("|-切割类型:网站")
|
||||
fmt.Println("|-切割类型:website")
|
||||
fmt.Printf("|-切割目标:%s\n", website.Name)
|
||||
if err = s.backupRepo.CutoffLog(path, filepath.Join(app.Root, "wwwlogs", website.Name+".log")); err != nil {
|
||||
return err
|
||||
@@ -820,11 +819,13 @@ func (s *CliService) Init(ctx context.Context, cmd *cli.Command) error {
|
||||
|
||||
settings := []biz.Setting{
|
||||
{Key: biz.SettingKeyName, Value: "耗子面板"},
|
||||
{Key: biz.SettingKeyMonitor, Value: "1"},
|
||||
{Key: biz.SettingKeyMonitor, Value: "true"},
|
||||
{Key: biz.SettingKeyMonitorDays, Value: "30"},
|
||||
{Key: biz.SettingKeyBackupPath, Value: filepath.Join(app.Root, "backup")},
|
||||
{Key: biz.SettingKeyWebsitePath, Value: filepath.Join(app.Root, "wwwroot")},
|
||||
{Key: biz.SettingKeyVersion, Value: app.Version},
|
||||
{Key: biz.SettingKeyOfflineMode, Value: "false"},
|
||||
{Key: biz.SettingKeyAutoUpdate, Value: "true"},
|
||||
}
|
||||
if err := s.db.Create(&settings).Error; err != nil {
|
||||
return fmt.Errorf("初始化失败:%v", err)
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type ContainerComposeService struct {
|
||||
containerComposeRepo biz.ContainerComposeRepo
|
||||
}
|
||||
|
||||
func NewContainerComposeService(containerCompose biz.ContainerComposeRepo) *ContainerComposeService {
|
||||
return &ContainerComposeService{
|
||||
containerComposeRepo: containerCompose,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) List(w http.ResponseWriter, r *http.Request) {
|
||||
composes, err := s.containerComposeRepo.List()
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
paged, total := Paginate(r, composes)
|
||||
|
||||
Success(w, chix.M{
|
||||
"total": total,
|
||||
"items": paged,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Get(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeGet](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
compose, envs, err := s.containerComposeRepo.Get(req.Name)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, chix.M{
|
||||
"compose": compose,
|
||||
"envs": envs,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Create(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeCreate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerComposeRepo.Create(req.Name, req.Compose, req.Envs); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Update(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeUpdate](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerComposeRepo.Update(req.Name, req.Compose, req.Envs); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Up(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeUp](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerComposeRepo.Up(req.Name, req.Force); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Down(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeDown](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerComposeRepo.Down(req.Name); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *ContainerComposeService) Remove(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ContainerComposeRemove](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.containerComposeRepo.Remove(req.Name); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func (s *FileService) Create(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = io.Mkdir(req.Path, 0755); err != nil {
|
||||
if err = stdos.MkdirAll(req.Path, 0755); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
@@ -159,7 +159,7 @@ func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if !io.Exists(filepath.Dir(path)) {
|
||||
if err = io.Mkdir(filepath.Dir(path), 0755); err != nil {
|
||||
if err = stdos.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "create directory error: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -291,7 +291,7 @@ func (s *FileService) RemoteDownload(w http.ResponseWriter, r *http.Request) {
|
||||
task := new(biz.Task)
|
||||
task.Name = "下载远程文件"
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = fmt.Sprintf(`wget -o /tmp/remote-download-%s.log -O '%s' '%s'`, timestamp, req.Path, req.URL)
|
||||
task.Shell = fmt.Sprintf(`wget -o /tmp/remote-download-%s.log -O '%s' '%s' && chmod 0755 '%s' && chown www:www '%s'`, timestamp, req.Path, req.URL, req.Path, req.Path)
|
||||
task.Log = fmt.Sprintf("/tmp/remote-download-%s.log", timestamp)
|
||||
|
||||
if err = s.taskRepo.Push(task); err != nil {
|
||||
@@ -420,7 +420,7 @@ func (s *FileService) List(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
list, err := io.ReadDir(req.Path)
|
||||
list, err := stdos.ReadDir(req.Path)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
|
||||
@@ -11,6 +11,7 @@ var ProviderSet = wire.NewSet(
|
||||
NewCertDNSService,
|
||||
NewCliService,
|
||||
NewContainerService,
|
||||
NewContainerComposeService,
|
||||
NewContainerImageService,
|
||||
NewContainerNetworkService,
|
||||
NewContainerVolumeService,
|
||||
|
||||
@@ -23,7 +23,7 @@ func (s *SystemctlService) Status(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
status, err := systemctl.Status(req.Service)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "获取 %s 服务运行状态失败", req.Service)
|
||||
Error(w, http.StatusInternalServerError, "获取 %s 服务运行状态失败: %v", req.Service, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func (s *SystemctlService) IsEnabled(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
enabled, err := systemctl.IsEnabled(req.Service)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "获取 %s 服务启用状态失败", req.Service)
|
||||
Error(w, http.StatusInternalServerError, "获取 %s 服务启用状态失败: %v", req.Service, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func (s *SystemctlService) Enable(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = systemctl.Enable(req.Service); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "启用 %s 服务失败", req.Service)
|
||||
Error(w, http.StatusInternalServerError, "启用 %s 服务失败: %v", req.Service, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (s *SystemctlService) Disable(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = systemctl.Disable(req.Service); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "禁用 %s 服务失败", req.Service)
|
||||
Error(w, http.StatusInternalServerError, "禁用 %s 服务失败: %v", req.Service, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ func (s *SystemctlService) Restart(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = systemctl.Restart(req.Service); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "重启 %s 服务失败", req.Service)
|
||||
Error(w, http.StatusInternalServerError, "重启 %s 服务失败: %v", req.Service, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func (s *SystemctlService) Reload(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = systemctl.Reload(req.Service); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "重载 %s 服务失败", req.Service)
|
||||
Error(w, http.StatusInternalServerError, "重载 %s 服务失败: %v", req.Service, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func (s *SystemctlService) Start(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = systemctl.Start(req.Service); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "启动 %s 服务失败", req.Service)
|
||||
Error(w, http.StatusInternalServerError, "启动 %s 服务失败: %v", req.Service, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ func (s *SystemctlService) Stop(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = systemctl.Stop(req.Service); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "停止 %s 服务失败", req.Service)
|
||||
Error(w, http.StatusInternalServerError, "停止 %s 服务失败: %v", req.Service, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ func (s *WsService) Exec(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
out, err := shell.ExecfWithPipe(ctx, string(cmd)) // nolint: govet
|
||||
out, err := shell.ExecfWithPipe(ctx, string(cmd))
|
||||
if err != nil {
|
||||
_ = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "failed to run command"))
|
||||
cancel()
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
// Code generated by mockery. DO NOT EDIT.
|
||||
|
||||
package biz
|
||||
|
||||
import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
types "github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
// ContainerComposeRepo is an autogenerated mock type for the ContainerComposeRepo type
|
||||
type ContainerComposeRepo struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type ContainerComposeRepo_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *ContainerComposeRepo) EXPECT() *ContainerComposeRepo_Expecter {
|
||||
return &ContainerComposeRepo_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: name, compose, envs
|
||||
func (_m *ContainerComposeRepo) Create(name string, compose string, envs []types.KV) error {
|
||||
ret := _m.Called(name, compose, envs)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Create")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, []types.KV) error); ok {
|
||||
r0 = rf(name, compose, envs)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
|
||||
type ContainerComposeRepo_Create_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Create is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - compose string
|
||||
// - envs []types.KV
|
||||
func (_e *ContainerComposeRepo_Expecter) Create(name interface{}, compose interface{}, envs interface{}) *ContainerComposeRepo_Create_Call {
|
||||
return &ContainerComposeRepo_Create_Call{Call: _e.mock.On("Create", name, compose, envs)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Create_Call) Run(run func(name string, compose string, envs []types.KV)) *ContainerComposeRepo_Create_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].([]types.KV))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Create_Call) Return(_a0 error) *ContainerComposeRepo_Create_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Create_Call) RunAndReturn(run func(string, string, []types.KV) error) *ContainerComposeRepo_Create_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Down provides a mock function with given fields: name
|
||||
func (_m *ContainerComposeRepo) Down(name string) error {
|
||||
ret := _m.Called(name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Down")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Down_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Down'
|
||||
type ContainerComposeRepo_Down_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Down is a helper method to define mock.On call
|
||||
// - name string
|
||||
func (_e *ContainerComposeRepo_Expecter) Down(name interface{}) *ContainerComposeRepo_Down_Call {
|
||||
return &ContainerComposeRepo_Down_Call{Call: _e.mock.On("Down", name)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Down_Call) Run(run func(name string)) *ContainerComposeRepo_Down_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Down_Call) Return(_a0 error) *ContainerComposeRepo_Down_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Down_Call) RunAndReturn(run func(string) error) *ContainerComposeRepo_Down_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: name
|
||||
func (_m *ContainerComposeRepo) Get(name string) (string, []types.KV, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Get")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 []types.KV
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func(string) (string, []types.KV, error)); ok {
|
||||
return rf(name)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) []types.KV); ok {
|
||||
r1 = rf(name)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).([]types.KV)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(string) error); ok {
|
||||
r2 = rf(name)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'
|
||||
type ContainerComposeRepo_Get_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Get is a helper method to define mock.On call
|
||||
// - name string
|
||||
func (_e *ContainerComposeRepo_Expecter) Get(name interface{}) *ContainerComposeRepo_Get_Call {
|
||||
return &ContainerComposeRepo_Get_Call{Call: _e.mock.On("Get", name)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Get_Call) Run(run func(name string)) *ContainerComposeRepo_Get_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Get_Call) Return(_a0 string, _a1 []types.KV, _a2 error) *ContainerComposeRepo_Get_Call {
|
||||
_c.Call.Return(_a0, _a1, _a2)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Get_Call) RunAndReturn(run func(string) (string, []types.KV, error)) *ContainerComposeRepo_Get_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// List provides a mock function with no fields
|
||||
func (_m *ContainerComposeRepo) List() ([]types.ContainerCompose, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for List")
|
||||
}
|
||||
|
||||
var r0 []types.ContainerCompose
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() ([]types.ContainerCompose, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() []types.ContainerCompose); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]types.ContainerCompose)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
|
||||
type ContainerComposeRepo_List_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// List is a helper method to define mock.On call
|
||||
func (_e *ContainerComposeRepo_Expecter) List() *ContainerComposeRepo_List_Call {
|
||||
return &ContainerComposeRepo_List_Call{Call: _e.mock.On("List")}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_List_Call) Run(run func()) *ContainerComposeRepo_List_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_List_Call) Return(_a0 []types.ContainerCompose, _a1 error) *ContainerComposeRepo_List_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_List_Call) RunAndReturn(run func() ([]types.ContainerCompose, error)) *ContainerComposeRepo_List_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Remove provides a mock function with given fields: name
|
||||
func (_m *ContainerComposeRepo) Remove(name string) error {
|
||||
ret := _m.Called(name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Remove")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove'
|
||||
type ContainerComposeRepo_Remove_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Remove is a helper method to define mock.On call
|
||||
// - name string
|
||||
func (_e *ContainerComposeRepo_Expecter) Remove(name interface{}) *ContainerComposeRepo_Remove_Call {
|
||||
return &ContainerComposeRepo_Remove_Call{Call: _e.mock.On("Remove", name)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Remove_Call) Run(run func(name string)) *ContainerComposeRepo_Remove_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Remove_Call) Return(_a0 error) *ContainerComposeRepo_Remove_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Remove_Call) RunAndReturn(run func(string) error) *ContainerComposeRepo_Remove_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Up provides a mock function with given fields: name, force
|
||||
func (_m *ContainerComposeRepo) Up(name string, force bool) error {
|
||||
ret := _m.Called(name, force)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Up")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
|
||||
r0 = rf(name, force)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Up_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Up'
|
||||
type ContainerComposeRepo_Up_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Up is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - force bool
|
||||
func (_e *ContainerComposeRepo_Expecter) Up(name interface{}, force interface{}) *ContainerComposeRepo_Up_Call {
|
||||
return &ContainerComposeRepo_Up_Call{Call: _e.mock.On("Up", name, force)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Up_Call) Run(run func(name string, force bool)) *ContainerComposeRepo_Up_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Up_Call) Return(_a0 error) *ContainerComposeRepo_Up_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Up_Call) RunAndReturn(run func(string, bool) error) *ContainerComposeRepo_Up_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: name, compose, envs
|
||||
func (_m *ContainerComposeRepo) Update(name string, compose string, envs []types.KV) error {
|
||||
ret := _m.Called(name, compose, envs)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Update")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, []types.KV) error); ok {
|
||||
r0 = rf(name, compose, envs)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ContainerComposeRepo_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
|
||||
type ContainerComposeRepo_Update_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Update is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - compose string
|
||||
// - envs []types.KV
|
||||
func (_e *ContainerComposeRepo_Expecter) Update(name interface{}, compose interface{}, envs interface{}) *ContainerComposeRepo_Update_Call {
|
||||
return &ContainerComposeRepo_Update_Call{Call: _e.mock.On("Update", name, compose, envs)}
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Update_Call) Run(run func(name string, compose string, envs []types.KV)) *ContainerComposeRepo_Update_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].([]types.KV))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Update_Call) Return(_a0 error) *ContainerComposeRepo_Update_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ContainerComposeRepo_Update_Call) RunAndReturn(run func(string, string, []types.KV) error) *ContainerComposeRepo_Update_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewContainerComposeRepo creates a new instance of ContainerComposeRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewContainerComposeRepo(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *ContainerComposeRepo {
|
||||
mock := &ContainerComposeRepo{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
+7
-5
@@ -8,6 +8,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/mholt/acmez/v3"
|
||||
@@ -38,8 +39,8 @@ const (
|
||||
|
||||
type EAB = acme.EAB
|
||||
|
||||
func NewRegisterAccount(ctx context.Context, email, CA string, eab *EAB, keyType KeyType) (*Client, error) {
|
||||
client, err := getClient(CA)
|
||||
func NewRegisterAccount(ctx context.Context, email, CA string, eab *EAB, keyType KeyType, log *slog.Logger) (*Client, error) {
|
||||
client, err := getClient(CA, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -68,8 +69,8 @@ func NewRegisterAccount(ctx context.Context, email, CA string, eab *EAB, keyType
|
||||
return &Client{Account: account, zClient: client}, nil
|
||||
}
|
||||
|
||||
func NewPrivateKeyAccount(email string, privateKey string, CA string, eab *EAB) (*Client, error) {
|
||||
client, err := getClient(CA)
|
||||
func NewPrivateKeyAccount(email string, privateKey string, CA string, eab *EAB, log *slog.Logger) (*Client, error) {
|
||||
client, err := getClient(CA, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -116,11 +117,12 @@ func generatePrivateKey(keyType KeyType) (crypto.Signer, error) {
|
||||
return nil, errors.New("未知的密钥类型")
|
||||
}
|
||||
|
||||
func getClient(CA string) (acmez.Client, error) {
|
||||
func getClient(CA string, log *slog.Logger) (acmez.Client, error) {
|
||||
client := acmez.Client{
|
||||
Client: &acme.Client{
|
||||
Directory: CA,
|
||||
HTTPClient: http.DefaultClient,
|
||||
Logger: log,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -26,10 +26,10 @@ type Client struct {
|
||||
// UseDns 使用 DNS 接口验证
|
||||
func (c *Client) UseDns(dnsType DnsType, param DNSParam) {
|
||||
c.zClient.ChallengeSolvers = map[string]acmez.Solver{
|
||||
acme.ChallengeTypeDNS01: dnsSolver{
|
||||
acme.ChallengeTypeDNS01: &dnsSolver{
|
||||
dns: dnsType,
|
||||
param: param,
|
||||
records: &[]libdns.Record{},
|
||||
records: []libdns.Record{},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -39,11 +39,11 @@ func (c *Client) UseManualDns(total int, check ...bool) {
|
||||
c.controlChan = make(chan struct{})
|
||||
c.dataChan = make(chan any)
|
||||
c.zClient.ChallengeSolvers = map[string]acmez.Solver{
|
||||
acme.ChallengeTypeDNS01: manualDNSSolver{
|
||||
acme.ChallengeTypeDNS01: &manualDNSSolver{
|
||||
check: len(check) > 0 && check[0],
|
||||
controlChan: c.controlChan,
|
||||
dataChan: c.dataChan,
|
||||
records: &[]DNSRecord{},
|
||||
records: []DNSRecord{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
@@ -17,7 +18,7 @@ func TestClientTestSuite(t *testing.T) {
|
||||
|
||||
func (s *ClientTestSuite) TestObtainSSL() {
|
||||
ctx := context.Background()
|
||||
client, err := NewRegisterAccount(ctx, "ci@haozi.net", CALetsEncryptStaging, nil, KeyEC256)
|
||||
client, err := NewRegisterAccount(ctx, "ci@haozi.net", CALetsEncryptStaging, nil, KeyEC256, slog.Default())
|
||||
s.Nil(err)
|
||||
|
||||
client.UseDns(AliYun, DNSParam{
|
||||
|
||||
+120
-24
@@ -7,11 +7,23 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/devhaozi/westcn"
|
||||
"github.com/libdns/alidns"
|
||||
"github.com/libdns/cloudflare"
|
||||
"github.com/libdns/cloudns"
|
||||
"github.com/libdns/duckdns"
|
||||
"github.com/libdns/gcore"
|
||||
"github.com/libdns/godaddy"
|
||||
"github.com/libdns/hetzner"
|
||||
"github.com/libdns/huaweicloud"
|
||||
"github.com/libdns/libdns"
|
||||
"github.com/libdns/linode"
|
||||
"github.com/libdns/namecheap"
|
||||
"github.com/libdns/namedotcom"
|
||||
"github.com/libdns/namesilo"
|
||||
"github.com/libdns/porkbun"
|
||||
"github.com/libdns/tencentcloud"
|
||||
"github.com/libdns/vercel"
|
||||
"github.com/mholt/acmez/v3/acme"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
|
||||
@@ -30,11 +42,11 @@ func (s httpSolver) Present(_ context.Context, challenge acme.Challenge) error {
|
||||
}
|
||||
`, challenge.HTTP01ResourcePath(), challenge.KeyAuthorization)
|
||||
if err := os.WriteFile(s.conf, []byte(conf), 0644); err != nil {
|
||||
return fmt.Errorf("无法写入 Nginx 配置文件: %w", err)
|
||||
return fmt.Errorf("failed to write nginx config %q: %w", s.conf, err)
|
||||
}
|
||||
if err := systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
return fmt.Errorf("无法重载 Nginx: %w", err)
|
||||
return fmt.Errorf("failed to reload nginx: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -50,44 +62,45 @@ func (s httpSolver) CleanUp(_ context.Context, challenge acme.Challenge) error {
|
||||
type dnsSolver struct {
|
||||
dns DnsType
|
||||
param DNSParam
|
||||
records *[]libdns.Record
|
||||
records []libdns.Record
|
||||
}
|
||||
|
||||
func (s dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error {
|
||||
func (s *dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error {
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
keyAuth := challenge.DNS01KeyAuthorization()
|
||||
provider, err := s.getDNSProvider()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 DNS 提供商失败: %w", err)
|
||||
return fmt.Errorf("failed to get DNS provider: %w", err)
|
||||
}
|
||||
zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err)
|
||||
return fmt.Errorf("failed to get the effective TLD+1 for %q: %w", dnsName, err)
|
||||
}
|
||||
|
||||
rec := libdns.Record{
|
||||
Type: "TXT",
|
||||
Name: libdns.RelativeName(dnsName+".", zone+"."),
|
||||
Value: keyAuth,
|
||||
TTL: 10 * time.Minute,
|
||||
}
|
||||
|
||||
results, err := provider.SetRecords(ctx, zone+".", []libdns.Record{rec})
|
||||
if err != nil {
|
||||
return fmt.Errorf("域名 %q 添加临时记录 %q 失败: %w", zone, dnsName, err)
|
||||
return fmt.Errorf("failed to set DNS record %q for %q: %w", dnsName, zone, err)
|
||||
}
|
||||
if len(results) != 1 {
|
||||
return fmt.Errorf("预期添加 1 条记录,但实际添加了 %d 条记录", len(results))
|
||||
return fmt.Errorf("expected to add 1 record, but actually added %d records", len(results))
|
||||
}
|
||||
|
||||
s.records = &results
|
||||
s.records = results
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s dnsSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error {
|
||||
func (s *dnsSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error {
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
provider, err := s.getDNSProvider()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 DNS 提供商失败: %w", err)
|
||||
return fmt.Errorf("failed to get DNS provider: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
@@ -95,13 +108,14 @@ func (s dnsSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error
|
||||
|
||||
zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err)
|
||||
return fmt.Errorf("failed to get the effective TLD+1 for %q: %w", dnsName, err)
|
||||
}
|
||||
_, _ = provider.DeleteRecords(ctx, zone+".", *s.records)
|
||||
|
||||
_, _ = provider.DeleteRecords(ctx, zone+".", s.records)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s dnsSolver) getDNSProvider() (DNSProvider, error) {
|
||||
func (s *dnsSolver) getDNSProvider() (DNSProvider, error) {
|
||||
var dns DNSProvider
|
||||
|
||||
switch s.dns {
|
||||
@@ -120,12 +134,73 @@ func (s dnsSolver) getDNSProvider() (DNSProvider, error) {
|
||||
AccessKeyId: s.param.AK,
|
||||
SecretAccessKey: s.param.SK,
|
||||
}
|
||||
case Westcn:
|
||||
dns = &westcn.Provider{
|
||||
Username: s.param.SK,
|
||||
APIPassword: s.param.AK,
|
||||
}
|
||||
case CloudFlare:
|
||||
dns = &cloudflare.Provider{
|
||||
APIToken: s.param.AK,
|
||||
}
|
||||
case Godaddy:
|
||||
dns = &godaddy.Provider{
|
||||
APIToken: s.param.AK,
|
||||
}
|
||||
case Gcore:
|
||||
dns = &gcore.Provider{
|
||||
APIKey: s.param.AK,
|
||||
}
|
||||
case Porkbun:
|
||||
dns = &porkbun.Provider{
|
||||
APIKey: s.param.AK,
|
||||
APISecretKey: s.param.SK,
|
||||
}
|
||||
case Namecheap:
|
||||
dns = &namecheap.Provider{
|
||||
APIKey: s.param.AK,
|
||||
User: s.param.SK,
|
||||
}
|
||||
case NameSilo:
|
||||
dns = &namesilo.Provider{
|
||||
APIToken: s.param.AK,
|
||||
}
|
||||
case Namecom:
|
||||
dns = &namedotcom.Provider{
|
||||
Token: s.param.AK,
|
||||
User: s.param.SK,
|
||||
Server: "https://api.name.com",
|
||||
}
|
||||
case ClouDNS:
|
||||
if strings.HasPrefix(s.param.AK, "sub-") {
|
||||
dns = &cloudns.Provider{
|
||||
SubAuthId: strings.TrimPrefix(s.param.AK, "sub-"),
|
||||
AuthPassword: s.param.SK,
|
||||
}
|
||||
} else {
|
||||
dns = &cloudns.Provider{
|
||||
AuthId: s.param.AK,
|
||||
AuthPassword: s.param.SK,
|
||||
}
|
||||
}
|
||||
case DuckDNS:
|
||||
dns = &duckdns.Provider{
|
||||
APIToken: s.param.AK,
|
||||
}
|
||||
case Hetzner:
|
||||
dns = &hetzner.Provider{
|
||||
AuthAPIToken: s.param.AK,
|
||||
}
|
||||
case Linode:
|
||||
dns = &linode.Provider{
|
||||
APIToken: s.param.AK,
|
||||
}
|
||||
case Vercel:
|
||||
dns = &vercel.Provider{
|
||||
AuthAPIToken: s.param.AK,
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("未知的DNS提供商 %q", s.dns)
|
||||
return nil, fmt.Errorf("unsupported DNS provider: %s", s.dns)
|
||||
}
|
||||
|
||||
return dns, nil
|
||||
@@ -134,10 +209,22 @@ func (s dnsSolver) getDNSProvider() (DNSProvider, error) {
|
||||
type DnsType string
|
||||
|
||||
const (
|
||||
Tencent DnsType = "tencent"
|
||||
AliYun DnsType = "aliyun"
|
||||
Tencent DnsType = "tencent"
|
||||
Huawei DnsType = "huawei"
|
||||
Westcn DnsType = "westcn"
|
||||
CloudFlare DnsType = "cloudflare"
|
||||
Godaddy DnsType = "godaddy"
|
||||
Gcore DnsType = "gcore"
|
||||
Porkbun DnsType = "porkbun"
|
||||
Namecheap DnsType = "namecheap"
|
||||
NameSilo DnsType = "namesilo"
|
||||
Namecom DnsType = "namecom"
|
||||
ClouDNS DnsType = "cloudns"
|
||||
DuckDNS DnsType = "duckdns"
|
||||
Hetzner DnsType = "hetzner"
|
||||
Linode DnsType = "linode"
|
||||
Vercel DnsType = "vercel"
|
||||
)
|
||||
|
||||
type DNSParam struct {
|
||||
@@ -154,29 +241,38 @@ type manualDNSSolver struct {
|
||||
check bool
|
||||
controlChan chan struct{}
|
||||
dataChan chan any
|
||||
records *[]DNSRecord
|
||||
records []DNSRecord
|
||||
}
|
||||
|
||||
func (s manualDNSSolver) Present(ctx context.Context, challenge acme.Challenge) error {
|
||||
func (s *manualDNSSolver) Present(ctx context.Context, challenge acme.Challenge) error {
|
||||
full := challenge.DNS01TXTRecordName()
|
||||
keyAuth := challenge.DNS01KeyAuthorization()
|
||||
domain, err := publicsuffix.EffectiveTLDPlusOne(full)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 %q 的顶级域失败: %w", full, err)
|
||||
return fmt.Errorf("failed to get the effective TLD+1 for %q: %w", full, err)
|
||||
}
|
||||
|
||||
*s.records = append(*s.records, DNSRecord{
|
||||
s.records = append(s.records, DNSRecord{
|
||||
Name: strings.TrimSuffix(full, "."+domain),
|
||||
Domain: domain,
|
||||
Value: keyAuth,
|
||||
})
|
||||
s.dataChan <- *s.records
|
||||
s.dataChan <- s.records
|
||||
|
||||
<-s.controlChan
|
||||
return nil
|
||||
select {
|
||||
case <-s.controlChan:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (s manualDNSSolver) CleanUp(_ context.Context, _ acme.Challenge) error {
|
||||
func (s *manualDNSSolver) CleanUp(_ context.Context, _ acme.Challenge) error {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
close(s.controlChan)
|
||||
close(s.dataChan)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package apploader
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
var apps sync.Map
|
||||
|
||||
type Loader struct{}
|
||||
|
||||
func (r *Loader) Add(app ...types.App) {
|
||||
for item := range slices.Values(app) {
|
||||
slug := getSlug(item)
|
||||
apps.Store(slug, item)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Loader) Register(mux chi.Router) {
|
||||
/*for slug, item := range r.Apps {
|
||||
mux.Route("/"+slug, item.Route)
|
||||
}*/
|
||||
|
||||
apps.Range(func(key, value any) bool {
|
||||
app := value.(types.App)
|
||||
mux.Route("/"+key.(string), app.Route)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func Slugs() []string {
|
||||
var slugs []string
|
||||
apps.Range(func(key, value any) bool {
|
||||
slugs = append(slugs, key.(string))
|
||||
return true
|
||||
})
|
||||
return slugs
|
||||
}
|
||||
|
||||
func getSlug(app types.App) string {
|
||||
if app == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(app)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
pkgPath := t.PkgPath()
|
||||
if pkgPath == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
parts := strings.Split(pkgPath, "/")
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
+45
-13
@@ -4,6 +4,7 @@ import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
@@ -95,10 +96,12 @@ func EncodeKey(key crypto.Signer) ([]byte, error) {
|
||||
|
||||
// GenerateSelfSigned 生成自签名证书
|
||||
func GenerateSelfSigned(names []string) (cert []byte, key []byte, err error) {
|
||||
rootPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
// 生成根密钥对
|
||||
rootPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var ips []net.IP
|
||||
ip := false
|
||||
for _, item := range names {
|
||||
@@ -113,43 +116,59 @@ func GenerateSelfSigned(names []string) (cert []byte, key []byte, err error) {
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{CommonName: "Rat Panel Root CA"},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(40, 0, 0),
|
||||
NotAfter: time.Now().AddDate(20, 0, 0),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
KeyUsage: x509.KeyUsageCertSign,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
|
||||
}
|
||||
|
||||
rootCertBytes, _ := x509.CreateCertificate(rand.Reader, &rootTemplate, &rootTemplate, &rootPrivateKey.PublicKey, rootPrivateKey)
|
||||
rootCertBytes, err := x509.CreateCertificate(rand.Reader, &rootTemplate, &rootTemplate, &rootPrivateKey.PublicKey, rootPrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rootCertBlock := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: rootCertBytes,
|
||||
}
|
||||
|
||||
interPrivateKey, _ := rsa.GenerateKey(rand.Reader, 4096)
|
||||
// 生成中间证书密钥对
|
||||
interPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
interTemplate := x509.Certificate{
|
||||
SerialNumber: big.NewInt(2),
|
||||
Subject: pkix.Name{CommonName: "Rat Panel CA"},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(30, 0, 0),
|
||||
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
KeyUsage: x509.KeyUsageCertSign,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
interCertBytes, _ := x509.CreateCertificate(rand.Reader, &interTemplate, &rootTemplate, &interPrivateKey.PublicKey, rootPrivateKey)
|
||||
interCertBytes, err := x509.CreateCertificate(rand.Reader, &interTemplate, &rootTemplate, &interPrivateKey.PublicKey, rootPrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
interCertBlock := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: interCertBytes,
|
||||
}
|
||||
|
||||
clientPrivateKey, _ := rsa.GenerateKey(rand.Reader, 4096)
|
||||
// 生成客户端证书密钥对
|
||||
clientPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
clientTemplate := x509.Certificate{
|
||||
SerialNumber: big.NewInt(3),
|
||||
Subject: pkix.Name{CommonName: "Rat Panel"},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(20, 0, 0),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
if ip {
|
||||
@@ -158,16 +177,29 @@ func GenerateSelfSigned(names []string) (cert []byte, key []byte, err error) {
|
||||
clientTemplate.DNSNames = names
|
||||
}
|
||||
|
||||
clientCertBytes, _ := x509.CreateCertificate(rand.Reader, &clientTemplate, &interTemplate, &clientPrivateKey.PublicKey, interPrivateKey)
|
||||
clientCertBytes, err := x509.CreateCertificate(rand.Reader, &clientTemplate, &interTemplate, &clientPrivateKey.PublicKey, interPrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
clientCertBlock := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: clientCertBytes,
|
||||
}
|
||||
|
||||
// 拼接证书链
|
||||
cert = append(cert, pem.EncodeToMemory(clientCertBlock)...)
|
||||
cert = append(cert, pem.EncodeToMemory(interCertBlock)...)
|
||||
cert = append(cert, pem.EncodeToMemory(rootCertBlock)...)
|
||||
key = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientPrivateKey)})
|
||||
|
||||
// 编码私钥
|
||||
privateKeyBytes, err := x509.MarshalECPrivateKey(clientPrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
key = pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: privateKeyBytes,
|
||||
})
|
||||
|
||||
return cert, key, nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package db
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
|
||||
@@ -20,8 +19,6 @@ type MySQL struct {
|
||||
}
|
||||
|
||||
func NewMySQL(username, password, address string, typ ...string) (*MySQL, error) {
|
||||
username = url.QueryEscape(username)
|
||||
password = url.QueryEscape(password)
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s)/", username, password, address)
|
||||
if len(typ) > 0 && typ[0] == "unix" {
|
||||
dsn = fmt.Sprintf("%s:%s@unix(%s)/", username, password, address)
|
||||
|
||||
+9
-4
@@ -2,6 +2,7 @@ package io
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -59,7 +60,7 @@ func UnCompress(src string, dst string) error {
|
||||
return errors.New("src and dst must be absolute path")
|
||||
}
|
||||
if !Exists(dst) {
|
||||
if err := Mkdir(dst, 0755); err != nil {
|
||||
if err := os.MkdirAll(dst, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -123,13 +124,17 @@ func formatArchiveByPath(path string) (FormatArchive, error) {
|
||||
return Bz2, nil
|
||||
case ".tar":
|
||||
return Tar, nil
|
||||
case ".gz", ".tar.gz", ".tgz":
|
||||
case ".tgz":
|
||||
return TGz, nil
|
||||
case ".gz":
|
||||
if strings.HasSuffix(path, ".tar.gz") {
|
||||
return TGz, nil
|
||||
}
|
||||
case ".xz":
|
||||
return Xz, nil
|
||||
case ".7z":
|
||||
return SevenZip, nil
|
||||
default:
|
||||
return "", errors.New("unknown format")
|
||||
}
|
||||
|
||||
return "", errors.New("unknown format")
|
||||
}
|
||||
|
||||
@@ -120,8 +120,3 @@ func GetSymlink(path string) string {
|
||||
}
|
||||
return linkPath
|
||||
}
|
||||
|
||||
// TempFile 创建临时文件
|
||||
func TempFile(dir, prefix string) (*os.File, error) {
|
||||
return os.CreateTemp(dir, prefix)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user