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/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}