server.go

 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}