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