package main import ( "context" "flag" "fmt" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "github.com/syncserver/internal/api" "github.com/syncserver/internal/auth" "github.com/syncserver/internal/config" "github.com/syncserver/internal/db" "github.com/syncserver/internal/scheduler" "github.com/syncserver/internal/sshmanager" "github.com/syncserver/internal/syncengine" ) var version = "1.0.40" func main() { cfgPath := flag.String("config", "", "Path to config.yaml") dataDir := flag.String("data-dir", "", "Data directory") addr := flag.String("addr", "", "HTTP listen address") showVersion := flag.Bool("version", false, "Print version") flag.Parse() if *showVersion { fmt.Println(version) return } cfg, err := config.Load(*cfgPath, *dataDir, *addr) if err != nil { fmt.Fprintf(os.Stderr, "config error: %v\n", err) os.Exit(1) } cfg.Version = version if err := cfg.EnsureDirs(); err != nil { fmt.Fprintf(os.Stderr, "failed to create dirs: %v\n", err) os.Exit(1) } slogHandler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelInfo, }) slog.SetDefault(slog.New(slogHandler)) slog.Info("starting syncserver", "version", version, "data_dir", cfg.DataDir, "addr", cfg.Addr, ) database, err := db.Open(cfg.DBPath()) if err != nil { slog.Error("failed to open database", "error", err) os.Exit(1) } defer database.Close() if err := database.RunMigrations(); err != nil { slog.Error("failed to run migrations", "error", err) os.Exit(1) } if err := auth.SeedAdmin(database.DB, cfg.Auth.AdminUser, cfg.Auth.AdminPass); err != nil { slog.Warn("admin seeding skipped or failed", "error", err) } privKeyPath, _, pubKey, err := sshmanager.EnsureServerKey(cfg.SSHDir()) if err != nil { slog.Error("failed to ensure server SSH key", "error", err) os.Exit(1) } slog.Info("server SSH key ready", "pub_key", pubKey) _ = privKeyPath engine := syncengine.New(database, cfg) sched := scheduler.New(database, engine, cfg) srv := &http.Server{ Addr: config.NormalizeAddr(cfg.Addr), ReadTimeout: 30 * time.Second, WriteTimeout: 60 * time.Second, IdleTimeout: 120 * time.Second, } apiServer := api.NewServer(cfg, database.DB, engine) srv.Handler = apiServer go sched.Start() go engine.Start() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) go func() { slog.Info("http server listening", "addr", srv.Addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { slog.Error("http server error", "error", err) os.Exit(1) } }() <-sigCh slog.Info("shutting down...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() sched.Stop() engine.Stop() if err := srv.Shutdown(ctx); err != nil { slog.Error("server shutdown error", "error", err) } slog.Info("bye") }