mirror of
https://github.com/tnb-labs/panel.git
synced 2026-10-13 08:59:07 +08:00
fix: 修复一些已知问题
This commit is contained in:
+4
-2
@@ -71,12 +71,13 @@ func initAce() (*app.Ace, func() error, error) {
|
||||
|
||||
wireValue18_0 := caddy.NewApp(wireValue1_0)
|
||||
|
||||
wireValue4_0, wireErr4 := bootstrap.NewDB(wireValue0_0)
|
||||
wireValue4_0, wireResourceCleanup4, wireErr4 := bootstrap.NewDB(wireValue0_0)
|
||||
if wireErr4 != nil {
|
||||
wireCommitted = true
|
||||
cleanupErr := wireCleanup.close()
|
||||
return wireZero0, nil, errors.Join(wireErr4, cleanupErr)
|
||||
}
|
||||
wireCleanup.arm(wireResourceCleanup4)
|
||||
|
||||
wireValue106_0 := data.NewDatabaseServerRepo(wireValue4_0)
|
||||
|
||||
@@ -177,12 +178,13 @@ func initAce() (*app.Ace, func() error, error) {
|
||||
|
||||
wireValue123_0 := data.NewUserTokenRepo(wireValue0_0, wireValue4_0, wireValue1_0)
|
||||
|
||||
wireValue9_0, wireErr9 := middleware.NewMiddlewares(wireValue0_0, wireValue1_0, wireValue6_0, wireValue90_0, wireValue123_0)
|
||||
wireValue9_0, wireResourceCleanup9, wireErr9 := middleware.NewMiddlewares(wireValue0_0, wireValue1_0, wireValue6_0, wireValue90_0, wireValue123_0)
|
||||
if wireErr9 != nil {
|
||||
wireCommitted = true
|
||||
cleanupErr := wireCleanup.close()
|
||||
return wireZero0, nil, errors.Join(wireErr9, cleanupErr)
|
||||
}
|
||||
wireCleanup.arm(wireResourceCleanup9)
|
||||
|
||||
wireValue8_0 := bootstrap.NewValidator(wireValue0_0, wireValue4_0, wireValue1_0)
|
||||
|
||||
|
||||
+2
-1
@@ -30,12 +30,13 @@ func initCli() (*app.Cli, func() error, error) {
|
||||
|
||||
wireValue1_0 := bootstrap.NewT(wireValue0_0)
|
||||
|
||||
wireValue4_0, wireErr4 := bootstrap.NewDB(wireValue0_0)
|
||||
wireValue4_0, wireResourceCleanup4, wireErr4 := bootstrap.NewDB(wireValue0_0)
|
||||
if wireErr4 != nil {
|
||||
wireCommitted = true
|
||||
cleanupErr := wireCleanup.close()
|
||||
return wireZero0, nil, errors.Join(wireErr4, cleanupErr)
|
||||
}
|
||||
wireCleanup.arm(wireResourceCleanup4)
|
||||
|
||||
wireValue2_0, wireResourceCleanup2, wireErr2 := bootstrap.NewLogger(wireValue0_0)
|
||||
if wireErr2 != nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -16,7 +17,8 @@ import (
|
||||
"github.com/acepanel/panel/v3/pkg/config"
|
||||
)
|
||||
|
||||
func NewDB(conf *config.Config) (*gorm.DB, error) {
|
||||
// NewDB 打开面板数据库,返回的 cleanup 在进程退出时关闭连接
|
||||
func NewDB(conf *config.Config) (*gorm.DB, func() error, error) {
|
||||
// db 日志写入轮转文件
|
||||
w, err := logrotate.New(filepath.Join(app.Root, "panel/storage/logs/db.log"),
|
||||
logrotate.WithMaxSize(10*logrotate.MB),
|
||||
@@ -26,7 +28,7 @@ func NewDB(conf *config.Config) (*gorm.DB, error) {
|
||||
logrotate.WithLocation(time.Local),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
handler := slog.New(slog.NewJSONHandler(w, nil)).Handler()
|
||||
@@ -42,17 +44,17 @@ func NewDB(conf *config.Config) (*gorm.DB, error) {
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
sqlDB.SetMaxIdleConns(1)
|
||||
|
||||
return db, nil
|
||||
return db, func() error { return errors.Join(sqlDB.Close(), w.Close()) }, nil
|
||||
}
|
||||
|
||||
func NewMigrate(db *gorm.DB) *gormigrate.Gormigrate {
|
||||
|
||||
+10
-8
@@ -1670,10 +1670,12 @@ func (r *backupRepo) FixPanel(ctx context.Context) error {
|
||||
return errors.New(r.t.Get("Files are normal and do not need to be repaired, please run acepanel update to update the panel"))
|
||||
}
|
||||
|
||||
// 有异常,先停止面板
|
||||
// 有异常,先同步停掉面板再动文件,避免运行中的面板往新目录写东西
|
||||
// 停掉面板后的修复被取消会让面板起不来,后续全程不可取消
|
||||
ctx = context.WithoutCancel(ctx)
|
||||
tools.StopPanel(ctx)
|
||||
if err := systemctl.Stop(ctx, "acepanel"); err != nil {
|
||||
return errors.New(r.t.Get("Failed to stop panel: %v", err))
|
||||
}
|
||||
|
||||
// 删除损坏的辅助数据库(会自动重建)
|
||||
for _, name := range brokenAuxDBs {
|
||||
@@ -1686,12 +1688,14 @@ func (r *backupRepo) FixPanel(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// 仅辅助数据库异常,重启即可恢复
|
||||
// 仅辅助数据库异常,重新启动即可恢复
|
||||
if !panelBroken {
|
||||
if err := systemctl.Start(ctx, "acepanel"); err != nil {
|
||||
return errors.New(r.t.Get("Failed to start panel: %v", err))
|
||||
}
|
||||
if app.IsCli {
|
||||
fmt.Println(r.t.Get("|-Fix completed"))
|
||||
}
|
||||
tools.RestartPanel(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1727,6 +1731,7 @@ func (r *backupRepo) FixPanel(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// restorePanel 用指定的面板备份覆盖当前面板,完成后重启面板
|
||||
// 面板内恢复备份与 CLI 修复共用,重启必须走 RestartPanel 的延迟排程,面板内调用才不会自杀在半路
|
||||
func (r *backupRepo) restorePanel(ctx context.Context, backup string) error {
|
||||
// 覆盖的是运行中的面板二进制与 systemd 单元,中途取消会留下起不来的面板
|
||||
ctx = context.WithoutCancel(ctx)
|
||||
@@ -1898,10 +1903,7 @@ func (r *backupRepo) UpdatePanel(ctx context.Context, version, url, checksum str
|
||||
r.log.Info("panel updated", slog.String("version", version))
|
||||
progress(r.t.Get("Update completed"))
|
||||
|
||||
// 由调用方重启面板
|
||||
if sqlDB, err := r.db.DB(); err == nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
// 由调用方重启面板,数据库连接在进程退出时统一关闭
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ type Middlewares struct {
|
||||
userToken biz.UserTokenRepo
|
||||
}
|
||||
|
||||
func NewMiddlewares(conf *config.Config, t *gotext.Locale, session *sessions.Manager, appRepo biz.AppRepo, userTokenRepo biz.UserTokenRepo) (*Middlewares, error) {
|
||||
func NewMiddlewares(conf *config.Config, t *gotext.Locale, session *sessions.Manager, appRepo biz.AppRepo, userTokenRepo biz.UserTokenRepo) (*Middlewares, func() error, error) {
|
||||
// http 访问日志写入轮转文件
|
||||
w, err := logrotate.New(filepath.Join(app.Root, "panel/storage/logs/http.log"),
|
||||
logrotate.WithMaxSize(10*logrotate.MB),
|
||||
@@ -38,7 +38,7 @@ func NewMiddlewares(conf *config.Config, t *gotext.Locale, session *sessions.Man
|
||||
logrotate.WithLocation(time.Local),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &Middlewares{
|
||||
@@ -48,7 +48,7 @@ func NewMiddlewares(conf *config.Config, t *gotext.Locale, session *sessions.Man
|
||||
session: session,
|
||||
appRepo: appRepo,
|
||||
userToken: userTokenRepo,
|
||||
}, nil
|
||||
}, w.Close, nil
|
||||
}
|
||||
|
||||
// Globals 全局中间件集合,应用到每个请求;whitelist 为登录白名单路径。
|
||||
|
||||
@@ -44,7 +44,10 @@ func (s *FirewallService) GetStatus(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, running)
|
||||
Success(w, chix.M{
|
||||
"running": running,
|
||||
"name": s.firewall.Name(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *FirewallService) UpdateStatus(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/acepanel/panel/v3/internal/request"
|
||||
"github.com/acepanel/panel/v3/pkg/firewall"
|
||||
"github.com/acepanel/panel/v3/pkg/io"
|
||||
"github.com/acepanel/panel/v3/pkg/os"
|
||||
"github.com/acepanel/panel/v3/pkg/shell"
|
||||
@@ -17,8 +18,9 @@ import (
|
||||
)
|
||||
|
||||
type ToolboxSSHService struct {
|
||||
t *gotext.Locale
|
||||
service string
|
||||
t *gotext.Locale
|
||||
service string
|
||||
firewall firewall.Firewall
|
||||
}
|
||||
|
||||
func NewToolboxSSHService(t *gotext.Locale) *ToolboxSSHService {
|
||||
@@ -28,8 +30,9 @@ func NewToolboxSSHService(t *gotext.Locale) *ToolboxSSHService {
|
||||
service = "ssh"
|
||||
}
|
||||
return &ToolboxSSHService{
|
||||
t: t,
|
||||
service: service,
|
||||
t: t,
|
||||
service: service,
|
||||
firewall: firewall.NewFirewall(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +90,26 @@ func (s *ToolboxSSHService) UpdatePort(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// 先放行再改端口
|
||||
running, err := s.firewall.Status(r.Context())
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if running {
|
||||
if err = s.firewall.Port(r.Context(), firewall.FireInfo{
|
||||
Type: firewall.TypeNormal,
|
||||
PortStart: req.Port,
|
||||
PortEnd: req.Port,
|
||||
Protocol: firewall.ProtocolTCP,
|
||||
Strategy: firewall.StrategyAccept,
|
||||
Direction: firewall.DirectionIn,
|
||||
}, firewall.OperationAdd); err != nil {
|
||||
Error(w, http.StatusInternalServerError, s.t.Get("failed to update firewall rule: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.updateSSHConfig("Port", cast.ToString(req.Port)); err != nil {
|
||||
Error(w, http.StatusInternalServerError, s.t.Get("failed to update SSH port: %v", err))
|
||||
return
|
||||
|
||||
@@ -13,8 +13,15 @@ import (
|
||||
)
|
||||
|
||||
// Firewall 防火墙统一接口
|
||||
// 变更方法由 NewFirewall 返回的实现统一加锁并断开取消链,各后端实现内无需再处理
|
||||
type Firewall interface {
|
||||
backend
|
||||
// Name 当前使用的防火墙软件名,未检测到支持的防火墙时为空
|
||||
Name() string
|
||||
}
|
||||
|
||||
// backend 各防火墙软件需实现的操作
|
||||
// 变更方法由 NewFirewall 返回的实现统一加锁并断开取消链,各后端实现内无需再处理
|
||||
type backend interface {
|
||||
// Status 获取防火墙运行状态
|
||||
Status(ctx context.Context) (bool, error)
|
||||
// Enable 启用防火墙
|
||||
@@ -45,18 +52,24 @@ func NewFirewall() Firewall {
|
||||
return lockedFirewall{}
|
||||
}
|
||||
|
||||
// detected 探测到的防火墙后端及其软件名
|
||||
type detected struct {
|
||||
backend
|
||||
name string
|
||||
}
|
||||
|
||||
// detectFirewall 防火墙类型是开机后不再变化的环境事实,探测一次后全局复用
|
||||
// 探测用不可取消 ctx:跟随调用方 ctx 时,取消会让两次探测都失败而退回 firewalld 默认值,并把这个错误结论缓存到进程结束
|
||||
var detectFirewall = sync.OnceValue(func() Firewall {
|
||||
var detectFirewall = sync.OnceValue(func() detected {
|
||||
ctx := context.Background()
|
||||
if _, err := shell.Execf(ctx, "firewall-cmd --version"); err == nil {
|
||||
return newFirewalld()
|
||||
return detected{newFirewalld(), "firewalld"}
|
||||
}
|
||||
if _, err := shell.Execf(ctx, "ufw version"); err == nil {
|
||||
return newUFW()
|
||||
return detected{newUFW(), "ufw"}
|
||||
}
|
||||
// 默认 firewalld
|
||||
return newFirewalld()
|
||||
// 都没装时按 firewalld 处理,名字留空供上层提示未安装
|
||||
return detected{newFirewalld(), ""}
|
||||
})
|
||||
|
||||
// mu 串行化所有防火墙变更操作
|
||||
@@ -68,6 +81,10 @@ var mu sync.Mutex
|
||||
// firewalld 下规则进了永久配置却没进 runtime,ufw 下面板读的规则文件已改而内核里的规则还在跑
|
||||
type lockedFirewall struct{}
|
||||
|
||||
func (lockedFirewall) Name() string {
|
||||
return detectFirewall().name
|
||||
}
|
||||
|
||||
func (lockedFirewall) Status(ctx context.Context) (bool, error) {
|
||||
return detectFirewall().Status(ctx)
|
||||
}
|
||||
|
||||
+4
-9
@@ -186,22 +186,17 @@ func CollectTopProcesses() types.TopProcesses {
|
||||
return result
|
||||
}
|
||||
|
||||
// 以下三个自杀式操作靠 sleep 1 熬过 HTTP 响应,ExecfAsync 入口已断开取消链,
|
||||
// 调用方直接传请求 ctx 即可,无需再包 WithoutCancel
|
||||
|
||||
// StopPanel 停止面板
|
||||
func StopPanel(ctx context.Context) {
|
||||
_ = shell.ExecfAsync(ctx, "sleep 1 && systemctl stop acepanel")
|
||||
}
|
||||
// 以下两个自杀式操作交给 systemd 的瞬态定时器延迟 1 秒执行,既熬过 HTTP 响应,
|
||||
// 又脱离面板自身的 cgroup,不会在停服务时被连带杀掉而误报命令失败
|
||||
|
||||
// RestartPanel 重启面板
|
||||
func RestartPanel(ctx context.Context) {
|
||||
_ = shell.ExecfAsync(ctx, "sleep 1 && systemctl restart acepanel")
|
||||
_ = shell.ExecfAsync(ctx, "systemd-run --quiet --collect --on-active=1 systemctl restart acepanel")
|
||||
}
|
||||
|
||||
// RestartServer 重启服务器
|
||||
func RestartServer(ctx context.Context) {
|
||||
_ = shell.ExecfAsync(ctx, "sleep 1 && reboot")
|
||||
_ = shell.ExecfAsync(ctx, "systemd-run --quiet --collect --on-active=1 systemctl reboot")
|
||||
}
|
||||
|
||||
// IsChina 是否中国大陆
|
||||
|
||||
@@ -20,12 +20,14 @@ const currentTab = ref('rule')
|
||||
// n-tabs 对 v-if 动态增删子 tab 不会重算指示条位置,用 key 强制重挂
|
||||
const ruleTabs = ['rule', 'ip-rule', 'forward']
|
||||
const firewallRunning = ref(false)
|
||||
const firewallName = ref('')
|
||||
const ready = ref(false)
|
||||
|
||||
const { send: refreshStatus } = useRequest(firewall.status)
|
||||
.onSuccess(({ data }) => {
|
||||
firewallRunning.value = data
|
||||
if (!data && ruleTabs.includes(currentTab.value)) currentTab.value = 'scan'
|
||||
firewallRunning.value = data.running
|
||||
firewallName.value = data.name
|
||||
if (!data.running && ruleTabs.includes(currentTab.value)) currentTab.value = 'scan'
|
||||
})
|
||||
.onComplete(() => {
|
||||
ready.value = true
|
||||
@@ -52,14 +54,20 @@ onUnmounted(() => {
|
||||
<n-tab name="setting" :tab="$gettext('Settings')" />
|
||||
</n-tabs>
|
||||
</template>
|
||||
<template v-if="ready">
|
||||
<n-flex v-if="ready" vertical :size="20">
|
||||
<n-alert v-if="firewallName" type="info" :bordered="false">
|
||||
{{ $gettext('Current firewall: %{ name }', { name: firewallName }) }}
|
||||
</n-alert>
|
||||
<n-alert v-else type="warning" :bordered="false">
|
||||
{{ $gettext('No supported firewall detected, please install firewalld or ufw') }}
|
||||
</n-alert>
|
||||
<rule-view v-if="currentTab === 'rule'" />
|
||||
<ip-rule-view v-if="currentTab === 'ip-rule'" />
|
||||
<forward-view v-if="currentTab === 'forward'" />
|
||||
<scan-view v-if="currentTab === 'scan'" />
|
||||
<tamper-view v-if="currentTab === 'tamper'" />
|
||||
<setting-view v-if="currentTab === 'setting'" />
|
||||
</template>
|
||||
</n-flex>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ const model = ref({
|
||||
})
|
||||
|
||||
useRequest(firewall.status).onSuccess(({ data }) => {
|
||||
model.value.firewallStatus = data
|
||||
model.value.firewallStatus = data.running
|
||||
})
|
||||
useRequest(safe.pingStatus).onSuccess(({ data }) => {
|
||||
model.value.pingStatus = data
|
||||
|
||||
Reference in New Issue
Block a user