2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 05:31:44 +08:00

feat: 添加翻译

This commit is contained in:
2025-04-12 17:35:22 +08:00
parent 2ff01579d6
commit 53fdcfb715
17 changed files with 286 additions and 160 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -1 +0,0 @@
package acme

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -49,7 +49,7 @@
<body>
<div class="container">
<h1>404 Not Found</h1>
<p> <a target="_blank" href="https://panel.haozi.net">耗子面板</a> 强力驱动</p>
<p>Powered by <a target="_blank" href="https://panel.haozi.net">Rat Panel</a></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,55 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 Not Found</title>
<style>
body {
background-color: #f9f9f9;
margin: 0;
padding: 0;
}
.container {
max-width: 800px;
margin: 2em auto;
background-color: #ffffff;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 2.5em;
margin-top: 0;
margin-bottom: 20px;
text-align: center;
color: #333;
border-bottom: 2px solid #ddd;
padding-bottom: 0.5em;
}
p {
color: #555;
line-height: 1.8;
text-align: center;
}
a {
text-decoration: none;
color: #007bff;
}
@media screen and (max-width: 768px) {
.container {
padding: 15px;
margin: 2em 15px;
}
h1 {
font-size: 1.8em;
}
}
</style>
</head>
<body>
<div class="container">
<h1>404 Not Found</h1>
<p> <a target="_blank" href="https://panel.haozi.net">耗子面板</a> 强力驱动</p>
</div>
</body>
</html>

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>耗子面板</title>
<title>Rat Panel</title>
<style>
body {
background-color: #f9f9f9;
@@ -49,10 +49,10 @@
</head>
<body>
<div class="container">
<h1>耗子面板</h1>
<p>这是耗子面板的网站默认页面</p>
<p>当您看到此页面说明您的网站已创建成功</p>
<p> <a target="_blank" href="https://panel.haozi.net">耗子面板</a> 强力驱动</p>
<h1>Rat Panel</h1>
<p>This is the default page for the Rat Panel website!</p>
<p>When you see this page, it means your website has been created successfully.</p>
<p>Powered by <a target="_blank" href="https://panel.haozi.net">Rat Panel</a></p>
</div>
</body>
</html>

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>耗子面板</title>
<style>
body {
background-color: #f9f9f9;
margin: 0;
padding: 0;
}
.container {
max-width: 800px;
margin: 2em auto;
background-color: #ffffff;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 2.5em;
margin-top: 0;
margin-bottom: 20px;
text-align: center;
color: #333;
border-bottom: 2px solid #ddd;
padding-bottom: 0.5em;
}
p {
color: #555;
line-height: 1.8;
text-align: center;
}
a {
text-decoration: none;
color: #007bff;
}
@media screen and (max-width: 768px) {
.container {
padding: 15px;
margin: 2em 15px;
}
h1 {
font-size: 1.8em;
}
}
</style>
</head>
<body>
<div class="container">
<h1>耗子面板</h1>
<p>这是耗子面板的网站默认页面</p>
<p>当您看到此页面说明您的网站已创建成功</p>
<p> <a target="_blank" href="https://panel.haozi.net">耗子面板</a> 强力驱动</p>
</div>
</body>
</html>

View File

@@ -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

View File

@@ -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;
}
`

View File

@@ -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", // 中科院国家授时中心的服务器很快,但是多刷几次就会被封