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