package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/spf13/cobra" "scratchbox/internal/cleanup" "scratchbox/internal/config" httpapi "scratchbox/internal/http" "scratchbox/internal/storage" ) func newServerCmd() *cobra.Command { var configPath string cmd := &cobra.Command{ Use: "server", Short: "Run the scratchbox service", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return run(configPath) }, } cmd.Flags().StringVarP(&configPath, "config", "c", "", "path to config file") return cmd } func run(configPath string) error { cfg, err := config.Load(configPath) if err != nil { return fmt.Errorf("config load failed: %w", err) } logger := log.New(os.Stdout, "[pastebin] ", log.LstdFlags|log.LUTC) store, err := storage.NewFilesystemStore(cfg.Storage.DataDir) if err != nil { return fmt.Errorf("storage init failed: %w", err) } server, err := httpapi.NewServer(cfg, store, logger) if err != nil { return fmt.Errorf("http init failed: %w", err) } cleanupWorker := cleanup.NewWorker(store, cfg.Storage.CleanupIntervalParsed, logger) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer cancel() go cleanupWorker.Start(ctx) httpServer := &http.Server{ Addr: cfg.Server.ListenAddr, Handler: server.Routes(), } go func() { <-ctx.Done() shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownCancel() if err := httpServer.Shutdown(shutdownCtx); err != nil { logger.Printf("http shutdown failed: %v", err) } }() logger.Printf("listening on %s", cfg.Server.ListenAddr) if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { return fmt.Errorf("http server exited with error: %w", err) } return nil }