server.go

 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}