mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-05-10 17:43:43 +08:00
Three community-requested features: 1. CLI password reset: `backupx reset-password --username admin --password xxx` Docker users can run via `docker exec`. No full app init needed. 2. Audit logging: async fire-and-forget audit trail for all key operations (login, CRUD on tasks/targets/records, settings changes). New UI page at /audit with category filter and pagination. 3. Multi-source path backup: file backup tasks now support multiple source directories packed into a single tar archive. Backward compatible with existing single sourcePath field.
116 lines
2.9 KiB
Go
116 lines
2.9 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"flag"
|
||
"fmt"
|
||
"os"
|
||
"os/signal"
|
||
"syscall"
|
||
|
||
"backupx/server/internal/app"
|
||
"backupx/server/internal/config"
|
||
"backupx/server/internal/security"
|
||
"github.com/glebarez/sqlite"
|
||
"gorm.io/gorm"
|
||
gormlogger "gorm.io/gorm/logger"
|
||
)
|
||
|
||
var version = "dev"
|
||
|
||
func main() {
|
||
// 子命令分发:reset-password
|
||
if len(os.Args) > 1 && os.Args[1] == "reset-password" {
|
||
runResetPassword(os.Args[2:])
|
||
return
|
||
}
|
||
|
||
var configPath string
|
||
var showVersion bool
|
||
|
||
flag.StringVar(&configPath, "config", "", "path to config file")
|
||
flag.BoolVar(&showVersion, "version", false, "print version")
|
||
flag.Parse()
|
||
|
||
if showVersion {
|
||
fmt.Println(version)
|
||
return
|
||
}
|
||
|
||
cfg, err := config.Load(configPath)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "load config: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||
defer stop()
|
||
|
||
application, err := app.New(ctx, cfg, version)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "bootstrap app: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
defer application.Close()
|
||
|
||
if err := application.Run(ctx); err != nil {
|
||
application.Logger().Error("application exited with error", app.ErrorField(err))
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
|
||
// runResetPassword 通过 CLI 直接操作 SQLite 重置用户密码,无需完整 app 初始化。
|
||
// 用法:backupx reset-password --username admin --password newpass123 [--config path]
|
||
func runResetPassword(args []string) {
|
||
fs := flag.NewFlagSet("reset-password", flag.ExitOnError)
|
||
username := fs.String("username", "admin", "要重置密码的用户名")
|
||
password := fs.String("password", "", "新密码(至少 8 个字符)")
|
||
configPath := fs.String("config", "", "配置文件路径")
|
||
if err := fs.Parse(args); err != nil {
|
||
os.Exit(1)
|
||
}
|
||
|
||
if *password == "" {
|
||
fmt.Fprintln(os.Stderr, "错误:--password 参数为必填项")
|
||
fs.Usage()
|
||
os.Exit(1)
|
||
}
|
||
if len(*password) < 8 {
|
||
fmt.Fprintln(os.Stderr, "错误:密码长度至少 8 个字符")
|
||
os.Exit(1)
|
||
}
|
||
|
||
cfg, err := config.Load(*configPath)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "加载配置失败:%v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
db, err := gorm.Open(sqlite.Open(cfg.Database.Path), &gorm.Config{Logger: gormlogger.Default.LogMode(gormlogger.Silent)})
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "打开数据库失败:%v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
var count int64
|
||
db.Table("users").Where("username = ?", *username).Count(&count)
|
||
if count == 0 {
|
||
fmt.Fprintf(os.Stderr, "错误:用户 %q 不存在\n", *username)
|
||
os.Exit(1)
|
||
}
|
||
|
||
hash, err := security.HashPassword(*password)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "密码哈希失败:%v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
result := db.Table("users").Where("username = ?", *username).Update("password_hash", hash)
|
||
if result.Error != nil {
|
||
fmt.Fprintf(os.Stderr, "密码更新失败:%v\n", result.Error)
|
||
os.Exit(1)
|
||
}
|
||
|
||
fmt.Printf("用户 %q 密码已重置成功\n", *username)
|
||
}
|