mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 20:05:34 +08:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa080dab15 | |||
| b4e5498b48 | |||
| 63838bffa1 | |||
| fa41b18ced | |||
| 31bd15217b | |||
| e6b6f06706 | |||
| 1b836297bf | |||
| 96bea2cf90 | |||
| 8aff513284 | |||
| 368e41a512 | |||
| 924fdee7c5 | |||
| 438ba1db1e | |||
| d4e52a2d87 | |||
| 4432a12c7a | |||
| a75e36e447 | |||
| 05674ee04e | |||
| 09703761eb | |||
| dfa14c3fd4 | |||
| 7b80f034ea | |||
| 71923093fd | |||
| aae3cf0518 | |||
| ed22e79552 | |||
| 15dd5bb718 | |||
| 0e7af2914e | |||
| 7ec378829b |
@@ -46,8 +46,8 @@ func (receiver *Monitoring) Handle(console.Context) error {
|
||||
|
||||
// 将等待中的任务分发
|
||||
// TODO 有bug,需要设计一个锁机制防止重复分发
|
||||
//task := services.NewTaskImpl()
|
||||
//_ = task.DispatchWaiting()
|
||||
task := services.NewTaskImpl()
|
||||
_ = task.DispatchWaiting()
|
||||
|
||||
setting := services.NewSettingImpl()
|
||||
monitor := setting.Get(models.SettingKeyMonitor)
|
||||
|
||||
@@ -614,7 +614,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = website.Add(types.Website{
|
||||
_, err = website.Add(types.WebsiteAdd{
|
||||
Name: name,
|
||||
Status: true,
|
||||
Domains: domains,
|
||||
|
||||
@@ -2,6 +2,8 @@ package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/goravel/framework/contracts/console"
|
||||
"github.com/goravel/framework/contracts/console/command"
|
||||
@@ -68,6 +70,10 @@ func (receiver *PanelTask) Handle(console.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 回收内存
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
types.Status = types.StatusNormal
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func Success(ctx http.Context, data any) http.Response {
|
||||
// Error 响应错误
|
||||
func Error(ctx http.Context, code int, message string) http.Response {
|
||||
return ctx.Response().Json(code, &ErrorResponse{
|
||||
Message: facades.Lang(ctx).Get("messages.mistake") + ": " + message,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
stdio "io"
|
||||
stdos "os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
@@ -354,10 +355,16 @@ func (r *FileController) Permission(ctx http.Context) http.Response {
|
||||
return sanitize
|
||||
}
|
||||
|
||||
if err := io.Chmod(request.Path, stdos.FileMode(request.Mode)); err != nil {
|
||||
// 解析成8进制
|
||||
mode, err := strconv.ParseUint(request.Mode, 8, 64)
|
||||
if err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if err := io.Chown(request.Path, request.Owner, request.Group); err != nil {
|
||||
|
||||
if err = io.Chmod(request.Path, stdos.FileMode(mode)); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if err = io.Chown(request.Path, request.Owner, request.Group); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
@@ -505,7 +512,7 @@ func (r *FileController) List(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
// setPermission
|
||||
func (r *FileController) setPermission(path string, mode uint, owner, group string) {
|
||||
_ = io.Chmod(path, stdos.FileMode(mode))
|
||||
func (r *FileController) setPermission(path string, mode stdos.FileMode, owner, group string) {
|
||||
_ = io.Chmod(path, mode)
|
||||
_ = io.Chown(path, owner, group)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
@@ -71,7 +71,7 @@ func (r *MySQLController) Load(ctx http.Context) http.Response {
|
||||
return controllers.Success(ctx, []types.NV{})
|
||||
}
|
||||
|
||||
raw, err := shell.Execf("/www/server/mysql/bin/mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
|
||||
raw, err := shell.Execf("mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL负载失败")
|
||||
}
|
||||
@@ -175,80 +175,45 @@ func (r *MySQLController) GetRootPassword(ctx http.Context) http.Response {
|
||||
|
||||
// SetRootPassword 设置root密码
|
||||
func (r *MySQLController) SetRootPassword(ctx http.Context) http.Response {
|
||||
status, err := systemctl.Status("mysqld")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取MySQL状态失败")
|
||||
}
|
||||
if !status {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "MySQL 未运行")
|
||||
}
|
||||
|
||||
rootPassword := ctx.Request().Input("password")
|
||||
if len(rootPassword) == 0 {
|
||||
return controllers.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码不能为空")
|
||||
}
|
||||
|
||||
oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
if oldRootPassword != rootPassword {
|
||||
if _, err = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';"`, oldRootPassword, rootPassword)); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置root密码失败: %v", err))
|
||||
mysql, err := db.NewMySQL("root", oldRootPassword, r.getSock(), "unix")
|
||||
if err != nil {
|
||||
// 尝试安全模式直接改密
|
||||
if err = db.MySQLResetRootPassword(rootPassword); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if _, err = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "FLUSH PRIVILEGES;"`, rootPassword)); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "设置root密码失败")
|
||||
}
|
||||
if err = r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword); err != nil {
|
||||
_, _ = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';"`, rootPassword, oldRootPassword))
|
||||
_, _ = shell.Execf(fmt.Sprintf(`/www/server/mysql/bin/mysql -uroot -p%s -e "FLUSH PRIVILEGES;"`, oldRootPassword))
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err))
|
||||
} else {
|
||||
if err = mysql.UserPassword("root", rootPassword); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
if err = r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// DatabaseList 获取数据库列表
|
||||
func (r *MySQLController) DatabaseList(ctx http.Context) http.Response {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
type database struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
|
||||
password := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
mysql, err := db.NewMySQL("root", password, r.getSock(), "unix")
|
||||
if err != nil {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []database{},
|
||||
})
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if err = db.Ping(); err != nil {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []database{},
|
||||
"items": []types.MySQLDatabase{},
|
||||
})
|
||||
}
|
||||
|
||||
rows, err := db.Query("SHOW DATABASES")
|
||||
databases, err := mysql.Databases()
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var databases []database
|
||||
for rows.Next() {
|
||||
var d database
|
||||
if err = rows.Scan(&d.Name); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
databases = append(databases, d)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
|
||||
}
|
||||
|
||||
paged, total := controllers.Paginate(ctx, databases)
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
@@ -272,17 +237,18 @@ func (r *MySQLController) AddDatabase(ctx http.Context) http.Response {
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE DATABASE IF NOT EXISTS " + database + " DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
if err = mysql.DatabaseCreate(database); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
if err = mysql.UserCreate(user, password); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
if err = mysql.PrivilegesGrant(user, database); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
@@ -298,8 +264,12 @@ func (r *MySQLController) DeleteDatabase(ctx http.Context) http.Response {
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
database := ctx.Request().Input("database")
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if err = mysql.DatabaseDrop(database); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
@@ -394,71 +364,20 @@ func (r *MySQLController) RestoreBackup(ctx http.Context) http.Response {
|
||||
|
||||
// UserList 用户列表
|
||||
func (r *MySQLController) UserList(ctx http.Context) http.Response {
|
||||
type user struct {
|
||||
User string `json:"user"`
|
||||
Host string `json:"host"`
|
||||
Grants []string `json:"grants"`
|
||||
}
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
db, err := sql.Open("mysql", "root:"+rootPassword+"@unix(/tmp/mysql.sock)/")
|
||||
password := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
mysql, err := db.NewMySQL("root", password, r.getSock(), "unix")
|
||||
if err != nil {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []user{},
|
||||
})
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if err = db.Ping(); err != nil {
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": 0,
|
||||
"items": []user{},
|
||||
"items": []types.MySQLUser{},
|
||||
})
|
||||
}
|
||||
|
||||
rows, err := db.Query("SELECT user, host FROM mysql.user")
|
||||
users, err := mysql.Users()
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var userGrants []user
|
||||
|
||||
for rows.Next() {
|
||||
var u user
|
||||
if err = rows.Scan(&u.User, &u.Host); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 查询用户权限
|
||||
grantsRows, err := db.Query(fmt.Sprintf("SHOW GRANTS FOR '%s'@'%s'", u.User, u.Host))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer grantsRows.Close()
|
||||
|
||||
for grantsRows.Next() {
|
||||
var grant string
|
||||
if err = grantsRows.Scan(&grant); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
u.Grants = append(u.Grants, grant)
|
||||
}
|
||||
|
||||
if err = grantsRows.Err(); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
userGrants = append(userGrants, u)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "获取用户列表失败")
|
||||
}
|
||||
|
||||
paged, total := controllers.Paginate(ctx, userGrants)
|
||||
paged, total := controllers.Paginate(ctx, users)
|
||||
|
||||
return controllers.Success(ctx, http.Json{
|
||||
"total": total,
|
||||
@@ -480,14 +399,15 @@ func (r *MySQLController) AddUser(ctx http.Context) http.Response {
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
database := ctx.Request().Input("database")
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"CREATE USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + ";'\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
if err = mysql.UserCreate(user, password); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
if err = mysql.PrivilegesGrant(user, database); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
@@ -503,8 +423,12 @@ func (r *MySQLController) DeleteUser(ctx http.Context) http.Response {
|
||||
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if err = mysql.UserDrop(user); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
@@ -522,11 +446,12 @@ func (r *MySQLController) SetUserPassword(ctx http.Context) http.Response {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
password := ctx.Request().Input("password")
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
if err = mysql.UserPassword(user, password); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
@@ -544,15 +469,38 @@ func (r *MySQLController) SetUserPrivileges(ctx http.Context) http.Response {
|
||||
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
|
||||
user := ctx.Request().Input("user")
|
||||
database := ctx.Request().Input("database")
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"GRANT ALL PRIVILEGES ON " + database + ".* TO '" + user + "'@'localhost';\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
}
|
||||
if out, err := shell.Execf("/www/server/mysql/bin/mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\""); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, out)
|
||||
if err = mysql.PrivilegesGrant(user, database); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
// getSock 获取sock文件位置
|
||||
func (r *MySQLController) getSock() string {
|
||||
if io.Exists("/tmp/mysql.sock") {
|
||||
return "/tmp/mysql.sock"
|
||||
}
|
||||
if io.Exists("/www/server/mysql/config/my.cnf") {
|
||||
config, _ := io.Read("/www/server/mysql/config/my.cnf")
|
||||
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) > 2 {
|
||||
return matches[2]
|
||||
}
|
||||
}
|
||||
if io.Exists("/etc/my.cnf") {
|
||||
config, _ := io.Read("/etc/my.cnf")
|
||||
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) > 2 {
|
||||
return matches[2]
|
||||
}
|
||||
}
|
||||
|
||||
return "/tmp/mysql.sock"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
@@ -61,7 +62,8 @@ func (r *OpenRestyController) SaveConfig(ctx http.Context) http.Response {
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重载服务失败")
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载服务失败: %v", err))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -87,8 +88,39 @@ func (r *PhpMyAdminController) SetPort(ctx http.Context) http.Response {
|
||||
}
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "重载OpenResty失败")
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
}
|
||||
|
||||
func (r *PhpMyAdminController) GetConfig(ctx http.Context) http.Response {
|
||||
config, err := io.Read("/www/server/vhost/phpmyadmin.conf")
|
||||
if err != nil {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, config)
|
||||
}
|
||||
|
||||
func (r *PhpMyAdminController) SaveConfig(ctx http.Context) http.Response {
|
||||
config := ctx.Request().Input("config")
|
||||
if len(config) == 0 {
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, "配置不能为空")
|
||||
}
|
||||
|
||||
if err := io.Write("/www/server/vhost/phpmyadmin.conf", config, 0644); err != nil {
|
||||
facades.Log().Request(ctx.Request()).Tags("插件", "phpMyAdmin").With(map[string]any{
|
||||
"error": err.Error(),
|
||||
}).Info("修改 phpMyAdmin 配置失败")
|
||||
return controllers.ErrorSystem(ctx)
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return controllers.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
|
||||
}
|
||||
|
||||
return controllers.Success(ctx, nil)
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/TheTNB/panel/internal"
|
||||
"github.com/TheTNB/panel/internal/services"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
@@ -84,7 +85,7 @@ func (r *WebsiteController) Add(ctx http.Context) http.Response {
|
||||
addRequest.Path = r.setting.Get(models.SettingKeyWebsitePath) + "/" + addRequest.Name
|
||||
}
|
||||
|
||||
website := types.Website{
|
||||
website := types.WebsiteAdd{
|
||||
Name: addRequest.Name,
|
||||
Status: true,
|
||||
Domains: addRequest.Domains,
|
||||
@@ -202,7 +203,7 @@ func (r *WebsiteController) SaveDefaultConfig(ctx http.Context) http.Response {
|
||||
// @Produce json
|
||||
// @Security BearerToken
|
||||
// @Param id path int true "网站 ID"
|
||||
// @Success 200 {object} SuccessResponse{data=types.Website}
|
||||
// @Success 200 {object} SuccessResponse{data=types.WebsiteAdd}
|
||||
// @Router /panel/websites/{id}/config [get]
|
||||
func (r *WebsiteController) GetConfig(ctx http.Context) http.Response {
|
||||
var idRequest requests.ID
|
||||
@@ -549,9 +550,12 @@ server
|
||||
# waf标记位结束
|
||||
|
||||
# 错误页配置,可自行设置
|
||||
#error_page 404 /404.html;
|
||||
error_page 404 /404.html;
|
||||
#error_page 502 /502.html;
|
||||
|
||||
# acme证书签发配置,不可修改
|
||||
include /www/server/vhost/acme/%s.conf;
|
||||
|
||||
# 伪静态规则引入,修改后将导致面板设置的伪静态规则失效
|
||||
include /www/server/vhost/rewrite/%s.conf;
|
||||
|
||||
@@ -572,15 +576,19 @@ server
|
||||
error_log /www/wwwlogs/%s.log;
|
||||
}
|
||||
|
||||
`, website.Path, website.Php, website.Name, website.Name, website.Name)
|
||||
`, website.Path, website.Php, website.Name, website.Name, website.Name, website.Name)
|
||||
if err := io.Write("/www/server/vhost/"+website.Name+".conf", raw, 0644); err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := io.Write("/www/server/vhost/rewrite"+website.Name+".conf", "", 0644); err != nil {
|
||||
if err := io.Write("/www/server/vhost/rewrite/"+website.Name+".conf", "", 0644); err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := io.Write("/www/server/vhost/acme/"+website.Name+".conf", "", 0644); err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
@@ -646,7 +654,8 @@ func (r *WebsiteController) Status(ctx http.Context) http.Response {
|
||||
return ErrorSystem(ctx)
|
||||
}
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
return Error(ctx, http.StatusInternalServerError, err.Error())
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return Error(ctx, http.StatusInternalServerError, fmt.Sprintf("重载OpenResty失败: %v", err))
|
||||
}
|
||||
|
||||
return Success(ctx, nil)
|
||||
|
||||
@@ -13,6 +13,7 @@ type Kernel struct {
|
||||
// These middleware are run during every request to your application.
|
||||
func (kernel Kernel) Middleware() []http.Middleware {
|
||||
return []http.Middleware{
|
||||
middleware.Log(),
|
||||
middleware.Status(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
// Log 记录请求日志
|
||||
func Log() http.Middleware {
|
||||
return func(ctx http.Context) {
|
||||
facades.Log().Channel("http").With(map[string]any{
|
||||
"Method": ctx.Request().Method(),
|
||||
"URL": ctx.Request().FullUrl(),
|
||||
"IP": ctx.Request().Ip(),
|
||||
"UA": ctx.Request().Header("User-Agent"),
|
||||
"Body": ctx.Request().All(),
|
||||
}).Info("HTTP Request")
|
||||
ctx.Request().Next()
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,8 @@ func (r *UserStore) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"ca": "required|in:letsencrypt,zerossl,sslcom,google,buypass",
|
||||
"email": "required|email",
|
||||
"kid": "required_unless:ca,letsencrypt,buypass",
|
||||
"hmac_encoded": "required_unless:ca,letsencrypt,buypass",
|
||||
"kid": "required_if:ca,sslcom,google",
|
||||
"hmac_encoded": "required_if:ca,sslcom,google",
|
||||
"key_type": "required|in:P256,P384,2048,4096",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ func (r *UserUpdate) Rules(ctx http.Context) map[string]string {
|
||||
"id": "required|uint|min:1|exists:cert_users,id",
|
||||
"ca": "required|in:letsencrypt,zerossl,sslcom,google,buypass",
|
||||
"email": "required|email",
|
||||
"kid": "required_unless:ca,letsencrypt,buypass",
|
||||
"hmac_encoded": "required_unless:ca,letsencrypt,buypass",
|
||||
"kid": "required_if:ca,sslcom,google",
|
||||
"hmac_encoded": "required_if:ca,sslcom,google",
|
||||
"key_type": "required|in:P256,P384,2048,4096",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
type Permission struct {
|
||||
Path string `form:"path" json:"path"`
|
||||
Mode uint `form:"mode" json:"mode" filter:"uint"`
|
||||
Mode string `form:"mode" json:"mode"`
|
||||
Owner string `form:"owner" json:"owner"`
|
||||
Group string `form:"group" json:"group"`
|
||||
}
|
||||
@@ -19,7 +19,7 @@ func (r *Permission) Authorize(ctx http.Context) error {
|
||||
func (r *Permission) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"path": `regex:^/.*$|path_exists`,
|
||||
"mode": "regex:^[0-7]{3}$|uint",
|
||||
"mode": "regex:^0[0-7]{3}$",
|
||||
"owner": "regex:^[a-zA-Z0-9_-]+$",
|
||||
"group": "regex:^[a-zA-Z0-9_-]+$",
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ func (r *UnArchive) Authorize(ctx http.Context) error {
|
||||
func (r *UnArchive) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"file": `regex:^/.*$|path_exists`,
|
||||
"path": `regex:^/.*$|path_not_exists`,
|
||||
"path": `regex:^/.*$`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ type SaveConfig struct {
|
||||
ID uint `form:"id" json:"id" filter:"uint"`
|
||||
Domains []string `form:"domains" json:"domains"`
|
||||
Ports []uint `form:"ports" json:"ports"`
|
||||
TLSPorts []uint `form:"tls_ports" json:"tls_ports"`
|
||||
Hsts bool `form:"hsts" json:"hsts"`
|
||||
Ssl bool `form:"ssl" json:"ssl"`
|
||||
HttpRedirect bool `form:"http_redirect" json:"http_redirect"`
|
||||
@@ -36,6 +37,7 @@ func (r *SaveConfig) Rules(ctx http.Context) map[string]string {
|
||||
"id": "required|exists:websites,id",
|
||||
"domains": "required|slice",
|
||||
"ports": "required|slice",
|
||||
"tls_ports": "required_if:ssl,true|slice|not_in:80",
|
||||
"hsts": "bool",
|
||||
"ssl": "bool",
|
||||
"http_redirect": "bool",
|
||||
|
||||
@@ -27,20 +27,11 @@ func (receiver *ProcessTask) Handle(args ...any) error {
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
if err := facades.Orm().Query().Where("id = ?", taskID).Get(&task); err != nil {
|
||||
_ = facades.Orm().Query().Where("id = ?", taskID).Get(&task)
|
||||
if task.ID == 0 {
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("获取任务失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
task.Status = models.TaskStatusRunning
|
||||
if err := facades.Orm().Query().Save(&task); err != nil {
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("更新任务失败")
|
||||
}).Infof("任务不存在")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -50,13 +41,7 @@ func (receiver *ProcessTask) Handle(args ...any) error {
|
||||
|
||||
if _, err := shell.Execf(task.Shell); err != nil {
|
||||
task.Status = models.TaskStatusFailed
|
||||
if err := facades.Orm().Query().Save(&task); err != nil {
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("更新任务失败")
|
||||
return nil
|
||||
}
|
||||
_ = facades.Orm().Query().Save(&task)
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
@@ -65,13 +50,7 @@ func (receiver *ProcessTask) Handle(args ...any) error {
|
||||
}
|
||||
|
||||
task.Status = models.TaskStatusSuccess
|
||||
if err := facades.Orm().Query().Save(&task); err != nil {
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
"error": err.Error(),
|
||||
}).Infof("更新任务失败")
|
||||
return nil
|
||||
}
|
||||
_ = facades.Orm().Query().Save(&task)
|
||||
|
||||
facades.Log().Tags("面板", "异步任务").With(map[string]any{
|
||||
"task_id": taskID,
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goravel/framework/contracts/database/seeder"
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/database/gorm"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
"github.com/TheTNB/panel/pkg/migrate"
|
||||
)
|
||||
|
||||
type DatabaseServiceProvider struct {
|
||||
@@ -20,19 +18,5 @@ func (receiver *DatabaseServiceProvider) Register(app foundation.Application) {
|
||||
|
||||
func (receiver *DatabaseServiceProvider) Boot(app foundation.Application) {
|
||||
facades.Seeder().Register([]seeder.Seeder{})
|
||||
if err := facades.Orm().Query().(*gorm.QueryImpl).Instance().AutoMigrate(
|
||||
&models.Cert{},
|
||||
&models.CertDNS{},
|
||||
&models.CertUser{},
|
||||
&models.Cron{},
|
||||
&models.Database{},
|
||||
&models.Monitor{},
|
||||
&models.Plugin{},
|
||||
&models.Setting{},
|
||||
&models.Task{},
|
||||
&models.User{},
|
||||
&models.Website{},
|
||||
); err != nil {
|
||||
panic(fmt.Sprintf("Failed to migrate database: %v", err))
|
||||
}
|
||||
migrate.Migrate(facades.Orm().Query().(*gorm.QueryImpl).Instance())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package providers
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
contractshttp "github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/http/limit"
|
||||
|
||||
"github.com/TheTNB/panel/app/http"
|
||||
"github.com/TheTNB/panel/routes"
|
||||
@@ -25,5 +27,23 @@ func (receiver *RouteServiceProvider) Boot(app foundation.Application) {
|
||||
}
|
||||
|
||||
func (receiver *RouteServiceProvider) configureRateLimiting() {
|
||||
|
||||
facades.RateLimiter().ForWithLimits("login", func(ctx contractshttp.Context) []contractshttp.Limit {
|
||||
return []contractshttp.Limit{
|
||||
limit.PerMinute(5).By(ctx.Request().Ip()).Response(func(ctx contractshttp.Context) {
|
||||
ctx.Request().AbortWithStatusJson(contractshttp.StatusTooManyRequests, contractshttp.Json{
|
||||
"message": "请求过于频繁,请等待一分钟后再试",
|
||||
})
|
||||
}),
|
||||
limit.PerHour(100).By(ctx.Request().Ip()).Response(func(ctx contractshttp.Context) {
|
||||
ctx.Request().AbortWithStatusJson(contractshttp.StatusTooManyRequests, contractshttp.Json{
|
||||
"message": "请求过于频繁,请等待一小时后再试",
|
||||
})
|
||||
}),
|
||||
limit.PerDay(1000).Response(func(ctx contractshttp.Context) {
|
||||
ctx.Request().AbortWithStatusJson(contractshttp.StatusTooManyRequests, contractshttp.Json{
|
||||
"message": "面板遭受登录爆破攻击过多,已暂时屏蔽登录,请立刻更换面板端口",
|
||||
})
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/gookit/validate/locales/zhcn"
|
||||
"github.com/goravel/framework/foundation"
|
||||
|
||||
@@ -8,6 +10,9 @@ import (
|
||||
)
|
||||
|
||||
func Boot() {
|
||||
debug.SetGCPercent(10)
|
||||
debug.SetMemoryLimit(512 << 20)
|
||||
|
||||
zhcn.RegisterGlobal()
|
||||
|
||||
app := foundation.NewApplication()
|
||||
|
||||
+2
-2
@@ -14,10 +14,10 @@ func init() {
|
||||
// in web browsers. You are free to adjust these settings as needed.
|
||||
//
|
||||
// To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||
"paths": []string{"*"},
|
||||
"paths": []string{"1145141919810"}, // 避免框架使用CORS中间件
|
||||
"allowed_methods": []string{"*"},
|
||||
"allowed_origins": []string{"*"},
|
||||
"allowed_headers": []string{"*"},
|
||||
"allowed_headers": []string{"“"},
|
||||
"exposed_headers": []string{""},
|
||||
"max_age": 0,
|
||||
"supports_credentials": false,
|
||||
|
||||
@@ -37,6 +37,13 @@ func init() {
|
||||
"days": 7,
|
||||
"print": true,
|
||||
},
|
||||
"http": map[string]any{
|
||||
"driver": "daily",
|
||||
"path": "storage/logs/http.log",
|
||||
"level": "info",
|
||||
"days": 7,
|
||||
"print": false,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ func init() {
|
||||
config := facades.Config()
|
||||
config.Add("panel", map[string]any{
|
||||
"name": "耗子面板",
|
||||
"version": "v2.2.13",
|
||||
"version": "v2.2.17",
|
||||
"ssl": config.Env("APP_SSL", false),
|
||||
})
|
||||
}
|
||||
|
||||
+9
-3
@@ -3648,7 +3648,7 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/types.Website"
|
||||
"$ref": "#/definitions/types.WebsiteAdd"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5638,7 +5638,7 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"type": "integer"
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
@@ -5721,6 +5721,12 @@ const docTemplate = `{
|
||||
"ssl_certificate_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"tls_ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"waf": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -5915,7 +5921,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"types.Website": {
|
||||
"types.WebsiteAdd": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"db": {
|
||||
|
||||
+9
-3
@@ -3641,7 +3641,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/types.Website"
|
||||
"$ref": "#/definitions/types.WebsiteAdd"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5631,7 +5631,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"type": "integer"
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
@@ -5714,6 +5714,12 @@
|
||||
"ssl_certificate_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"tls_ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"waf": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -5908,7 +5914,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"types.Website": {
|
||||
"types.WebsiteAdd": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"db": {
|
||||
|
||||
+7
-3
@@ -462,7 +462,7 @@ definitions:
|
||||
group:
|
||||
type: string
|
||||
mode:
|
||||
type: integer
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
path:
|
||||
@@ -516,6 +516,10 @@ definitions:
|
||||
type: string
|
||||
ssl_certificate_key:
|
||||
type: string
|
||||
tls_ports:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
waf:
|
||||
type: boolean
|
||||
waf_cache:
|
||||
@@ -641,7 +645,7 @@ definitions:
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
types.Website:
|
||||
types.WebsiteAdd:
|
||||
properties:
|
||||
db:
|
||||
type: boolean
|
||||
@@ -2878,7 +2882,7 @@ paths:
|
||||
- $ref: '#/definitions/controllers.SuccessResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/types.Website'
|
||||
$ref: '#/definitions/types.WebsiteAdd'
|
||||
type: object
|
||||
security:
|
||||
- BearerToken: []
|
||||
|
||||
@@ -5,7 +5,9 @@ go 1.22
|
||||
require (
|
||||
github.com/docker/docker v26.1.4+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.2
|
||||
github.com/go-resty/resty/v2 v2.13.1
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/gookit/validate v1.5.2
|
||||
github.com/goravel/framework v1.14.1-0.20240618022250-731b8d9930a3
|
||||
github.com/goravel/gin v1.2.1
|
||||
@@ -26,6 +28,7 @@ require (
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.24.0
|
||||
golang.org/x/net v0.26.0
|
||||
gorm.io/gorm v1.25.10
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -87,7 +90,6 @@ require (
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
@@ -203,7 +205,6 @@ require (
|
||||
gorm.io/driver/mysql v1.5.7 // indirect
|
||||
gorm.io/driver/postgres v1.5.9 // indirect
|
||||
gorm.io/driver/sqlserver v1.5.3 // indirect
|
||||
gorm.io/gorm v1.25.10 // indirect
|
||||
gorm.io/plugin/dbresolver v1.5.2 // indirect
|
||||
gotest.tools/v3 v3.5.0 // indirect
|
||||
modernc.org/libc v1.37.6 // indirect
|
||||
|
||||
@@ -170,6 +170,8 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
|
||||
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-gormigrate/gormigrate/v2 v2.1.2 h1:F/d1hpHbRAvKezziV2CC5KUE82cVe9zTgHSBoOOZ4CY=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.2/go.mod h1:9nHVX6z3FCMCQPA7PThGcA55t22yKQfK/Dnsf5i7hUo=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
|
||||
@@ -4,9 +4,11 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
requests "github.com/TheTNB/panel/app/http/requests/cert"
|
||||
@@ -14,6 +16,7 @@ import (
|
||||
"github.com/TheTNB/panel/pkg/acme"
|
||||
"github.com/TheTNB/panel/pkg/cert"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
@@ -42,7 +45,11 @@ func (s *CertImpl) UserStore(request requests.UserStore) error {
|
||||
case "buypass":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CABuypass, nil, acme.KeyType(user.KeyType))
|
||||
case "zerossl":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, &acme.EAB{KeyID: user.Kid, MACKey: user.HmacEncoded}, acme.KeyType(user.KeyType))
|
||||
eab, eabErr := s.getZeroSSLEAB(user.Email)
|
||||
if eabErr != nil {
|
||||
return eabErr
|
||||
}
|
||||
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, eab, acme.KeyType(user.KeyType))
|
||||
case "sslcom":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CASSLcom, &acme.EAB{KeyID: user.Kid, MACKey: user.HmacEncoded}, acme.KeyType(user.KeyType))
|
||||
case "google":
|
||||
@@ -85,7 +92,11 @@ func (s *CertImpl) UserUpdate(request requests.UserUpdate) error {
|
||||
case "buypass":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CABuypass, nil, acme.KeyType(user.KeyType))
|
||||
case "zerossl":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, &acme.EAB{KeyID: user.Kid, MACKey: user.HmacEncoded}, acme.KeyType(user.KeyType))
|
||||
eab, eabErr := s.getZeroSSLEAB(user.Email)
|
||||
if eabErr != nil {
|
||||
return eabErr
|
||||
}
|
||||
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CAZeroSSL, eab, acme.KeyType(user.KeyType))
|
||||
case "sslcom":
|
||||
client, err = acme.NewRegisterAccount(context.Background(), user.Email, acme.CASSLcom, &acme.EAB{KeyID: user.Kid, MACKey: user.HmacEncoded}, acme.KeyType(user.KeyType))
|
||||
case "google":
|
||||
@@ -107,6 +118,31 @@ func (s *CertImpl) UserUpdate(request requests.UserUpdate) error {
|
||||
return facades.Orm().Query().Save(&user)
|
||||
}
|
||||
|
||||
// getZeroSSLEAB 获取 ZeroSSL EAB
|
||||
func (s *CertImpl) getZeroSSLEAB(email string) (*acme.EAB, error) {
|
||||
type data struct {
|
||||
Success bool `json:"success"`
|
||||
EabKid string `json:"eab_kid"`
|
||||
EabHmacKey string `json:"eab_hmac_key"`
|
||||
}
|
||||
client := resty.New()
|
||||
client.SetTimeout(5 * time.Second)
|
||||
client.SetRetryCount(2)
|
||||
|
||||
resp, err := client.R().SetFormData(map[string]string{
|
||||
"email": email,
|
||||
}).SetResult(&data{}).Post("https://api.zerossl.com/acme/eab-credentials-email")
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
return &acme.EAB{}, errors.New("获取ZeroSSL EAB失败")
|
||||
}
|
||||
eab := resp.Result().(*data)
|
||||
if !eab.Success {
|
||||
return &acme.EAB{}, errors.New("获取ZeroSSL EAB失败")
|
||||
}
|
||||
|
||||
return &acme.EAB{KeyID: eab.EabKid, MACKey: eab.EabHmacKey}, nil
|
||||
}
|
||||
|
||||
// UserShow 根据 ID 获取用户
|
||||
func (s *CertImpl) UserShow(ID uint) (models.CertUser, error) {
|
||||
var user models.CertUser
|
||||
@@ -255,7 +291,8 @@ func (s *CertImpl) ObtainAuto(ID uint) (acme.Certificate, error) {
|
||||
return acme.Certificate{}, errors.New("通配符域名无法使用 HTTP 验证")
|
||||
}
|
||||
}
|
||||
client.UseHTTP(cert.Website.Path)
|
||||
conf := fmt.Sprintf("/www/server/vhost/acme/%s.conf", cert.Website.Name)
|
||||
client.UseHTTP(conf, cert.Website.Path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +317,7 @@ func (s *CertImpl) ObtainAuto(ID uint) (acme.Certificate, error) {
|
||||
return acme.Certificate{}, err
|
||||
}
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return acme.Certificate{}, err
|
||||
}
|
||||
}
|
||||
@@ -320,6 +358,7 @@ func (s *CertImpl) ObtainManual(ID uint) (acme.Certificate, error) {
|
||||
return acme.Certificate{}, err
|
||||
}
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return acme.Certificate{}, err
|
||||
}
|
||||
}
|
||||
@@ -380,7 +419,8 @@ func (s *CertImpl) Renew(ID uint) (acme.Certificate, error) {
|
||||
return acme.Certificate{}, errors.New("通配符域名无法使用 HTTP 验证")
|
||||
}
|
||||
}
|
||||
client.UseHTTP(cert.Website.Path)
|
||||
conf := fmt.Sprintf("/www/server/vhost/acme/%s.conf", cert.Website.Name)
|
||||
client.UseHTTP(conf, cert.Website.Path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,6 +445,7 @@ func (s *CertImpl) Renew(ID uint) (acme.Certificate, error) {
|
||||
return acme.Certificate{}, err
|
||||
}
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return acme.Certificate{}, err
|
||||
}
|
||||
}
|
||||
@@ -437,6 +478,7 @@ func (s *CertImpl) Deploy(ID, WebsiteID uint) error {
|
||||
return err
|
||||
}
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,10 @@ func (r *PluginImpl) Install(slug string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.checkTaskExists(slug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "安装插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
@@ -159,6 +163,10 @@ func (r *PluginImpl) Uninstall(slug string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.checkTaskExists(slug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "卸载插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
@@ -205,6 +213,10 @@ func (r *PluginImpl) Update(slug string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.checkTaskExists(slug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var task models.Task
|
||||
task.Name = "更新插件 " + plugin.Name
|
||||
task.Status = models.TaskStatusWaiting
|
||||
@@ -217,3 +229,18 @@ func (r *PluginImpl) Update(slug string) error {
|
||||
_ = io.Remove(task.Log)
|
||||
return r.task.Process(task.ID)
|
||||
}
|
||||
|
||||
func (r *PluginImpl) checkTaskExists(slug string) error {
|
||||
var count int64
|
||||
if err := facades.Orm().Query().
|
||||
Model(&models.Task{}).
|
||||
Where("log LIKE ? AND (status = ? OR status = ?)", "%"+slug+"%", models.TaskStatusWaiting, models.TaskStatusRunning).
|
||||
Count(&count); err != nil {
|
||||
return errors.New("查询任务失败")
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("任务已添加,请勿重复添加")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/goravel/framework/database/orm"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"github.com/TheTNB/panel/app/jobs"
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
)
|
||||
|
||||
var taskMap sync.Map
|
||||
|
||||
type TaskImpl struct {
|
||||
}
|
||||
|
||||
@@ -15,6 +20,9 @@ func NewTaskImpl() *TaskImpl {
|
||||
}
|
||||
|
||||
func (r *TaskImpl) Process(taskID uint) error {
|
||||
if err := r.markAsRunning(taskID); err != nil {
|
||||
return err
|
||||
}
|
||||
return facades.Queue().Job(&jobs.ProcessTask{}, []any{taskID}).Dispatch()
|
||||
}
|
||||
|
||||
@@ -25,6 +33,9 @@ func (r *TaskImpl) DispatchWaiting() error {
|
||||
}
|
||||
|
||||
for _, task := range tasks {
|
||||
if _, ok := taskMap.Load(task.ID); ok {
|
||||
continue
|
||||
}
|
||||
if err := r.Process(task.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -32,3 +43,16 @@ func (r *TaskImpl) DispatchWaiting() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *TaskImpl) markAsRunning(taskID uint) error {
|
||||
task := models.Task{
|
||||
Model: orm.Model{ID: taskID},
|
||||
Status: models.TaskStatusRunning,
|
||||
}
|
||||
if _, err := facades.Orm().Query().Where("id", taskID).Update(&task); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
taskMap.Store(taskID, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ package services
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -44,7 +46,7 @@ func (r *WebsiteImpl) List(page, limit int) (int64, []models.Website, error) {
|
||||
}
|
||||
|
||||
// Add 添加网站
|
||||
func (r *WebsiteImpl) Add(website types.Website) (models.Website, error) {
|
||||
func (r *WebsiteImpl) Add(website types.WebsiteAdd) (models.Website, error) {
|
||||
w := models.Website{
|
||||
Name: website.Name,
|
||||
Status: website.Status,
|
||||
@@ -240,6 +242,9 @@ server
|
||||
error_page 404 /404.html;
|
||||
#error_page 502 /502.html;
|
||||
|
||||
# acme证书签发配置,不可修改
|
||||
include /www/server/vhost/acme/%s.conf;
|
||||
|
||||
# 伪静态规则引入,修改后将导致面板设置的伪静态规则失效
|
||||
include /www/server/vhost/rewrite/%s.conf;
|
||||
|
||||
@@ -259,7 +264,7 @@ server
|
||||
access_log /www/wwwlogs/%s.log;
|
||||
error_log /www/wwwlogs/%s.log;
|
||||
}
|
||||
`, portList, domainList, website.Path, website.Php, website.Name, website.Name, website.Name)
|
||||
`, portList, domainList, website.Path, website.Php, website.Name, website.Name, website.Name, website.Name)
|
||||
|
||||
if err := io.Write("/www/server/vhost/"+website.Name+".conf", nginxConf, 0644); err != nil {
|
||||
return models.Website{}, err
|
||||
@@ -267,6 +272,9 @@ server
|
||||
if err := io.Write("/www/server/vhost/rewrite/"+website.Name+".conf", "", 0644); err != nil {
|
||||
return models.Website{}, err
|
||||
}
|
||||
if err := io.Write("/www/server/vhost/acme/"+website.Name+".conf", "", 0644); err != nil {
|
||||
return models.Website{}, err
|
||||
}
|
||||
if err := io.Write("/www/server/vhost/ssl/"+website.Name+".pem", "", 0644); err != nil {
|
||||
return models.Website{}, err
|
||||
}
|
||||
@@ -282,6 +290,7 @@ server
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return models.Website{}, err
|
||||
}
|
||||
|
||||
@@ -326,6 +335,7 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
|
||||
return err
|
||||
}
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -356,34 +366,30 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
|
||||
raw = strings.Replace(raw, domainConfigOld, "\n "+domain+"\n ", -1)
|
||||
|
||||
// 端口
|
||||
var port strings.Builder
|
||||
var portConf strings.Builder
|
||||
ports := config.Ports
|
||||
for i, v := range ports {
|
||||
vStr := cast.ToString(v)
|
||||
if v == 443 && config.Ssl {
|
||||
vStr = ` listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
listen 443 quic;
|
||||
listen [::]:443 quic;`
|
||||
port.WriteString(vStr)
|
||||
if i != len(ports)-1 {
|
||||
port.WriteString("\n")
|
||||
}
|
||||
continue
|
||||
for _, port := range ports {
|
||||
https := ""
|
||||
quic := false
|
||||
if slices.Contains(config.TLSPorts, port) {
|
||||
https = " ssl"
|
||||
quic = true
|
||||
}
|
||||
if i != len(ports)-1 {
|
||||
port.WriteString(" listen " + vStr + ";\n")
|
||||
port.WriteString(" listen [::]:" + vStr + ";\n")
|
||||
} else {
|
||||
port.WriteString(" listen " + vStr + ";\n")
|
||||
port.WriteString(" listen [::]:" + vStr + ";")
|
||||
|
||||
portConf.WriteString(fmt.Sprintf(" listen %d%s;\n", port, https))
|
||||
portConf.WriteString(fmt.Sprintf(" listen [::]:%d%s;\n", port, https))
|
||||
if quic {
|
||||
portConf.WriteString(fmt.Sprintf(" listen %d%s;\n", port, " quic"))
|
||||
portConf.WriteString(fmt.Sprintf(" listen [::]:%d%s;\n", port, " quic"))
|
||||
}
|
||||
}
|
||||
portConfigOld := str.Cut(raw, "# port标记位开始", "# port标记位结束")
|
||||
if len(strings.TrimSpace(portConfigOld)) == 0 {
|
||||
portConf.WriteString(" ")
|
||||
portConfNew := portConf.String()
|
||||
portConfOld := str.Cut(raw, "# port标记位开始", "# port标记位结束")
|
||||
if len(strings.TrimSpace(portConfOld)) == 0 {
|
||||
return errors.New("配置文件中缺少port标记位")
|
||||
}
|
||||
raw = strings.Replace(raw, portConfigOld, "\n"+port.String()+"\n ", -1)
|
||||
raw = strings.Replace(raw, portConfOld, "\n"+portConfNew, -1)
|
||||
|
||||
// 运行目录
|
||||
root := str.Cut(raw, "# root标记位开始", "# root标记位结束")
|
||||
@@ -525,7 +531,12 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return systemctl.Reload("openresty")
|
||||
err = systemctl.Reload("openresty")
|
||||
if err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete 删除网站
|
||||
@@ -543,23 +554,19 @@ func (r *WebsiteImpl) Delete(id uint) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := io.Remove("/www/server/vhost/" + website.Name + ".conf"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := io.Remove("/www/server/vhost/rewrite/" + website.Name + ".conf"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := io.Remove("/www/server/vhost/ssl/" + website.Name + ".pem"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := io.Remove("/www/server/vhost/ssl/" + website.Name + ".key"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := io.Remove(website.Path); err != nil {
|
||||
return err
|
||||
_ = io.Remove("/www/server/vhost/" + website.Name + ".conf")
|
||||
_ = io.Remove("/www/server/vhost/rewrite/" + website.Name + ".conf")
|
||||
_ = io.Remove("/www/server/vhost/acme/" + website.Name + ".conf")
|
||||
_ = io.Remove("/www/server/vhost/ssl/" + website.Name + ".pem")
|
||||
_ = io.Remove("/www/server/vhost/ssl/" + website.Name + ".key")
|
||||
_ = io.Remove(website.Path)
|
||||
|
||||
err := systemctl.Reload("openresty")
|
||||
if err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
}
|
||||
|
||||
return systemctl.Reload("openresty")
|
||||
return err
|
||||
}
|
||||
|
||||
// GetConfig 获取网站配置
|
||||
@@ -582,7 +589,7 @@ func (r *WebsiteImpl) GetConfig(id uint) (types.WebsiteSetting, error) {
|
||||
setting.Raw = config
|
||||
|
||||
ports := str.Cut(config, "# port标记位开始", "# port标记位结束")
|
||||
matches := regexp.MustCompile(`listen\s+(.*);`).FindAllStringSubmatch(ports, -1)
|
||||
matches := regexp.MustCompile(`listen\s+([^;]*);?`).FindAllStringSubmatch(ports, -1)
|
||||
for _, match := range matches {
|
||||
if len(match) < 2 {
|
||||
continue
|
||||
@@ -598,26 +605,27 @@ func (r *WebsiteImpl) GetConfig(id uint) (types.WebsiteSetting, error) {
|
||||
setting.Ports = append(setting.Ports, cast.ToUint(ports[0]))
|
||||
} else if len(ports) > 1 && ports[1] == "ssl" {
|
||||
setting.Ports = append(setting.Ports, cast.ToUint(ports[0]))
|
||||
setting.TLSPorts = append(setting.TLSPorts, cast.ToUint(ports[0]))
|
||||
}
|
||||
}
|
||||
serverName := str.Cut(config, "# server_name标记位开始", "# server_name标记位结束")
|
||||
match := regexp.MustCompile(`server_name\s+(.*);`).FindStringSubmatch(serverName)
|
||||
match := regexp.MustCompile(`server_name\s+([^;]*);?`).FindStringSubmatch(serverName)
|
||||
if len(match) > 1 {
|
||||
setting.Domains = strings.Split(match[1], " ")
|
||||
}
|
||||
root := str.Cut(config, "# root标记位开始", "# root标记位结束")
|
||||
match = regexp.MustCompile(`root\s+(.*);`).FindStringSubmatch(root)
|
||||
match = regexp.MustCompile(`root\s+([^;]*);?`).FindStringSubmatch(root)
|
||||
if len(match) > 1 {
|
||||
setting.Root = match[1]
|
||||
}
|
||||
index := str.Cut(config, "# index标记位开始", "# index标记位结束")
|
||||
match = regexp.MustCompile(`index\s+(.*);`).FindStringSubmatch(index)
|
||||
match = regexp.MustCompile(`index\s+([^;]*);?`).FindStringSubmatch(index)
|
||||
if len(match) > 1 {
|
||||
setting.Index = match[1]
|
||||
}
|
||||
|
||||
if io.Exists(setting.Root + "/.user.ini") {
|
||||
userIni, _ := io.Read(setting.Root + "/.user.ini")
|
||||
if io.Exists(filepath.Join(setting.Root, ".user.ini")) {
|
||||
userIni, _ := io.Read(filepath.Join(setting.Root, ".user.ini"))
|
||||
if strings.Contains(userIni, "open_basedir") {
|
||||
setting.OpenBasedir = true
|
||||
} else {
|
||||
@@ -635,29 +643,31 @@ func (r *WebsiteImpl) GetConfig(id uint) (types.WebsiteSetting, error) {
|
||||
ssl := str.Cut(config, "# ssl标记位开始", "# ssl标记位结束")
|
||||
setting.HttpRedirect = strings.Contains(ssl, "# http重定向标记位")
|
||||
setting.Hsts = strings.Contains(ssl, "# hsts标记位")
|
||||
if decode, err := cert.ParseCert(crt); err == nil {
|
||||
setting.SslNotBefore = decode.NotBefore.Format("2006-01-02 15:04:05")
|
||||
setting.SslNotAfter = decode.NotAfter.Format("2006-01-02 15:04:05")
|
||||
setting.SslIssuer = decode.Issuer.CommonName
|
||||
setting.SslOCSPServer = decode.OCSPServer
|
||||
setting.SSlDNSNames = decode.DNSNames
|
||||
}
|
||||
} else {
|
||||
setting.HttpRedirect = false
|
||||
setting.Hsts = false
|
||||
}
|
||||
|
||||
// 解析证书信息
|
||||
if decode, err := cert.ParseCert(crt); err == nil {
|
||||
setting.SslNotBefore = decode.NotBefore.Format("2006-01-02 15:04:05")
|
||||
setting.SslNotAfter = decode.NotAfter.Format("2006-01-02 15:04:05")
|
||||
setting.SslIssuer = decode.Issuer.CommonName
|
||||
setting.SslOCSPServer = decode.OCSPServer
|
||||
setting.SSlDNSNames = decode.DNSNames
|
||||
}
|
||||
|
||||
waf := str.Cut(config, "# waf标记位开始", "# waf标记位结束")
|
||||
setting.Waf = strings.Contains(waf, "waf on;")
|
||||
match = regexp.MustCompile(`waf_mode\s+(.+);`).FindStringSubmatch(waf)
|
||||
match = regexp.MustCompile(`waf_mode\s+([^;]*);?`).FindStringSubmatch(waf)
|
||||
if len(match) > 1 {
|
||||
setting.WafMode = match[1]
|
||||
}
|
||||
match = regexp.MustCompile(`waf_cc_deny\s+(.+);`).FindStringSubmatch(waf)
|
||||
match = regexp.MustCompile(`waf_cc_deny\s+([^;]*);?`).FindStringSubmatch(waf)
|
||||
if len(match) > 1 {
|
||||
setting.WafCcDeny = match[1]
|
||||
}
|
||||
match = regexp.MustCompile(`waf_cache\s+(.+);`).FindStringSubmatch(waf)
|
||||
match = regexp.MustCompile(`waf_cache\s+([^;]*);?`).FindStringSubmatch(waf)
|
||||
if len(match) > 1 {
|
||||
setting.WafCache = match[1]
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
type Website interface {
|
||||
List(page int, limit int) (int64, []models.Website, error)
|
||||
Add(website types.Website) (models.Website, error)
|
||||
Add(website types.WebsiteAdd) (models.Website, error)
|
||||
SaveConfig(config requests.SaveConfig) error
|
||||
Delete(id uint) error
|
||||
GetConfig(id uint) (types.WebsiteSetting, error)
|
||||
|
||||
@@ -194,9 +194,6 @@
|
||||
"incompatible": "plugin :slug is incompatible with :exclude plugin"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"mistake": "mistake"
|
||||
},
|
||||
"status": {
|
||||
"upgrade": "Panel is currently undergoing an upgrade. Please try again later.",
|
||||
"maintain": "Panel is currently undergoing maintenance. Please try again later.",
|
||||
|
||||
@@ -194,9 +194,6 @@
|
||||
"incompatible": "插件 :slug 不兼容 :exclude 插件"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"mistake": "错误"
|
||||
},
|
||||
"status": {
|
||||
"upgrade": "面板升级中,请稍后",
|
||||
"maintain": "面板正在运行维护,请稍后",
|
||||
|
||||
+4
-1
@@ -49,9 +49,12 @@ func (c *Client) UseManualDns(total int, check ...bool) {
|
||||
}
|
||||
|
||||
// UseHTTP 使用 HTTP 验证
|
||||
func (c *Client) UseHTTP(path string) {
|
||||
// conf openresty 配置文件路径
|
||||
// path 验证文件存放路径
|
||||
func (c *Client) UseHTTP(conf, path string) {
|
||||
c.zClient.ChallengeSolvers = map[string]acmez.Solver{
|
||||
acme.ChallengeTypeHTTP01: httpSolver{
|
||||
conf: conf,
|
||||
path: path,
|
||||
},
|
||||
}
|
||||
|
||||
+32
-23
@@ -13,9 +13,13 @@ import (
|
||||
"github.com/libdns/libdns"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type httpSolver struct {
|
||||
conf string
|
||||
path string
|
||||
}
|
||||
|
||||
@@ -26,12 +30,25 @@ func (s httpSolver) Present(_ context.Context, challenge acme.Challenge) error {
|
||||
}
|
||||
|
||||
challengeFilePath := filepath.Join(s.path, challenge.HTTP01ResourcePath())
|
||||
if err = os.MkdirAll(filepath.Dir(challengeFilePath), 0o755); err != nil {
|
||||
return fmt.Errorf("无法在网站目录创建 HTTP 挑战所需的目录: %w", err)
|
||||
if err = os.MkdirAll(filepath.Dir(challengeFilePath), 0755); err != nil {
|
||||
return fmt.Errorf("无法在网站目录创建HTTP挑战所需的目录: %w", err)
|
||||
}
|
||||
|
||||
if err = os.WriteFile(challengeFilePath, []byte(challenge.KeyAuthorization), 0o644); err != nil {
|
||||
return fmt.Errorf("无法在网站目录创建 HTTP 挑战所需的文件: %w", err)
|
||||
if err = os.WriteFile(challengeFilePath, []byte(challenge.KeyAuthorization), 0644); err != nil {
|
||||
return fmt.Errorf("无法在网站目录创建HTTP挑战所需的文件: %w", err)
|
||||
}
|
||||
|
||||
conf := fmt.Sprintf(`location = /.well-known/acme-challenge/%s {
|
||||
default_type text/plain;
|
||||
return 200 %q;
|
||||
}
|
||||
`, challenge.Token, challenge.KeyAuthorization)
|
||||
if err = os.WriteFile(s.conf, []byte(conf), 0644); err != nil {
|
||||
return fmt.Errorf("无法写入OpenResty配置文件: %w", err)
|
||||
}
|
||||
if err = systemctl.Reload("openresty"); err != nil {
|
||||
_, err = shell.Execf("openresty -t")
|
||||
return fmt.Errorf("无法重载OpenResty: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -43,10 +60,9 @@ func (s httpSolver) CleanUp(_ context.Context, challenge acme.Challenge) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.Remove(filepath.Join(s.path, challenge.HTTP01ResourcePath())); err != nil {
|
||||
return fmt.Errorf("无法删除 HTTP 挑战文件: %w", err)
|
||||
}
|
||||
|
||||
_ = os.Remove(filepath.Join(s.path, challenge.HTTP01ResourcePath()))
|
||||
_ = os.WriteFile(s.conf, []byte{}, 0644)
|
||||
_ = systemctl.Reload("openresty")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -61,11 +77,11 @@ func (s dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error
|
||||
keyAuth := challenge.DNS01KeyAuthorization()
|
||||
provider, err := s.getDNSProvider()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 DNS 提供商失败: %w", err)
|
||||
return fmt.Errorf("获取DNS提供商失败: %w", err)
|
||||
}
|
||||
zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err)
|
||||
return fmt.Errorf("获取域名%q的顶级域失败: %w", dnsName, err)
|
||||
}
|
||||
|
||||
rec := libdns.Record{
|
||||
@@ -76,10 +92,10 @@ func (s dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error
|
||||
|
||||
results, err := provider.AppendRecords(ctx, zone+".", []libdns.Record{rec})
|
||||
if err != nil {
|
||||
return fmt.Errorf("域名 %q 添加临时记录 %q 失败: %w", zone, dnsName, err)
|
||||
return fmt.Errorf("域名%q添加临时记录%q失败: %w", zone, dnsName, err)
|
||||
}
|
||||
if len(results) != 1 {
|
||||
return fmt.Errorf("预期添加 1 条记录,但实际添加了 %d 条记录", len(results))
|
||||
return fmt.Errorf("预期添加1条记录,但实际添加了%d条记录", len(results))
|
||||
}
|
||||
|
||||
s.records = &results
|
||||
@@ -90,21 +106,14 @@ func (s dnsSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error
|
||||
dnsName := challenge.DNS01TXTRecordName()
|
||||
provider, err := s.getDNSProvider()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 DNS 提供商失败: %w", err)
|
||||
}
|
||||
zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取域名 %q 的顶级域失败: %w", dnsName, err)
|
||||
return fmt.Errorf("获取DNS提供商失败: %w", err)
|
||||
}
|
||||
zone, _ := publicsuffix.EffectiveTLDPlusOne(dnsName)
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
_, err = provider.DeleteRecords(ctx, zone+".", *s.records)
|
||||
if err != nil {
|
||||
return fmt.Errorf("域名 %q 删除临时记录 %q 失败: %w", zone, dnsName, err)
|
||||
}
|
||||
|
||||
_, _ = provider.DeleteRecords(ctx, zone+".", *s.records)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -126,7 +135,7 @@ func (s dnsSolver) getDNSProvider() (DNSProvider, error) {
|
||||
APIToken: s.param.APIkey,
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("未知的 DNS 提供商 %q", s.dns)
|
||||
return nil, fmt.Errorf("未知的DNS提供商 %q", s.dns)
|
||||
}
|
||||
|
||||
return dns, nil
|
||||
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type MySQL struct {
|
||||
db *sql.DB
|
||||
username string
|
||||
password string
|
||||
address string
|
||||
}
|
||||
|
||||
func NewMySQL(username, password, address string, typ ...string) (*MySQL, error) {
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s)/", username, password, address)
|
||||
if len(typ) > 0 && typ[0] == "unix" {
|
||||
dsn = fmt.Sprintf("%s:%s@unix(%s)/", username, password, address)
|
||||
}
|
||||
db, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("初始化MySQL连接失败: %w", err)
|
||||
}
|
||||
if db.Ping() != nil {
|
||||
return nil, fmt.Errorf("连接MySQL失败: %w", err)
|
||||
}
|
||||
return &MySQL{
|
||||
db: db,
|
||||
username: username,
|
||||
password: password,
|
||||
address: address,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MySQL) Close() error {
|
||||
return m.db.Close()
|
||||
}
|
||||
|
||||
func (m *MySQL) Ping() error {
|
||||
return m.db.Ping()
|
||||
}
|
||||
|
||||
func (m *MySQL) Query(query string, args ...any) (*sql.Rows, error) {
|
||||
return m.db.Query(query, args...)
|
||||
}
|
||||
|
||||
func (m *MySQL) QueryRow(query string, args ...any) *sql.Row {
|
||||
return m.db.QueryRow(query, args...)
|
||||
}
|
||||
|
||||
func (m *MySQL) Exec(query string, args ...any) (sql.Result, error) {
|
||||
return m.db.Exec(query, args...)
|
||||
}
|
||||
|
||||
func (m *MySQL) Prepare(query string) (*sql.Stmt, error) {
|
||||
return m.db.Prepare(query)
|
||||
}
|
||||
|
||||
func (m *MySQL) DatabaseCreate(name string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", name))
|
||||
m.flushPrivileges()
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *MySQL) DatabaseDrop(name string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", name))
|
||||
m.flushPrivileges()
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *MySQL) UserCreate(user, password string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("CREATE USER IF NOT EXISTS '%s'@'localhost' IDENTIFIED BY '%s'", user, password))
|
||||
m.flushPrivileges()
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *MySQL) UserDrop(user string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("DROP USER IF EXISTS '%s'@'localhost'", user))
|
||||
m.flushPrivileges()
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *MySQL) UserPassword(user, password string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("ALTER USER '%s'@'localhost' IDENTIFIED BY '%s'", user, password))
|
||||
m.flushPrivileges()
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *MySQL) PrivilegesGrant(user, database string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("GRANT ALL PRIVILEGES ON %s.* TO '%s'@'localhost'", database, user))
|
||||
m.flushPrivileges()
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *MySQL) PrivilegesRevoke(user, database string) error {
|
||||
_, err := m.Exec(fmt.Sprintf("REVOKE ALL PRIVILEGES ON %s.* FROM '%s'@'localhost'", database, user))
|
||||
m.flushPrivileges()
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *MySQL) Users() ([]types.MySQLUser, error) {
|
||||
rows, err := m.Query("SELECT user, host FROM mysql.user")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []types.MySQLUser
|
||||
for rows.Next() {
|
||||
var user, host string
|
||||
if err := rows.Scan(&user, &host); err != nil {
|
||||
continue
|
||||
}
|
||||
grants, err := m.userGrants(user, host)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
users = append(users, types.MySQLUser{
|
||||
User: user,
|
||||
Host: host,
|
||||
Grants: grants,
|
||||
})
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (m *MySQL) Databases() ([]types.MySQLDatabase, error) {
|
||||
rows, err := m.Query("SHOW DATABASES")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var databases []types.MySQLDatabase
|
||||
for rows.Next() {
|
||||
var database string
|
||||
if err := rows.Scan(&database); err != nil {
|
||||
continue
|
||||
}
|
||||
databases = append(databases, types.MySQLDatabase{
|
||||
Name: database,
|
||||
})
|
||||
}
|
||||
return databases, nil
|
||||
}
|
||||
|
||||
func (m *MySQL) userGrants(user, host string) ([]string, error) {
|
||||
rows, err := m.Query(fmt.Sprintf("SHOW GRANTS FOR '%s'@'%s'", user, host))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var grants []string
|
||||
for rows.Next() {
|
||||
var grant string
|
||||
if err := rows.Scan(&grant); err != nil {
|
||||
continue
|
||||
}
|
||||
grants = append(grants, grant)
|
||||
}
|
||||
return grants, nil
|
||||
}
|
||||
|
||||
func (m *MySQL) flushPrivileges() {
|
||||
_, _ = m.Exec("FLUSH PRIVILEGES")
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
// MySQLResetRootPassword 重置 MySQL root密码
|
||||
func MySQLResetRootPassword(password string) error {
|
||||
_ = systemctl.Stop("mysqld")
|
||||
if run, err := systemctl.Status("mysqld"); err != nil || run {
|
||||
return fmt.Errorf("停止MySQL失败: %w", err)
|
||||
}
|
||||
_, _ = shell.Execf(`systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking"`)
|
||||
if err := systemctl.Start("mysqld"); err != nil {
|
||||
return fmt.Errorf("以安全模式启动MySQL失败: %w", err)
|
||||
}
|
||||
if _, err := shell.Execf(`mysql -uroot -e "FLUSH PRIVILEGES;UPDATE mysql.user SET authentication_string=null WHERE user='root' AND host='localhost';ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';FLUSH PRIVILEGES;"`, password); err != nil {
|
||||
return errors.New("设置root密码失败")
|
||||
}
|
||||
if err := systemctl.Stop("mysqld"); err != nil {
|
||||
return fmt.Errorf("停止MySQL失败: %w", err)
|
||||
}
|
||||
_, _ = shell.Execf(`systemctl unset-environment MYSQLD_OPTS`)
|
||||
if err := systemctl.Start("mysqld"); err != nil {
|
||||
return fmt.Errorf("启动MySQL失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Migrate(db *gorm.DB) {
|
||||
options := &gormigrate.Options{
|
||||
TableName: "new_migrations",
|
||||
IDColumnName: "id",
|
||||
IDColumnSize: 255,
|
||||
}
|
||||
migrator := gormigrate.New(db, options, []*gormigrate.Migration{
|
||||
Init,
|
||||
})
|
||||
if err := migrator.Migrate(); err != nil {
|
||||
panic(fmt.Sprintf("Failed to migrate database: %v", err))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/TheTNB/panel/app/models"
|
||||
)
|
||||
|
||||
var Init = &gormigrate.Migration{
|
||||
ID: "20240624-init",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(
|
||||
&models.Cert{},
|
||||
&models.CertDNS{},
|
||||
&models.CertUser{},
|
||||
&models.Cron{},
|
||||
&models.Database{},
|
||||
&models.Monitor{},
|
||||
&models.Plugin{},
|
||||
&models.Setting{},
|
||||
&models.Task{},
|
||||
&models.User{},
|
||||
&models.Website{},
|
||||
)
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return tx.Migrator().DropTable(
|
||||
&models.Cert{},
|
||||
&models.CertDNS{},
|
||||
&models.CertUser{},
|
||||
&models.Cron{},
|
||||
&models.Database{},
|
||||
&models.Monitor{},
|
||||
&models.Plugin{},
|
||||
&models.Setting{},
|
||||
&models.Task{},
|
||||
&models.User{},
|
||||
&models.Website{},
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package types
|
||||
|
||||
type MySQLUser struct {
|
||||
User string `json:"user"`
|
||||
Host string `json:"host"`
|
||||
Grants []string `json:"grants"`
|
||||
}
|
||||
|
||||
type MySQLDatabase struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
@@ -137,7 +137,7 @@ var PluginPHPMyAdmin = Plugin{
|
||||
Description: "phpMyAdmin 是一个以 PHP 为基础,以 Web-Base 方式架构在网站主机上的 MySQL 数据库管理工具",
|
||||
Slug: "phpmyadmin",
|
||||
Version: "5.2.1",
|
||||
Requires: []string{},
|
||||
Requires: []string{"openresty"},
|
||||
Excludes: []string{},
|
||||
Install: `bash /www/panel/scripts/phpmyadmin/install.sh`,
|
||||
Uninstall: `bash /www/panel/scripts/phpmyadmin/uninstall.sh`,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package types
|
||||
|
||||
type Website struct {
|
||||
type WebsiteAdd struct {
|
||||
Name string `json:"name"`
|
||||
Status bool `json:"status"`
|
||||
Domains []string `json:"domains"`
|
||||
@@ -21,6 +21,7 @@ type WebsiteSetting struct {
|
||||
Name string `json:"name"`
|
||||
Domains []string `json:"domains"`
|
||||
Ports []uint `json:"ports"`
|
||||
TLSPorts []uint `json:"tls_ports"`
|
||||
Root string `json:"root"`
|
||||
Path string `json:"path"`
|
||||
Index string `json:"index"`
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ package routes
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/route"
|
||||
"github.com/goravel/framework/facades"
|
||||
frameworkmiddleware "github.com/goravel/framework/http/middleware"
|
||||
|
||||
"github.com/TheTNB/panel/app/http/controllers"
|
||||
"github.com/TheTNB/panel/app/http/middleware"
|
||||
@@ -25,7 +26,7 @@ func Api() {
|
||||
})
|
||||
r.Prefix("user").Group(func(r route.Router) {
|
||||
userController := controllers.NewUserController()
|
||||
r.Post("login", userController.Login)
|
||||
r.Middleware(frameworkmiddleware.Throttle("login")).Post("login", userController.Login)
|
||||
r.Middleware(middleware.Jwt()).Get("info", userController.Info)
|
||||
})
|
||||
r.Prefix("task").Middleware(middleware.Jwt()).Group(func(r route.Router) {
|
||||
|
||||
@@ -85,6 +85,8 @@ func Plugin() {
|
||||
phpMyAdminController := plugins.NewPhpMyAdminController()
|
||||
route.Get("info", phpMyAdminController.Info)
|
||||
route.Post("port", phpMyAdminController.SetPort)
|
||||
route.Get("config", phpMyAdminController.GetConfig)
|
||||
route.Post("config", phpMyAdminController.SaveConfig)
|
||||
})
|
||||
r.Prefix("pureftpd").Group(func(route route.Router) {
|
||||
pureFtpdController := plugins.NewPureFtpdController()
|
||||
|
||||
@@ -204,12 +204,15 @@ Auto_Swap() {
|
||||
}
|
||||
|
||||
Init_Panel() {
|
||||
systemctl stop panel
|
||||
systemctl disable panel
|
||||
rm -f /etc/systemd/system/panel.service
|
||||
rm -rf ${setup_Path}/panel
|
||||
mkdir ${setup_Path}/server
|
||||
mkdir ${setup_Path}/server/cron
|
||||
mkdir ${setup_Path}/server/cron/logs
|
||||
chmod -R 755 ${setup_Path}/server
|
||||
mkdir ${setup_Path}/panel
|
||||
rm -rf ${setup_Path}/panel/*
|
||||
# 下载面板zip包并解压
|
||||
if [ "${ARCH}" == "x86_64" ]; then
|
||||
if ${inChina}; then
|
||||
|
||||
@@ -287,8 +287,8 @@ chmod 644 ${mysqlPath}/conf/my.cnf
|
||||
|
||||
${mysqlPath}/bin/mysqld --initialize-insecure --user=mysql --basedir=${mysqlPath} --datadir=${mysqlPath}/data
|
||||
|
||||
echo "export PATH=${mysqlPath}/bin:\$PATH" >> /etc/profile.d/mysql.sh
|
||||
source /etc/profile
|
||||
# 软链接
|
||||
ln -sf ${mysqlPath}/bin/* /usr/bin/
|
||||
|
||||
# 写入 systemd 配置
|
||||
cat > /etc/systemd/system/mysqld.service << EOF
|
||||
|
||||
@@ -34,9 +34,6 @@ rm -f /usr/lib64/libmysql*
|
||||
userdel -r mysql
|
||||
groupdel mysql
|
||||
|
||||
rm -f /etc/profile.d/mysql.sh
|
||||
source /etc/profile
|
||||
|
||||
panel deletePlugin mysql${1}
|
||||
|
||||
echo -e "${HR}\nMySQL-${1} 卸载完成\n${HR}"
|
||||
|
||||
@@ -120,6 +120,9 @@ chown -R mysql:mysql ${mysqlPath}
|
||||
chmod -R 755 ${mysqlPath}
|
||||
chmod 644 ${mysqlPath}/conf/my.cnf
|
||||
|
||||
# 软链接
|
||||
ln -sf ${mysqlPath}/bin/* /usr/bin/
|
||||
|
||||
# 启动服务
|
||||
systemctl start mysqld
|
||||
|
||||
|
||||
@@ -282,6 +282,7 @@ mkdir -p /www/server/vhost
|
||||
mkdir -p /www/server/vhost
|
||||
mkdir -p /www/server/vhost/rewrite
|
||||
mkdir -p /www/server/vhost/ssl
|
||||
mkdir -p /www/server/vhost/acme
|
||||
|
||||
# 写入主配置文件
|
||||
cat > ${openrestyPath}/conf/nginx.conf << EOF
|
||||
@@ -561,7 +562,7 @@ cat > ${openrestyPath}/html/block.html << EOF
|
||||
<div class="container">
|
||||
<h1>耗子面板</h1>
|
||||
<p>本次请求判断为危险的攻击请求,已被拦截!</p>
|
||||
<p>当您看到此页面,说明您的请求被WAF拦截,可能是由于您的请求中包含了危险的攻击内容,或者您的请求被误判为攻击请求。</p>
|
||||
<p>可能您的请求中包含了危险的攻击内容,或者您的请求被误判为攻击请求。</p>
|
||||
<p>如果您认为这是误判,请联系服务器管理员解决。</p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -28,6 +28,12 @@ systemctl disable php-fpm-${phpVersion}
|
||||
rm -rf /lib/systemd/system/php-fpm-${phpVersion}.service
|
||||
systemctl daemon-reload
|
||||
|
||||
# 检查是否存在phpMyAdmin
|
||||
if [ -d "${setupPath}/server/phpmyadmin" ]; then
|
||||
sed -i "s/enable-php-${phpVersion}/enable-php-0/g" ${setupPath}/server/vhost/phpmyadmin.conf
|
||||
systemctl reload openresty
|
||||
fi
|
||||
|
||||
rm -rf ${phpPath}
|
||||
rm -f /usr/bin/php-${phpVersion}
|
||||
|
||||
|
||||
@@ -124,6 +124,25 @@ if version_lt "$oldVersion" "2.2.10"; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if version_lt "$oldVersion" "2.2.14"; then
|
||||
echo "更新面板到 v2.2.14 ..."
|
||||
echo "Update panel to v2.2.14 ..."
|
||||
if [ -f "/www/server/openresty/bin/openresty" ]; then
|
||||
mkdir -p /www/server/vhost/acme
|
||||
chmod -R 644 /www/server/vhost/acme
|
||||
fi
|
||||
fi
|
||||
|
||||
if version_lt "$oldVersion" "2.2.16"; then
|
||||
echo "更新面板到 v2.2.16 ..."
|
||||
echo "Update panel to v2.2.16 ..."
|
||||
if [ -f "/www/server/mysql/bin/mysql" ]; then
|
||||
ln -sf /www/server/mysql/bin/* /usr/bin/
|
||||
rm -f /etc/profile.d/mysql.sh
|
||||
source /etc/profile
|
||||
fi
|
||||
fi
|
||||
|
||||
echo $HR
|
||||
echo "更新结束"
|
||||
echo "Update finished"
|
||||
|
||||
Reference in New Issue
Block a user