Files
BackupX/server/cmd/backupx/main.go
Awuqing 5a25690f3f feat: add community enhancements — password reset, audit logs, multi-source backup
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.
2026-03-30 23:04:37 +08:00

116 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}