From 53fdcfb715afae05a3ab6ac56a9fca697c0051d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sat, 12 Apr 2025 17:35:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/apps/toolbox/app.go | 3 +- internal/data/app.go | 33 +++--- internal/data/backup.go | 187 +++++++++++++++----------------- internal/data/website.go | 14 ++- internal/service/file.go | 10 +- pkg/acme/acme.go | 2 +- pkg/acme/ssl.go | 1 - pkg/cert/cert.go | 8 +- pkg/db/mysql_tools.go | 11 +- pkg/db/postgres.go | 10 +- pkg/embed/website/404.html | 2 +- pkg/embed/website/404_zh.html | 55 ++++++++++ pkg/embed/website/index.html | 10 +- pkg/embed/website/index_zh.html | 58 ++++++++++ pkg/io/file.go | 10 -- pkg/nginx/data.go | 28 +++++ pkg/ntp/ntp.go | 4 +- 17 files changed, 286 insertions(+), 160 deletions(-) delete mode 100644 pkg/acme/ssl.go create mode 100644 pkg/embed/website/404_zh.html create mode 100644 pkg/embed/website/index_zh.html diff --git a/internal/apps/toolbox/app.go b/internal/apps/toolbox/app.go index 796b228a..a77a54eb 100644 --- a/internal/apps/toolbox/app.go +++ b/internal/apps/toolbox/app.go @@ -2,6 +2,7 @@ package toolbox import ( "net/http" + "os" "path/filepath" "regexp" "strings" @@ -88,7 +89,7 @@ func (s *App) GetSWAP(w http.ResponseWriter, r *http.Request) { var total, used, free string var size int64 if io.Exists(filepath.Join(app.Root, "swap")) { - file, err := io.FileInfo(filepath.Join(app.Root, "swap")) + file, err := os.Stat(filepath.Join(app.Root, "swap")) if err != nil { service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get SWAP: %v", err)) return diff --git a/internal/data/app.go b/internal/data/app.go index 2f101937..893b7b36 100644 --- a/internal/data/app.go +++ b/internal/data/app.go @@ -9,6 +9,7 @@ import ( "github.com/expr-lang/expr" "github.com/go-rat/utils/collect" "github.com/hashicorp/go-version" + "github.com/leonelquinteros/gotext" "github.com/spf13/cast" "gorm.io/gorm" @@ -19,13 +20,15 @@ import ( ) type appRepo struct { + t *gotext.Locale db *gorm.DB cache biz.CacheRepo task biz.TaskRepo } -func NewAppRepo(db *gorm.DB, cache biz.CacheRepo, task biz.TaskRepo) biz.AppRepo { +func NewAppRepo(t *gotext.Locale, db *gorm.DB, cache biz.CacheRepo, task biz.TaskRepo) biz.AppRepo { return &appRepo{ + t: t, db: db, cache: cache, task: task, @@ -50,7 +53,7 @@ func (r *appRepo) Get(slug string) (*api.App, error) { return item, nil } } - return nil, errors.New("应用不存在") + return nil, errors.New(r.t.Get("app %s not found", slug)) } func (r *appRepo) UpdateExist(slug string) bool { @@ -153,7 +156,7 @@ func (r *appRepo) Install(channel, slug string) error { } if installed, _ := r.IsInstalled(slug); installed { - return errors.New("应用已安装") + return errors.New(r.t.Get("app %s already installed", slug)) } shellUrl, shellChannel, shellVersion := "", "", "" @@ -164,7 +167,7 @@ func (r *appRepo) Install(channel, slug string) error { } if ch.Slug == channel { if vs.GreaterThan(panel) { - return fmt.Errorf("应用 %s 需要面板版本 %s,当前版本 %s", item.Name, ch.Panel, app.Version) + return errors.New(r.t.Get("app %s requires panel version %s, current version %s", item.Name, ch.Panel, app.Version)) } shellUrl = ch.Install shellChannel = ch.Slug @@ -173,7 +176,7 @@ func (r *appRepo) Install(channel, slug string) error { } } if shellUrl == "" { - return fmt.Errorf("应用 %s 不支持当前面板版本", item.Name) + return errors.New(r.t.Get("app %s not support current panel version", item.Name)) } if err = r.preCheck(item); err != nil { @@ -185,7 +188,7 @@ func (r *appRepo) Install(channel, slug string) error { } task := new(biz.Task) - task.Name = "安装应用 " + item.Name + task.Name = r.t.Get("Install app %s", item.Name) task.Status = biz.TaskStatusWaiting task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug) task.Log = "/tmp/" + item.Slug + ".log" @@ -204,7 +207,7 @@ func (r *appRepo) UnInstall(slug string) error { } if installed, _ := r.IsInstalled(slug); !installed { - return errors.New("应用未安装") + return errors.New(r.t.Get("app %s not installed", item.Name)) } installed, err := r.GetInstalled(slug) if err != nil { @@ -219,7 +222,7 @@ func (r *appRepo) UnInstall(slug string) error { } if ch.Slug == installed.Channel { if vs.GreaterThan(panel) { - return fmt.Errorf("应用 %s 需要面板版本 %s,当前版本 %s", item.Name, ch.Panel, app.Version) + return errors.New(r.t.Get("app %s requires panel version %s, current version %s", item.Name, ch.Panel, app.Version)) } shellUrl = ch.Uninstall shellChannel = ch.Slug @@ -228,7 +231,7 @@ func (r *appRepo) UnInstall(slug string) error { } } if shellUrl == "" { - return fmt.Errorf("无法获取应用 %s 的卸载脚本", item.Name) + return errors.New(r.t.Get("failed to get uninstall script for app %s", item.Name)) } if err = r.preCheck(item); err != nil { @@ -240,7 +243,7 @@ func (r *appRepo) UnInstall(slug string) error { } task := new(biz.Task) - task.Name = "卸载应用 " + item.Name + task.Name = r.t.Get("Uninstall app %s", item.Name) task.Status = biz.TaskStatusWaiting task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug) task.Log = "/tmp/" + item.Slug + ".log" @@ -259,7 +262,7 @@ func (r *appRepo) Update(slug string) error { } if installed, _ := r.IsInstalled(slug); !installed { - return errors.New("应用未安装") + return errors.New(r.t.Get("app %s not installed", item.Name)) } installed, err := r.GetInstalled(slug) if err != nil { @@ -274,7 +277,7 @@ func (r *appRepo) Update(slug string) error { } if ch.Slug == installed.Channel { if vs.GreaterThan(panel) { - return fmt.Errorf("应用 %s 需要面板版本 %s,当前版本 %s", item.Name, ch.Panel, app.Version) + return errors.New(r.t.Get("app %s requires panel version %s, current version %s", item.Name, ch.Panel, app.Version)) } shellUrl = ch.Update shellChannel = ch.Slug @@ -283,7 +286,7 @@ func (r *appRepo) Update(slug string) error { } } if shellUrl == "" { - return fmt.Errorf("应用 %s 不支持当前面板版本", item.Name) + return errors.New(r.t.Get("app %s not support current panel version", item.Name)) } if err = r.preCheck(item); err != nil { @@ -295,7 +298,7 @@ func (r *appRepo) Update(slug string) error { } task := new(biz.Task) - task.Name = "更新应用 " + item.Name + task.Name = r.t.Get("Update app %s", item.Name) task.Status = biz.TaskStatusWaiting task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug) task.Log = "/tmp/" + item.Slug + ".log" @@ -341,7 +344,7 @@ func (r *appRepo) preCheck(app *api.App) error { result := cast.ToString(output) if result != "ok" { - return fmt.Errorf("应用 %s %s", app.Name, result) + return errors.New(r.t.Get("App %s %s", app.Name, result)) } return nil diff --git a/internal/data/backup.go b/internal/data/backup.go index 046498d5..042d1ed4 100644 --- a/internal/data/backup.go +++ b/internal/data/backup.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/leonelquinteros/gotext" "github.com/shirou/gopsutil/disk" "gorm.io/gorm" @@ -22,13 +23,15 @@ import ( ) type backupRepo struct { + t *gotext.Locale db *gorm.DB setting biz.SettingRepo website biz.WebsiteRepo } -func NewBackupRepo(db *gorm.DB, setting biz.SettingRepo, website biz.WebsiteRepo) biz.BackupRepo { +func NewBackupRepo(t *gotext.Locale, db *gorm.DB, setting biz.SettingRepo, website biz.WebsiteRepo) biz.BackupRepo { return &backupRepo{ + t: t, db: db, setting: setting, website: website, @@ -89,7 +92,7 @@ func (r *backupRepo) Create(typ biz.BackupType, target string, path ...string) e } - return errors.New("未知备份类型") + return errors.New(r.t.Get("unknown backup type")) } // Delete 删除备份 @@ -125,7 +128,7 @@ func (r *backupRepo) Restore(typ biz.BackupType, backup, target string) error { return r.restorePostgres(backup, target) } - return errors.New("未知备份类型") + return errors.New(r.t.Get("unknown backup type")) } // CutoffLog 切割日志 @@ -133,7 +136,7 @@ func (r *backupRepo) Restore(typ biz.BackupType, backup, target string) error { // target 待切割日志文件绝对路径 func (r *backupRepo) CutoffLog(path, target string) error { if !io.Exists(target) { - return errors.New("日志文件不存在") + return errors.New(r.t.Get("log file %s not exists", target)) } to := filepath.Join(path, fmt.Sprintf("%s_%s.zip", time.Now().Format("20060102150405"), filepath.Base(target))) @@ -184,14 +187,10 @@ func (r *backupRepo) ClearExpired(path, prefix string, save int) error { for _, file := range toDelete { filePath := filepath.Join(path, file.Name()) if app.IsCli { - fmt.Printf("|-清理过期文件:%s\n", filePath) + fmt.Println(r.t.Get("|-Cleaning expired file: %s", filePath)) } if err = os.Remove(filePath); err != nil { - if app.IsCli { - fmt.Printf("|-清理失败:%v\n", err) - } else { - return fmt.Errorf("清理失败:%v", err) - } + return errors.New(r.t.Get("Cleanup failed: %v", err)) } } @@ -205,7 +204,7 @@ func (r *backupRepo) GetPath(typ biz.BackupType) (string, error) { return "", err } if !slices.Contains([]biz.BackupType{biz.BackupTypePath, biz.BackupTypeWebsite, biz.BackupTypeMySQL, biz.BackupTypePostgres, biz.BackupTypeRedis, biz.BackupTypePanel}, typ) { - return "", errors.New("未知备份类型") + return "", errors.New(r.t.Get("unknown backup type")) } backupPath = filepath.Join(backupPath, string(typ)) @@ -236,8 +235,8 @@ func (r *backupRepo) createWebsite(to string, name string) error { } if app.IsCli { - fmt.Printf("|-备份耗时:%s\n", time.Since(start).String()) - fmt.Printf("|-已备份至文件:%s\n", filepath.Base(backup)) + fmt.Println(r.t.Get("|-Backup time: %s", time.Since(start).String())) + fmt.Println(r.t.Get("|-Backed up to file: %s", filepath.Base(backup))) } return nil } @@ -256,7 +255,7 @@ func (r *backupRepo) createMySQL(to string, name string) error { _ = mysql.Close() }(mysql) if exist, _ := mysql.DatabaseExists(name); !exist { - return fmt.Errorf("数据库不存在:%s", name) + return errors.New(r.t.Get("database does not exist: %s", name)) } size, err := mysql.DatabaseSize(name) if err != nil { @@ -286,8 +285,8 @@ func (r *backupRepo) createMySQL(to string, name string) error { } if app.IsCli { - fmt.Printf("|-备份耗时:%s\n", time.Since(start).String()) - fmt.Printf("|-已备份至文件:%s\n", filepath.Base(backup+".zip")) + fmt.Println(r.t.Get("|-Backup time: %s", time.Since(start).String())) + fmt.Println(r.t.Get("|-Backed up to file: %s", filepath.Base(backup+".zip"))) } return nil } @@ -302,7 +301,7 @@ func (r *backupRepo) createPostgres(to string, name string) error { _ = postgres.Close() }(postgres) if exist, _ := postgres.DatabaseExist(name); !exist { - return fmt.Errorf("数据库不存在:%s", name) + return errors.New(r.t.Get("database does not exist: %s", name)) } size, err := postgres.DatabaseSize(name) if err != nil { @@ -326,8 +325,8 @@ func (r *backupRepo) createPostgres(to string, name string) error { } if app.IsCli { - fmt.Printf("|-备份耗时:%s\n", time.Since(start).String()) - fmt.Printf("|-已备份至文件:%s\n", filepath.Base(backup+".zip")) + fmt.Println(r.t.Get("|-Backup time: %s", time.Since(start).String())) + fmt.Println(r.t.Get("|-Backed up to file: %s", filepath.Base(backup+".zip"))) } return nil } @@ -366,8 +365,8 @@ func (r *backupRepo) createPanel(to string) error { } if app.IsCli { - fmt.Printf("|-备份耗时:%s\n", time.Since(start).String()) - fmt.Printf("|-已备份至文件:%s\n", filepath.Base(backup)) + fmt.Println(r.t.Get("|-Backup time: %s", time.Since(start).String())) + fmt.Println(r.t.Get("|-Backed up to file: %s", filepath.Base(backup))) } return io.Remove(temp) @@ -376,7 +375,7 @@ func (r *backupRepo) createPanel(to string) error { // restoreWebsite 恢复网站备份 func (r *backupRepo) restoreWebsite(backup, target string) error { if !io.Exists(backup) { - return errors.New("备份文件不存在") + return errors.New(r.t.Get("backup file %s not exists", backup)) } website, err := r.website.GetByName(target) @@ -403,7 +402,7 @@ func (r *backupRepo) restoreWebsite(backup, target string) error { // restoreMySQL 恢复 MySQL 备份 func (r *backupRepo) restoreMySQL(backup, target string) error { if !io.Exists(backup) { - return errors.New("备份文件不存在") + return errors.New(r.t.Get("backup file %s not exists", backup)) } rootPassword, err := r.setting.Get(biz.SettingKeyMySQLRootPassword) @@ -418,7 +417,7 @@ func (r *backupRepo) restoreMySQL(backup, target string) error { _ = mysql.Close() }(mysql) if exist, _ := mysql.DatabaseExists(target); !exist { - return fmt.Errorf("数据库不存在:%s", target) + return errors.New(r.t.Get("database does not exist: %s", target)) } if err = os.Setenv("MYSQL_PWD", rootPassword); err != nil { return err @@ -449,7 +448,7 @@ func (r *backupRepo) restoreMySQL(backup, target string) error { // restorePostgres 恢复 PostgreSQL 备份 func (r *backupRepo) restorePostgres(backup, target string) error { if !io.Exists(backup) { - return errors.New("备份文件不存在") + return errors.New(r.t.Get("backup file %s not exists", backup)) } postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432) @@ -460,7 +459,7 @@ func (r *backupRepo) restorePostgres(backup, target string) error { _ = postgres.Close() }(postgres) if exist, _ := postgres.DatabaseExist(target); !exist { - return fmt.Errorf("数据库不存在:%s", target) + return errors.New(r.t.Get("database does not exist: %s", target)) } clean := false @@ -501,18 +500,18 @@ func (r *backupRepo) preCheckPath(to, path string) error { } if app.IsCli { - fmt.Printf("|-目标大小:%s\n", tools.FormatBytes(float64(size))) - fmt.Printf("|-目标文件数:%d\n", files) - fmt.Printf("|-备份目录可用空间:%s\n", tools.FormatBytes(float64(usage.Free))) - fmt.Printf("|-备份目录可用Inode:%d\n", usage.InodesFree) + fmt.Println(r.t.Get("|-Target size: %s", tools.FormatBytes(float64(size)))) + fmt.Println(r.t.Get("|-Target file count: %d", files)) + fmt.Println(r.t.Get("|-Backup directory available space: %s", tools.FormatBytes(float64(usage.Free)))) + fmt.Println(r.t.Get("|-Backup directory available Inode: %d", usage.InodesFree)) } if uint64(size) > usage.Free { - return errors.New("备份目录空间不足") + return errors.New(r.t.Get("Insufficient backup directory space")) } // 对于 fuse 等文件系统,可能没有 inode 的概念 /*if uint64(files) > usage.InodesFree { - return errors.New("备份目录Inode不足") + return errors.New(r.t.Get("Insufficient backup directory inode")) }*/ return nil @@ -528,13 +527,13 @@ func (r *backupRepo) preCheckDB(to string, size int64) error { } if app.IsCli { - fmt.Printf("|-目标大小:%s\n", tools.FormatBytes(float64(size))) - fmt.Printf("|-备份目录可用空间:%s\n", tools.FormatBytes(float64(usage.Free))) - fmt.Printf("|-备份目录可用Inode:%d\n", usage.InodesFree) + fmt.Println(r.t.Get("|-Target size: %s", tools.FormatBytes(float64(size)))) + fmt.Println(r.t.Get("|-Backup directory available space: %s", tools.FormatBytes(float64(usage.Free)))) + fmt.Println(r.t.Get("|-Backup directory available Inode: %d", usage.InodesFree)) } if uint64(size) > usage.Free { - return errors.New("备份目录空间不足") + return errors.New(r.t.Get("Insufficient backup directory space")) } return nil @@ -554,7 +553,7 @@ func (r *backupRepo) autoUnCompressSQL(backup string) (string, error) { backup = "" // 置空,防止干扰后续判断 if files, err := os.ReadDir(temp); err == nil { if len(files) != 1 { - return "", fmt.Errorf("压缩文件中包含的文件数量不为1,实际为%d", len(files)) + return "", errors.New(r.t.Get("The number of files contained in the compressed file is not 1, actual %d", len(files))) } if strings.HasSuffix(files[0].Name(), ".sql") { backup = filepath.Join(temp, files[0].Name()) @@ -562,7 +561,7 @@ func (r *backupRepo) autoUnCompressSQL(backup string) (string, error) { } if backup == "" { - return "", errors.New("无法找到.sql备份文件") + return "", errors.New(r.t.Get("could not find .sql backup file")) } return backup, nil @@ -570,7 +569,7 @@ func (r *backupRepo) autoUnCompressSQL(backup string) (string, error) { func (r *backupRepo) FixPanel() error { if app.IsCli { - fmt.Println("|-开始修复面板...") + fmt.Println(r.t.Get("|-Start fixing the panel...")) } // 检查关键文件是否正常 @@ -586,7 +585,7 @@ func (r *backupRepo) FixPanel() error { flag = true } if !flag { - return fmt.Errorf("文件正常无需修复,请运行 panel-cli update 更新面板") + return errors.New(r.t.Get("Files are normal and do not need to be repaired, please run panel-cli update to update the panel")) } // 再次确认是否需要修复 @@ -596,10 +595,10 @@ func (r *backupRepo) FixPanel() error { io.Exists(filepath.Join(app.Root, "panel", "storage", "app.db")) && io.Exists("/usr/local/etc/panel/config.yml") { if err := io.Remove("/tmp/panel-storage.zip"); err != nil { - return fmt.Errorf("清理临时文件失败:%w", err) + return errors.New(r.t.Get("failed to clean temporary files: %w", err)) } if app.IsCli { - fmt.Println("|-已清理临时文件,请运行 panel-cli update 更新面板") + fmt.Println(r.t.Get("|-Cleaned up temporary files, please run panel-cli update to update the panel")) } return nil } @@ -614,57 +613,57 @@ func (r *backupRepo) FixPanel() error { return int(b.Time.Unix() - a.Time.Unix()) }) if len(list) == 0 { - return fmt.Errorf("未找到备份文件,无法自动修复") + return errors.New(r.t.Get("No backup file found, unable to automatically repair")) } latest := list[0] if app.IsCli { - fmt.Printf("|-使用备份文件:%s\n", latest.Name) + fmt.Println(r.t.Get("|-Backup file used: %s", latest.Name)) } // 解压备份文件 if app.IsCli { - fmt.Println("|-解压备份文件...") + fmt.Println(r.t.Get("|-Unzip backup file...")) } if err = io.Remove("/tmp/panel-fix"); err != nil { - return fmt.Errorf("清理临时目录失败:%w", err) + return errors.New(r.t.Get("Cleaning temporary directory failed: %w", err)) } if err = io.UnCompress(latest.Path, "/tmp/panel-fix"); err != nil { - return fmt.Errorf("解压备份文件失败:%w", err) + return errors.New(r.t.Get("Unzip backup file failed: %w", err)) } // 移动文件到对应位置 if app.IsCli { - fmt.Println("|-移动备份文件...") + fmt.Println(r.t.Get("|-Move backup file...")) } if io.Exists(filepath.Join("/tmp/panel-fix", "panel")) && io.IsDir(filepath.Join("/tmp/panel-fix", "panel")) { if err = io.Remove(filepath.Join(app.Root, "panel")); err != nil { - return fmt.Errorf("删除目录失败:%w", err) + return errors.New(r.t.Get("Remove panel file failed: %w", err)) } if err = io.Mv(filepath.Join("/tmp/panel-fix", "panel"), filepath.Join(app.Root)); err != nil { - return fmt.Errorf("移动目录失败:%w", err) + return errors.New(r.t.Get("Move panel file failed: %w", err)) } } if io.Exists(filepath.Join("/tmp/panel-fix", "config.yml")) { if err = io.Mv(filepath.Join("/tmp/panel-fix", "config.yml"), "/usr/local/etc/panel/config.yml"); err != nil { - return fmt.Errorf("移动文件失败:%w", err) + return errors.New(r.t.Get("Move panel config failed: %w", err)) } } if io.Exists(filepath.Join("/tmp/panel-fix", "panel-cli")) { if err = io.Mv(filepath.Join("/tmp/panel-fix", "panel-cli"), "/usr/local/sbin/panel-cli"); err != nil { - return fmt.Errorf("移动文件失败:%w", err) + return errors.New(r.t.Get("Move panel-cli file failed: %w", err)) } } // tmp 目录下如果有 storage 备份,则解压回去 if app.IsCli { - fmt.Println("|-恢复面板数据...") + fmt.Println(r.t.Get("|-Restore panel data...")) } if io.Exists("/tmp/panel-storage.zip") { if err = io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel")); err != nil { - return fmt.Errorf("恢复面板数据失败:%w", err) + return errors.New(r.t.Get("Unzip panel data failed: %w", err)) } if err = io.Remove("/tmp/panel-storage.zip"); err != nil { - return fmt.Errorf("清理临时文件失败:%w", err) + return errors.New(r.t.Get("Cleaning temporary file failed: %w", err)) } } @@ -677,7 +676,7 @@ func (r *backupRepo) FixPanel() error { // 处理权限 if app.IsCli { - fmt.Println("|-设置关键文件权限...") + fmt.Println(r.t.Get("|-Set key file permissions...")) } if err = io.Chmod("/usr/local/etc/panel/config.yml", 0600); err != nil { return err @@ -697,7 +696,7 @@ func (r *backupRepo) FixPanel() error { } if app.IsCli { - fmt.Println("|-修复完成") + fmt.Println(r.t.Get("|-Fix completed")) } tools.RestartPanel() @@ -715,119 +714,107 @@ func (r *backupRepo) UpdatePanel(version, url, checksum string) error { name := filepath.Base(url) if app.IsCli { - fmt.Printf("|-目标版本:%s\n", version) - fmt.Printf("|-下载链接:%s\n", url) - fmt.Printf("|-文件名:%s\n", name) + fmt.Println(r.t.Get("|-Target version: %s", version)) + fmt.Println(r.t.Get("|-Download link: %s", url)) + fmt.Println(r.t.Get("|-File name: %s", name)) } if app.IsCli { - fmt.Println("|-正在下载...") + fmt.Println(r.t.Get("|-Downloading...")) } if _, err := shell.Execf("wget -T 120 -t 3 -O /tmp/%s %s", name, url); err != nil { - return fmt.Errorf("下载失败:%w", err) + return errors.New(r.t.Get("Download failed: %w", err)) } if _, err := shell.Execf("wget -T 20 -t 3 -O /tmp/%s %s", name+".sha256", checksum); err != nil { - return fmt.Errorf("下载失败:%w", err) + return errors.New(r.t.Get("Download failed: %w", err)) } if !io.Exists(filepath.Join("/tmp", name)) || !io.Exists(filepath.Join("/tmp", name+".sha256")) { - return errors.New("下载文件检查失败") + return errors.New(r.t.Get("Download file check failed")) } if app.IsCli { - fmt.Println("|-校验下载文件...") + fmt.Println(r.t.Get("|-Verify download file...")) } if check, err := shell.Execf("cd /tmp && sha256sum -c %s --ignore-missing", name+".sha256"); check != name+": OK" || err != nil { - return errors.New("下载文件校验失败") + return errors.New(r.t.Get("Verify download file failed: %w", err)) } if err := io.Remove(filepath.Join("/tmp", name+".sha256")); err != nil { - if app.IsCli { - fmt.Println("|-清理校验文件失败:", err) - } - return fmt.Errorf("清理校验文件失败:%w", err) + return errors.New(r.t.Get("|-Clean up verification file failed: %w", err)) } - if app.IsCli { - fmt.Println("|-前置检查...") - } if io.Exists("/tmp/panel-storage.zip") { - return errors.New("检测到 /tmp 存在临时文件,可能是上次更新失败所致,请运行 panel-cli fix 修复后重试") + return errors.New(r.t.Get("Temporary file detected in /tmp, this may be caused by the last update failure, please run panel-cli fix to repair and try again")) } if app.IsCli { - fmt.Println("|-备份面板数据...") + fmt.Println(r.t.Get("|-Backup panel data...")) } // 备份面板 if err := r.Create(biz.BackupTypePanel, ""); err != nil { - if app.IsCli { - fmt.Println("|-备份面板失败:", err) - } - return fmt.Errorf("备份面板失败:%w", err) + return errors.New(r.t.Get("|-Backup panel data failed: %w", err)) } if err := io.Compress(filepath.Join(app.Root, "panel/storage"), nil, "/tmp/panel-storage.zip"); err != nil { - if app.IsCli { - fmt.Println("|-备份面板数据失败:", err) - } - return fmt.Errorf("备份面板数据失败:%w", err) + return errors.New(r.t.Get("|-Backup panel data failed: %w", err)) } if !io.Exists("/tmp/panel-storage.zip") { - return errors.New("已备份面板数据检查失败") + return errors.New(r.t.Get("|-Backup panel data failed, missing file")) } if app.IsCli { - fmt.Println("|-清理旧版本...") + fmt.Println(r.t.Get("|-Cleaning old version...")) } if _, err := shell.Execf("rm -rf %s/panel/*", app.Root); err != nil { - return fmt.Errorf("清理旧版本失败:%w", err) + return errors.New(r.t.Get("|-Cleaning old version failed: %w", err)) } if app.IsCli { - fmt.Println("|-解压新版本...") + fmt.Println(r.t.Get("|-Unzip new version...")) } if err := io.UnCompress(filepath.Join("/tmp", name), filepath.Join(app.Root, "panel")); err != nil { - return fmt.Errorf("解压失败:%w", err) + return errors.New(r.t.Get("|-Unzip new version failed: %w", err)) } if !io.Exists(filepath.Join(app.Root, "panel", "web")) { - return errors.New("解压失败,缺失文件") + return errors.New(r.t.Get("|-Unzip new version failed, missing file")) } if err := io.Remove(filepath.Join("/tmp", name)); err != nil { - return fmt.Errorf("清理临时文件失败:%w", err) + return errors.New(r.t.Get("|-Clean up temporary file failed: %w", err)) } if app.IsCli { - fmt.Println("|-恢复面板数据...") + fmt.Println(r.t.Get("|-Restore panel data...")) } if err := io.UnCompress("/tmp/panel-storage.zip", filepath.Join(app.Root, "panel", "storage")); err != nil { - return fmt.Errorf("恢复面板数据失败:%w", err) + return errors.New(r.t.Get("|-Restore panel data failed: %w", err)) } if !io.Exists(filepath.Join(app.Root, "panel/storage/app.db")) { - return errors.New("恢复面板数据失败") + return errors.New(r.t.Get("|-Restore panel data failed, missing file")) } if app.IsCli { - fmt.Println("|-运行更新后脚本...") + fmt.Println(r.t.Get("|-Run post-update script...")) } if _, err := shell.Execf("curl -fsLm 10 https://dl.cdn.haozi.net/panel/auto_update.sh | bash"); err != nil { - return fmt.Errorf("运行面板更新后脚本失败:%w", err) + return errors.New(r.t.Get("|-Run post-update script failed: %w", err)) } if _, err := shell.Execf(`wget -O /etc/systemd/system/panel.service https://dl.cdn.haozi.net/panel/panel.service && sed -i "s|/www|%s|g" /etc/systemd/system/panel.service`, app.Root); err != nil { - return fmt.Errorf("下载面板服务文件失败:%w", err) + return errors.New(r.t.Get("|-Download panel service file failed: %w", err)) } if _, err := shell.Execf("panel-cli setting write version %s", version); err != nil { - return fmt.Errorf("写入面板版本号失败:%w", err) + return errors.New(r.t.Get("|-Write new panel version failed: %w", err)) } if err := io.Mv(filepath.Join(app.Root, "panel/cli"), "/usr/local/sbin/panel-cli"); err != nil { - return fmt.Errorf("移动面板命令行工具失败:%w", err) + return errors.New(r.t.Get("|-Move panel-cli tool failed: %w", err)) } if app.IsCli { - fmt.Println("|-设置关键文件权限...") + fmt.Println(r.t.Get("|-Set key file permissions...")) } _ = io.Chmod("/usr/local/sbin/panel-cli", 0700) _ = io.Chmod("/etc/systemd/system/panel.service", 0644) _ = io.Chmod(filepath.Join(app.Root, "panel"), 0700) if app.IsCli { - fmt.Println("|-更新完成") + fmt.Println(r.t.Get("|-Update completed")) } _, _ = shell.Execf("systemctl daemon-reload") diff --git a/internal/data/website.go b/internal/data/website.go index 7c63dec6..5d17818f 100644 --- a/internal/data/website.go +++ b/internal/data/website.go @@ -276,14 +276,24 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) { if err = os.MkdirAll(req.Path, 0755); err != nil { return nil, err } - index, err := embed.WebsiteFS.ReadFile(filepath.Join("website", "index.html")) + var index []byte + if app.Locale == "zh_CN" { + index, err = embed.WebsiteFS.ReadFile(filepath.Join("website", "index_zh.html")) + } else { + index, err = embed.WebsiteFS.ReadFile(filepath.Join("website", "index.html")) + } if err != nil { return nil, fmt.Errorf("获取index模板文件失败: %w", err) } if err = io.Write(filepath.Join(req.Path, "index.html"), string(index), 0644); err != nil { return nil, err } - notFound, err := embed.WebsiteFS.ReadFile(filepath.Join("website", "404.html")) + var notFound []byte + if app.Locale == "zh_CN" { + notFound, err = embed.WebsiteFS.ReadFile(filepath.Join("website", "404_zh.html")) + } else { + notFound, err = embed.WebsiteFS.ReadFile(filepath.Join("website", "404.html")) + } if err != nil { return nil, fmt.Errorf("获取404模板文件失败: %w", err) } diff --git a/internal/service/file.go b/internal/service/file.go index 86fcc933..f9e49314 100644 --- a/internal/service/file.go +++ b/internal/service/file.go @@ -68,7 +68,7 @@ func (s *FileService) Content(w http.ResponseWriter, r *http.Request) { return } - fileInfo, err := io.FileInfo(req.Path) + fileInfo, err := stdos.Stat(req.Path) if err != nil { Error(w, http.StatusInternalServerError, "%v", err) return @@ -82,7 +82,7 @@ func (s *FileService) Content(w http.ResponseWriter, r *http.Request) { return } - content, err := io.ReadBytes(req.Path) + content, err := stdos.ReadFile(req.Path) if err != nil { Error(w, http.StatusInternalServerError, "%v", err) return @@ -106,7 +106,7 @@ func (s *FileService) Save(w http.ResponseWriter, r *http.Request) { return } - fileInfo, err := io.FileInfo(req.Path) + fileInfo, err := stdos.Stat(req.Path) if err != nil { Error(w, http.StatusInternalServerError, "%v", err) return @@ -265,7 +265,7 @@ func (s *FileService) Download(w http.ResponseWriter, r *http.Request) { return } - info, err := io.FileInfo(req.Path) + info, err := stdos.Stat(req.Path) if err != nil { Error(w, http.StatusInternalServerError, "%v", err) return @@ -309,7 +309,7 @@ func (s *FileService) Info(w http.ResponseWriter, r *http.Request) { return } - info, err := io.FileInfo(req.Path) + info, err := stdos.Stat(req.Path) if err != nil { Error(w, http.StatusInternalServerError, "%v", err) return diff --git a/pkg/acme/acme.go b/pkg/acme/acme.go index 92d1a4df..ece1c12f 100644 --- a/pkg/acme/acme.go +++ b/pkg/acme/acme.go @@ -114,7 +114,7 @@ func generatePrivateKey(keyType KeyType) (crypto.Signer, error) { return rsa.GenerateKey(rand.Reader, 4096) } - return nil, errors.New("未知的密钥类型") + return nil, errors.New("unsupported key type") } func getClient(CA string, log *slog.Logger) (acmez.Client, error) { diff --git a/pkg/acme/ssl.go b/pkg/acme/ssl.go deleted file mode 100644 index 8d2a213b..00000000 --- a/pkg/acme/ssl.go +++ /dev/null @@ -1 +0,0 @@ -package acme diff --git a/pkg/cert/cert.go b/pkg/cert/cert.go index 32e7d9cb..f43dc1b1 100644 --- a/pkg/cert/cert.go +++ b/pkg/cert/cert.go @@ -54,11 +54,7 @@ func ParseKey(key string) (crypto.Signer, error) { } } - if parse, err := x509.ParseECPrivateKey(keyBlockDER.Bytes); err == nil { - return parse, nil - } - - return nil, errors.New("解析私钥失败") + return x509.ParseECPrivateKey(keyBlockDER.Bytes) } func EncodeCert(cert x509.Certificate) ([]byte, error) { @@ -88,7 +84,7 @@ func EncodeKey(key crypto.Signer) ([]byte, error) { return nil, err } default: - return nil, fmt.Errorf("未知的密钥类型 %T", key) + return nil, fmt.Errorf("unsupported key type %T", key) } pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes} return pem.EncodeToMemory(&pemKey), nil diff --git a/pkg/db/mysql_tools.go b/pkg/db/mysql_tools.go index c17a9392..3a369d0a 100644 --- a/pkg/db/mysql_tools.go +++ b/pkg/db/mysql_tools.go @@ -1,7 +1,6 @@ package db import ( - "errors" "fmt" "github.com/tnb-labs/panel/pkg/shell" @@ -12,21 +11,21 @@ import ( func MySQLResetRootPassword(password string) error { _ = systemctl.Stop("mysqld") if run, err := systemctl.Status("mysqld"); err != nil || run { - return fmt.Errorf("停止MySQL失败: %w", err) + return fmt.Errorf("failed to stop 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) + return fmt.Errorf("failed to start MySQL in safe mode: %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密码失败") + return fmt.Errorf("failed to reset MySQL root password: %w", err) } if err := systemctl.Stop("mysqld"); err != nil { - return fmt.Errorf("停止MySQL失败: %w", err) + return fmt.Errorf("failed to stop MySQL: %w", err) } _, _ = shell.Execf(`systemctl unset-environment MYSQLD_OPTS`) if err := systemctl.Start("mysqld"); err != nil { - return fmt.Errorf("启动MySQL失败: %w", err) + return fmt.Errorf("failed to start MySQL: %w", err) } return nil diff --git a/pkg/db/postgres.go b/pkg/db/postgres.go index 4e6eaf53..c75f6d21 100644 --- a/pkg/db/postgres.go +++ b/pkg/db/postgres.go @@ -205,11 +205,11 @@ func (r *Postgres) Users() ([]types.PostgresUser, error) { } permissions := map[string]bool{ - "超级用户": super, - "创建角色": canCreateRole, - "创建数据库": canCreateDb, - "可以复制": replication, - "绕过行级安全": bypassRls, + "Super": super, + "CreateRole": canCreateRole, + "CreateDB": canCreateDb, + "Replication": replication, + "BypassRLS": bypassRls, } for perm, enabled := range permissions { if enabled { diff --git a/pkg/embed/website/404.html b/pkg/embed/website/404.html index e98dae3b..61fbff6b 100644 --- a/pkg/embed/website/404.html +++ b/pkg/embed/website/404.html @@ -49,7 +49,7 @@

404 Not Found

-

耗子面板 强力驱动

+

Powered by Rat Panel

diff --git a/pkg/embed/website/404_zh.html b/pkg/embed/website/404_zh.html new file mode 100644 index 00000000..e98dae3b --- /dev/null +++ b/pkg/embed/website/404_zh.html @@ -0,0 +1,55 @@ + + + + + 404 Not Found + + + +
+

404 Not Found

+

耗子面板 强力驱动

+
+ + diff --git a/pkg/embed/website/index.html b/pkg/embed/website/index.html index cb4b9029..dcae1966 100644 --- a/pkg/embed/website/index.html +++ b/pkg/embed/website/index.html @@ -3,7 +3,7 @@ - 耗子面板 + Rat Panel + + +
+

耗子面板

+

这是耗子面板的网站默认页面!

+

当您看到此页面,说明您的网站已创建成功。

+

耗子面板 强力驱动

+
+ + diff --git a/pkg/io/file.go b/pkg/io/file.go index 7c17ec21..142d2ce8 100644 --- a/pkg/io/file.go +++ b/pkg/io/file.go @@ -95,16 +95,6 @@ func Read(path string) (string, error) { return string(data), err } -// ReadBytes 读取文件 -func ReadBytes(path string) ([]byte, error) { - return os.ReadFile(path) -} - -// FileInfo 获取文件大小 -func FileInfo(path string) (os.FileInfo, error) { - return os.Stat(path) -} - // IsSymlink 判读是否为软链接 func IsSymlink(mode os.FileMode) bool { return mode&os.ModeSymlink != 0 diff --git a/pkg/nginx/data.go b/pkg/nginx/data.go index 5b804fb1..455a8892 100644 --- a/pkg/nginx/data.go +++ b/pkg/nginx/data.go @@ -31,3 +31,31 @@ const defaultConf = `server { error_log /www/wwwlogs/default.log; } ` + +const defaultConfEn = `server { + listen 80; + server_name localhost; + index index.php index.html index.htm; + root /www/wwwroot/default; + # Error page configuration + error_page 404 /404.html; + include enable-php-0.conf; + # Do not log static files + location ~ .*\.(bmp|jpg|jpeg|png|gif|svg|ico|tiff|webp|avif|heif|heic|jxl)$ { + expires 30d; + access_log /dev/null; + error_log /dev/null; + } + location ~ .*\.(js|css|ttf|otf|woff|woff2|eot)$ { + expires 6h; + access_log /dev/null; + error_log /dev/null; + } + # Deny access to sensitive directories + location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) { + return 404; + } + access_log /www/wwwlogs/default.log; + error_log /www/wwwlogs/default.log; +} +` diff --git a/pkg/ntp/ntp.go b/pkg/ntp/ntp.go index 24a5475a..eabb5ead 100644 --- a/pkg/ntp/ntp.go +++ b/pkg/ntp/ntp.go @@ -11,9 +11,9 @@ import ( "github.com/tnb-labs/panel/pkg/shell" ) -var ErrNotReachable = errors.New("无法连接到 NTP 服务器") +var ErrNotReachable = errors.New("failed to reach NTP server") -var ErrNoAvailableServer = errors.New("无可用的 NTP 服务器") +var ErrNoAvailableServer = errors.New("no available NTP server found") var defaultAddresses = []string{ //"ntp.ntsc.ac.cn", // 中科院国家授时中心的服务器很快,但是多刷几次就会被封