mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 01:12:17 +08:00
Compare commits
360 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99eb46c2a9 | |||
| 2cae2fc807 | |||
| bfc0ff5c28 | |||
| 504210cee1 | |||
| 1d6d388d37 | |||
| 727ff7a52d | |||
| 8638dcb9c8 | |||
| d641c78000 | |||
| 49131d1d00 | |||
| ccc43cd175 | |||
| 0131f464cd | |||
| 752bc0a64e | |||
| fac981e1ee | |||
| c6ce1a403d | |||
| 79f7b4a510 | |||
| 815418627f | |||
| d82e59b4eb | |||
| a6a42f0f11 | |||
| 4d6b62ac18 | |||
| 0547157c69 | |||
| 493da1552d | |||
| 0402427b71 | |||
| 623dcbce1c | |||
| 98d9af89ec | |||
| b4546ec0ba | |||
| f140eb3f5f | |||
| 432b5466e8 | |||
| 402562ba93 | |||
| 77bcf6e086 | |||
| d9fee73e87 | |||
| e512187b60 | |||
| 31a0617383 | |||
| 98d80cd359 | |||
| b0cae832c5 | |||
| 00b9a859f8 | |||
| 8b5a7ece43 | |||
| 391b2a9c68 | |||
| cf6deb665c | |||
| 096890bd2f | |||
| 4dba4e5216 | |||
| 2b63c50434 | |||
| 0a2d9262c9 | |||
| 4693cc8fe3 | |||
| b6bad9ea64 | |||
| a3b0bf10e9 | |||
| a627816694 | |||
| a4ea9298a4 | |||
| 233bb6e23b | |||
| bdd20107c7 | |||
| e296008112 | |||
| 3dd01e6c1f | |||
| 5b633e6528 | |||
| 2da260bba0 | |||
| acd8403e4b | |||
| 90e66175ef | |||
| 4f1a7cc1bf | |||
| 053d7d3ca5 | |||
| dae0e8274a | |||
| 2b3d1cd220 | |||
| 5f0a4ca89e | |||
| 67d350ebe2 | |||
| 66c30f3e33 | |||
| 82d763fdee | |||
| 7159c4cccf | |||
| 60357222df | |||
| f6c43cf5a0 | |||
| f1341c5364 | |||
| b8cb8d70d3 | |||
| fa7fca1f11 | |||
| 59cfa096b4 | |||
| 706c04a21f | |||
| 255284caad | |||
| 85b34fc017 | |||
| d42deb54eb | |||
| 002104b0eb | |||
| 4dc943438e | |||
| 81a320cd93 | |||
| df32b3b5de | |||
| db0679cd92 | |||
| 20bbbc6202 | |||
| ce652f45e6 | |||
| c0f7183bc8 | |||
| 60d8ccd1a0 | |||
| e9561af93a | |||
| 7e0ad2c799 | |||
| fe9f1c6e95 | |||
| 193ec9a6ae | |||
| 72c3bed203 | |||
| 2ae259e53a | |||
| 3cd25e19de | |||
| 9bdffedb7e | |||
| c298a7182e | |||
| d613a06ad2 | |||
| cc2cdaa76b | |||
| 2c38a51515 | |||
| d2e89353c5 | |||
| 998bc02f8c | |||
| 42e3e20eb8 | |||
| 225795db74 | |||
| bd35c42eb0 | |||
| d2587e77d4 | |||
| b0018ee71b | |||
| 985f4a6e6b | |||
| cf910c111f | |||
| d7e0cb02cd | |||
| c9c6c8b23a | |||
| 3d0cb594ae | |||
| b614c9097b | |||
| 78b94fe89c | |||
| 90140f4229 | |||
| d36c3b0dae | |||
| 88b64d6fc8 | |||
| ce4725ed09 | |||
| a2efa9a70b | |||
| 5126e2030a | |||
| b7b5744f17 | |||
| 0f08c10f02 | |||
| 6b4154f4a6 | |||
| 2d45e84b66 | |||
| 3f90402164 | |||
| 53fdcfb715 | |||
| 2ff01579d6 | |||
| 774d13980c | |||
| ae76a0171d | |||
| 137665b77d | |||
| 3b7bf35bf3 | |||
| 0b0c4da3eb | |||
| 7dfcfb98d5 | |||
| 00b9be6bc9 | |||
| 771a9d1f28 | |||
| 3021f466f0 | |||
| 486a9fb58b | |||
| dbc3050f2b | |||
| dc584a9382 | |||
| e1f6afe08f | |||
| a7c95d929f | |||
| 9ff5596aca | |||
| 4369eedfe5 | |||
| 5af46a5039 | |||
| 4e854515c8 | |||
| 552487b9d3 | |||
| 8cadfe3e53 | |||
| 15c0f28252 | |||
| 8e4b73c695 | |||
| cb8f2a0ee1 | |||
| e6b3858937 | |||
| 1bdcf0473e | |||
| edb5e858e4 | |||
| cfedad8a1b | |||
| 08a5c66e62 | |||
| 1d59fad7ad | |||
| e0145ee9e6 | |||
| 4703d8c3ca | |||
| 8b95095e55 | |||
| 709c345201 | |||
| 288968006a | |||
| 7f619f786d | |||
| c9c3de1348 | |||
| 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 |
@@ -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
|
||||
|
||||
@@ -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 ]
|
||||
@@ -56,7 +55,7 @@ jobs:
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: frontend
|
||||
path: internal/embed/frontend
|
||||
path: pkg/embed/frontend
|
||||
- name: Set build info
|
||||
run: |
|
||||
echo "VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo '0.0.0')" >> $GITHUB_ENV
|
||||
|
||||
@@ -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,8 +39,6 @@ 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'
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
name: L10n
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
jobs:
|
||||
xgotext:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install xgotext
|
||||
run: |
|
||||
go install github.com/leonelquinteros/gotext/cli/xgotext@latest
|
||||
- name: Generate pot files
|
||||
run: |
|
||||
~/go/bin/xgotext -default backend -pkg-tree ./cmd/web -out ./pkg/embed/locales
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
name: Commit changes
|
||||
with:
|
||||
commit_message: "chore(l10n): update backend pot files"
|
||||
vue3-gettext:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Generate pot files and merge po files
|
||||
working-directory: web
|
||||
run: pnpm run gettext:extract
|
||||
- name: Compile po files to json
|
||||
working-directory: web
|
||||
run: pnpm run gettext:compile
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
name: Commit changes
|
||||
with:
|
||||
commit_message: "chore(l10n): update frontend pot files"
|
||||
@@ -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
|
||||
@@ -19,13 +19,13 @@ jobs:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
uses: golangci/golangci-lint-action@v7
|
||||
with:
|
||||
skip-cache: true
|
||||
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
|
||||
|
||||
@@ -63,3 +63,6 @@ out/
|
||||
*.sublime*
|
||||
__debug_bin
|
||||
.project
|
||||
|
||||
# 傻逼 node_modules
|
||||
node_modules/
|
||||
|
||||
+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)
|
||||
|
||||
|
||||
+1
-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)
|
||||
|
||||
|
||||
+42
-34
@@ -9,11 +9,13 @@ 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/codeserver"
|
||||
"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"
|
||||
@@ -49,6 +51,10 @@ func initCli() (*app.Cli, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locale, err := bootstrap.NewT(koanf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger := bootstrap.NewLog(koanf)
|
||||
db, err := bootstrap.NewDB(koanf, logger)
|
||||
if err != nil {
|
||||
@@ -56,45 +62,47 @@ func initCli() (*app.Cli, error) {
|
||||
}
|
||||
cacheRepo := data.NewCacheRepo(db)
|
||||
queue := bootstrap.NewQueue()
|
||||
taskRepo := data.NewTaskRepo(db, logger, queue)
|
||||
appRepo := data.NewAppRepo(db, cacheRepo, taskRepo)
|
||||
userRepo := data.NewUserRepo(db)
|
||||
settingRepo := data.NewSettingRepo(db, koanf, taskRepo)
|
||||
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)
|
||||
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)
|
||||
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
|
||||
appRepo := data.NewAppRepo(locale, db, cacheRepo, taskRepo)
|
||||
userRepo := data.NewUserRepo(locale, db)
|
||||
settingRepo := data.NewSettingRepo(locale, db, koanf, taskRepo)
|
||||
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
|
||||
databaseUserRepo := data.NewDatabaseUserRepo(locale, db, databaseServerRepo)
|
||||
databaseRepo := data.NewDatabaseRepo(locale, db, databaseServerRepo, databaseUserRepo)
|
||||
certRepo := data.NewCertRepo(locale, db, logger)
|
||||
certAccountRepo := data.NewCertAccountRepo(locale, db, userRepo, logger)
|
||||
websiteRepo := data.NewWebsiteRepo(locale, db, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo)
|
||||
backupRepo := data.NewBackupRepo(locale, db, settingRepo, websiteRepo)
|
||||
cliService := service.NewCliService(locale, koanf, db, appRepo, cacheRepo, userRepo, settingRepo, backupRepo, websiteRepo, databaseServerRepo)
|
||||
cli := route.NewCli(locale, cliService)
|
||||
command := bootstrap.NewCli(locale, cli)
|
||||
gormigrate := bootstrap.NewMigrate(db)
|
||||
benchmarkApp := benchmark.NewApp()
|
||||
benchmarkApp := benchmark.NewApp(locale)
|
||||
codeserverApp := codeserver.NewApp()
|
||||
dockerApp := docker.NewApp()
|
||||
fail2banApp := fail2ban.NewApp(websiteRepo)
|
||||
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
|
||||
frpApp := frp.NewApp()
|
||||
giteaApp := gitea.NewApp()
|
||||
memcachedApp := memcached.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()
|
||||
memcachedApp := memcached.NewApp(locale)
|
||||
minioApp := minio.NewApp()
|
||||
mysqlApp := mysql.NewApp(locale, settingRepo)
|
||||
nginxApp := nginx.NewApp(locale)
|
||||
php74App := php74.NewApp(locale, taskRepo)
|
||||
php80App := php80.NewApp(locale, taskRepo)
|
||||
php81App := php81.NewApp(locale, taskRepo)
|
||||
php82App := php82.NewApp(locale, taskRepo)
|
||||
php83App := php83.NewApp(locale, taskRepo)
|
||||
php84App := php84.NewApp(locale, taskRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp(locale)
|
||||
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, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
|
||||
postgresqlApp := postgresql.NewApp(locale)
|
||||
pureftpdApp := pureftpd.NewApp(locale)
|
||||
redisApp := redis.NewApp(locale)
|
||||
rsyncApp := rsync.NewApp(locale)
|
||||
s3fsApp := s3fs.NewApp(locale, settingRepo)
|
||||
supervisorApp := supervisor.NewApp(locale)
|
||||
toolboxApp := toolbox.NewApp(locale)
|
||||
loader := bootstrap.NewLoader(benchmarkApp, codeserverApp, 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
|
||||
}
|
||||
|
||||
+53
-43
@@ -9,11 +9,13 @@ 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/codeserver"
|
||||
"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"
|
||||
@@ -60,80 +62,88 @@ func initWeb() (*app.Web, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locale, err := bootstrap.NewT(koanf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cacheRepo := data.NewCacheRepo(db)
|
||||
queue := bootstrap.NewQueue()
|
||||
taskRepo := data.NewTaskRepo(db, logger, queue)
|
||||
appRepo := data.NewAppRepo(db, cacheRepo, taskRepo)
|
||||
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
|
||||
appRepo := data.NewAppRepo(locale, db, cacheRepo, taskRepo)
|
||||
middlewares := middleware.NewMiddlewares(koanf, logger, manager, appRepo)
|
||||
userRepo := data.NewUserRepo(db)
|
||||
userService := service.NewUserService(koanf, manager, userRepo)
|
||||
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)
|
||||
websiteRepo := data.NewWebsiteRepo(db, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo)
|
||||
settingRepo := data.NewSettingRepo(db, koanf, taskRepo)
|
||||
cronRepo := data.NewCronRepo(db)
|
||||
backupRepo := data.NewBackupRepo(db, settingRepo, websiteRepo)
|
||||
dashboardService := service.NewDashboardService(koanf, taskRepo, websiteRepo, appRepo, settingRepo, cronRepo, backupRepo)
|
||||
userRepo := data.NewUserRepo(locale, db)
|
||||
userService := service.NewUserService(locale, koanf, manager, userRepo)
|
||||
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
|
||||
databaseUserRepo := data.NewDatabaseUserRepo(locale, db, databaseServerRepo)
|
||||
databaseRepo := data.NewDatabaseRepo(locale, db, databaseServerRepo, databaseUserRepo)
|
||||
certRepo := data.NewCertRepo(locale, db, logger)
|
||||
certAccountRepo := data.NewCertAccountRepo(locale, db, userRepo, logger)
|
||||
websiteRepo := data.NewWebsiteRepo(locale, db, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo)
|
||||
settingRepo := data.NewSettingRepo(locale, db, koanf, taskRepo)
|
||||
cronRepo := data.NewCronRepo(locale, db)
|
||||
backupRepo := data.NewBackupRepo(locale, db, settingRepo, websiteRepo)
|
||||
dashboardService := service.NewDashboardService(locale, koanf, taskRepo, websiteRepo, appRepo, settingRepo, cronRepo, backupRepo)
|
||||
taskService := service.NewTaskService(taskRepo)
|
||||
websiteService := service.NewWebsiteService(websiteRepo, settingRepo)
|
||||
databaseService := service.NewDatabaseService(databaseRepo)
|
||||
databaseServerService := service.NewDatabaseServerService(databaseServerRepo)
|
||||
databaseUserService := service.NewDatabaseUserService(databaseUserRepo)
|
||||
backupService := service.NewBackupService(backupRepo)
|
||||
certService := service.NewCertService(certRepo)
|
||||
backupService := service.NewBackupService(locale, backupRepo)
|
||||
certService := service.NewCertService(locale, certRepo)
|
||||
certDNSRepo := data.NewCertDNSRepo(db)
|
||||
certDNSService := service.NewCertDNSService(certDNSRepo)
|
||||
certAccountService := service.NewCertAccountService(certAccountRepo)
|
||||
appService := service.NewAppService(appRepo, cacheRepo, settingRepo)
|
||||
appService := service.NewAppService(locale, appRepo, cacheRepo, settingRepo)
|
||||
cronService := service.NewCronService(cronRepo)
|
||||
processService := service.NewProcessService()
|
||||
safeRepo := data.NewSafeRepo()
|
||||
safeService := service.NewSafeService(safeRepo)
|
||||
firewallService := service.NewFirewallService()
|
||||
sshRepo := data.NewSSHRepo(db)
|
||||
sshRepo := data.NewSSHRepo(locale, db)
|
||||
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()
|
||||
containerImageService := service.NewContainerImageService(containerImageRepo)
|
||||
containerVolumeRepo := data.NewContainerVolumeRepo()
|
||||
containerVolumeService := service.NewContainerVolumeService(containerVolumeRepo)
|
||||
fileService := service.NewFileService(taskRepo)
|
||||
fileService := service.NewFileService(locale, taskRepo)
|
||||
monitorRepo := data.NewMonitorRepo(db, settingRepo)
|
||||
monitorService := service.NewMonitorService(settingRepo, monitorRepo)
|
||||
settingService := service.NewSettingService(settingRepo)
|
||||
systemctlService := service.NewSystemctlService()
|
||||
benchmarkApp := benchmark.NewApp()
|
||||
systemctlService := service.NewSystemctlService(locale)
|
||||
benchmarkApp := benchmark.NewApp(locale)
|
||||
codeserverApp := codeserver.NewApp()
|
||||
dockerApp := docker.NewApp()
|
||||
fail2banApp := fail2ban.NewApp(websiteRepo)
|
||||
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
|
||||
frpApp := frp.NewApp()
|
||||
giteaApp := gitea.NewApp()
|
||||
memcachedApp := memcached.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()
|
||||
memcachedApp := memcached.NewApp(locale)
|
||||
minioApp := minio.NewApp()
|
||||
mysqlApp := mysql.NewApp(locale, settingRepo)
|
||||
nginxApp := nginx.NewApp(locale)
|
||||
php74App := php74.NewApp(locale, taskRepo)
|
||||
php80App := php80.NewApp(locale, taskRepo)
|
||||
php81App := php81.NewApp(locale, taskRepo)
|
||||
php82App := php82.NewApp(locale, taskRepo)
|
||||
php83App := php83.NewApp(locale, taskRepo)
|
||||
php84App := php84.NewApp(locale, taskRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp(locale)
|
||||
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, 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, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader)
|
||||
wsService := service.NewWsService(koanf, sshRepo)
|
||||
postgresqlApp := postgresql.NewApp(locale)
|
||||
pureftpdApp := pureftpd.NewApp(locale)
|
||||
redisApp := redis.NewApp(locale)
|
||||
rsyncApp := rsync.NewApp(locale)
|
||||
s3fsApp := s3fs.NewApp(locale, settingRepo)
|
||||
supervisorApp := supervisor.NewApp(locale)
|
||||
toolboxApp := toolbox.NewApp(locale)
|
||||
loader := bootstrap.NewLoader(benchmarkApp, codeserverApp, 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(locale, koanf, sshRepo)
|
||||
ws := route.NewWs(wsService)
|
||||
mux, err := bootstrap.NewRouter(middlewares, http, ws)
|
||||
if err != nil {
|
||||
@@ -149,7 +159,7 @@ func initWeb() (*app.Web, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validation := bootstrap.NewValidator(db)
|
||||
validation := bootstrap.NewValidator(koanf, db)
|
||||
web := app.NewWeb(koanf, mux, server, gormigrate, cron, queue, validation)
|
||||
return web, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
commit_message: 'Update translations (%language%) %original_file_name%'
|
||||
pull_request_title: 'l10n: update translations'
|
||||
files:
|
||||
- source: /pkg/embed/locales/*.pot
|
||||
translation: /pkg/embed/locales/%locale_with_underscore%/%file_name%.po
|
||||
- source: /web/src/locales/*.pot
|
||||
translation: /web/src/locales/%locale_with_underscore%.po
|
||||
@@ -1,48 +1,63 @@
|
||||
module github.com/tnb-labs/panel
|
||||
|
||||
go 1.23
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
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/devhaozi/westcn v0.0.0-20250407083704-b49375823872
|
||||
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.3
|
||||
github.com/go-rat/chix v1.1.4
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.4
|
||||
github.com/go-rat/chix v1.1.6
|
||||
github.com/go-rat/gormstore v1.0.6
|
||||
github.com/go-rat/sessions v1.0.11
|
||||
github.com/go-rat/utils v1.0.9
|
||||
github.com/go-rat/sessions v1.1.0
|
||||
github.com/go-rat/utils v1.1.5
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/go-sql-driver/mysql v1.9.2
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a
|
||||
github.com/gomodule/redigo v1.9.2
|
||||
github.com/google/wire v0.6.0
|
||||
github.com/gookit/color v1.5.4
|
||||
github.com/gookit/validate v1.5.4
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hashicorp/go-version v1.7.0
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0
|
||||
github.com/knadh/koanf/providers/file v1.1.2
|
||||
github.com/knadh/koanf/v2 v2.1.2
|
||||
github.com/leonelquinteros/gotext v1.7.1
|
||||
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/alidns v1.0.4
|
||||
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.5
|
||||
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/mholt/acmez/v3 v3.0.1
|
||||
github.com/libdns/vercel v0.0.2
|
||||
github.com/mholt/acmez/v3 v3.1.1
|
||||
github.com/ncruces/go-sqlite3 v0.25.0
|
||||
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.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-20250120210832-12a9c7ae0c8a
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/net v0.34.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250225174229-c03497ddaef6
|
||||
github.com/urfave/cli/v3 v3.1.1
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/net v0.39.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.25.12
|
||||
@@ -50,43 +65,40 @@ 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/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // 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/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/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/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // 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.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/text v0.24.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-20250101070450-67fb7b3d86d1
|
||||
)
|
||||
replace github.com/mholt/acmez/v3 => github.com/tnb-labs/acmez/v3 v3.0.0-20250329064837-dd8e7d30835a
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
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=
|
||||
@@ -8,60 +10,54 @@ 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/westcn v0.0.0-20250407083704-b49375823872 h1:cns0Vj69pUuQ+bfS3KQrZyQycjZRxyPMiF3Njjz5Voc=
|
||||
github.com/devhaozi/westcn v0.0.0-20250407083704-b49375823872/go.mod h1:3DfSvYwyUHt1JtN+JCWAGtOLFWlRXz19wJa/Xk159ao=
|
||||
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/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/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.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.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/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.6 h1:od2EIx8ihNyqUev0G71j0YnVagsTQ0giFCnn9H3qEAw=
|
||||
github.com/go-rat/chix v1.1.6/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.9 h1:16zq6CpeULJgoW+TaJZ+gS4EPl0tQYAcuAdx3/GNCNs=
|
||||
github.com/go-rat/utils v1.0.9/go.mod h1:LhzbrdsTHIo6Rjlz27Nh8edkugmoxbZjN87iV+ch+5M=
|
||||
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.5 h1:nB5qNU4TxqG9BJe7PdeDzznmh6lk+GIWb7hZcvAY4FQ=
|
||||
github.com/go-rat/utils v1.1.5/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.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
|
||||
github.com/go-sql-driver/mysql v1.9.2/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 +78,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=
|
||||
@@ -94,33 +92,67 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leonelquinteros/gotext v1.7.1 h1:/JNPeE3lY5JeVYv2+KBpz39994W3W9fmZCGq3eO9Ri8=
|
||||
github.com/leonelquinteros/gotext v1.7.1/go.mod h1:I0WoFDn9u2D3VbPnnDPT8mzZu0iSXG8iih+AH2fHHqg=
|
||||
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/alidns v1.0.4 h1:Rc3Yy2SzMoho+3q3+fNy9vOVr2h9dcL8OLTgNKgxYbU=
|
||||
github.com/libdns/alidns v1.0.4/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||
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.5 h1:TXv0QKkqgzEMSfqNUbAhoBj/2nrsiu1W6R0lKKH2vfc=
|
||||
github.com/libdns/huaweicloud v0.3.5/go.mod h1:fadizEwwxdO/+Owy5VVdnpm7rMRrbo/B9ofBAH5UN5M=
|
||||
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.25.0 h1:trugKUs98Zwy9KwRr/EUxZHL92LYt7UqcKqAfpGpK+I=
|
||||
github.com/ncruces/go-sqlite3 v0.25.0/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=
|
||||
@@ -129,82 +161,88 @@ 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-20250101070450-67fb7b3d86d1 h1:7IhZYEiBWy3KfKi9wZkv4Um8QYYF2fYzQ+xNrOx/tHI=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250101070450-67fb7b3d86d1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a h1:bFMBmB409YhHd+B4yfM7JPYTCgxnQjqzIfFOiN4Tpsc=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a/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/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.1.1 h1:bNnl8pFI5dxPOjeONvFCDFoECLQsceDG4ejahs4Jtxk=
|
||||
github.com/urfave/cli/v3 v3.1.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.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.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
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.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.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.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
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=
|
||||
@@ -212,22 +250,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.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
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.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
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=
|
||||
@@ -236,27 +277,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=
|
||||
|
||||
+2
-2
@@ -2,10 +2,10 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"github.com/gookit/color"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/tnb-labs/panel/pkg/apploader"
|
||||
@@ -30,7 +30,7 @@ func (r *Cli) Run() error {
|
||||
_ = r.migrator.Migrate()
|
||||
|
||||
if err := r.cmd.Run(context.TODO(), os.Args); err != nil {
|
||||
fmt.Printf("|-%v\n", err)
|
||||
color.Errorf("|-%v\n", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -4,11 +4,13 @@ import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/benchmark"
|
||||
"github.com/tnb-labs/panel/internal/apps/codeserver"
|
||||
"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"
|
||||
@@ -30,11 +32,13 @@ import (
|
||||
|
||||
var ProviderSet = wire.NewSet(
|
||||
benchmark.NewApp,
|
||||
codeserver.NewApp,
|
||||
docker.NewApp,
|
||||
fail2ban.NewApp,
|
||||
frp.NewApp,
|
||||
gitea.NewApp,
|
||||
memcached.NewApp,
|
||||
minio.NewApp,
|
||||
mysql.NewApp,
|
||||
nginx.NewApp,
|
||||
php74.NewApp,
|
||||
|
||||
@@ -22,15 +22,19 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -74,7 +78,7 @@ func (s *App) Test(w http.ResponseWriter, r *http.Request) {
|
||||
result := s.memoryTestTask()
|
||||
service.Success(w, result)
|
||||
default:
|
||||
service.Error(w, http.StatusUnprocessableEntity, "未知测试类型")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("unknown test type"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,7 +614,9 @@ func (s *App) diskTestTask() map[string]any {
|
||||
func (s *App) diskIOTest(blockSize int64, fileSize int64) map[string]any {
|
||||
result := make(map[string]any)
|
||||
tempFile := fmt.Sprintf("tempfile_%d", blockSize)
|
||||
defer os.Remove(tempFile)
|
||||
defer func(name string) {
|
||||
_ = os.Remove(name)
|
||||
}(tempFile)
|
||||
|
||||
// 写测试
|
||||
writeSpeed, writeIOPS := s.diskWriteTest(tempFile, blockSize, fileSize)
|
||||
@@ -635,7 +641,9 @@ func (s *App) diskWriteTest(fileName string, blockSize int64, fileSize int64) (f
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
defer file.Close()
|
||||
defer func(file *os.File) {
|
||||
_ = file.Close()
|
||||
}(file)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
@@ -668,7 +676,9 @@ func (s *App) diskReadTest(fileName string, blockSize int64, fileSize int64) (fl
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
defer file.Close()
|
||||
defer func(file *os.File) {
|
||||
_ = file.Close()
|
||||
}(file)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package codeserver
|
||||
|
||||
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("/config", s.GetConfig)
|
||||
r.Post("/config", s.UpdateConfig)
|
||||
}
|
||||
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, _ := io.Read("/root/.config/code-server/config.yaml")
|
||||
service.Success(w, config)
|
||||
}
|
||||
|
||||
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateConfig](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/root/.config/code-server/config.yaml", req.Config, 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Restart("code-server"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package codeserver
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/go-rat/utils/str"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -19,11 +20,13 @@ import (
|
||||
)
|
||||
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
websiteRepo biz.WebsiteRepo
|
||||
}
|
||||
|
||||
func NewApp(website biz.WebsiteRepo) *App {
|
||||
func NewApp(t *gotext.Locale, website biz.WebsiteRepo) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
websiteRepo: website,
|
||||
}
|
||||
}
|
||||
@@ -47,12 +50,8 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
jailList := regexp.MustCompile(`\[(.*?)]`).FindAllStringSubmatch(raw, -1)
|
||||
if len(jailList) == 0 {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "Fail2ban 规则为空")
|
||||
return
|
||||
}
|
||||
|
||||
var jails []Jail
|
||||
jails := make([]Jail, 0)
|
||||
for i, jail := range jailList {
|
||||
if i == 0 {
|
||||
continue
|
||||
@@ -107,7 +106,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if (strings.Contains(raw, "["+jailName+"]") && jailType == "service") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-cc") && jailType == "website" && jailWebsiteMode == "cc") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-path") && jailType == "website" && jailWebsiteMode == "path") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "规则已存在")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("rule already exists"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -115,7 +114,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
case "website":
|
||||
website, err := s.websiteRepo.GetByName(jailWebsiteName)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "获取网站配置失败:%v", err)
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
var ports string
|
||||
@@ -140,7 +139,7 @@ logpath = ` + app.Root + `/wwwlogs/` + website.Name + `.log
|
||||
`
|
||||
raw += rule
|
||||
if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -159,7 +158,7 @@ ignoreregex =
|
||||
`
|
||||
}
|
||||
if err = io.Write("/etc/fail2ban/filter.d/haozi-"+jailWebsiteName+"-"+jailWebsiteMode+".conf", filter, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -178,11 +177,11 @@ ignoreregex =
|
||||
filter = "pure-ftpd"
|
||||
port, err = shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, app.Root)
|
||||
default:
|
||||
service.Error(w, http.StatusUnprocessableEntity, "未知服务")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("unknown service"))
|
||||
return
|
||||
}
|
||||
if len(port) == 0 || err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "获取服务端口失败,请检查是否安装")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("get service port failed, please check if it is installed"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -199,13 +198,13 @@ bantime = ` + jailBanTime + `
|
||||
`
|
||||
raw += rule
|
||||
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("fail2ban-client reload"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -226,20 +225,20 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !strings.Contains(raw, "["+req.Name+"]") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "规则不存在")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("rule not found"))
|
||||
return
|
||||
}
|
||||
|
||||
rule := str.Cut(raw, "# "+req.Name+"-START", "# "+req.Name+"-END")
|
||||
raw = strings.Replace(raw, "\n# "+req.Name+"-START"+rule+"# "+req.Name+"-END", "", -1)
|
||||
raw = strings.ReplaceAll(raw, "\n# "+req.Name+"-START"+rule+"# "+req.Name+"-END", "")
|
||||
raw = strings.TrimSpace(raw)
|
||||
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -256,17 +255,17 @@ func (s *App) BanList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
currentlyBan, err := shell.Execf(`fail2ban-client status %s | grep "Currently banned" | awk '{print $4}'`, req.Name)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get current banned list"))
|
||||
return
|
||||
}
|
||||
totalBan, err := shell.Execf(`fail2ban-client status %s | grep "Total banned" | awk '{print $4}'`, req.Name)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get total banned list"))
|
||||
return
|
||||
}
|
||||
bannedIp, err := shell.Execf(`fail2ban-client status %s | grep "Banned IP list" | awk -F ":" '{print $2}'`, req.Name)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get banned ip list"))
|
||||
return
|
||||
}
|
||||
bannedIpList := strings.Split(bannedIp, " ")
|
||||
@@ -300,7 +299,7 @@ func (s *App) Unban(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("fail2ban-client set %s unbanip %s", req.Name, req.IP); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "解封失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -325,17 +324,17 @@ func (s *App) SetWhiteList(w http.ResponseWriter, r *http.Request) {
|
||||
if reg.MatchString(raw) {
|
||||
raw = reg.ReplaceAllString(raw, "ignoreip = "+req.IP+"\n")
|
||||
} else {
|
||||
service.Error(w, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to parse the ignoreip of fail2ban"))
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("fail2ban-client reload"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
service.Success(w, nil)
|
||||
@@ -353,7 +352,7 @@ func (s *App) GetWhiteList(w http.ResponseWriter, r *http.Request) {
|
||||
ignoreIp := reg.FindStringSubmatch(raw)[1]
|
||||
service.Success(w, ignoreIp)
|
||||
} else {
|
||||
service.Error(w, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to parse the ignoreip of fail2ban"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
@@ -14,10 +15,14 @@ import (
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -29,7 +34,7 @@ func (s *App) Route(r chi.Router) {
|
||||
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := systemctl.Status("memcached")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get Memcached status: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get Memcached status: %v", err))
|
||||
return
|
||||
}
|
||||
if !status {
|
||||
@@ -42,11 +47,13 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func(conn net.Conn) {
|
||||
_ = conn.Close()
|
||||
}(conn)
|
||||
|
||||
_, err = conn.Write([]byte("stats\nquit\n"))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to write to Memcached: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to write to Memcached: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,7 +74,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to read from Memcached: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to read from Memcached: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
+35
-34
@@ -7,6 +7,7 @@ import (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -21,11 +22,13 @@ import (
|
||||
)
|
||||
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
settingRepo biz.SettingRepo
|
||||
}
|
||||
|
||||
func NewApp(setting biz.SettingRepo) *App {
|
||||
func NewApp(t *gotext.Locale, setting biz.SettingRepo) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
settingRepo: setting,
|
||||
}
|
||||
}
|
||||
@@ -45,7 +48,7 @@ func (s *App) Route(r chi.Router) {
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(app.Root + "/server/mysql/conf/my.cnf")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -61,12 +64,12 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = io.Write(app.Root+"/server/mysql/conf/my.cnf", req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入配置失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Restart("mysqld"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重启失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to restart MySQL: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -77,12 +80,12 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
rootPassword, err := s.settingRepo.Get(biz.SettingKeyMySQLRootPassword)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取root密码失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to load MySQL root password: %v", err))
|
||||
return
|
||||
|
||||
}
|
||||
if len(rootPassword) == 0 {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "root密码为空")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("MySQL root password is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -93,12 +96,12 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = os.Setenv("MYSQL_PWD", rootPassword); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "设置环境变量失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set MYSQL_PWD env: %v", err))
|
||||
return
|
||||
}
|
||||
raw, err := shell.Execf(`mysqladmin -u root extended-status`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取负载失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get MySQL status: %v", err))
|
||||
return
|
||||
}
|
||||
_ = os.Unsetenv("MYSQL_PWD")
|
||||
@@ -108,24 +111,24 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
regex string
|
||||
name string
|
||||
}{
|
||||
{`Uptime\s+\|\s+(\d+)\s+\|`, "运行时间"},
|
||||
{`Queries\s+\|\s+(\d+)\s+\|`, "总查询次数"},
|
||||
{`Connections\s+\|\s+(\d+)\s+\|`, "总连接次数"},
|
||||
{`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒事务"},
|
||||
{`Com_rollback\s+\|\s+(\d+)\s+\|`, "每秒回滚"},
|
||||
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, "发送"},
|
||||
{`Bytes_received\s+\|\s+(\d+)\s+\|`, "接收"},
|
||||
{`Threads_connected\s+\|\s+(\d+)\s+\|`, "活动连接数"},
|
||||
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, "峰值连接数"},
|
||||
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, "索引命中率"},
|
||||
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"},
|
||||
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "创建临时表到硬盘"},
|
||||
{`Open_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"},
|
||||
{`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"},
|
||||
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"},
|
||||
{`Select_range_check\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"},
|
||||
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"},
|
||||
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, "锁表次数"},
|
||||
{`Uptime\s+\|\s+(\d+)\s+\|`, s.t.Get("Uptime")},
|
||||
{`Queries\s+\|\s+(\d+)\s+\|`, s.t.Get("Total Queries")},
|
||||
{`Connections\s+\|\s+(\d+)\s+\|`, s.t.Get("Total Connections")},
|
||||
{`Com_commit\s+\|\s+(\d+)\s+\|`, s.t.Get("Transactions per Second")},
|
||||
{`Com_rollback\s+\|\s+(\d+)\s+\|`, s.t.Get("Rollbacks per Second")},
|
||||
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, s.t.Get("Bytes Sent")},
|
||||
{`Bytes_received\s+\|\s+(\d+)\s+\|`, s.t.Get("Bytes Received")},
|
||||
{`Threads_connected\s+\|\s+(\d+)\s+\|`, s.t.Get("Active Connections")},
|
||||
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, s.t.Get("Peak Connections")},
|
||||
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, s.t.Get("Index Hit Rate")},
|
||||
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, s.t.Get("Innodb Index Hit Rate")},
|
||||
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, s.t.Get("Temporary Tables Created on Disk")},
|
||||
{`Open_tables\s+\|\s+(\d+)\s+\|`, s.t.Get("Open Tables")},
|
||||
{`Select_full_join\s+\|\s+(\d+)\s+\|`, s.t.Get("Full Joins without Index")},
|
||||
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, s.t.Get("Full Range Joins without Index")},
|
||||
{`Select_range_check\s+\|\s+(\d+)\s+\|`, s.t.Get("Subqueries without Index")},
|
||||
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, s.t.Get("Sort Merge Passes")},
|
||||
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, s.t.Get("Table Locks Waited")},
|
||||
}
|
||||
|
||||
for _, expression := range expressions {
|
||||
@@ -133,7 +136,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
matches := re.FindStringSubmatch(raw)
|
||||
if len(matches) > 1 {
|
||||
d := map[string]string{"name": expression.name, "value": matches[1]}
|
||||
if expression.name == "发送" || expression.name == "接收" {
|
||||
if expression.name == s.t.Get("Bytes Sent") || expression.name == s.t.Get("Bytes Received") {
|
||||
d["value"] = tools.FormatBytes(cast.ToFloat64(matches[1]))
|
||||
}
|
||||
|
||||
@@ -182,11 +185,7 @@ func (s *App) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *App) GetRootPassword(w http.ResponseWriter, r *http.Request) {
|
||||
rootPassword, err := s.settingRepo.Get(biz.SettingKeyMySQLRootPassword)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取root密码失败")
|
||||
return
|
||||
}
|
||||
if len(rootPassword) == 0 {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "root密码为空")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to load MySQL root password: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -210,14 +209,16 @@ func (s *App) SetRootPassword(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
defer mysql.Close()
|
||||
defer func(mysql *db.MySQL) {
|
||||
_ = mysql.Close()
|
||||
}(mysql)
|
||||
if err = mysql.UserPassword("root", req.Password, "localhost"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err = s.settingRepo.Set(biz.SettingKeyMySQLRootPassword, req.Password); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "设置保存失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+21
-17
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -20,11 +21,13 @@ import (
|
||||
)
|
||||
|
||||
type App struct {
|
||||
// Dependent services
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -38,7 +41,7 @@ func (s *App) Route(r chi.Router) {
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -53,13 +56,13 @@ func (s *App) SaveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "保存配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
service.Error(w, http.StatusInternalServerError, "重载服务失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -84,6 +87,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
resp, err := client.R().Get("http://127.0.0.1/nginx_status")
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
raw := resp.String()
|
||||
@@ -91,29 +95,29 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
workers, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | wc -l")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取负载失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get nginx workers: %v", err))
|
||||
return
|
||||
}
|
||||
data = append(data, types.NV{
|
||||
Name: "工作进程",
|
||||
Name: s.t.Get("Workers"),
|
||||
Value: workers,
|
||||
})
|
||||
|
||||
out, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取负载失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get nginx workers: %v", err))
|
||||
return
|
||||
}
|
||||
mem := tools.FormatBytes(cast.ToFloat64(out))
|
||||
data = append(data, types.NV{
|
||||
Name: "内存占用",
|
||||
Name: s.t.Get("Memory"),
|
||||
Value: mem,
|
||||
})
|
||||
|
||||
match := regexp.MustCompile(`Active connections:\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 2 {
|
||||
data = append(data, types.NV{
|
||||
Name: "活跃连接数",
|
||||
Name: s.t.Get("Active connections"),
|
||||
Value: match[1],
|
||||
})
|
||||
}
|
||||
@@ -121,15 +125,15 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
match = regexp.MustCompile(`server accepts handled requests\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 4 {
|
||||
data = append(data, types.NV{
|
||||
Name: "总连接次数",
|
||||
Name: s.t.Get("Total connections"),
|
||||
Value: match[1],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "总握手次数",
|
||||
Name: s.t.Get("Total handshakes"),
|
||||
Value: match[2],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "总请求次数",
|
||||
Name: s.t.Get("Total requests"),
|
||||
Value: match[3],
|
||||
})
|
||||
}
|
||||
@@ -137,15 +141,15 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
match = regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 4 {
|
||||
data = append(data, types.NV{
|
||||
Name: "请求数",
|
||||
Name: s.t.Get("Reading"),
|
||||
Value: match[1],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "响应数",
|
||||
Name: s.t.Get("Writing"),
|
||||
Value: match[2],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "驻留进程",
|
||||
Name: s.t.Get("Waiting"),
|
||||
Value: match[3],
|
||||
})
|
||||
}
|
||||
|
||||
+223
-134
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -22,11 +23,13 @@ import (
|
||||
|
||||
type App struct {
|
||||
version uint
|
||||
t *gotext.Locale
|
||||
taskRepo biz.TaskRepo
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
taskRepo: task,
|
||||
}
|
||||
}
|
||||
@@ -35,6 +38,7 @@ func (s *App) Route(version uint) func(r chi.Router) {
|
||||
return func(r chi.Router) {
|
||||
php := new(App)
|
||||
php.version = version
|
||||
php.t = s.t
|
||||
php.taskRepo = s.taskRepo
|
||||
r.Post("/setCli", php.SetCli)
|
||||
r.Get("/config", php.GetConfig)
|
||||
@@ -120,8 +124,36 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
dataKeys := []string{"应用池", "工作模式", "启动时间", "接受连接", "监听队列", "最大监听队列", "监听队列长度", "空闲进程数量", "活动进程数量", "总进程数量", "最大活跃进程数量", "达到进程上限次数", "慢请求"}
|
||||
rawKeys := []string{"pool", "process manager", "start time", "accepted conn", "listen queue", "max listen queue", "listen queue len", "idle processes", "active processes", "total processes", "max active processes", "max children reached", "slow requests"}
|
||||
dataKeys := []string{
|
||||
s.t.Get("Application Pool"),
|
||||
s.t.Get("Process Manager"),
|
||||
s.t.Get("Start Time"),
|
||||
s.t.Get("Accepted Connections"),
|
||||
s.t.Get("Listen Queue"),
|
||||
s.t.Get("Max Listen Queue"),
|
||||
s.t.Get("Listen Queue Length"),
|
||||
s.t.Get("Idle Processes"),
|
||||
s.t.Get("Active Processes"),
|
||||
s.t.Get("Total Processes"),
|
||||
s.t.Get("Max Active Processes"),
|
||||
s.t.Get("Max Children Reached"),
|
||||
s.t.Get("Slow Requests"),
|
||||
}
|
||||
rawKeys := []string{
|
||||
"pool",
|
||||
"process manager",
|
||||
"start time",
|
||||
"accepted conn",
|
||||
"listen queue",
|
||||
"max listen queue",
|
||||
"listen queue len",
|
||||
"idle processes",
|
||||
"active processes",
|
||||
"total processes",
|
||||
"max active processes",
|
||||
"max children reached",
|
||||
"slow requests",
|
||||
}
|
||||
|
||||
loads := make([]types.NV, 0)
|
||||
for i := range dataKeys {
|
||||
@@ -194,7 +226,7 @@ func (s *App) InstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if !s.checkExtension(req.Slug) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "扩展不存在")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("extension %s does not exist", req.Slug))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -205,7 +237,7 @@ func (s *App) InstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = fmt.Sprintf("安装PHP-%d扩展 %s", s.version, req.Slug)
|
||||
task.Name = s.t.Get("Install PHP-%d %s extension", s.version, req.Slug)
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = cmd
|
||||
task.Log = "/tmp/" + req.Slug + ".log"
|
||||
@@ -225,7 +257,7 @@ func (s *App) UninstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if !s.checkExtension(req.Slug) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "扩展不存在")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("extension %s does not exist", req.Slug))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -236,7 +268,7 @@ func (s *App) UninstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = fmt.Sprintf("卸载PHP-%d扩展 %s", s.version, req.Slug)
|
||||
task.Name = s.t.Get("Uninstall PHP-%d %s extension", s.version, req.Slug)
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = cmd
|
||||
task.Log = "/tmp/" + req.Slug + ".log"
|
||||
@@ -253,149 +285,206 @@ func (s *App) getExtensions() []Extension {
|
||||
{
|
||||
Name: "fileinfo",
|
||||
Slug: "fileinfo",
|
||||
Description: "Fileinfo 是一个用于识别文件类型的库",
|
||||
Description: s.t.Get("Fileinfo is a library used to identify file types"),
|
||||
},
|
||||
{
|
||||
Name: "OPcache",
|
||||
Slug: "Zend OPcache",
|
||||
Description: "OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码可以省去每次加载和解析 PHP 脚本的开销",
|
||||
},
|
||||
{
|
||||
Name: "Redis",
|
||||
Slug: "redis",
|
||||
Description: "PhpRedis 是一个用 C 语言编写的 PHP 模块,用来连接并操作 Redis 数据库上的数据(需先安装 igbinary 拓展)",
|
||||
},
|
||||
{
|
||||
Name: "Memcached",
|
||||
Slug: "memcached",
|
||||
Description: "Memcached 使用 libmemcached 库连接 Memcached 服务器",
|
||||
},
|
||||
{
|
||||
Name: "ImageMagick",
|
||||
Slug: "imagick",
|
||||
Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件",
|
||||
},
|
||||
{
|
||||
Name: "exif",
|
||||
Slug: "exif",
|
||||
Description: "通过 exif 扩展,您可以操作图像元数据",
|
||||
},
|
||||
{
|
||||
Name: "pgsql",
|
||||
Slug: "pgsql",
|
||||
Description: "pgsql 是一个驱动程序,它启用从 PHP 到 PostgreSQL 数据库的访问(需先安装 PostgreSQL 应用)",
|
||||
},
|
||||
{
|
||||
Name: "pdo_pgsql",
|
||||
Slug: "pdo_pgsql",
|
||||
Description: "pdo_pgsql 是一个驱动程序,它实现了 PHP 数据对象(PDO)接口以启用从 PHP 到 PostgreSQL 数据库的访问(需先安装 PostgreSQL 应用)",
|
||||
},
|
||||
{
|
||||
Name: "imap",
|
||||
Slug: "imap",
|
||||
Description: "IMAP 扩展允许 PHP 读取、搜索、删除、下载和管理邮件",
|
||||
},
|
||||
{
|
||||
Name: "zip",
|
||||
Slug: "zip",
|
||||
Description: "Zip 是一个用于处理 ZIP 文件的库",
|
||||
},
|
||||
{
|
||||
Name: "bz2",
|
||||
Slug: "bz2",
|
||||
Description: "Bzip2 是一个用于压缩和解压缩文件的库",
|
||||
},
|
||||
{
|
||||
Name: "readline",
|
||||
Slug: "readline",
|
||||
Description: "Readline 是一个库,它提供了一种用于处理文本的接口",
|
||||
},
|
||||
{
|
||||
Name: "snmp",
|
||||
Slug: "snmp",
|
||||
Description: "SNMP 是一种用于网络管理的协议",
|
||||
},
|
||||
{
|
||||
Name: "ldap",
|
||||
Slug: "ldap",
|
||||
Description: "LDAP 是一种用于访问目录服务的协议",
|
||||
},
|
||||
{
|
||||
Name: "enchant",
|
||||
Slug: "enchant",
|
||||
Description: "Enchant 是一个拼写检查库",
|
||||
},
|
||||
{
|
||||
Name: "pspell",
|
||||
Slug: "pspell",
|
||||
Description: "Pspell 是一个拼写检查库",
|
||||
},
|
||||
{
|
||||
Name: "calendar",
|
||||
Slug: "calendar",
|
||||
Description: "Calendar 是一个用于处理日期的库",
|
||||
},
|
||||
{
|
||||
Name: "gmp",
|
||||
Slug: "gmp",
|
||||
Description: "GMP 是一个用于处理大整数的库",
|
||||
},
|
||||
{
|
||||
Name: "sysvmsg",
|
||||
Slug: "sysvmsg",
|
||||
Description: "Sysvmsg 是一个用于处理 System V 消息队列的库",
|
||||
},
|
||||
{
|
||||
Name: "sysvsem",
|
||||
Slug: "sysvsem",
|
||||
Description: "Sysvsem 是一个用于处理 System V 信号量的库",
|
||||
},
|
||||
{
|
||||
Name: "sysvshm",
|
||||
Slug: "sysvshm",
|
||||
Description: "Sysvshm 是一个用于处理 System V 共享内存的库",
|
||||
},
|
||||
{
|
||||
Name: "xsl",
|
||||
Slug: "xsl",
|
||||
Description: "XSL 是一个用于处理 XML 文档的库",
|
||||
},
|
||||
{
|
||||
Name: "intl",
|
||||
Slug: "intl",
|
||||
Description: "Intl 是一个用于处理国际化和本地化的库",
|
||||
},
|
||||
{
|
||||
Name: "gettext",
|
||||
Slug: "gettext",
|
||||
Description: "Gettext 是一个用于处理多语言的库",
|
||||
Description: s.t.Get("OPcache stores precompiled PHP script bytecode in shared memory to improve PHP performance"),
|
||||
},
|
||||
{
|
||||
Name: "igbinary",
|
||||
Slug: "igbinary",
|
||||
Description: "Igbinary 是一个用于序列化和反序列化数据的库",
|
||||
Description: s.t.Get("Igbinary is a library for serializing and deserializing data"),
|
||||
},
|
||||
{
|
||||
Name: "Redis",
|
||||
Slug: "redis",
|
||||
Description: s.t.Get("PhpRedis connects to and operates on data in Redis databases (requires the igbinary extension installed above)"),
|
||||
},
|
||||
{
|
||||
Name: "Memcached",
|
||||
Slug: "memcached",
|
||||
Description: s.t.Get("Memcached is a driver for connecting to Memcached servers"),
|
||||
},
|
||||
{
|
||||
Name: "ImageMagick",
|
||||
Slug: "imagick",
|
||||
Description: s.t.Get("ImageMagick is free software for creating, editing, and composing images"),
|
||||
},
|
||||
{
|
||||
Name: "exif",
|
||||
Slug: "exif",
|
||||
Description: s.t.Get("Exif is a library for reading and writing image metadata"),
|
||||
},
|
||||
{
|
||||
Name: "pgsql",
|
||||
Slug: "pgsql",
|
||||
Description: s.t.Get("pgsql is a driver for connecting to PostgreSQL (requires PostgreSQL installed)"),
|
||||
},
|
||||
{
|
||||
Name: "pdo_pgsql",
|
||||
Slug: "pdo_pgsql",
|
||||
Description: s.t.Get("pdo_pgsql is a PDO driver for connecting to PostgreSQL (requires PostgreSQL installed)"),
|
||||
},
|
||||
{
|
||||
Name: "sqlsrv",
|
||||
Slug: "sqlsrv",
|
||||
Description: s.t.Get("sqlsrv is a driver for connecting to SQL Server"),
|
||||
},
|
||||
{
|
||||
Name: "pdo_sqlsrv",
|
||||
Slug: "pdo_sqlsrv",
|
||||
Description: s.t.Get("pdo_sqlsrv is a PDO driver for connecting to SQL Server"),
|
||||
},
|
||||
{
|
||||
Name: "imap",
|
||||
Slug: "imap",
|
||||
Description: s.t.Get("IMAP extension allows PHP to read, search, delete, download, and manage emails"),
|
||||
},
|
||||
{
|
||||
Name: "zip",
|
||||
Slug: "zip",
|
||||
Description: s.t.Get("Zip is a library for handling ZIP files"),
|
||||
},
|
||||
{
|
||||
Name: "bz2",
|
||||
Slug: "bz2",
|
||||
Description: s.t.Get("Bzip2 is a library for compressing and decompressing files"),
|
||||
},
|
||||
{
|
||||
Name: "ssh2",
|
||||
Slug: "ssh2",
|
||||
Description: s.t.Get("SSH2 is a library for connecting to SSH servers"),
|
||||
},
|
||||
{
|
||||
Name: "event",
|
||||
Slug: "event",
|
||||
Description: s.t.Get("Event is a library for handling events"),
|
||||
},
|
||||
{
|
||||
Name: "readline",
|
||||
Slug: "readline",
|
||||
Description: s.t.Get("Readline is a library for processing text"),
|
||||
},
|
||||
{
|
||||
Name: "snmp",
|
||||
Slug: "snmp",
|
||||
Description: s.t.Get("SNMP is a protocol for network management"),
|
||||
},
|
||||
{
|
||||
Name: "ldap",
|
||||
Slug: "ldap",
|
||||
Description: s.t.Get("LDAP is a protocol for accessing directory services"),
|
||||
},
|
||||
{
|
||||
Name: "enchant",
|
||||
Slug: "enchant",
|
||||
Description: s.t.Get("Enchant is a spell-checking library"),
|
||||
},
|
||||
{
|
||||
Name: "pspell",
|
||||
Slug: "pspell",
|
||||
Description: s.t.Get("Pspell is a spell-checking library"),
|
||||
},
|
||||
{
|
||||
Name: "calendar",
|
||||
Slug: "calendar",
|
||||
Description: s.t.Get("Calendar is a library for handling dates"),
|
||||
},
|
||||
{
|
||||
Name: "gmp",
|
||||
Slug: "gmp",
|
||||
Description: s.t.Get("GMP is a library for handling large integers"),
|
||||
},
|
||||
{
|
||||
Name: "xlswriter",
|
||||
Slug: "xlswriter",
|
||||
Description: s.t.Get("XLSWriter is a high-performance library for reading and writing Excel files"),
|
||||
},
|
||||
{
|
||||
Name: "xsl",
|
||||
Slug: "xsl",
|
||||
Description: s.t.Get("XSL is a library for processing XML documents"),
|
||||
},
|
||||
{
|
||||
Name: "intl",
|
||||
Slug: "intl",
|
||||
Description: s.t.Get("Intl is a library for handling internationalization and localization"),
|
||||
},
|
||||
{
|
||||
Name: "gettext",
|
||||
Slug: "gettext",
|
||||
Description: s.t.Get("Gettext is a library for handling multilingual support"),
|
||||
},
|
||||
{
|
||||
Name: "grpc",
|
||||
Slug: "grpc",
|
||||
Description: s.t.Get("gRPC is a high-performance, open-source, and general-purpose RPC framework"),
|
||||
},
|
||||
{
|
||||
Name: "protobuf",
|
||||
Slug: "protobuf",
|
||||
Description: s.t.Get("protobuf is a library for serializing and deserializing data"),
|
||||
},
|
||||
{
|
||||
Name: "rdkafka",
|
||||
Slug: "rdkafka",
|
||||
Description: s.t.Get("rdkafka is a library for connecting to Apache Kafka"),
|
||||
},
|
||||
{
|
||||
Name: "xhprof",
|
||||
Slug: "xhprof",
|
||||
Description: s.t.Get("xhprof is a library for performance profiling"),
|
||||
},
|
||||
{
|
||||
Name: "xdebug",
|
||||
Slug: "xdebug",
|
||||
Description: s.t.Get("xdebug is a library for debugging and profiling PHP code"),
|
||||
},
|
||||
{
|
||||
Name: "yaml",
|
||||
Slug: "yaml",
|
||||
Description: s.t.Get("yaml is a library for handling YAML"),
|
||||
},
|
||||
{
|
||||
Name: "zstd",
|
||||
Slug: "zstd",
|
||||
Description: s.t.Get("zstd is a library for compressing and decompressing files"),
|
||||
},
|
||||
{
|
||||
Name: "sysvmsg",
|
||||
Slug: "sysvmsg",
|
||||
Description: s.t.Get("Sysvmsg is a library for handling System V message queues"),
|
||||
},
|
||||
{
|
||||
Name: "sysvsem",
|
||||
Slug: "sysvsem",
|
||||
Description: s.t.Get("Sysvsem is a library for handling System V semaphores"),
|
||||
},
|
||||
{
|
||||
Name: "sysvshm",
|
||||
Slug: "sysvshm",
|
||||
Description: s.t.Get("Sysvshm is a library for handling System V shared memory"),
|
||||
},
|
||||
{
|
||||
Name: "ionCube",
|
||||
Slug: "ionCube Loader",
|
||||
Description: s.t.Get("ionCube is a professional-grade PHP encryption and decryption tool (must be installed after OPcache)"),
|
||||
},
|
||||
{
|
||||
Name: "Swoole",
|
||||
Slug: "swoole",
|
||||
Description: s.t.Get("Swoole is a PHP extension for building high-performance asynchronous concurrent servers"),
|
||||
},
|
||||
}
|
||||
|
||||
// ionCube Swoole 不支持 PHP 8.4
|
||||
if cast.ToUint(s.version) < 84 {
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "ionCube",
|
||||
Slug: "ionCube Loader",
|
||||
Description: "ionCube 是一个专业级的 PHP 加密解密工具(需在 OPcache 之后安装)",
|
||||
})
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "Swoole",
|
||||
Slug: "swoole",
|
||||
Description: "Swoole 是一个用于构建高性能的异步并发服务器的 PHP 扩展",
|
||||
})
|
||||
}
|
||||
// Swow 不支持 PHP 8.0 以下版本且目前不支持 PHP 8.4
|
||||
if cast.ToUint(s.version) >= 80 && cast.ToUint(s.version) < 84 {
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "Swow",
|
||||
Slug: "Swow",
|
||||
Description: "Swow 是一个用于构建高性能的异步并发服务器的 PHP 扩展",
|
||||
Description: s.t.Get("Swow is a PHP extension for building high-performance asynchronous concurrent servers"),
|
||||
})
|
||||
}
|
||||
// PHP 8.4 移除了 pspell 和 imap 并且不再建议使用
|
||||
|
||||
@@ -2,6 +2,7 @@ package php74
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -11,9 +12,9 @@ type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package php80
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -11,9 +12,9 @@ type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package php81
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -11,9 +12,9 @@ type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package php82
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -11,9 +12,9 @@ type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package php83
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -11,9 +12,9 @@ type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package php84
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -11,9 +12,9 @@ type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -19,10 +20,14 @@ import (
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -35,7 +40,7 @@ func (s *App) Route(r chi.Router) {
|
||||
func (s *App) Info(w http.ResponseWriter, r *http.Request) {
|
||||
files, err := os.ReadDir(fmt.Sprintf("%s/server/phpmyadmin", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin directory not found"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,7 +51,7 @@ func (s *App) Info(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
if len(phpmyadmin) == 0 {
|
||||
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin directory not found"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -57,7 +62,7 @@ func (s *App) Info(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
match := regexp.MustCompile(`listen\s+(\d+);`).FindStringSubmatch(conf)
|
||||
if len(match) == 0 {
|
||||
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 端口")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin port not found"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -81,7 +86,7 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
conf = regexp.MustCompile(`listen\s+(\d+);`).ReplaceAllString(conf, "listen "+cast.ToString(req.Port)+";")
|
||||
if err = io.Write(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", app.Root), conf, 0644); err != nil {
|
||||
service.ErrorSystem(w)
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,7 +105,7 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
service.Error(w, http.StatusInternalServerError, "重载OpenResty失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -131,7 +136,7 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
service.Error(w, http.StatusInternalServerError, "重载OpenResty失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
@@ -15,10 +16,14 @@ import (
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -36,7 +41,7 @@ func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
// 获取配置
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -51,13 +56,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 {
|
||||
service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败")
|
||||
if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
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, s.t.Get("failed to reload PostgreSQL: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,7 +74,7 @@ func (s *App) GetUserConfig(w http.ResponseWriter, r *http.Request) {
|
||||
// 获取配置
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -84,13 +89,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 {
|
||||
service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败")
|
||||
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, "%v", err)
|
||||
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, s.t.Get("failed to reload PostgreSQL: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -107,36 +112,36 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
start, err := shell.Execf(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL启动时间失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL start time: %v", err))
|
||||
return
|
||||
}
|
||||
pid, err := shell.Execf(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL进程PID失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL backend pid: %v", err))
|
||||
return
|
||||
}
|
||||
process, err := shell.Execf(`ps aux | grep postgres | grep -v grep | wc -l`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL进程数失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL process: %v", err))
|
||||
return
|
||||
}
|
||||
connections, err := shell.Execf(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL连接数失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL connections: %v", err))
|
||||
return
|
||||
}
|
||||
storage, err := shell.Execf(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL空间占用失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL database size: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
data := []types.NV{
|
||||
{Name: "启动时间", Value: start},
|
||||
{Name: "进程 PID", Value: pid},
|
||||
{Name: "进程数", Value: process},
|
||||
{Name: "总连接数", Value: connections},
|
||||
{Name: "空间占用", Value: storage},
|
||||
{Name: s.t.Get("Start Time"), Value: start},
|
||||
{Name: s.t.Get("Process PID"), Value: pid},
|
||||
{Name: s.t.Get("Process Count"), Value: process},
|
||||
{Name: s.t.Get("Total Connections"), Value: connections},
|
||||
{Name: s.t.Get("Storage Usage"), Value: storage},
|
||||
}
|
||||
|
||||
service.Success(w, data)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -17,10 +18,14 @@ import (
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -76,16 +81,16 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
req.Path = "/" + req.Path
|
||||
}
|
||||
if !io.Exists(req.Path) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "目录不存在")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("directory %s does not exist", req.Path))
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Chmod(req.Path, 0755); err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "修改目录权限失败")
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = io.Chown(req.Path, "www", "www"); err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "修改目录权限失败")
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf(`yes '%s' | pure-pw useradd '%s' -u www -g www -d '%s'`, req.Password, req.Username, req.Path); err != nil {
|
||||
@@ -144,7 +149,7 @@ func (s *App) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *App) GetPort(w http.ResponseWriter, r *http.Request) {
|
||||
port, err := shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, app.Root)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PureFtpd端口失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get port: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+24
-19
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
@@ -16,10 +17,14 @@ import (
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -31,7 +36,7 @@ func (s *App) Route(r chi.Router) {
|
||||
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := systemctl.Status("redis")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取 Redis 状态失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get redis status: %v", err))
|
||||
return
|
||||
}
|
||||
if !status {
|
||||
@@ -46,7 +51,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
re := regexp.MustCompile(`requirepass\s+(.+)`)
|
||||
re := regexp.MustCompile(`^requirepass\s+(.+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) == 2 {
|
||||
withPassword = " -a " + matches[1]
|
||||
@@ -54,7 +59,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
raw, err := shell.Execf("redis-cli%s info", withPassword)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取 Redis 负载失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get redis info: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,19 +74,19 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
data := []types.NV{
|
||||
{Name: "TCP 端口", Value: dataRaw["tcp_port"]},
|
||||
{Name: "已运行天数", Value: dataRaw["uptime_in_days"]},
|
||||
{Name: "连接的客户端数", Value: dataRaw["connected_clients"]},
|
||||
{Name: "已分配的内存总量", Value: dataRaw["used_memory_human"]},
|
||||
{Name: "占用内存总量", Value: dataRaw["used_memory_rss_human"]},
|
||||
{Name: "占用内存峰值", Value: dataRaw["used_memory_peak_human"]},
|
||||
{Name: "内存碎片比率", Value: dataRaw["mem_fragmentation_ratio"]},
|
||||
{Name: "运行以来连接过的客户端的总数", Value: dataRaw["total_connections_received"]},
|
||||
{Name: "运行以来执行过的命令的总数", Value: dataRaw["total_commands_processed"]},
|
||||
{Name: "每秒执行的命令数", Value: dataRaw["instantaneous_ops_per_sec"]},
|
||||
{Name: "查找数据库键成功次数", Value: dataRaw["keyspace_hits"]},
|
||||
{Name: "查找数据库键失败次数", Value: dataRaw["keyspace_misses"]},
|
||||
{Name: "最近一次 fork() 操作耗费的毫秒数", Value: dataRaw["latest_fork_usec"]},
|
||||
{Name: s.t.Get("TCP Port"), Value: dataRaw["tcp_port"]},
|
||||
{Name: s.t.Get("Uptime in Days"), Value: dataRaw["uptime_in_days"]},
|
||||
{Name: s.t.Get("Connected Clients"), Value: dataRaw["connected_clients"]},
|
||||
{Name: s.t.Get("Total Allocated Memory"), Value: dataRaw["used_memory_human"]},
|
||||
{Name: s.t.Get("Total Memory Usage"), Value: dataRaw["used_memory_rss_human"]},
|
||||
{Name: s.t.Get("Peak Memory Usage"), Value: dataRaw["used_memory_peak_human"]},
|
||||
{Name: s.t.Get("Memory Fragmentation Ratio"), Value: dataRaw["mem_fragmentation_ratio"]},
|
||||
{Name: s.t.Get("Total Connections Received"), Value: dataRaw["total_connections_received"]},
|
||||
{Name: s.t.Get("Total Commands Processed"), Value: dataRaw["total_commands_processed"]},
|
||||
{Name: s.t.Get("Commands Per Second"), Value: dataRaw["instantaneous_ops_per_sec"]},
|
||||
{Name: s.t.Get("Keyspace Hits"), Value: dataRaw["keyspace_hits"]},
|
||||
{Name: s.t.Get("Keyspace Misses"), Value: dataRaw["keyspace_misses"]},
|
||||
{Name: s.t.Get("Latest Fork Time (ms)"), Value: dataRaw["latest_fork_usec"]},
|
||||
}
|
||||
|
||||
service.Success(w, data)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/go-rat/utils/str"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
@@ -16,10 +17,14 @@ import (
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -74,7 +79,7 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) {
|
||||
currentModule.AuthUser = value
|
||||
currentModule.Secret, err = shell.Execf(`grep -E '^%s:.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'`, currentModule.AuthUser)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取模块%s的密钥失败", currentModule.AuthUser)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get the secret key for module %s", currentModule.AuthUser))
|
||||
return
|
||||
}
|
||||
case "hosts allow":
|
||||
@@ -109,7 +114,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if strings.Contains(config, "["+req.Name+"]") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "模块%s已存在", req.Name)
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s already exists", req.Name))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -154,12 +159,12 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !strings.Contains(config, "["+req.Name+"]") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "模块%s不存在", req.Name)
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s does not exist", req.Name))
|
||||
return
|
||||
}
|
||||
|
||||
module := str.Cut(config, "# "+req.Name+"-START", "# "+req.Name+"-END")
|
||||
config = strings.Replace(config, "\n# "+req.Name+"-START"+module+"# "+req.Name+"-END", "", -1)
|
||||
config = strings.ReplaceAll(config, "\n# "+req.Name+"-START"+module+"# "+req.Name+"-END", "")
|
||||
|
||||
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
|
||||
if len(match) == 2 {
|
||||
@@ -196,7 +201,7 @@ func (s *App) Update(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !strings.Contains(config, "["+req.Name+"]") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "模块%s不存在", req.Name)
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s does not exist", req.Name))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -211,7 +216,7 @@ secrets file = /etc/rsyncd.secrets
|
||||
# ` + req.Name + `-END`
|
||||
|
||||
module := str.Cut(config, "# "+req.Name+"-START", "# "+req.Name+"-END")
|
||||
config = strings.Replace(config, "# "+req.Name+"-START"+module+"# "+req.Name+"-END", newConf, -1)
|
||||
config = strings.ReplaceAll(config, "# "+req.Name+"-START"+module+"# "+req.Name+"-END", newConf)
|
||||
|
||||
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
|
||||
if len(match) == 2 {
|
||||
|
||||
+25
-22
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -18,11 +19,13 @@ import (
|
||||
)
|
||||
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
settingRepo biz.SettingRepo
|
||||
}
|
||||
|
||||
func NewApp(setting biz.SettingRepo) *App {
|
||||
func NewApp(t *gotext.Locale, setting biz.SettingRepo) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
settingRepo: setting,
|
||||
}
|
||||
}
|
||||
@@ -38,12 +41,12 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) {
|
||||
var s3fsList []Mount
|
||||
list, err := s.settingRepo.Get("s3fs", "[]")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(list), &s3fsList); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -65,36 +68,36 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 检查下地域节点中是否包含bucket,如果包含了,肯定是错误的
|
||||
if strings.Contains(req.URL, req.Bucket) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "endpoint should not contain bucket")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("endpoint should not contain bucket"))
|
||||
return
|
||||
}
|
||||
|
||||
// 检查挂载目录是否存在且为空
|
||||
if !io.Exists(req.Path) {
|
||||
if err = os.MkdirAll(req.Path, 0755); err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "failed to create mount path: %v", err)
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("failed to create mount path: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
if !io.Empty(req.Path) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "mount path is not empty")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount path is not empty"))
|
||||
return
|
||||
}
|
||||
|
||||
var s3fsList []Mount
|
||||
list, err := s.settingRepo.Get("s3fs", "[]")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal([]byte(list), &s3fsList); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, s := range s3fsList {
|
||||
if s.Path == req.Path {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "mount path already exists")
|
||||
for _, item := range s3fsList {
|
||||
if item.Path == req.Path {
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount path already exists"))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -102,7 +105,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
id := time.Now().UnixMicro()
|
||||
password := req.Ak + ":" + req.Sk
|
||||
if err = io.Write("/etc/passwd-s3fs-"+cast.ToString(id), password, 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to create passwd file: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to create passwd file: %v", err))
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf(`echo 's3fs#%s %s fuse _netdev,allow_other,nonempty,url=%s,passwd_file=/etc/passwd-s3fs-%s 0 0' >> /etc/fstab`, req.Bucket, req.Path, req.URL, cast.ToString(id)); err != nil {
|
||||
@@ -111,12 +114,12 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
if _, err = shell.Execf("mount -a"); err != nil {
|
||||
_, _ = shell.Execf(`sed -i 's@^s3fs#%s\s%s.*$@@g' /etc/fstab`, req.Bucket, req.Path)
|
||||
service.Error(w, http.StatusInternalServerError, "invalid /etc/fstab: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf(`df -h | grep '%s'`, req.Path); err != nil {
|
||||
_, _ = shell.Execf(`sed -i 's@^s3fs#%s\s%s.*$@@g' /etc/fstab`, req.Bucket, req.Path)
|
||||
service.Error(w, http.StatusInternalServerError, "mount failed: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("mount failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -128,12 +131,12 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
encoded, err := json.Marshal(s3fsList)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to add s3fs mount")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to add s3fs mount: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.settingRepo.Set("s3fs", string(encoded)); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to add s3fs mount")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to add s3fs mount: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -151,11 +154,11 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
var s3fsList []Mount
|
||||
list, err := s.settingRepo.Get("s3fs", "[]")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal([]byte(list), &s3fsList); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -167,7 +170,7 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
if mount.ID == 0 {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "mount not found")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount not found"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -184,7 +187,7 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf("mount -a"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "invalid /etc/fstab: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = io.Remove("/etc/passwd-s3fs-" + cast.ToString(mount.ID)); err != nil {
|
||||
@@ -200,11 +203,11 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
encoded, err := json.Marshal(newS3fsList)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to delete s3fs mount")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to delete s3fs mount: %v", err))
|
||||
return
|
||||
}
|
||||
if err = s.settingRepo.Set("s3fs", string(encoded)); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to delete s3fs mount")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to delete s3fs mount: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
@@ -16,10 +17,11 @@ import (
|
||||
)
|
||||
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
name string
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
var name string
|
||||
if os.IsRHEL() {
|
||||
name = "supervisord"
|
||||
@@ -28,6 +30,7 @@ func NewApp() *App {
|
||||
}
|
||||
|
||||
return &App{
|
||||
t: t,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
@@ -102,7 +105,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.t.Get("failed to restart %s: %v", s.name, err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -156,8 +159,8 @@ func (s *App) StartProcess(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`supervisorctl start %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
if out, err := shell.Execf(`supervisorctl start %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -172,8 +175,8 @@ func (s *App) StopProcess(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`supervisorctl stop %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
if out, err := shell.Execf(`supervisorctl stop %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -188,8 +191,8 @@ func (s *App) RestartProcess(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`supervisorctl restart %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
if out, err := shell.Execf(`supervisorctl restart %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -212,7 +215,7 @@ func (s *App) ProcessLog(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -235,7 +238,7 @@ func (s *App) ClearProcessLog(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -343,8 +346,8 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`supervisorctl stop '%s'`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
if out, err := shell.Execf(`supervisorctl stop '%s'`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -352,7 +355,7 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) {
|
||||
if os.IsRHEL() {
|
||||
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
|
||||
return
|
||||
}
|
||||
if err = io.Remove(`/etc/supervisord.d/` + req.Process + `.conf`); err != nil {
|
||||
@@ -362,7 +365,7 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
|
||||
return
|
||||
}
|
||||
if err = io.Remove(`/etc/supervisor/conf.d/` + req.Process + `.conf`); err != nil {
|
||||
|
||||
@@ -2,12 +2,14 @@ package toolbox
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -19,10 +21,14 @@ import (
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -48,13 +54,9 @@ func (s *App) GetDNS(w http.ResponseWriter, r *http.Request) {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
match := regexp.MustCompile(`nameserver\s+(\S+)`).FindAllStringSubmatch(raw, -1)
|
||||
if len(match) == 0 {
|
||||
service.Error(w, http.StatusInternalServerError, "找不到 DNS 信息")
|
||||
return
|
||||
}
|
||||
|
||||
var dns []string
|
||||
match := regexp.MustCompile(`nameserver\s+(\S+)`).FindAllStringSubmatch(raw, -1)
|
||||
dns := make([]string, 0)
|
||||
for _, m := range match {
|
||||
dns = append(dns, m[1])
|
||||
}
|
||||
@@ -75,7 +77,7 @@ func (s *App) UpdateDNS(w http.ResponseWriter, r *http.Request) {
|
||||
dns += "nameserver " + req.DNS2 + "\n"
|
||||
|
||||
if err := io.Write("/etc/resolv.conf", dns, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入 DNS 信息失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to update DNS: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,9 +89,9 @@ func (s *App) GetSWAP(w http.ResponseWriter, r *http.Request) {
|
||||
var total, used, free string
|
||||
var size int64
|
||||
if io.Exists(filepath.Join(app.Root, "swap")) {
|
||||
file, err := io.FileInfo(filepath.Join(app.Root, "swap"))
|
||||
file, err := os.Stat(filepath.Join(app.Root, "swap"))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取 SWAP 信息失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get SWAP: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -102,7 +104,7 @@ func (s *App) GetSWAP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
raw, err := shell.Execf("free | grep Swap")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取 SWAP 信息失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get SWAP: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -147,11 +149,11 @@ func (s *App) UpdateSWAP(w http.ResponseWriter, r *http.Request) {
|
||||
var free string
|
||||
free, err = shell.Execf("df -k %s | awk '{print $4}' | tail -n 1", app.Root)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取硬盘空间失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get disk space: %v", err))
|
||||
return
|
||||
}
|
||||
if cast.ToInt64(free)*1024 < req.Size*1024*1024 {
|
||||
service.Error(w, http.StatusInternalServerError, "硬盘空间不足,当前剩余%s", tools.FormatBytes(cast.ToFloat64(free)))
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("disk space is insufficient, current free %s", tools.FormatBytes(cast.ToFloat64(free))))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -171,7 +173,7 @@ func (s *App) UpdateSWAP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if err = io.Chmod(filepath.Join(app.Root, "swap"), 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "设置 SWAP 权限失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set SWAP permission: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -192,19 +194,18 @@ func (s *App) UpdateSWAP(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *App) GetTimezone(w http.ResponseWriter, r *http.Request) {
|
||||
raw, err := shell.Execf("timedatectl | grep zone")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取时区信息失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get timezone: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
match := regexp.MustCompile(`zone:\s+(\S+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 0 {
|
||||
service.Error(w, http.StatusInternalServerError, "找不到时区信息")
|
||||
return
|
||||
match = append(match, "")
|
||||
}
|
||||
|
||||
zonesRaw, err := shell.Execf("timedatectl list-timezones")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取时区列表失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get available timezones: %v", err))
|
||||
return
|
||||
}
|
||||
zones := strings.Split(zonesRaw, "\n")
|
||||
@@ -289,7 +290,7 @@ func (s *App) UpdateHostname(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err = shell.Execf("hostnamectl set-hostname '%s'", req.Hostname); err != nil {
|
||||
// 直接写 /etc/hostname
|
||||
if err = io.Write("/etc/hostname", req.Hostname, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入主机名失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set hostname: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -312,7 +313,7 @@ func (s *App) UpdateHosts(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = io.Write("/etc/hosts", req.Hosts, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入 hosts 信息失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set hosts: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -329,7 +330,7 @@ func (s *App) UpdateRootPassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
req.Password = strings.ReplaceAll(req.Password, `'`, `\'`)
|
||||
if _, err = shell.Execf(`yes '%s' | passwd root`, req.Password); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, "%v", s.t.Get("failed to set root password: %v", 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"`
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ const (
|
||||
|
||||
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"`
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@ package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/tnb-labs/panel/internal/apps/benchmark"
|
||||
"github.com/tnb-labs/panel/internal/apps/codeserver"
|
||||
"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"
|
||||
@@ -29,11 +31,13 @@ import (
|
||||
|
||||
func NewLoader(
|
||||
benchmark *benchmark.App,
|
||||
codeserver *codeserver.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,
|
||||
@@ -53,6 +57,6 @@ func NewLoader(
|
||||
toolbox *toolbox.App,
|
||||
) *apploader.Loader {
|
||||
loader := new(apploader.Loader)
|
||||
loader.Add(benchmark, docker, fail2ban, frp, gitea, memcached, mysql, nginx, php74, php80, php81, php82, php83, php84, phpmyadmin, podman, postgresql, pureftpd, redis, rsync, s3fs, supervisor, toolbox)
|
||||
loader.Add(benchmark, codeserver, 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, NewLoader, NewSession, NewCron, NewQueue)
|
||||
var ProviderSet = wire.NewSet(NewConf, NewT, NewLog, NewCli, NewValidator, NewRouter, NewHttp, NewDB, NewMigrate, NewLoader, NewSession, NewCron, NewQueue)
|
||||
|
||||
+24
-23
@@ -3,39 +3,40 @@ package bootstrap
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/route"
|
||||
)
|
||||
|
||||
func NewCli(cmd *route.Cli) *cli.Command {
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "NAME", "名称")
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "USAGE", "用法")
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "VERSION", "版本")
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "DESCRIPTION", "描述")
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "AUTHOR", "作者")
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "COMMANDS", "命令")
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "GLOBAL OPTIONS", "全局选项")
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "COPYRIGHT", "版权")
|
||||
cli.CommandHelpTemplate = strings.ReplaceAll(cli.CommandHelpTemplate, "NAME", "名称")
|
||||
cli.CommandHelpTemplate = strings.ReplaceAll(cli.CommandHelpTemplate, "USAGE", "用法")
|
||||
cli.CommandHelpTemplate = strings.ReplaceAll(cli.CommandHelpTemplate, "CATEGORY", "分类")
|
||||
cli.CommandHelpTemplate = strings.ReplaceAll(cli.CommandHelpTemplate, "DESCRIPTION", "描述")
|
||||
cli.CommandHelpTemplate = strings.ReplaceAll(cli.CommandHelpTemplate, "OPTIONS", "选项")
|
||||
cli.SubcommandHelpTemplate = strings.ReplaceAll(cli.SubcommandHelpTemplate, "NAME", "名称")
|
||||
cli.SubcommandHelpTemplate = strings.ReplaceAll(cli.SubcommandHelpTemplate, "USAGE", "用法")
|
||||
cli.SubcommandHelpTemplate = strings.ReplaceAll(cli.SubcommandHelpTemplate, "DESCRIPTION", "描述")
|
||||
cli.SubcommandHelpTemplate = strings.ReplaceAll(cli.SubcommandHelpTemplate, "COMMANDS", "命令")
|
||||
cli.SubcommandHelpTemplate = strings.ReplaceAll(cli.SubcommandHelpTemplate, "OPTIONS", "选项")
|
||||
func NewCli(t *gotext.Locale, cmd *route.Cli) *cli.Command {
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "NAME", t.Get("NAME"))
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "USAGE", t.Get("USAGE"))
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "VERSION", t.Get("VERSION"))
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "DESCRIPTION", t.Get("DESCRIPTION"))
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "AUTHOR", t.Get("AUTHOR"))
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "COMMANDS", t.Get("COMMANDS"))
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "GLOBAL OPTIONS", t.Get("GLOBAL OPTIONS"))
|
||||
cli.RootCommandHelpTemplate = strings.ReplaceAll(cli.RootCommandHelpTemplate, "COPYRIGHT", t.Get("COPYRIGHT"))
|
||||
cli.CommandHelpTemplate = strings.ReplaceAll(cli.CommandHelpTemplate, "NAME", t.Get("NAME"))
|
||||
cli.CommandHelpTemplate = strings.ReplaceAll(cli.CommandHelpTemplate, "USAGE", t.Get("USAGE"))
|
||||
cli.CommandHelpTemplate = strings.ReplaceAll(cli.CommandHelpTemplate, "CATEGORY", t.Get("CATEGORY"))
|
||||
cli.CommandHelpTemplate = strings.ReplaceAll(cli.CommandHelpTemplate, "DESCRIPTION", t.Get("DESCRIPTION"))
|
||||
cli.CommandHelpTemplate = strings.ReplaceAll(cli.CommandHelpTemplate, "OPTIONS", t.Get("OPTIONS"))
|
||||
cli.SubcommandHelpTemplate = strings.ReplaceAll(cli.SubcommandHelpTemplate, "NAME", t.Get("NAME"))
|
||||
cli.SubcommandHelpTemplate = strings.ReplaceAll(cli.SubcommandHelpTemplate, "USAGE", t.Get("USAGE"))
|
||||
cli.SubcommandHelpTemplate = strings.ReplaceAll(cli.SubcommandHelpTemplate, "DESCRIPTION", t.Get("USAGE"))
|
||||
cli.SubcommandHelpTemplate = strings.ReplaceAll(cli.SubcommandHelpTemplate, "COMMANDS", t.Get("COMMANDS"))
|
||||
cli.SubcommandHelpTemplate = strings.ReplaceAll(cli.SubcommandHelpTemplate, "OPTIONS", t.Get("OPTIONS"))
|
||||
|
||||
cli.RootCommandHelpTemplate += "\n官网:https://panel.haozi.net"
|
||||
cli.RootCommandHelpTemplate += "\n论坛:https://bbs.haozi.net"
|
||||
cli.RootCommandHelpTemplate += "\nQ群:12370907\n"
|
||||
cli.RootCommandHelpTemplate += "\n" + t.Get("Website:https://panel.haozi.net")
|
||||
cli.RootCommandHelpTemplate += "\n" + t.Get("Forum:https://bbs.haozi.net")
|
||||
cli.RootCommandHelpTemplate += "\n" + t.Get("QQ Group:12370907") + "\n"
|
||||
|
||||
return &cli.Command{
|
||||
Name: "panel-cli",
|
||||
Usage: "耗子面板命令行工具",
|
||||
Usage: t.Get("Rat Panel CLI Tool"),
|
||||
Version: app.Version,
|
||||
Commands: cmd.Commands(),
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewConf() (*koanf.Koanf, error) {
|
||||
func initGlobal(conf *koanf.Koanf) {
|
||||
app.Key = conf.MustString("app.key")
|
||||
if len(app.Key) != 32 {
|
||||
log.Fatalf("app key must be 32 characters")
|
||||
log.Fatalf("panel app key must be 32 characters")
|
||||
}
|
||||
|
||||
app.Root = conf.MustString("app.root")
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"github.com/knadh/koanf/v2"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/gormlite"
|
||||
sloggorm "github.com/orandin/slog-gorm"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -16,7 +17,7 @@ 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{
|
||||
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 {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/pkg/embed"
|
||||
)
|
||||
|
||||
func NewT(conf *koanf.Koanf) (*gotext.Locale, error) {
|
||||
locale := conf.String("app.locale")
|
||||
l := gotext.NewLocaleFSWithPath(locale, embed.LocalesFS, "locales")
|
||||
l.AddDomain("backend")
|
||||
|
||||
return l, nil
|
||||
}
|
||||
@@ -2,15 +2,24 @@ package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/gookit/validate"
|
||||
"github.com/gookit/validate/locales/ruru"
|
||||
"github.com/gookit/validate/locales/zhcn"
|
||||
"github.com/gookit/validate/locales/zhtw"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/http/rule"
|
||||
)
|
||||
|
||||
// NewValidator just for register global rules
|
||||
func NewValidator(db *gorm.DB) *validate.Validation {
|
||||
zhcn.RegisterGlobal()
|
||||
func NewValidator(conf *koanf.Koanf, db *gorm.DB) *validate.Validation {
|
||||
if conf.String("app.locale") == "zh_CN" {
|
||||
zhcn.RegisterGlobal()
|
||||
} else if conf.String("app.locale") == "zh_TW" {
|
||||
zhtw.RegisterGlobal()
|
||||
} else if conf.String("app.locale") == "ru_RU" {
|
||||
ruru.RegisterGlobal()
|
||||
}
|
||||
validate.Config(func(opt *validate.GlobalOption) {
|
||||
opt.StopOnError = false
|
||||
opt.SkipOnEmpty = true
|
||||
|
||||
+18
-15
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/go-rat/utils/collect"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -19,13 +20,15 @@ import (
|
||||
)
|
||||
|
||||
type appRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
cache biz.CacheRepo
|
||||
task biz.TaskRepo
|
||||
}
|
||||
|
||||
func NewAppRepo(db *gorm.DB, cache biz.CacheRepo, task biz.TaskRepo) biz.AppRepo {
|
||||
func NewAppRepo(t *gotext.Locale, db *gorm.DB, cache biz.CacheRepo, task biz.TaskRepo) biz.AppRepo {
|
||||
return &appRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
cache: cache,
|
||||
task: task,
|
||||
@@ -50,7 +53,7 @@ func (r *appRepo) Get(slug string) (*api.App, error) {
|
||||
return item, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("应用不存在")
|
||||
return nil, errors.New(r.t.Get("app %s not found", slug))
|
||||
}
|
||||
|
||||
func (r *appRepo) UpdateExist(slug string) bool {
|
||||
@@ -153,7 +156,7 @@ func (r *appRepo) Install(channel, slug string) error {
|
||||
}
|
||||
|
||||
if installed, _ := r.IsInstalled(slug); installed {
|
||||
return errors.New("应用已安装")
|
||||
return errors.New(r.t.Get("app %s already installed", slug))
|
||||
}
|
||||
|
||||
shellUrl, shellChannel, shellVersion := "", "", ""
|
||||
@@ -164,7 +167,7 @@ func (r *appRepo) Install(channel, slug string) error {
|
||||
}
|
||||
if ch.Slug == channel {
|
||||
if vs.GreaterThan(panel) {
|
||||
return fmt.Errorf("应用 %s 需要面板版本 %s,当前版本 %s", item.Name, ch.Panel, app.Version)
|
||||
return errors.New(r.t.Get("app %s requires panel version %s, current version %s", item.Name, ch.Panel, app.Version))
|
||||
}
|
||||
shellUrl = ch.Install
|
||||
shellChannel = ch.Slug
|
||||
@@ -173,7 +176,7 @@ func (r *appRepo) Install(channel, slug string) error {
|
||||
}
|
||||
}
|
||||
if shellUrl == "" {
|
||||
return fmt.Errorf("应用 %s 不支持当前面板版本", item.Name)
|
||||
return errors.New(r.t.Get("app %s not support current panel version", item.Name))
|
||||
}
|
||||
|
||||
if err = r.preCheck(item); err != nil {
|
||||
@@ -185,7 +188,7 @@ func (r *appRepo) Install(channel, slug string) error {
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = "安装应用 " + item.Name
|
||||
task.Name = r.t.Get("Install app %s", item.Name)
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug)
|
||||
task.Log = "/tmp/" + item.Slug + ".log"
|
||||
@@ -204,7 +207,7 @@ func (r *appRepo) UnInstall(slug string) error {
|
||||
}
|
||||
|
||||
if installed, _ := r.IsInstalled(slug); !installed {
|
||||
return errors.New("应用未安装")
|
||||
return errors.New(r.t.Get("app %s not installed", item.Name))
|
||||
}
|
||||
installed, err := r.GetInstalled(slug)
|
||||
if err != nil {
|
||||
@@ -219,7 +222,7 @@ func (r *appRepo) UnInstall(slug string) error {
|
||||
}
|
||||
if ch.Slug == installed.Channel {
|
||||
if vs.GreaterThan(panel) {
|
||||
return fmt.Errorf("应用 %s 需要面板版本 %s,当前版本 %s", item.Name, ch.Panel, app.Version)
|
||||
return errors.New(r.t.Get("app %s requires panel version %s, current version %s", item.Name, ch.Panel, app.Version))
|
||||
}
|
||||
shellUrl = ch.Uninstall
|
||||
shellChannel = ch.Slug
|
||||
@@ -228,7 +231,7 @@ func (r *appRepo) UnInstall(slug string) error {
|
||||
}
|
||||
}
|
||||
if shellUrl == "" {
|
||||
return fmt.Errorf("无法获取应用 %s 的卸载脚本", item.Name)
|
||||
return errors.New(r.t.Get("failed to get uninstall script for app %s", item.Name))
|
||||
}
|
||||
|
||||
if err = r.preCheck(item); err != nil {
|
||||
@@ -240,7 +243,7 @@ func (r *appRepo) UnInstall(slug string) error {
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = "卸载应用 " + item.Name
|
||||
task.Name = r.t.Get("Uninstall app %s", item.Name)
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug)
|
||||
task.Log = "/tmp/" + item.Slug + ".log"
|
||||
@@ -259,7 +262,7 @@ func (r *appRepo) Update(slug string) error {
|
||||
}
|
||||
|
||||
if installed, _ := r.IsInstalled(slug); !installed {
|
||||
return errors.New("应用未安装")
|
||||
return errors.New(r.t.Get("app %s not installed", item.Name))
|
||||
}
|
||||
installed, err := r.GetInstalled(slug)
|
||||
if err != nil {
|
||||
@@ -274,7 +277,7 @@ func (r *appRepo) Update(slug string) error {
|
||||
}
|
||||
if ch.Slug == installed.Channel {
|
||||
if vs.GreaterThan(panel) {
|
||||
return fmt.Errorf("应用 %s 需要面板版本 %s,当前版本 %s", item.Name, ch.Panel, app.Version)
|
||||
return errors.New(r.t.Get("app %s requires panel version %s, current version %s", item.Name, ch.Panel, app.Version))
|
||||
}
|
||||
shellUrl = ch.Update
|
||||
shellChannel = ch.Slug
|
||||
@@ -283,7 +286,7 @@ func (r *appRepo) Update(slug string) error {
|
||||
}
|
||||
}
|
||||
if shellUrl == "" {
|
||||
return fmt.Errorf("应用 %s 不支持当前面板版本", item.Name)
|
||||
return errors.New(r.t.Get("app %s not support current panel version", item.Name))
|
||||
}
|
||||
|
||||
if err = r.preCheck(item); err != nil {
|
||||
@@ -295,7 +298,7 @@ func (r *appRepo) Update(slug string) error {
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = "更新应用 " + item.Name
|
||||
task.Name = r.t.Get("Update app %s", item.Name)
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug)
|
||||
task.Log = "/tmp/" + item.Slug + ".log"
|
||||
@@ -341,7 +344,7 @@ func (r *appRepo) preCheck(app *api.App) error {
|
||||
|
||||
result := cast.ToString(output)
|
||||
if result != "ok" {
|
||||
return fmt.Errorf("应用 %s %s", app.Name, result)
|
||||
return errors.New(r.t.Get("App %s %s", app.Name, result))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+109
-119
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -22,13 +23,15 @@ import (
|
||||
)
|
||||
|
||||
type backupRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
setting biz.SettingRepo
|
||||
website biz.WebsiteRepo
|
||||
}
|
||||
|
||||
func NewBackupRepo(db *gorm.DB, setting biz.SettingRepo, website biz.WebsiteRepo) biz.BackupRepo {
|
||||
func NewBackupRepo(t *gotext.Locale, db *gorm.DB, setting biz.SettingRepo, website biz.WebsiteRepo) biz.BackupRepo {
|
||||
return &backupRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
setting: setting,
|
||||
website: website,
|
||||
@@ -89,7 +92,7 @@ func (r *backupRepo) Create(typ biz.BackupType, target string, path ...string) e
|
||||
|
||||
}
|
||||
|
||||
return errors.New("未知备份类型")
|
||||
return errors.New(r.t.Get("unknown backup type"))
|
||||
}
|
||||
|
||||
// Delete 删除备份
|
||||
@@ -125,7 +128,7 @@ func (r *backupRepo) Restore(typ biz.BackupType, backup, target string) error {
|
||||
return r.restorePostgres(backup, target)
|
||||
}
|
||||
|
||||
return errors.New("未知备份类型")
|
||||
return errors.New(r.t.Get("unknown backup type"))
|
||||
}
|
||||
|
||||
// CutoffLog 切割日志
|
||||
@@ -133,7 +136,7 @@ func (r *backupRepo) Restore(typ biz.BackupType, backup, target string) error {
|
||||
// target 待切割日志文件绝对路径
|
||||
func (r *backupRepo) CutoffLog(path, target string) error {
|
||||
if !io.Exists(target) {
|
||||
return errors.New("日志文件不存在")
|
||||
return errors.New(r.t.Get("log file %s not exists", target))
|
||||
}
|
||||
|
||||
to := filepath.Join(path, fmt.Sprintf("%s_%s.zip", time.Now().Format("20060102150405"), filepath.Base(target)))
|
||||
@@ -184,14 +187,10 @@ func (r *backupRepo) ClearExpired(path, prefix string, save int) error {
|
||||
for _, file := range toDelete {
|
||||
filePath := filepath.Join(path, file.Name())
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-清理过期文件:%s\n", filePath)
|
||||
fmt.Println(r.t.Get("|-Cleaning expired file: %s", filePath))
|
||||
}
|
||||
if err = os.Remove(filePath); err != nil {
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-清理失败:%v\n", err)
|
||||
} else {
|
||||
return fmt.Errorf("清理失败:%v", err)
|
||||
}
|
||||
return errors.New(r.t.Get("Cleanup failed: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +204,7 @@ func (r *backupRepo) GetPath(typ biz.BackupType) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
if !slices.Contains([]biz.BackupType{biz.BackupTypePath, biz.BackupTypeWebsite, biz.BackupTypeMySQL, biz.BackupTypePostgres, biz.BackupTypeRedis, biz.BackupTypePanel}, typ) {
|
||||
return "", errors.New("未知备份类型")
|
||||
return "", errors.New(r.t.Get("unknown backup type"))
|
||||
}
|
||||
|
||||
backupPath = filepath.Join(backupPath, string(typ))
|
||||
@@ -236,8 +235,8 @@ func (r *backupRepo) createWebsite(to string, name string) error {
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-备份耗时:%s\n", time.Since(start).String())
|
||||
fmt.Printf("|-已备份至文件:%s\n", filepath.Base(backup))
|
||||
fmt.Println(r.t.Get("|-Backup time: %s", time.Since(start).String()))
|
||||
fmt.Println(r.t.Get("|-Backed up to file: %s", filepath.Base(backup)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -252,9 +251,11 @@ func (r *backupRepo) createMySQL(to string, name string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mysql.Close()
|
||||
defer func(mysql *db.MySQL) {
|
||||
_ = mysql.Close()
|
||||
}(mysql)
|
||||
if exist, _ := mysql.DatabaseExists(name); !exist {
|
||||
return fmt.Errorf("数据库不存在:%s", name)
|
||||
return errors.New(r.t.Get("database does not exist: %s", name))
|
||||
}
|
||||
size, err := mysql.DatabaseSize(name)
|
||||
if err != nil {
|
||||
@@ -284,8 +285,8 @@ func (r *backupRepo) createMySQL(to string, name string) error {
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-备份耗时:%s\n", time.Since(start).String())
|
||||
fmt.Printf("|-已备份至文件:%s\n", filepath.Base(backup+".zip"))
|
||||
fmt.Println(r.t.Get("|-Backup time: %s", time.Since(start).String()))
|
||||
fmt.Println(r.t.Get("|-Backed up to file: %s", filepath.Base(backup+".zip")))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -296,9 +297,11 @@ func (r *backupRepo) createPostgres(to string, name string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postgres.Close()
|
||||
defer func(postgres *db.Postgres) {
|
||||
_ = postgres.Close()
|
||||
}(postgres)
|
||||
if exist, _ := postgres.DatabaseExist(name); !exist {
|
||||
return fmt.Errorf("数据库不存在:%s", name)
|
||||
return errors.New(r.t.Get("database does not exist: %s", name))
|
||||
}
|
||||
size, err := postgres.DatabaseSize(name)
|
||||
if err != nil {
|
||||
@@ -322,8 +325,8 @@ func (r *backupRepo) createPostgres(to string, name string) error {
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-备份耗时:%s\n", time.Since(start).String())
|
||||
fmt.Printf("|-已备份至文件:%s\n", filepath.Base(backup+".zip"))
|
||||
fmt.Println(r.t.Get("|-Backup time: %s", time.Since(start).String()))
|
||||
fmt.Println(r.t.Get("|-Backed up to file: %s", filepath.Base(backup+".zip")))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -362,8 +365,8 @@ func (r *backupRepo) createPanel(to string) error {
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-备份耗时:%s\n", time.Since(start).String())
|
||||
fmt.Printf("|-已备份至文件:%s\n", filepath.Base(backup))
|
||||
fmt.Println(r.t.Get("|-Backup time: %s", time.Since(start).String()))
|
||||
fmt.Println(r.t.Get("|-Backed up to file: %s", filepath.Base(backup)))
|
||||
}
|
||||
|
||||
return io.Remove(temp)
|
||||
@@ -372,7 +375,7 @@ func (r *backupRepo) createPanel(to string) error {
|
||||
// restoreWebsite 恢复网站备份
|
||||
func (r *backupRepo) restoreWebsite(backup, target string) error {
|
||||
if !io.Exists(backup) {
|
||||
return errors.New("备份文件不存在")
|
||||
return errors.New(r.t.Get("backup file %s not exists", backup))
|
||||
}
|
||||
|
||||
website, err := r.website.GetByName(target)
|
||||
@@ -399,7 +402,7 @@ func (r *backupRepo) restoreWebsite(backup, target string) error {
|
||||
// restoreMySQL 恢复 MySQL 备份
|
||||
func (r *backupRepo) restoreMySQL(backup, target string) error {
|
||||
if !io.Exists(backup) {
|
||||
return errors.New("备份文件不存在")
|
||||
return errors.New(r.t.Get("backup file %s not exists", backup))
|
||||
}
|
||||
|
||||
rootPassword, err := r.setting.Get(biz.SettingKeyMySQLRootPassword)
|
||||
@@ -410,9 +413,11 @@ func (r *backupRepo) restoreMySQL(backup, target string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mysql.Close()
|
||||
defer func(mysql *db.MySQL) {
|
||||
_ = mysql.Close()
|
||||
}(mysql)
|
||||
if exist, _ := mysql.DatabaseExists(target); !exist {
|
||||
return fmt.Errorf("数据库不存在:%s", target)
|
||||
return errors.New(r.t.Get("database does not exist: %s", target))
|
||||
}
|
||||
if err = os.Setenv("MYSQL_PWD", rootPassword); err != nil {
|
||||
return err
|
||||
@@ -443,16 +448,18 @@ func (r *backupRepo) restoreMySQL(backup, target string) error {
|
||||
// restorePostgres 恢复 PostgreSQL 备份
|
||||
func (r *backupRepo) restorePostgres(backup, target string) error {
|
||||
if !io.Exists(backup) {
|
||||
return errors.New("备份文件不存在")
|
||||
return errors.New(r.t.Get("backup file %s not exists", backup))
|
||||
}
|
||||
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postgres.Close()
|
||||
defer func(postgres *db.Postgres) {
|
||||
_ = postgres.Close()
|
||||
}(postgres)
|
||||
if exist, _ := postgres.DatabaseExist(target); !exist {
|
||||
return fmt.Errorf("数据库不存在:%s", target)
|
||||
return errors.New(r.t.Get("database does not exist: %s", target))
|
||||
}
|
||||
|
||||
clean := false
|
||||
@@ -493,18 +500,19 @@ func (r *backupRepo) preCheckPath(to, path string) error {
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-目标大小:%s\n", tools.FormatBytes(float64(size)))
|
||||
fmt.Printf("|-目标文件数:%d\n", files)
|
||||
fmt.Printf("|-备份目录可用空间:%s\n", tools.FormatBytes(float64(usage.Free)))
|
||||
fmt.Printf("|-备份目录可用Inode:%d\n", usage.InodesFree)
|
||||
fmt.Println(r.t.Get("|-Target size: %s", tools.FormatBytes(float64(size))))
|
||||
fmt.Println(r.t.Get("|-Target file count: %d", files))
|
||||
fmt.Println(r.t.Get("|-Backup directory available space: %s", tools.FormatBytes(float64(usage.Free))))
|
||||
fmt.Println(r.t.Get("|-Backup directory available Inode: %d", usage.InodesFree))
|
||||
}
|
||||
|
||||
if uint64(size) > usage.Free {
|
||||
return errors.New("备份目录空间不足")
|
||||
}
|
||||
if uint64(files) > usage.InodesFree {
|
||||
return errors.New("备份目录Inode不足")
|
||||
return errors.New(r.t.Get("Insufficient backup directory space"))
|
||||
}
|
||||
// 对于 fuse 等文件系统,可能没有 inode 的概念
|
||||
/*if uint64(files) > usage.InodesFree {
|
||||
return errors.New(r.t.Get("Insufficient backup directory inode"))
|
||||
}*/
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -519,13 +527,13 @@ func (r *backupRepo) preCheckDB(to string, size int64) error {
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-目标大小:%s\n", tools.FormatBytes(float64(size)))
|
||||
fmt.Printf("|-备份目录可用空间:%s\n", tools.FormatBytes(float64(usage.Free)))
|
||||
fmt.Printf("|-备份目录可用Inode:%d\n", usage.InodesFree)
|
||||
fmt.Println(r.t.Get("|-Target size: %s", tools.FormatBytes(float64(size))))
|
||||
fmt.Println(r.t.Get("|-Backup directory available space: %s", tools.FormatBytes(float64(usage.Free))))
|
||||
fmt.Println(r.t.Get("|-Backup directory available Inode: %d", usage.InodesFree))
|
||||
}
|
||||
|
||||
if uint64(size) > usage.Free {
|
||||
return errors.New("备份目录空间不足")
|
||||
return errors.New(r.t.Get("Insufficient backup directory space"))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -545,7 +553,7 @@ func (r *backupRepo) autoUnCompressSQL(backup string) (string, error) {
|
||||
backup = "" // 置空,防止干扰后续判断
|
||||
if files, err := os.ReadDir(temp); err == nil {
|
||||
if len(files) != 1 {
|
||||
return "", fmt.Errorf("压缩文件中包含的文件数量不为1,实际为%d", len(files))
|
||||
return "", errors.New(r.t.Get("The number of files contained in the compressed file is not 1, actual %d", len(files)))
|
||||
}
|
||||
if strings.HasSuffix(files[0].Name(), ".sql") {
|
||||
backup = filepath.Join(temp, files[0].Name())
|
||||
@@ -553,7 +561,7 @@ func (r *backupRepo) autoUnCompressSQL(backup string) (string, error) {
|
||||
}
|
||||
|
||||
if backup == "" {
|
||||
return "", errors.New("无法找到.sql备份文件")
|
||||
return "", errors.New(r.t.Get("could not find .sql backup file"))
|
||||
}
|
||||
|
||||
return backup, nil
|
||||
@@ -561,23 +569,14 @@ func (r *backupRepo) autoUnCompressSQL(backup string) (string, error) {
|
||||
|
||||
func (r *backupRepo) FixPanel() error {
|
||||
if app.IsCli {
|
||||
fmt.Println("|-开始修复面板...")
|
||||
fmt.Println(r.t.Get("|-Start fixing the panel..."))
|
||||
}
|
||||
|
||||
// 检查关键文件是否正常
|
||||
flag := false
|
||||
if !io.Exists("/usr/local/etc/panel/config.yml") {
|
||||
flag = true
|
||||
}
|
||||
if !io.Exists(filepath.Join(app.Root, "panel", "web")) {
|
||||
flag = true
|
||||
}
|
||||
if !io.Exists(filepath.Join(app.Root, "panel", "storage", "app.db")) {
|
||||
flag = true
|
||||
}
|
||||
if io.Exists("/tmp/panel-storage.zip") {
|
||||
flag = true
|
||||
}
|
||||
flag := !io.Exists("/usr/local/etc/panel/config.yml") ||
|
||||
!io.Exists(filepath.Join(app.Root, "panel", "web")) ||
|
||||
!io.Exists(filepath.Join(app.Root, "panel", "storage", "app.db")) ||
|
||||
io.Exists("/tmp/panel-storage.zip")
|
||||
// 检查数据库连接
|
||||
if err := r.db.Exec("VACUUM").Error; err != nil {
|
||||
flag = true
|
||||
@@ -586,7 +585,7 @@ func (r *backupRepo) FixPanel() error {
|
||||
flag = true
|
||||
}
|
||||
if !flag {
|
||||
return fmt.Errorf("文件正常无需修复,请运行 panel-cli update 更新面板")
|
||||
return errors.New(r.t.Get("Files are normal and do not need to be repaired, please run panel-cli update to update the panel"))
|
||||
}
|
||||
|
||||
// 再次确认是否需要修复
|
||||
@@ -596,10 +595,10 @@ func (r *backupRepo) FixPanel() error {
|
||||
io.Exists(filepath.Join(app.Root, "panel", "storage", "app.db")) &&
|
||||
io.Exists("/usr/local/etc/panel/config.yml") {
|
||||
if err := io.Remove("/tmp/panel-storage.zip"); err != nil {
|
||||
return fmt.Errorf("清理临时文件失败:%w", err)
|
||||
return errors.New(r.t.Get("failed to clean temporary files: %v", err))
|
||||
}
|
||||
if app.IsCli {
|
||||
fmt.Println("|-已清理临时文件,请运行 panel-cli update 更新面板")
|
||||
fmt.Println(r.t.Get("|-Cleaned up temporary files, please run panel-cli update to update the panel"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -614,57 +613,57 @@ func (r *backupRepo) FixPanel() error {
|
||||
return int(b.Time.Unix() - a.Time.Unix())
|
||||
})
|
||||
if len(list) == 0 {
|
||||
return fmt.Errorf("未找到备份文件,无法自动修复")
|
||||
return errors.New(r.t.Get("No backup file found, unable to automatically repair"))
|
||||
}
|
||||
latest := list[0]
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-使用备份文件:%s\n", latest.Name)
|
||||
fmt.Println(r.t.Get("|-Backup file used: %s", latest.Name))
|
||||
}
|
||||
|
||||
// 解压备份文件
|
||||
if app.IsCli {
|
||||
fmt.Println("|-解压备份文件...")
|
||||
fmt.Println(r.t.Get("|-Unzip backup file..."))
|
||||
}
|
||||
if err = io.Remove("/tmp/panel-fix"); err != nil {
|
||||
return fmt.Errorf("清理临时目录失败:%w", err)
|
||||
return errors.New(r.t.Get("Cleaning temporary directory failed: %v", err))
|
||||
}
|
||||
if err = io.UnCompress(latest.Path, "/tmp/panel-fix"); err != nil {
|
||||
return fmt.Errorf("解压备份文件失败:%w", err)
|
||||
return errors.New(r.t.Get("Unzip backup file failed: %v", err))
|
||||
}
|
||||
|
||||
// 移动文件到对应位置
|
||||
if app.IsCli {
|
||||
fmt.Println("|-移动备份文件...")
|
||||
fmt.Println(r.t.Get("|-Move backup file..."))
|
||||
}
|
||||
if io.Exists(filepath.Join("/tmp/panel-fix", "panel")) && io.IsDir(filepath.Join("/tmp/panel-fix", "panel")) {
|
||||
if err = io.Remove(filepath.Join(app.Root, "panel")); err != nil {
|
||||
return fmt.Errorf("删除目录失败:%w", err)
|
||||
return errors.New(r.t.Get("Remove panel file failed: %v", err))
|
||||
}
|
||||
if err = io.Mv(filepath.Join("/tmp/panel-fix", "panel"), filepath.Join(app.Root)); err != nil {
|
||||
return fmt.Errorf("移动目录失败:%w", err)
|
||||
return errors.New(r.t.Get("Move panel file failed: %v", err))
|
||||
}
|
||||
}
|
||||
if io.Exists(filepath.Join("/tmp/panel-fix", "config.yml")) {
|
||||
if err = io.Mv(filepath.Join("/tmp/panel-fix", "config.yml"), "/usr/local/etc/panel/config.yml"); err != nil {
|
||||
return fmt.Errorf("移动文件失败:%w", err)
|
||||
return errors.New(r.t.Get("Move panel config failed: %v", err))
|
||||
}
|
||||
}
|
||||
if io.Exists(filepath.Join("/tmp/panel-fix", "panel-cli")) {
|
||||
if err = io.Mv(filepath.Join("/tmp/panel-fix", "panel-cli"), "/usr/local/sbin/panel-cli"); err != nil {
|
||||
return fmt.Errorf("移动文件失败:%w", err)
|
||||
return errors.New(r.t.Get("Move panel-cli file failed: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// tmp 目录下如果有 storage 备份,则解压回去
|
||||
if app.IsCli {
|
||||
fmt.Println("|-恢复面板数据...")
|
||||
fmt.Println(r.t.Get("|-Restore panel data..."))
|
||||
}
|
||||
if io.Exists("/tmp/panel-storage.zip") {
|
||||
if err = io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel")); err != nil {
|
||||
return fmt.Errorf("恢复面板数据失败:%w", err)
|
||||
return errors.New(r.t.Get("Unzip panel data failed: %v", err))
|
||||
}
|
||||
if err = io.Remove("/tmp/panel-storage.zip"); err != nil {
|
||||
return fmt.Errorf("清理临时文件失败:%w", err)
|
||||
return errors.New(r.t.Get("Cleaning temporary file failed: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,7 +676,7 @@ func (r *backupRepo) FixPanel() error {
|
||||
|
||||
// 处理权限
|
||||
if app.IsCli {
|
||||
fmt.Println("|-设置关键文件权限...")
|
||||
fmt.Println(r.t.Get("|-Set key file permissions..."))
|
||||
}
|
||||
if err = io.Chmod("/usr/local/etc/panel/config.yml", 0600); err != nil {
|
||||
return err
|
||||
@@ -697,7 +696,7 @@ func (r *backupRepo) FixPanel() error {
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println("|-修复完成")
|
||||
fmt.Println(r.t.Get("|-Fix completed"))
|
||||
}
|
||||
|
||||
tools.RestartPanel()
|
||||
@@ -715,124 +714,115 @@ func (r *backupRepo) UpdatePanel(version, url, checksum string) error {
|
||||
|
||||
name := filepath.Base(url)
|
||||
if app.IsCli {
|
||||
fmt.Printf("|-目标版本:%s\n", version)
|
||||
fmt.Printf("|-下载链接:%s\n", url)
|
||||
fmt.Printf("|-文件名:%s\n", name)
|
||||
fmt.Println(r.t.Get("|-Target version: %s", version))
|
||||
fmt.Println(r.t.Get("|-Download link: %s", url))
|
||||
fmt.Println(r.t.Get("|-File name: %s", name))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println("|-正在下载...")
|
||||
fmt.Println(r.t.Get("|-Downloading..."))
|
||||
}
|
||||
if _, err := shell.Execf("wget -T 120 -t 3 -O /tmp/%s %s", name, url); err != nil {
|
||||
return fmt.Errorf("下载失败:%w", err)
|
||||
return errors.New(r.t.Get("Download failed: %v", err))
|
||||
}
|
||||
if _, err := shell.Execf("wget -T 20 -t 3 -O /tmp/%s %s", name+".sha256", checksum); err != nil {
|
||||
return fmt.Errorf("下载失败:%w", err)
|
||||
return errors.New(r.t.Get("Download failed: %v", err))
|
||||
}
|
||||
if !io.Exists(filepath.Join("/tmp", name)) || !io.Exists(filepath.Join("/tmp", name+".sha256")) {
|
||||
return errors.New("下载文件检查失败")
|
||||
return errors.New(r.t.Get("Download file check failed"))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println("|-校验下载文件...")
|
||||
fmt.Println(r.t.Get("|-Verify download file..."))
|
||||
}
|
||||
if check, err := shell.Execf("cd /tmp && sha256sum -c %s --ignore-missing", name+".sha256"); check != name+": OK" || err != nil {
|
||||
return errors.New("下载文件校验失败")
|
||||
return errors.New(r.t.Get("Verify download file failed: %v", err))
|
||||
}
|
||||
if err := io.Remove(filepath.Join("/tmp", name+".sha256")); err != nil {
|
||||
if app.IsCli {
|
||||
fmt.Println("|-清理校验文件失败:", err)
|
||||
}
|
||||
return fmt.Errorf("清理校验文件失败:%w", err)
|
||||
return errors.New(r.t.Get("|-Clean up verification file failed: %v", err))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println("|-前置检查...")
|
||||
}
|
||||
if io.Exists("/tmp/panel-storage.zip") {
|
||||
return errors.New("检测到 /tmp 存在临时文件,可能是上次更新失败所致,请运行 panel-cli fix 修复后重试")
|
||||
return errors.New(r.t.Get("Temporary file detected in /tmp, this may be caused by the last update failure, please run panel-cli fix to repair and try again"))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println("|-备份面板数据...")
|
||||
fmt.Println(r.t.Get("|-Backup panel data..."))
|
||||
}
|
||||
// 备份面板
|
||||
if err := r.Create(biz.BackupTypePanel, ""); err != nil {
|
||||
if app.IsCli {
|
||||
fmt.Println("|-备份面板失败:", err)
|
||||
}
|
||||
return fmt.Errorf("备份面板失败:%w", err)
|
||||
return errors.New(r.t.Get("|-Backup panel data failed: %v", err))
|
||||
}
|
||||
if err := io.Compress(filepath.Join(app.Root, "panel/storage"), nil, "/tmp/panel-storage.zip"); err != nil {
|
||||
if app.IsCli {
|
||||
fmt.Println("|-备份面板数据失败:", err)
|
||||
}
|
||||
return fmt.Errorf("备份面板数据失败:%w", err)
|
||||
return errors.New(r.t.Get("|-Backup panel data failed: %v", err))
|
||||
}
|
||||
if !io.Exists("/tmp/panel-storage.zip") {
|
||||
return errors.New("已备份面板数据检查失败")
|
||||
return errors.New(r.t.Get("|-Backup panel data failed, missing file"))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println("|-清理旧版本...")
|
||||
fmt.Println(r.t.Get("|-Cleaning old version..."))
|
||||
}
|
||||
if _, err := shell.Execf("rm -rf %s/panel/*", app.Root); err != nil {
|
||||
return fmt.Errorf("清理旧版本失败:%w", err)
|
||||
return errors.New(r.t.Get("|-Cleaning old version failed: %v", err))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println("|-解压新版本...")
|
||||
fmt.Println(r.t.Get("|-Unzip new version..."))
|
||||
}
|
||||
if err := io.UnCompress(filepath.Join("/tmp", name), filepath.Join(app.Root, "panel")); err != nil {
|
||||
return fmt.Errorf("解压失败:%w", err)
|
||||
return errors.New(r.t.Get("|-Unzip new version failed: %v", err))
|
||||
}
|
||||
if !io.Exists(filepath.Join(app.Root, "panel", "web")) {
|
||||
return errors.New("解压失败,缺失文件")
|
||||
return errors.New(r.t.Get("|-Unzip new version failed, missing file"))
|
||||
}
|
||||
if err := io.Remove(filepath.Join("/tmp", name)); err != nil {
|
||||
return fmt.Errorf("清理临时文件失败:%w", err)
|
||||
return errors.New(r.t.Get("|-Clean up temporary file failed: %v", err))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println("|-恢复面板数据...")
|
||||
fmt.Println(r.t.Get("|-Restore panel data..."))
|
||||
}
|
||||
if err := io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel", "storage")); err != nil {
|
||||
return fmt.Errorf("恢复面板数据失败:%w", err)
|
||||
return errors.New(r.t.Get("|-Restore panel data failed: %v", err))
|
||||
}
|
||||
if !io.Exists(filepath.Join(app.Root, "panel/storage/app.db")) {
|
||||
return errors.New("恢复面板数据失败")
|
||||
return errors.New(r.t.Get("|-Restore panel data failed, missing file"))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println("|-运行更新后脚本...")
|
||||
fmt.Println(r.t.Get("|-Run post-update script..."))
|
||||
}
|
||||
if _, err := shell.Execf("curl -fsLm 10 https://dl.cdn.haozi.net/panel/auto_update.sh | bash"); err != nil {
|
||||
return fmt.Errorf("运行面板更新后脚本失败:%w", err)
|
||||
return errors.New(r.t.Get("|-Run post-update script failed: %v", err))
|
||||
}
|
||||
if _, err := shell.Execf(`wget -O /etc/systemd/system/panel.service https://dl.cdn.haozi.net/panel/panel.service && sed -i "s|/www|%s|g" /etc/systemd/system/panel.service`, app.Root); err != nil {
|
||||
return fmt.Errorf("下载面板服务文件失败:%w", err)
|
||||
return errors.New(r.t.Get("|-Download panel service file failed: %v", err))
|
||||
}
|
||||
if _, err := shell.Execf("panel-cli setting write version %s", version); err != nil {
|
||||
return fmt.Errorf("写入面板版本号失败:%w", err)
|
||||
return errors.New(r.t.Get("|-Write new panel version failed: %v", err))
|
||||
}
|
||||
if err := io.Mv(filepath.Join(app.Root, "panel/cli"), "/usr/local/sbin/panel-cli"); err != nil {
|
||||
return fmt.Errorf("移动面板命令行工具失败:%w", err)
|
||||
return errors.New(r.t.Get("|-Move panel-cli tool failed: %v", err))
|
||||
}
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println("|-设置关键文件权限...")
|
||||
fmt.Println(r.t.Get("|-Set key file permissions..."))
|
||||
}
|
||||
_ = io.Chmod("/usr/local/sbin/panel-cli", 0700)
|
||||
_ = io.Chmod("/etc/systemd/system/panel.service", 0644)
|
||||
_ = io.Chmod(filepath.Join(app.Root, "panel"), 0700)
|
||||
|
||||
if app.IsCli {
|
||||
fmt.Println("|-更新完成")
|
||||
fmt.Println(r.t.Get("|-Update completed"))
|
||||
}
|
||||
|
||||
_, _ = 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
|
||||
|
||||
@@ -20,7 +20,7 @@ type cacheRepo struct {
|
||||
|
||||
func NewCacheRepo(db *gorm.DB) biz.CacheRepo {
|
||||
return &cacheRepo{
|
||||
api: api.NewAPI(app.Version),
|
||||
api: api.NewAPI(app.Version, app.Locale),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
+76
-16
@@ -4,10 +4,13 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -22,13 +25,17 @@ import (
|
||||
)
|
||||
|
||||
type certRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
log *slog.Logger
|
||||
client *acme.Client
|
||||
}
|
||||
|
||||
func NewCertRepo(db *gorm.DB) biz.CertRepo {
|
||||
func NewCertRepo(t *gotext.Locale, db *gorm.DB, log *slog.Logger) biz.CertRepo {
|
||||
return &certRepo{
|
||||
db: db,
|
||||
t: t,
|
||||
db: db,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +56,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,
|
||||
}
|
||||
@@ -80,10 +89,10 @@ func (r *certRepo) GetByWebsite(WebsiteID uint) (*biz.Cert, error) {
|
||||
func (r *certRepo) Upload(req *request.CertUpload) (*biz.Cert, error) {
|
||||
info, err := pkgcert.ParseCert(req.Cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse certificate: %v", err)
|
||||
return nil, errors.New(r.t.Get("failed to parse certificate: %v", err))
|
||||
}
|
||||
if _, err = pkgcert.ParseKey(req.Key); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse private key: %v", err)
|
||||
return nil, errors.New(r.t.Get("failed to parse private key: %v", err))
|
||||
}
|
||||
|
||||
cert := &biz.Cert{
|
||||
@@ -119,6 +128,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(r.t.Get("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 +140,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,14 +162,14 @@ 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")
|
||||
return nil, errors.New(r.t.Get("this certificate is not associated with a website and cannot be obtained. You can try to obtain it manually"))
|
||||
} else {
|
||||
for _, domain := range cert.Domains {
|
||||
if strings.Contains(domain, "*") {
|
||||
return nil, errors.New("wildcard domains cannot use HTTP verification")
|
||||
return nil, errors.New(r.t.Get("wildcard domains cannot use HTTP verification"))
|
||||
}
|
||||
}
|
||||
conf := fmt.Sprintf("%s/server/vhost/acme/%s.conf", app.Root, cert.Website.Name)
|
||||
@@ -180,6 +193,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
|
||||
}
|
||||
|
||||
@@ -190,7 +207,7 @@ func (r *certRepo) ObtainManual(id uint) (*acme.Certificate, error) {
|
||||
}
|
||||
|
||||
if r.client == nil {
|
||||
return nil, errors.New("please retry the manual obtain operation")
|
||||
return nil, errors.New(r.t.Get("please retry the manual obtain operation"))
|
||||
}
|
||||
|
||||
ssl, err := r.client.ObtainCertificateManual()
|
||||
@@ -209,6 +226,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 +254,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
|
||||
}
|
||||
|
||||
@@ -248,18 +273,18 @@ func (r *certRepo) Renew(id uint) (*acme.Certificate, error) {
|
||||
}
|
||||
|
||||
if cert.CertURL == "" {
|
||||
return nil, errors.New("this certificate has not been signed successfully and cannot be renewed")
|
||||
return nil, errors.New(r.t.Get("this certificate has not been obtained successfully and cannot be renewed"))
|
||||
}
|
||||
|
||||
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")
|
||||
return nil, errors.New(r.t.Get("this certificate is not associated with a website and cannot be obtained. You can try to obtain it manually"))
|
||||
} else {
|
||||
for _, domain := range cert.Domains {
|
||||
if strings.Contains(domain, "*") {
|
||||
return nil, errors.New("wildcard domains cannot use HTTP verification")
|
||||
return nil, errors.New(r.t.Get("wildcard domains cannot use HTTP verification"))
|
||||
}
|
||||
}
|
||||
conf := fmt.Sprintf("%s/server/vhost/acme/%s.conf", app.Root, cert.Website.Name)
|
||||
@@ -269,7 +294,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
|
||||
@@ -319,7 +348,7 @@ func (r *certRepo) Deploy(ID, WebsiteID uint) error {
|
||||
}
|
||||
|
||||
if cert.Cert == "" || cert.Key == "" {
|
||||
return errors.New("this certificate has not been signed successfully and cannot be deployed")
|
||||
return errors.New(r.t.Get("this certificate has not been obtained successfully and cannot be deployed"))
|
||||
}
|
||||
|
||||
website := new(biz.Website)
|
||||
@@ -341,9 +370,40 @@ 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 func(name string) {
|
||||
_ = os.Remove(name)
|
||||
}(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")
|
||||
return nil, errors.New(r.t.Get("this certificate is not associated with an ACME account and cannot be obtained"))
|
||||
}
|
||||
|
||||
var ca string
|
||||
@@ -367,5 +427,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)
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@ package data
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -16,19 +17,23 @@ import (
|
||||
)
|
||||
|
||||
type certAccountRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
log *slog.Logger
|
||||
user biz.UserRepo
|
||||
}
|
||||
|
||||
func NewCertAccountRepo(db *gorm.DB, user biz.UserRepo) biz.CertAccountRepo {
|
||||
func NewCertAccountRepo(t *gotext.Locale, db *gorm.DB, user biz.UserRepo, log *slog.Logger) biz.CertAccountRepo {
|
||||
return &certAccountRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
log: log,
|
||||
user: user,
|
||||
}
|
||||
}
|
||||
|
||||
func (r certAccountRepo) List(page, limit uint) ([]*biz.CertAccount, int64, error) {
|
||||
var accounts []*biz.CertAccount
|
||||
accounts := make([]*biz.CertAccount, 0)
|
||||
var total int64
|
||||
err := r.db.Model(&biz.CertAccount{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&accounts).Error
|
||||
return accounts, total, err
|
||||
@@ -41,12 +46,12 @@ func (r certAccountRepo) GetDefault(userID uint) (*biz.CertAccount, error) {
|
||||
}
|
||||
|
||||
account := new(biz.CertAccount)
|
||||
if err = r.db.Model(&biz.CertAccount{}).Where("ca = ?", "googlecn").Where("email = ?", user.Email).First(account).Error; err == nil {
|
||||
if err = r.db.Model(&biz.CertAccount{}).Where("ca = ?", "letsencrypt").Where("email = ?", user.Email).First(account).Error; err == nil {
|
||||
return account, nil
|
||||
}
|
||||
|
||||
req := &request.CertAccountCreate{
|
||||
CA: "googlecn",
|
||||
CA: "letsencrypt",
|
||||
Email: user.Email,
|
||||
KeyType: string(acme.KeyEC256),
|
||||
}
|
||||
@@ -78,13 +83,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,20 +97,20 @@ 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")
|
||||
return nil, errors.New(r.t.Get("unsupported CA"))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to register account: %v", err)
|
||||
return nil, errors.New(r.t.Get("failed to register account: %v", err))
|
||||
}
|
||||
|
||||
privateKey, err := cert.EncodeKey(client.Account.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to get private key")
|
||||
return nil, errors.New(r.t.Get("failed to get private key"))
|
||||
}
|
||||
account.PrivateKey = string(privateKey)
|
||||
|
||||
@@ -137,13 +142,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,20 +156,20 @@ 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")
|
||||
return errors.New(r.t.Get("unsupported CA"))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errors.New("failed to register account")
|
||||
return errors.New(r.t.Get("failed to register account: %v", err))
|
||||
}
|
||||
|
||||
privateKey, err := cert.EncodeKey(client.Account.PrivateKey)
|
||||
if err != nil {
|
||||
return errors.New("failed to get private key")
|
||||
return errors.New(r.t.Get("failed to get private key: %v", err))
|
||||
}
|
||||
account.PrivateKey = string(privateKey)
|
||||
|
||||
@@ -190,11 +195,11 @@ func (r certAccountRepo) getGoogleEAB() (*acme.EAB, error) {
|
||||
|
||||
resp, err := client.R().SetResult(&data{}).Get("https://gts.rat.dev/eab")
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
return &acme.EAB{}, fmt.Errorf("failed to get Google EAB: %v", err)
|
||||
return &acme.EAB{}, errors.New(r.t.Get("failed to get Google EAB: %v", err))
|
||||
}
|
||||
eab := resp.Result().(*data)
|
||||
if eab.Message != "success" {
|
||||
return &acme.EAB{}, fmt.Errorf("failed to get Google EAB: %s", eab.Message)
|
||||
return &acme.EAB{}, errors.New(r.t.Get("failed to get Google EAB: %s", eab.Message))
|
||||
}
|
||||
|
||||
return &acme.EAB{KeyID: eab.Data.KeyId, MACKey: eab.Data.MacKey}, nil
|
||||
@@ -215,11 +220,11 @@ func (r certAccountRepo) getZeroSSLEAB(email string) (*acme.EAB, error) {
|
||||
"email": email,
|
||||
}).SetResult(&data{}).Post("https://api.zerossl.com/acme/eab-credentials-email")
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
return &acme.EAB{}, fmt.Errorf("failed to get ZeroSSL EAB: %v", err)
|
||||
return &acme.EAB{}, errors.New(r.t.Get("failed to get ZeroSSL EAB: %v", err))
|
||||
}
|
||||
eab := resp.Result().(*data)
|
||||
if !eab.Success {
|
||||
return &acme.EAB{}, fmt.Errorf("failed to get ZeroSSL EAB")
|
||||
return &acme.EAB{}, errors.New(r.t.Get("failed to get ZeroSSL EAB"))
|
||||
}
|
||||
|
||||
return &acme.EAB{KeyID: eab.EabKid, MACKey: eab.EabHmacKey}, nil
|
||||
|
||||
@@ -18,7 +18,7 @@ func NewCertDNSRepo(db *gorm.DB) biz.CertDNSRepo {
|
||||
}
|
||||
|
||||
func (r certDNSRepo) List(page, limit uint) ([]*biz.CertDNS, int64, error) {
|
||||
var certDNS []*biz.CertDNS
|
||||
certDNS := make([]*biz.CertDNS, 0)
|
||||
var total int64
|
||||
err := r.db.Model(&biz.CertDNS{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&certDNS).Error
|
||||
return certDNS, total, err
|
||||
|
||||
+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
|
||||
}
|
||||
|
||||
+11
-19
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-rat/utils/str"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
@@ -20,11 +21,13 @@ import (
|
||||
)
|
||||
|
||||
type cronRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewCronRepo(db *gorm.DB) biz.CronRepo {
|
||||
func NewCronRepo(t *gotext.Locale, db *gorm.DB) biz.CronRepo {
|
||||
return &cronRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
@@ -39,7 +42,7 @@ func (r *cronRepo) Count() (int64, error) {
|
||||
}
|
||||
|
||||
func (r *cronRepo) List(page, limit uint) ([]*biz.Cron, int64, error) {
|
||||
var cron []*biz.Cron
|
||||
cron := make([]*biz.Cron, 0)
|
||||
var total int64
|
||||
err := r.db.Model(&biz.Cron{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&cron).Error
|
||||
return cron, total, err
|
||||
@@ -61,18 +64,15 @@ func (r *cronRepo) Create(req *request.CronCreate) error {
|
||||
script = fmt.Sprintf(`#!/bin/bash
|
||||
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" {
|
||||
script = fmt.Sprintf(`#!/bin/bash
|
||||
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
|
||||
|
||||
# 耗子面板 - 数据库备份脚本
|
||||
|
||||
panel-cli backup database -t '%s' -n '%s' -p '%s'
|
||||
panel-cli backup clear -t '%s' -f '%s' -s '%d' -p '%s'
|
||||
`, req.BackupType, req.Target, req.BackupPath, req.BackupType, req.Target, req.Save, req.BackupPath)
|
||||
@@ -82,9 +82,6 @@ panel-cli backup clear -t '%s' -f '%s' -s '%d' -p '%s'
|
||||
script = fmt.Sprintf(`#!/bin/bash
|
||||
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'
|
||||
`, req.Target, req.BackupPath, req.Target, req.Save, req.BackupPath)
|
||||
@@ -96,18 +93,17 @@ panel-cli cutoff clear -t website -f '%s' -s '%d' -p '%s'
|
||||
shellDir := fmt.Sprintf("%s/server/cron/", app.Root)
|
||||
shellLogDir := fmt.Sprintf("%s/server/cron/logs/", app.Root)
|
||||
if !io.Exists(shellDir) {
|
||||
return errors.New("计划任务目录不存在")
|
||||
return errors.New(r.t.Get("cron directory %s not exists", shellDir))
|
||||
}
|
||||
if !io.Exists(shellLogDir) {
|
||||
return errors.New("计划任务日志目录不存在")
|
||||
return errors.New(r.t.Get("cron log directory %s not exists", shellLogDir))
|
||||
}
|
||||
shellFile := strconv.Itoa(int(time.Now().Unix())) + str.Random(16)
|
||||
if err := io.Write(filepath.Join(shellDir, shellFile+".sh"), script, 0700); err != nil {
|
||||
return errors.New(err.Error())
|
||||
}
|
||||
if out, err := shell.Execf("dos2unix %s%s.sh", shellDir, shellFile); err != nil {
|
||||
return errors.New(out)
|
||||
}
|
||||
// 编码转换
|
||||
_, _ = shell.Execf("dos2unix %s%s.sh", shellDir, shellFile)
|
||||
|
||||
cron := new(biz.Cron)
|
||||
cron.Name = req.Name
|
||||
@@ -133,10 +129,6 @@ func (r *cronRepo) Update(req *request.CronUpdate) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cron.Status {
|
||||
return errors.New("计划任务已禁用")
|
||||
}
|
||||
|
||||
cron.Time = req.Time
|
||||
cron.Name = req.Name
|
||||
if err = r.db.Save(cron).Error; err != nil {
|
||||
@@ -226,5 +218,5 @@ func (r *cronRepo) restartCron() error {
|
||||
return systemctl.Restart("cron")
|
||||
}
|
||||
|
||||
return errors.New("不支持的系统")
|
||||
return errors.New(r.t.Get("unsupported system"))
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ var ProviderSet = wire.NewSet(
|
||||
NewCertAccountRepo,
|
||||
NewCertDNSRepo,
|
||||
NewContainerRepo,
|
||||
NewContainerComposeRepo,
|
||||
NewContainerImageRepo,
|
||||
NewContainerNetworkRepo,
|
||||
NewContainerVolumeRepo,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -13,13 +14,15 @@ import (
|
||||
)
|
||||
|
||||
type databaseRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
server biz.DatabaseServerRepo
|
||||
user biz.DatabaseUserRepo
|
||||
}
|
||||
|
||||
func NewDatabaseRepo(db *gorm.DB, server biz.DatabaseServerRepo, user biz.DatabaseUserRepo) biz.DatabaseRepo {
|
||||
func NewDatabaseRepo(t *gotext.Locale, db *gorm.DB, server biz.DatabaseServerRepo, user biz.DatabaseUserRepo) biz.DatabaseRepo {
|
||||
return &databaseRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
server: server,
|
||||
user: user,
|
||||
@@ -86,7 +89,9 @@ func (r databaseRepo) Create(req *request.DatabaseCreate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mysql.Close()
|
||||
defer func(mysql *db.MySQL) {
|
||||
_ = mysql.Close()
|
||||
}(mysql)
|
||||
if req.CreateUser {
|
||||
if err = r.user.Create(&request.DatabaseUserCreate{
|
||||
ServerID: req.ServerID,
|
||||
@@ -110,7 +115,9 @@ func (r databaseRepo) Create(req *request.DatabaseCreate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postgres.Close()
|
||||
defer func(postgres *db.Postgres) {
|
||||
_ = postgres.Close()
|
||||
}(postgres)
|
||||
if req.CreateUser {
|
||||
if err = r.user.Create(&request.DatabaseUserCreate{
|
||||
ServerID: req.ServerID,
|
||||
@@ -149,14 +156,18 @@ func (r databaseRepo) Delete(serverID uint, name string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mysql.Close()
|
||||
defer func(mysql *db.MySQL) {
|
||||
_ = mysql.Close()
|
||||
}(mysql)
|
||||
return mysql.DatabaseDrop(name)
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postgres.Close()
|
||||
defer func(postgres *db.Postgres) {
|
||||
_ = postgres.Close()
|
||||
}(postgres)
|
||||
return postgres.DatabaseDrop(name)
|
||||
}
|
||||
|
||||
@@ -171,13 +182,15 @@ func (r databaseRepo) Comment(req *request.DatabaseComment) error {
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
return errors.New("mysql not support database comment")
|
||||
return errors.New(r.t.Get("mysql not support database comment"))
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postgres.Close()
|
||||
defer func(postgres *db.Postgres) {
|
||||
_ = postgres.Close()
|
||||
}(postgres)
|
||||
return postgres.DatabaseComment(req.Name, req.Comment)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -13,12 +15,14 @@ import (
|
||||
)
|
||||
|
||||
type databaseServerRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
func NewDatabaseServerRepo(db *gorm.DB, log *slog.Logger) biz.DatabaseServerRepo {
|
||||
func NewDatabaseServerRepo(t *gotext.Locale, db *gorm.DB, log *slog.Logger) biz.DatabaseServerRepo {
|
||||
return &databaseServerRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
log: log,
|
||||
}
|
||||
@@ -34,7 +38,7 @@ func (r databaseServerRepo) Count() (int64, error) {
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) List(page, limit uint) ([]*biz.DatabaseServer, int64, error) {
|
||||
var databaseServer []*biz.DatabaseServer
|
||||
databaseServer := make([]*biz.DatabaseServer, 0)
|
||||
var total int64
|
||||
err := r.db.Model(&biz.DatabaseServer{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&databaseServer).Error
|
||||
|
||||
@@ -79,7 +83,7 @@ func (r databaseServerRepo) Create(req *request.DatabaseServerCreate) error {
|
||||
}
|
||||
|
||||
if !r.checkServer(databaseServer) {
|
||||
return fmt.Errorf("check server connection failed")
|
||||
return errors.New(r.t.Get("check server connection failed"))
|
||||
}
|
||||
|
||||
return r.db.Create(databaseServer).Error
|
||||
@@ -99,7 +103,7 @@ func (r databaseServerRepo) Update(req *request.DatabaseServerUpdate) error {
|
||||
server.Remark = req.Remark
|
||||
|
||||
if !r.checkServer(server) {
|
||||
return fmt.Errorf("check server connection failed")
|
||||
return errors.New(r.t.Get("check server connection failed"))
|
||||
}
|
||||
|
||||
return r.db.Save(server).Error
|
||||
@@ -139,7 +143,9 @@ func (r databaseServerRepo) Sync(id uint) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mysql.Close()
|
||||
defer func(mysql *db.MySQL) {
|
||||
_ = mysql.Close()
|
||||
}(mysql)
|
||||
allUsers, err := mysql.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -152,7 +158,7 @@ func (r databaseServerRepo) Sync(id uint) error {
|
||||
ServerID: id,
|
||||
Username: user.User,
|
||||
Host: user.Host,
|
||||
Remark: fmt.Sprintf("sync from server %s", server.Name),
|
||||
Remark: r.t.Get("sync from server %s", server.Name),
|
||||
}
|
||||
if err = r.db.Create(newUser).Error; err != nil {
|
||||
r.log.Warn("sync database user failed", slog.Any("err", err))
|
||||
@@ -164,7 +170,9 @@ func (r databaseServerRepo) Sync(id uint) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postgres.Close()
|
||||
defer func(postgres *db.Postgres) {
|
||||
_ = postgres.Close()
|
||||
}(postgres)
|
||||
allUsers, err := postgres.Users()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -176,7 +184,7 @@ func (r databaseServerRepo) Sync(id uint) error {
|
||||
newUser := &biz.DatabaseUser{
|
||||
ServerID: id,
|
||||
Username: user.Role,
|
||||
Remark: fmt.Sprintf("sync from server %s", server.Name),
|
||||
Remark: r.t.Get("sync from server %s", server.Name),
|
||||
}
|
||||
r.db.Create(newUser)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -12,12 +13,14 @@ import (
|
||||
)
|
||||
|
||||
type databaseUserRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
server biz.DatabaseServerRepo
|
||||
}
|
||||
|
||||
func NewDatabaseUserRepo(db *gorm.DB, server biz.DatabaseServerRepo) biz.DatabaseUserRepo {
|
||||
func NewDatabaseUserRepo(t *gotext.Locale, db *gorm.DB, server biz.DatabaseServerRepo) biz.DatabaseUserRepo {
|
||||
return &databaseUserRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
server: server,
|
||||
}
|
||||
@@ -33,7 +36,7 @@ func (r databaseUserRepo) Count() (int64, error) {
|
||||
}
|
||||
|
||||
func (r databaseUserRepo) List(page, limit uint) ([]*biz.DatabaseUser, int64, error) {
|
||||
var user []*biz.DatabaseUser
|
||||
user := make([]*biz.DatabaseUser, 0)
|
||||
var total int64
|
||||
err := r.db.Model(&biz.DatabaseUser{}).Preload("Server").Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&user).Error
|
||||
|
||||
@@ -68,7 +71,9 @@ func (r databaseUserRepo) Create(req *request.DatabaseUserCreate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mysql.Close()
|
||||
defer func(mysql *db.MySQL) {
|
||||
_ = mysql.Close()
|
||||
}(mysql)
|
||||
if err = mysql.UserCreate(req.Username, req.Password, req.Host); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -87,7 +92,9 @@ func (r databaseUserRepo) Create(req *request.DatabaseUserCreate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postgres.Close()
|
||||
defer func(postgres *db.Postgres) {
|
||||
_ = postgres.Close()
|
||||
}(postgres)
|
||||
if err = postgres.UserCreate(req.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -129,7 +136,9 @@ func (r databaseUserRepo) Update(req *request.DatabaseUserUpdate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mysql.Close()
|
||||
defer func(mysql *db.MySQL) {
|
||||
_ = mysql.Close()
|
||||
}(mysql)
|
||||
if req.Password != "" {
|
||||
if err = mysql.UserPassword(user.Username, req.Password, user.Host); err != nil {
|
||||
return err
|
||||
@@ -145,7 +154,9 @@ func (r databaseUserRepo) Update(req *request.DatabaseUserUpdate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postgres.Close()
|
||||
defer func(postgres *db.Postgres) {
|
||||
_ = postgres.Close()
|
||||
}(postgres)
|
||||
if req.Password != "" {
|
||||
if err = postgres.UserPassword(user.Username, req.Password); err != nil {
|
||||
return err
|
||||
@@ -192,14 +203,18 @@ func (r databaseUserRepo) Delete(id uint) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mysql.Close()
|
||||
defer func(mysql *db.MySQL) {
|
||||
_ = mysql.Close()
|
||||
}(mysql)
|
||||
_ = mysql.UserDrop(user.Username, user.Host)
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postgres.Close()
|
||||
defer func(postgres *db.Postgres) {
|
||||
_ = postgres.Close()
|
||||
}(postgres)
|
||||
_ = postgres.UserDrop(user.Username)
|
||||
}
|
||||
|
||||
@@ -218,7 +233,9 @@ func (r databaseUserRepo) DeleteByNames(serverID uint, names []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mysql.Close()
|
||||
defer func(mysql *db.MySQL) {
|
||||
_ = mysql.Close()
|
||||
}(mysql)
|
||||
users := make([]*biz.DatabaseUser, 0)
|
||||
if err = r.db.Where("server_id = ? AND username IN ?", serverID, names).Find(&users).Error; err != nil {
|
||||
return err
|
||||
@@ -238,7 +255,9 @@ func (r databaseUserRepo) DeleteByNames(serverID uint, names []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postgres.Close()
|
||||
defer func(postgres *db.Postgres) {
|
||||
_ = postgres.Close()
|
||||
}(postgres)
|
||||
for name := range slices.Values(names) {
|
||||
_ = postgres.UserDrop(name)
|
||||
}
|
||||
@@ -254,7 +273,9 @@ func (r databaseUserRepo) fillUser(user *biz.DatabaseUser) {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err == nil {
|
||||
defer mysql.Close()
|
||||
defer func(mysql *db.MySQL) {
|
||||
_ = mysql.Close()
|
||||
}(mysql)
|
||||
privileges, _ := mysql.UserPrivileges(user.Username, user.Host)
|
||||
user.Privileges = privileges
|
||||
}
|
||||
@@ -267,7 +288,9 @@ func (r databaseUserRepo) fillUser(user *biz.DatabaseUser) {
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err == nil {
|
||||
defer postgres.Close()
|
||||
defer func(postgres *db.Postgres) {
|
||||
_ = postgres.Close()
|
||||
}(postgres)
|
||||
privileges, _ := postgres.UserPrivileges(user.Username)
|
||||
user.Privileges = privileges
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
@@ -56,14 +55,10 @@ func (r monitorRepo) Clear() error {
|
||||
}
|
||||
|
||||
func (r monitorRepo) List(start, end time.Time) ([]*biz.Monitor, error) {
|
||||
var monitors []*biz.Monitor
|
||||
monitors := make([]*biz.Monitor, 0)
|
||||
if err := r.db.Where("created_at BETWEEN ? AND ?", start, end).Find(&monitors).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(monitors) == 0 {
|
||||
return nil, errors.New("没有找到数据")
|
||||
}
|
||||
|
||||
return monitors, nil
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package data
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-rat/utils/hash"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/gorm"
|
||||
@@ -23,13 +23,15 @@ import (
|
||||
)
|
||||
|
||||
type settingRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
conf *koanf.Koanf
|
||||
task biz.TaskRepo
|
||||
}
|
||||
|
||||
func NewSettingRepo(db *gorm.DB, conf *koanf.Koanf, task biz.TaskRepo) biz.SettingRepo {
|
||||
func NewSettingRepo(t *gotext.Locale, db *gorm.DB, conf *koanf.Koanf, task biz.TaskRepo) biz.SettingRepo {
|
||||
return &settingRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
conf: conf,
|
||||
task: task,
|
||||
@@ -186,15 +188,15 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
|
||||
oldKey, _ := io.Read(filepath.Join(app.Root, "panel/storage/cert.key"))
|
||||
if oldCert != setting.Cert || oldKey != setting.Key {
|
||||
if r.task.HasRunningTask() {
|
||||
return false, errors.New("后台任务正在运行,禁止修改部分设置,请稍后再试")
|
||||
return false, errors.New(r.t.Get("background task is running, modifying some settings is prohibited, please try again later"))
|
||||
}
|
||||
restartFlag = true
|
||||
}
|
||||
if _, err := cert.ParseCert(setting.Cert); err != nil {
|
||||
return false, fmt.Errorf("failed to parse certificate: %w", err)
|
||||
return false, errors.New(r.t.Get("failed to parse certificate: %v", err))
|
||||
}
|
||||
if _, err := cert.ParseKey(setting.Key); err != nil {
|
||||
return false, fmt.Errorf("failed to parse private key: %w", err)
|
||||
return false, errors.New(r.t.Get("failed to parse private key: %v", err))
|
||||
}
|
||||
if err := io.Write(filepath.Join(app.Root, "panel/storage/cert.pem"), setting.Cert, 0644); err != nil {
|
||||
return false, err
|
||||
@@ -215,7 +217,7 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
|
||||
|
||||
if setting.Port != config.HTTP.Port {
|
||||
if os.TCPPortInUse(setting.Port) {
|
||||
return false, errors.New("端口已被占用")
|
||||
return false, errors.New(r.t.Get("port is already in use"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +245,7 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
|
||||
}
|
||||
if raw != string(encoded) {
|
||||
if r.task.HasRunningTask() {
|
||||
return false, errors.New("后台任务正在运行,禁止修改部分设置,请稍后再试")
|
||||
return false, errors.New(r.t.Get("background task is running, modifying some settings is prohibited, please try again later"))
|
||||
}
|
||||
restartFlag = true
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -11,17 +13,19 @@ import (
|
||||
)
|
||||
|
||||
type sshRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewSSHRepo(db *gorm.DB) biz.SSHRepo {
|
||||
func NewSSHRepo(t *gotext.Locale, db *gorm.DB) biz.SSHRepo {
|
||||
return &sshRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *sshRepo) List(page, limit uint) ([]*biz.SSH, int64, error) {
|
||||
var ssh []*biz.SSH
|
||||
ssh := make([]*biz.SSH, 0)
|
||||
var total int64
|
||||
err := r.db.Model(&biz.SSH{}).Omit("Hosts").Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&ssh).Error
|
||||
return ssh, total, err
|
||||
@@ -46,7 +50,7 @@ func (r *sshRepo) Create(req *request.SSHCreate) error {
|
||||
}
|
||||
_, err := pkgssh.NewSSHClient(conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check ssh connection: %v", err)
|
||||
return errors.New(r.t.Get("failed to check ssh connection: %v", err))
|
||||
}
|
||||
|
||||
ssh := &biz.SSH{
|
||||
@@ -70,7 +74,7 @@ func (r *sshRepo) Update(req *request.SSHUpdate) error {
|
||||
}
|
||||
_, err := pkgssh.NewSSHClient(conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check ssh connection: %v", err)
|
||||
return errors.New(r.t.Get("failed to check ssh connection: %v", err))
|
||||
}
|
||||
|
||||
ssh := &biz.SSH{
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"log/slog"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
@@ -12,13 +13,15 @@ import (
|
||||
)
|
||||
|
||||
type taskRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
log *slog.Logger
|
||||
queue *queue.Queue
|
||||
}
|
||||
|
||||
func NewTaskRepo(db *gorm.DB, log *slog.Logger, queue *queue.Queue) biz.TaskRepo {
|
||||
func NewTaskRepo(t *gotext.Locale, db *gorm.DB, log *slog.Logger, queue *queue.Queue) biz.TaskRepo {
|
||||
return &taskRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
log: log,
|
||||
queue: queue,
|
||||
@@ -32,7 +35,7 @@ func (r *taskRepo) HasRunningTask() bool {
|
||||
}
|
||||
|
||||
func (r *taskRepo) List(page, limit uint) ([]*biz.Task, int64, error) {
|
||||
var tasks []*biz.Task
|
||||
tasks := make([]*biz.Task, 0)
|
||||
var total int64
|
||||
err := r.db.Model(&biz.Task{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&tasks).Error
|
||||
return tasks, total, err
|
||||
@@ -53,12 +56,13 @@ func (r *taskRepo) UpdateStatus(id uint, status biz.TaskStatus) error {
|
||||
}
|
||||
|
||||
func (r *taskRepo) Push(task *biz.Task) error {
|
||||
// 防止有人喜欢酒吧点炒饭
|
||||
var count int64
|
||||
if err := r.db.Model(&biz.Task{}).Where("shell = ? and (status = ? or status = ?)", task.Shell, biz.TaskStatusWaiting, biz.TaskStatusRunning).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return fmt.Errorf("duplicate submission, please wait for the previous task to end")
|
||||
return errors.New(r.t.Get("duplicate submission, please wait for the previous task to end"))
|
||||
}
|
||||
|
||||
if err := r.db.Create(task).Error; err != nil {
|
||||
|
||||
@@ -4,18 +4,21 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-rat/utils/hash"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type userRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
hasher hash.Hasher
|
||||
}
|
||||
|
||||
func NewUserRepo(db *gorm.DB) biz.UserRepo {
|
||||
func NewUserRepo(t *gotext.Locale, db *gorm.DB) biz.UserRepo {
|
||||
return &userRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
hasher: hash.NewArgon2id(),
|
||||
}
|
||||
@@ -42,14 +45,14 @@ func (r *userRepo) CheckPassword(username, password string) (*biz.User, error) {
|
||||
user := new(biz.User)
|
||||
if err := r.db.Where("username = ?", username).First(user).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("用户名或密码错误")
|
||||
return nil, errors.New(r.t.Get("username or password error"))
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !r.hasher.Check(password, user.Password) {
|
||||
return nil, errors.New("用户名或密码错误")
|
||||
return nil, errors.New(r.t.Get("username or password error"))
|
||||
}
|
||||
|
||||
return user, nil
|
||||
|
||||
+61
-33
@@ -11,17 +11,18 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cast"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/internal/embed"
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
"github.com/tnb-labs/panel/pkg/acme"
|
||||
"github.com/tnb-labs/panel/pkg/api"
|
||||
"github.com/tnb-labs/panel/pkg/cert"
|
||||
"github.com/tnb-labs/panel/pkg/embed"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/nginx"
|
||||
"github.com/tnb-labs/panel/pkg/punycode"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
)
|
||||
|
||||
type websiteRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
cache biz.CacheRepo
|
||||
database biz.DatabaseRepo
|
||||
@@ -40,8 +42,9 @@ type websiteRepo struct {
|
||||
certAccount biz.CertAccountRepo
|
||||
}
|
||||
|
||||
func NewWebsiteRepo(db *gorm.DB, cache biz.CacheRepo, database biz.DatabaseRepo, databaseServer biz.DatabaseServerRepo, databaseUser biz.DatabaseUserRepo, cert biz.CertRepo, certAccount biz.CertAccountRepo) biz.WebsiteRepo {
|
||||
func NewWebsiteRepo(t *gotext.Locale, db *gorm.DB, cache biz.CacheRepo, database biz.DatabaseRepo, databaseServer biz.DatabaseServerRepo, databaseUser biz.DatabaseUserRepo, cert biz.CertRepo, certAccount biz.CertAccountRepo) biz.WebsiteRepo {
|
||||
return &websiteRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
cache: cache,
|
||||
database: database,
|
||||
@@ -186,7 +189,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
|
||||
}
|
||||
@@ -202,7 +211,7 @@ func (r *websiteRepo) GetByName(name string) (*types.WebsiteSetting, error) {
|
||||
}
|
||||
|
||||
func (r *websiteRepo) List(page, limit uint) ([]*biz.Website, int64, error) {
|
||||
var websites []*biz.Website
|
||||
websites := make([]*biz.Website, 0)
|
||||
var total int64
|
||||
|
||||
if err := r.db.Model(&biz.Website{}).Count(&total).Error; err != nil {
|
||||
@@ -218,7 +227,11 @@ func (r *websiteRepo) List(page, limit uint) ([]*biz.Website, int64, error) {
|
||||
|
||||
func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
// 初始化nginx配置
|
||||
p, err := nginx.NewParser()
|
||||
config := nginx.DefaultConf
|
||||
if app.Locale == "zh_CN" {
|
||||
config = nginx.DefaultConfZh
|
||||
}
|
||||
p, err := nginx.NewParser(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -253,7 +266,7 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
}
|
||||
includes = append(includes, filepath.Join(app.Root, "server/vhost/rewrite", req.Name+".conf"))
|
||||
includes = append(includes, filepath.Join(app.Root, "server/vhost/acme", req.Name+".conf"))
|
||||
comments = append(comments, []string{"# 伪静态规则"})
|
||||
comments = append(comments, []string{r.t.Get("# Rewrite rule")})
|
||||
comments = append(comments, []string{"# acme http-01"})
|
||||
if err = p.SetIncludes(includes, comments); err != nil {
|
||||
return nil, err
|
||||
@@ -270,16 +283,26 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
if err = os.MkdirAll(req.Path, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index, err := embed.WebsiteFS.ReadFile(filepath.Join("website", "index.html"))
|
||||
var index []byte
|
||||
if app.Locale == "zh_CN" {
|
||||
index, err = embed.WebsiteFS.ReadFile(filepath.Join("website", "index_zh.html"))
|
||||
} else {
|
||||
index, err = embed.WebsiteFS.ReadFile(filepath.Join("website", "index.html"))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取index模板文件失败: %w", err)
|
||||
return nil, errors.New(r.t.Get("failed to get index template file: %v", err))
|
||||
}
|
||||
if err = io.Write(filepath.Join(req.Path, "index.html"), string(index), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
notFound, err := embed.WebsiteFS.ReadFile(filepath.Join("website", "404.html"))
|
||||
var notFound []byte
|
||||
if app.Locale == "zh_CN" {
|
||||
notFound, err = embed.WebsiteFS.ReadFile(filepath.Join("website", "404_zh.html"))
|
||||
} else {
|
||||
notFound, err = embed.WebsiteFS.ReadFile(filepath.Join("website", "404.html"))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取404模板文件失败: %w", err)
|
||||
return nil, errors.New(r.t.Get("failed to get 404 template file: %v", err))
|
||||
}
|
||||
if err = io.Write(filepath.Join(req.Path, "404.html"), string(notFound), 0644); err != nil {
|
||||
return nil, err
|
||||
@@ -343,7 +366,7 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
if req.DB {
|
||||
server, err := r.databaseServer.GetByName(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`create database: can't find %s database server, please add it first`, name)
|
||||
return nil, errors.New(r.t.Get("can't find %s database server, please add it first", name))
|
||||
}
|
||||
if err = r.database.Create(&request.DatabaseCreate{
|
||||
ServerID: server.ID,
|
||||
@@ -366,9 +389,6 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||||
if err := r.db.Where("id", req.ID).First(website).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if !website.Status {
|
||||
return errors.New("网站已停用,请先启用")
|
||||
}
|
||||
|
||||
// 解析nginx配置
|
||||
config, err := io.Read(filepath.Join(app.Root, "server/vhost", website.Name+".conf"))
|
||||
@@ -424,14 +444,14 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
|
||||
}
|
||||
// 运行目录
|
||||
if !io.Exists(req.Root) {
|
||||
return errors.New("运行目录不存在")
|
||||
return errors.New(r.t.Get("runtime directory does not exist"))
|
||||
}
|
||||
if err = p.SetRoot(req.Root); err != nil {
|
||||
return err
|
||||
}
|
||||
// 运行目录
|
||||
if !io.Exists(req.Path) {
|
||||
return errors.New("网站目录不存在")
|
||||
return errors.New(r.t.Get("website directory does not exist"))
|
||||
}
|
||||
website.Path = req.Path
|
||||
// PHP
|
||||
@@ -529,7 +549,7 @@ func (r *websiteRepo) Delete(req *request.WebsiteDelete) error {
|
||||
return err
|
||||
}
|
||||
if website.Cert != nil {
|
||||
return errors.New("网站" + website.Name + "已绑定证书,请先删除证书")
|
||||
return errors.New(r.t.Get("website %s has bound certificates, please delete the certificate first", website.Name))
|
||||
}
|
||||
|
||||
_ = io.Remove(filepath.Join(app.Root, "server/vhost", website.Name+".conf"))
|
||||
@@ -593,7 +613,11 @@ func (r *websiteRepo) ResetConfig(id uint) error {
|
||||
}
|
||||
|
||||
// 初始化nginx配置
|
||||
p, err := nginx.NewParser()
|
||||
config := nginx.DefaultConf
|
||||
if app.Locale == "zh_CN" {
|
||||
config = nginx.DefaultConfZh
|
||||
}
|
||||
p, err := nginx.NewParser(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -608,7 +632,7 @@ func (r *websiteRepo) ResetConfig(id uint) error {
|
||||
}
|
||||
includes = append(includes, filepath.Join(app.Root, "server/vhost/rewrite", website.Name+".conf"))
|
||||
includes = append(includes, filepath.Join(app.Root, "server/vhost/acme", website.Name+".conf"))
|
||||
comments = append(comments, []string{"# 伪静态规则"})
|
||||
comments = append(comments, []string{r.t.Get("# Rewrite rule")})
|
||||
comments = append(comments, []string{"# acme http-01"})
|
||||
if err = p.SetIncludes(includes, comments); err != nil {
|
||||
return err
|
||||
@@ -674,23 +698,23 @@ func (r *websiteRepo) UpdateStatus(id uint, status bool) error {
|
||||
|
||||
if status {
|
||||
if len(rootComment) == 0 {
|
||||
return fmt.Errorf("未找到运行目录注释")
|
||||
return errors.New(r.t.Get("runtime directory comment not found"))
|
||||
}
|
||||
if len(rootComment) != 1 {
|
||||
return fmt.Errorf("运行目录注释数量不正确,预期1个,实际%d个", len(rootComment))
|
||||
return errors.New(r.t.Get("runtime directory comment count is incorrect, expected 1, actual %d", len(rootComment)))
|
||||
}
|
||||
rootComment[0] = strings.TrimPrefix(rootComment[0], "# ")
|
||||
if !io.Exists(rootComment[0]) {
|
||||
return fmt.Errorf("运行目录不存在")
|
||||
return errors.New(r.t.Get("runtime directory does not exist"))
|
||||
}
|
||||
if err = p.SetRoot(rootComment[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(indexComment) == 0 {
|
||||
return fmt.Errorf("未找到默认文档注释")
|
||||
return errors.New(r.t.Get("default document comment not found"))
|
||||
}
|
||||
if len(indexComment) != 1 {
|
||||
return fmt.Errorf("默认文档注释数量不正确,预期1个,实际%d个", len(indexComment))
|
||||
return errors.New(r.t.Get("default document comment count is incorrect, expected 1, actual %d", len(indexComment)))
|
||||
}
|
||||
indexComment[0] = strings.TrimPrefix(indexComment[0], "# ")
|
||||
if err = p.SetIndex(strings.Fields(indexComment[0])); err != nil {
|
||||
@@ -728,7 +752,7 @@ func (r *websiteRepo) ObtainCert(ctx context.Context, id uint) error {
|
||||
return err
|
||||
}
|
||||
if slices.Contains(website.Domains, "*") {
|
||||
return errors.New("cannot one-key obtain wildcard certificate")
|
||||
return errors.New(r.t.Get("not support one-key obtain wildcard certificate, please use Cert menu to obtain it with DNS method"))
|
||||
}
|
||||
|
||||
account, err := r.certAccount.GetDefault(cast.ToUint(ctx.Value("user_id")))
|
||||
@@ -738,14 +762,18 @@ func (r *websiteRepo) ObtainCert(ctx context.Context, id uint) error {
|
||||
|
||||
newCert, err := r.cert.GetByWebsite(website.ID)
|
||||
if err != nil {
|
||||
newCert, err = r.cert.Create(&request.CertCreate{
|
||||
Type: string(acme.KeyEC256),
|
||||
Domains: website.Domains,
|
||||
AutoRenew: true,
|
||||
AccountID: account.ID,
|
||||
WebsiteID: website.ID,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
newCert, err = r.cert.Create(&request.CertCreate{
|
||||
Type: string(acme.KeyEC256),
|
||||
Domains: website.Domains,
|
||||
AutoRenew: true,
|
||||
AccountID: account.ID,
|
||||
WebsiteID: website.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -16,10 +16,11 @@ type CertCreate struct {
|
||||
|
||||
type CertUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:certs,id"`
|
||||
Type string `form:"type" json:"type" validate:"required|in:P256,P384,2048,3072,4096"`
|
||||
Type string `form:"type" json:"type" validate:"required|in:P256,P384,2048,3072,4096,upload"`
|
||||
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"`
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ type PanelTask struct {
|
||||
|
||||
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),
|
||||
api: api.NewAPI(app.Version, app.Locale),
|
||||
db: db,
|
||||
log: log,
|
||||
backupRepo: backup,
|
||||
@@ -48,6 +48,11 @@ func (r *PanelTask) Run() {
|
||||
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("[Panel Task] failed to wal checkpoint database", slog.Any("err", err))
|
||||
|
||||
@@ -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")
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
+83
-80
@@ -1,17 +1,20 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
)
|
||||
|
||||
type Cli struct {
|
||||
t *gotext.Locale
|
||||
cli *service.CliService
|
||||
}
|
||||
|
||||
func NewCli(cli *service.CliService) *Cli {
|
||||
func NewCli(t *gotext.Locale, cli *service.CliService) *Cli {
|
||||
return &Cli{
|
||||
t: t,
|
||||
cli: cli,
|
||||
}
|
||||
}
|
||||
@@ -20,147 +23,147 @@ func (route *Cli) Commands() []*cli.Command {
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "restart",
|
||||
Usage: "重启面板服务",
|
||||
Usage: route.t.Get("Restart panel service"),
|
||||
Action: route.cli.Restart,
|
||||
},
|
||||
{
|
||||
Name: "stop",
|
||||
Usage: "停止面板服务",
|
||||
Usage: route.t.Get("Stop panel service"),
|
||||
Action: route.cli.Stop,
|
||||
},
|
||||
{
|
||||
Name: "start",
|
||||
Usage: "启动面板服务",
|
||||
Usage: route.t.Get("Start panel service"),
|
||||
Action: route.cli.Start,
|
||||
},
|
||||
{
|
||||
Name: "update",
|
||||
Usage: "更新面板",
|
||||
Usage: route.t.Get("Update panel"),
|
||||
Action: route.cli.Update,
|
||||
},
|
||||
{
|
||||
Name: "sync",
|
||||
Usage: "同步数据",
|
||||
Usage: route.t.Get("Sync panel data"),
|
||||
Action: route.cli.Sync,
|
||||
},
|
||||
{
|
||||
Name: "fix",
|
||||
Usage: "修复面板",
|
||||
Usage: route.t.Get("Fix panel"),
|
||||
Action: route.cli.Fix,
|
||||
},
|
||||
{
|
||||
Name: "info",
|
||||
Usage: "输出面板基本信息",
|
||||
Usage: route.t.Get("Output panel basic information and generate new password"),
|
||||
Action: route.cli.Info,
|
||||
},
|
||||
{
|
||||
Name: "user",
|
||||
Usage: "操作面板用户",
|
||||
Usage: route.t.Get("Operate panel users"),
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "列出所有用户",
|
||||
Usage: route.t.Get("List all users"),
|
||||
Action: route.cli.UserList,
|
||||
},
|
||||
{
|
||||
Name: "username",
|
||||
Usage: "修改用户名",
|
||||
Usage: route.t.Get("Change username"),
|
||||
Action: route.cli.UserName,
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Usage: "修改用户密码",
|
||||
Usage: route.t.Get("Change user password"),
|
||||
Action: route.cli.UserPassword,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "https",
|
||||
Usage: "操作面板HTTPS",
|
||||
Usage: route.t.Get("Operate panel HTTPS"),
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "on",
|
||||
Usage: "开启HTTPS",
|
||||
Usage: route.t.Get("Enable HTTPS"),
|
||||
Action: route.cli.HTTPSOn,
|
||||
},
|
||||
{
|
||||
Name: "off",
|
||||
Usage: "关闭HTTPS",
|
||||
Usage: route.t.Get("Disable HTTPS"),
|
||||
Action: route.cli.HTTPSOff,
|
||||
},
|
||||
{
|
||||
Name: "generate",
|
||||
Usage: "生成HTTPS证书",
|
||||
Usage: route.t.Get("Generate HTTPS certificate"),
|
||||
Action: route.cli.HTTPSGenerate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "entrance",
|
||||
Usage: "操作面板访问入口",
|
||||
Usage: route.t.Get("Operate panel access entrance"),
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "on",
|
||||
Usage: "开启访问入口",
|
||||
Usage: route.t.Get("Enable access entrance"),
|
||||
Action: route.cli.EntranceOn,
|
||||
},
|
||||
{
|
||||
Name: "off",
|
||||
Usage: "关闭访问入口",
|
||||
Usage: route.t.Get("Disable access entrance"),
|
||||
Action: route.cli.EntranceOff,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "port",
|
||||
Usage: "修改面板端口",
|
||||
Usage: route.t.Get("Change panel port"),
|
||||
Action: route.cli.Port,
|
||||
},
|
||||
{
|
||||
Name: "website",
|
||||
Usage: "网站管理",
|
||||
Usage: route.t.Get("Website management"),
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "create",
|
||||
Usage: "创建新网站",
|
||||
Usage: route.t.Get("Create new website"),
|
||||
Action: route.cli.WebsiteCreate,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "网站名称",
|
||||
Usage: route.t.Get("Website name"),
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "domains",
|
||||
Usage: "与网站关联的域名列表",
|
||||
Usage: route.t.Get("List of domains associated with the website"),
|
||||
Aliases: []string{"d"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "listens",
|
||||
Usage: "与网站关联的监听地址列表",
|
||||
Usage: route.t.Get("List of listening addresses associated with the website"),
|
||||
Aliases: []string{"l"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Usage: "网站托管的路径(不填则默认路径)",
|
||||
Usage: route.t.Get("Path where the website is hosted (default path if not filled)"),
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "php",
|
||||
Usage: "网站使用的 PHP 版本(不填不使用)",
|
||||
Usage: route.t.Get("PHP version used by the website (not used if not filled)"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "移除网站",
|
||||
Usage: route.t.Get("Remove website"),
|
||||
Action: route.cli.WebsiteRemove,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "网站名称",
|
||||
Usage: route.t.Get("Website name"),
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
},
|
||||
@@ -168,12 +171,12 @@ func (route *Cli) Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Usage: "删除网站(包括网站目录、同名数据库)",
|
||||
Usage: route.t.Get("Delete website (including website directory, database with the same name)"),
|
||||
Action: route.cli.WebsiteDelete,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "网站名称",
|
||||
Usage: route.t.Get("Website name"),
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
},
|
||||
@@ -181,7 +184,7 @@ func (route *Cli) Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "write",
|
||||
Usage: "写入网站数据(仅限指导下使用)",
|
||||
Usage: route.t.Get("Write website data (use only under guidance)"),
|
||||
Hidden: true,
|
||||
Action: route.cli.WebsiteWrite,
|
||||
},
|
||||
@@ -189,55 +192,55 @@ func (route *Cli) Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "database",
|
||||
Usage: "数据库管理",
|
||||
Usage: route.t.Get("Database management"),
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "add-server",
|
||||
Usage: "添加数据库服务器",
|
||||
Usage: route.t.Get("Add database server"),
|
||||
Action: route.cli.DatabaseAddServer,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "type",
|
||||
Usage: "服务器类型",
|
||||
Usage: route.t.Get("Server type"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "服务器名称",
|
||||
Usage: route.t.Get("Server name"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Usage: "服务器地址",
|
||||
Usage: route.t.Get("Server address"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "port",
|
||||
Usage: "服务器端口",
|
||||
Usage: route.t.Get("Server port"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Usage: "服务器用户名",
|
||||
Usage: route.t.Get("Server username"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "服务器密码",
|
||||
Usage: route.t.Get("Server password"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "remark",
|
||||
Usage: "服务器备注",
|
||||
Usage: route.t.Get("Server remark"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "delete-server",
|
||||
Usage: "删除数据库服务器",
|
||||
Usage: route.t.Get("Delete database server"),
|
||||
Action: route.cli.DatabaseDeleteServer,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "服务器名称",
|
||||
Usage: route.t.Get("Server name"),
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
},
|
||||
@@ -247,89 +250,89 @@ func (route *Cli) Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "backup",
|
||||
Usage: "数据备份",
|
||||
Usage: route.t.Get("Data backup"),
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "website",
|
||||
Usage: "备份网站",
|
||||
Usage: route.t.Get("Backup website"),
|
||||
Action: route.cli.BackupWebsite,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "网站名称",
|
||||
Usage: route.t.Get("Website name"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "保存目录(不填则默认路径)",
|
||||
Usage: route.t.Get("Save directory (default path if not filled)"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "database",
|
||||
Usage: "备份数据库",
|
||||
Usage: route.t.Get("Backup database"),
|
||||
Action: route.cli.BackupDatabase,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "type",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "数据库类型",
|
||||
Usage: route.t.Get("Database type"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "数据库名称",
|
||||
Usage: route.t.Get("Database name"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "保存目录(不填则默认路径)",
|
||||
Usage: route.t.Get("Save directory (default path if not filled)"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "panel",
|
||||
Usage: "备份面板",
|
||||
Usage: route.t.Get("Backup panel"),
|
||||
Action: route.cli.BackupPanel,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "保存目录(不填则默认路径)",
|
||||
Usage: route.t.Get("Save directory (default path if not filled)"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "clear",
|
||||
Usage: "清理备份",
|
||||
Usage: route.t.Get("Clear backups"),
|
||||
Action: route.cli.BackupClear,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "type",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "备份类型",
|
||||
Usage: route.t.Get("Backup type"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "file",
|
||||
Aliases: []string{"f"},
|
||||
Usage: "备份文件",
|
||||
Usage: route.t.Get("Backup file"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "save",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "保存份数",
|
||||
Usage: route.t.Get("Number of backups to keep"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "备份目录(不填则默认路径)",
|
||||
Usage: route.t.Get("Backup directory (default path if not filled)"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -337,54 +340,54 @@ func (route *Cli) Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "cutoff",
|
||||
Usage: "日志切割",
|
||||
Usage: route.t.Get("Log rotation"),
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "website",
|
||||
Usage: "网站",
|
||||
Usage: route.t.Get("Website"),
|
||||
Action: route.cli.CutoffWebsite,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "网站名称",
|
||||
Usage: route.t.Get("Website name"),
|
||||
Required: true,
|
||||
},
|
||||
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "保存目录(不填则默认路径)",
|
||||
Usage: route.t.Get("Save directory (default path if not filled)"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "clear",
|
||||
Usage: "清理切割的日志",
|
||||
Usage: route.t.Get("Clear rotated logs"),
|
||||
Action: route.cli.CutoffClear,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "type",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "切割类型",
|
||||
Usage: route.t.Get("Rotation type"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "file",
|
||||
Aliases: []string{"f"},
|
||||
Usage: "切割文件",
|
||||
Usage: route.t.Get("Rotation file"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "save",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "保存份数",
|
||||
Usage: route.t.Get("Number of logs to keep"),
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "切割目录(不填则默认路径)",
|
||||
Usage: route.t.Get("Rotation directory (default path if not filled)"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -392,32 +395,32 @@ func (route *Cli) Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "app",
|
||||
Usage: "应用管理",
|
||||
Usage: route.t.Get("Application management"),
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "install",
|
||||
Usage: "安装应用",
|
||||
Usage: route.t.Get("Install application"),
|
||||
Action: route.cli.AppInstall,
|
||||
},
|
||||
{
|
||||
Name: "uninstall",
|
||||
Usage: "卸载应用",
|
||||
Usage: route.t.Get("Uninstall application"),
|
||||
Action: route.cli.AppUnInstall,
|
||||
},
|
||||
{
|
||||
Name: "update",
|
||||
Usage: "更新应用",
|
||||
Usage: route.t.Get("Update application"),
|
||||
Action: route.cli.AppUpdate,
|
||||
},
|
||||
{
|
||||
Name: "write",
|
||||
Usage: "添加面板应用标记(仅限指导下使用)",
|
||||
Usage: route.t.Get("Add panel application mark (use only under guidance)"),
|
||||
Hidden: true,
|
||||
Action: route.cli.AppWrite,
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "移除面板应用标记(仅限指导下使用)",
|
||||
Usage: route.t.Get("Remove panel application mark (use only under guidance)"),
|
||||
Hidden: true,
|
||||
Action: route.cli.AppRemove,
|
||||
},
|
||||
@@ -425,24 +428,24 @@ func (route *Cli) Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "setting",
|
||||
Usage: "设置管理",
|
||||
Usage: route.t.Get("Setting management"),
|
||||
Hidden: true,
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "get",
|
||||
Usage: "获取面板设置(仅限指导下使用)",
|
||||
Usage: route.t.Get("Get panel setting (use only under guidance)"),
|
||||
Hidden: true,
|
||||
Action: route.cli.GetSetting,
|
||||
},
|
||||
{
|
||||
Name: "write",
|
||||
Usage: "写入面板设置(仅限指导下使用)",
|
||||
Usage: route.t.Get("Write panel setting (use only under guidance)"),
|
||||
Hidden: true,
|
||||
Action: route.cli.WriteSetting,
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "移除面板设置(仅限指导下使用)",
|
||||
Usage: route.t.Get("Remove panel setting (use only under guidance)"),
|
||||
Hidden: true,
|
||||
Action: route.cli.RemoveSetting,
|
||||
},
|
||||
@@ -450,18 +453,18 @@ func (route *Cli) Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "sync-time",
|
||||
Usage: "同步系统时间",
|
||||
Usage: route.t.Get("Sync system time"),
|
||||
Action: route.cli.SyncTime,
|
||||
},
|
||||
{
|
||||
Name: "clear-task",
|
||||
Usage: "清理面板任务队列(仅限指导下使用)",
|
||||
Usage: route.t.Get("Clear panel task queue (use only under guidance)"),
|
||||
Hidden: true,
|
||||
Action: route.cli.ClearTask,
|
||||
},
|
||||
{
|
||||
Name: "init",
|
||||
Usage: "初始化面板(仅限指导下使用)",
|
||||
Usage: route.t.Get("Initialize panel (use only under guidance)"),
|
||||
Hidden: true,
|
||||
Action: route.cli.Init,
|
||||
},
|
||||
|
||||
+19
-3
@@ -8,10 +8,10 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"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"
|
||||
"github.com/tnb-labs/panel/pkg/embed"
|
||||
)
|
||||
|
||||
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
|
||||
@@ -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,
|
||||
@@ -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)
|
||||
@@ -370,7 +382,9 @@ func (route *Http) Register(r *chi.Mux) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
defer indexFile.Close()
|
||||
defer func(indexFile http.File) {
|
||||
_ = indexFile.Close()
|
||||
}(indexFile)
|
||||
|
||||
fi, err := indexFile.Stat()
|
||||
if err != nil {
|
||||
@@ -381,7 +395,9 @@ func (route *Http) Register(r *chi.Mux) {
|
||||
http.ServeContent(w, r, "index.html", fi.ModTime(), indexFile)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
defer func(f http.File) {
|
||||
_ = f.Close()
|
||||
}(f)
|
||||
fileServer.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
@@ -11,13 +12,15 @@ import (
|
||||
)
|
||||
|
||||
type AppService struct {
|
||||
t *gotext.Locale
|
||||
appRepo biz.AppRepo
|
||||
cacheRepo biz.CacheRepo
|
||||
settingRepo biz.SettingRepo
|
||||
}
|
||||
|
||||
func NewAppService(app biz.AppRepo, cache biz.CacheRepo, setting biz.SettingRepo) *AppService {
|
||||
func NewAppService(t *gotext.Locale, app biz.AppRepo, cache biz.CacheRepo, setting biz.SettingRepo) *AppService {
|
||||
return &AppService{
|
||||
t: t,
|
||||
appRepo: app,
|
||||
cacheRepo: cache,
|
||||
settingRepo: setting,
|
||||
@@ -167,7 +170,7 @@ func (s *AppService) IsInstalled(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *AppService) UpdateCache(w http.ResponseWriter, r *http.Request) {
|
||||
if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline {
|
||||
Error(w, http.StatusForbidden, "离线模式下无法更新应用列表缓存")
|
||||
Error(w, http.StatusForbidden, s.t.Get("Unable to update app list cache in offline mode"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user