1package cmd
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "log/slog"
8 "os"
9 "os/signal"
10 "time"
11
12 "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/server"
14 "github.com/charmbracelet/log/v2"
15 "github.com/spf13/cobra"
16)
17
18var serverHost string
19
20func init() {
21 serverCmd.Flags().StringVarP(&serverHost, "host", "H", server.DefaultHost(), "Server host (TCP or Unix socket)")
22 rootCmd.AddCommand(serverCmd)
23}
24
25var serverCmd = &cobra.Command{
26 Use: "server",
27 Short: "Start the Crush server",
28 RunE: func(cmd *cobra.Command, args []string) error {
29 dataDir, err := cmd.Flags().GetString("data-dir")
30 if err != nil {
31 return fmt.Errorf("failed to get data directory: %v", err)
32 }
33 debug, err := cmd.Flags().GetBool("debug")
34 if err != nil {
35 return fmt.Errorf("failed to get debug flag: %v", err)
36 }
37
38 cfg, err := config.Load("", dataDir, debug)
39 if err != nil {
40 return fmt.Errorf("failed to load configuration: %v", err)
41 }
42
43 logger := log.New(os.Stderr)
44 logger.SetReportTimestamp(true)
45 slog.SetDefault(slog.New(logger))
46 if debug {
47 logger.SetLevel(log.DebugLevel)
48 slog.SetLogLoggerLevel(slog.LevelDebug)
49 }
50
51 hostURL, err := server.ParseHostURL(serverHost)
52 if err != nil {
53 return fmt.Errorf("invalid server host: %v", err)
54 }
55
56 srv := server.NewServer(cfg, hostURL.Scheme, hostURL.Host)
57 srv.SetLogger(slog.Default())
58 slog.Info("Starting Crush server...", "addr", serverHost)
59
60 errch := make(chan error, 1)
61 sigch := make(chan os.Signal, 1)
62 sigs := []os.Signal{os.Interrupt}
63 sigs = append(sigs, addSignals(sigs)...)
64 signal.Notify(sigch, sigs...)
65
66 go func() {
67 errch <- srv.ListenAndServe()
68 }()
69
70 select {
71 case <-sigch:
72 slog.Info("Received interrupt signal...")
73 case err = <-errch:
74 if err != nil && !errors.Is(err, server.ErrServerClosed) {
75 _ = srv.Close()
76 slog.Error("Server error", "error", err)
77 return fmt.Errorf("server error: %v", err)
78 }
79 }
80
81 if errors.Is(err, server.ErrServerClosed) {
82 return nil
83 }
84
85 ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
86 defer cancel()
87
88 slog.Info("Shutting down...")
89
90 if err := srv.Shutdown(ctx); err != nil {
91 slog.Error("Failed to shutdown server", "error", err)
92 return fmt.Errorf("failed to shutdown server: %v", err)
93 }
94
95 return nil
96 },
97}