package main import ( "context" "flag" "log/slog" "os" "os/signal" "path/filepath" "syscall" "time" "github.com/backupper-unraid/backupper/internal/api" "github.com/backupper-unraid/backupper/internal/notify" "github.com/backupper-unraid/backupper/internal/platform" "github.com/backupper-unraid/backupper/internal/restic" "github.com/backupper-unraid/backupper/internal/scheduler" "github.com/backupper-unraid/backupper/internal/secrets" "github.com/backupper-unraid/backupper/internal/service" "github.com/backupper-unraid/backupper/internal/store" ) var version = "dev" func main() { configDir := flag.String("config-dir", "/boot/config/plugins/backupper", "persistent configuration directory") runtimeDir := flag.String("runtime-dir", "/run/backupper", "runtime directory") stateDir := flag.String("state-dir", "/var/lib/backupper", "volatile state directory") socket := flag.String("socket", "/run/backupper/backupper.sock", "Unix API socket") flag.Parse() log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) if err := os.MkdirAll(*runtimeDir, 0750); err != nil { log.Error("create runtime directory", "error", err) os.Exit(1) } if err := os.MkdirAll(*stateDir, 0750); err != nil { log.Error("create state directory", "error", err) os.Exit(1) } configStore := store.New(*configDir) if err := configStore.Init(); err != nil { log.Error("initialize config store", "error", err) os.Exit(1) } secretStore := secrets.New(filepath.Join(*configDir, "secrets")) if err := secretStore.Init(); err != nil { log.Error("initialize secret store", "error", err) os.Exit(1) } config, err := configStore.LoadConfig() if err != nil { log.Error("load config", "error", err) os.Exit(1) } rr := &restic.Runner{Binary: config.Settings.ResticPath, RuntimeDir: *runtimeDir, Secrets: secretStore, Log: log} mounts := &platform.MountManager{RuntimeDir: *runtimeDir, Secrets: secretStore} workloads := &platform.WorkloadManager{RuntimeDir: *runtimeDir} notifier := ¬ify.Sender{Secrets: secretStore} svc, err := service.New(configStore, secretStore, rr, mounts, workloads, notifier, log) if err != nil { log.Error("initialize service", "error", err) os.Exit(1) } ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer cancel() go svc.RunQueue(ctx) api.Version = version sched := scheduler.New(svc.Config, svc.EnqueueScheduled, svc.EnqueueMaintenance, svc.LastRun, log) go sched.Run(ctx) server := api.New(*socket, svc, log) go func() { if err := server.ListenAndServe(); err != nil { log.Error("api server stopped", "error", err) cancel() } }() log.Info("backupper daemon started", "socket", *socket) <-ctx.Done() svc.Stop() shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second) defer shutdownCancel() _ = server.Shutdown(shutdownCtx) log.Info("backupper daemon stopped") }