1package serve23import (4 "context"5 "errors"6 "fmt"7 "net/http"89 "github.com/charmbracelet/log/v2"1011 "github.com/charmbracelet/soft-serve/pkg/backend"12 "github.com/charmbracelet/soft-serve/pkg/config"13 "github.com/charmbracelet/soft-serve/pkg/cron"14 "github.com/charmbracelet/soft-serve/pkg/daemon"15 "github.com/charmbracelet/soft-serve/pkg/db"16 "github.com/charmbracelet/soft-serve/pkg/jobs"17 sshsrv "github.com/charmbracelet/soft-serve/pkg/ssh"18 "github.com/charmbracelet/soft-serve/pkg/stats"19 "github.com/charmbracelet/soft-serve/pkg/web"20 "github.com/charmbracelet/ssh"21 "golang.org/x/sync/errgroup"22)2324// Server is the Soft Serve server.25type Server struct {26 SSHServer *sshsrv.SSHServer27 GitDaemon *daemon.GitDaemon28 HTTPServer *web.HTTPServer29 StatsServer *stats.StatsServer30 Cron *cron.Scheduler31 Config *config.Config32 Backend *backend.Backend33 DB *db.DB3435 logger *log.Logger36 ctx context.Context37}3839// NewServer returns a new *Server configured to serve Soft Serve. The SSH40// server key-pair will be created if none exists.41// It expects a context with *backend.Backend, *db.DB, *log.Logger, and42// *config.Config attached.43func NewServer(ctx context.Context) (*Server, error) {44 var err error45 cfg := config.FromContext(ctx)46 be := backend.FromContext(ctx)47 db := db.FromContext(ctx)48 logger := log.FromContext(ctx).WithPrefix("server")49 srv := &Server{50 Config: cfg,51 Backend: be,52 DB: db,53 logger: log.FromContext(ctx).WithPrefix("server"),54 ctx: ctx,55 }5657 // Add cron jobs.58 sched := cron.NewScheduler(ctx)59 for n, j := range jobs.List() {60 id, err := sched.AddFunc(j.Runner.Spec(ctx), j.Runner.Func(ctx))61 if err != nil {62 logger.Warn("error adding cron job", "job", n, "err", err)63 }6465 j.ID = id66 }6768 srv.Cron = sched6970 srv.SSHServer, err = sshsrv.NewSSHServer(ctx)71 if err != nil {72 return nil, fmt.Errorf("create ssh server: %w", err)73 }7475 srv.GitDaemon, err = daemon.NewGitDaemon(ctx)76 if err != nil {77 return nil, fmt.Errorf("create git daemon: %w", err)78 }7980 srv.HTTPServer, err = web.NewHTTPServer(ctx)81 if err != nil {82 return nil, fmt.Errorf("create http server: %w", err)83 }8485 srv.StatsServer, err = stats.NewStatsServer(ctx)86 if err != nil {87 return nil, fmt.Errorf("create stats server: %w", err)88 }8990 return srv, nil91}9293// Start starts the SSH server.94func (s *Server) Start() error {95 errg, _ := errgroup.WithContext(s.ctx)9697 // optionally start the SSH server98 if s.Config.SSH.Enabled {99 errg.Go(func() error {100 s.logger.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr)101 if err := s.SSHServer.ListenAndServe(); !errors.Is(err, ssh.ErrServerClosed) {102 return err103 }104 return nil105 })106 }107108 // optionally start the git daemon109 if s.Config.Git.Enabled {110 errg.Go(func() error {111 s.logger.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)112 if err := s.GitDaemon.ListenAndServe(); !errors.Is(err, daemon.ErrServerClosed) {113 return err114 }115 return nil116 })117 }118119 // optionally start the HTTP server120 if s.Config.HTTP.Enabled {121 errg.Go(func() error {122 s.logger.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr)123 if err := s.HTTPServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {124 return err125 }126 return nil127 })128 }129130 // optionally start the Stats server131 if s.Config.Stats.Enabled {132 errg.Go(func() error {133 s.logger.Print("Starting Stats server", "addr", s.Config.Stats.ListenAddr)134 if err := s.StatsServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {135 return err136 }137 return nil138 })139 }140141 errg.Go(func() error {142 s.Cron.Start()143 return nil144 })145 return errg.Wait()146}147148// Shutdown lets the server gracefully shutdown.149func (s *Server) Shutdown(ctx context.Context) error {150 errg, ctx := errgroup.WithContext(ctx)151 errg.Go(func() error {152 return s.GitDaemon.Shutdown(ctx)153 })154 errg.Go(func() error {155 return s.HTTPServer.Shutdown(ctx)156 })157 errg.Go(func() error {158 return s.SSHServer.Shutdown(ctx)159 })160 errg.Go(func() error {161 return s.StatsServer.Shutdown(ctx)162 })163 errg.Go(func() error {164 for _, j := range jobs.List() {165 s.Cron.Remove(j.ID)166 }167 s.Cron.Stop()168 return nil169 })170 // defer s.DB.Close() // nolint: errcheck171 return errg.Wait()172}173174// Close closes the SSH server.175func (s *Server) Close() error {176 var errg errgroup.Group177 errg.Go(s.GitDaemon.Close)178 errg.Go(s.HTTPServer.Close)179 errg.Go(s.SSHServer.Close)180 errg.Go(s.StatsServer.Close)181 errg.Go(func() error {182 s.Cron.Stop()183 return nil184 })185 // defer s.DB.Close() // nolint: errcheck186 return errg.Wait()187}