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