1package server
  2
  3import (
  4	"context"
  5	"net/http"
  6	"path/filepath"
  7
  8	"github.com/charmbracelet/keygen"
  9	"github.com/charmbracelet/log"
 10
 11	"github.com/charmbracelet/soft-serve/server/backend"
 12	"github.com/charmbracelet/soft-serve/server/backend/file"
 13	"github.com/charmbracelet/soft-serve/server/config"
 14	"github.com/charmbracelet/soft-serve/server/cron"
 15	"github.com/charmbracelet/ssh"
 16	"golang.org/x/sync/errgroup"
 17)
 18
 19var (
 20	logger = log.WithPrefix("server")
 21)
 22
 23// Server is the Soft Serve server.
 24type Server struct {
 25	SSHServer   *SSHServer
 26	GitDaemon   *GitDaemon
 27	HTTPServer  *HTTPServer
 28	StatsServer *StatsServer
 29	Cron        *cron.CronScheduler
 30	Config      *config.Config
 31	Backend     backend.Backend
 32}
 33
 34// NewServer returns a new *ssh.Server configured to serve Soft Serve. The SSH
 35// server key-pair will be created if none exists. An initial admin SSH public
 36// key can be provided with authKey. If authKey is provided, access will be
 37// restricted to that key. If authKey is not provided, the server will be
 38// publicly writable until configured otherwise by cloning the `config` repo.
 39func NewServer(cfg *config.Config) (*Server, error) {
 40	var err error
 41	if cfg.Backend == nil {
 42		fb, err := file.NewFileBackend(cfg.DataPath)
 43		if err != nil {
 44			logger.Fatal(err)
 45		}
 46		// Add the initial admin keys to the list of admins.
 47		fb.AdditionalAdmins = cfg.InitialAdminKeys
 48		cfg = cfg.WithBackend(fb)
 49
 50		// Create internal key.
 51		_, err = keygen.NewWithWrite(
 52			filepath.Join(cfg.DataPath, cfg.SSH.InternalKeyPath),
 53			nil,
 54			keygen.Ed25519,
 55		)
 56		if err != nil {
 57			return nil, err
 58		}
 59	}
 60
 61	srv := &Server{
 62		Cron:    cron.NewCronScheduler(),
 63		Config:  cfg,
 64		Backend: cfg.Backend,
 65	}
 66
 67	// Add cron jobs.
 68	srv.Cron.AddFunc(jobSpecs["mirror"], mirrorJob(cfg.Backend))
 69
 70	srv.SSHServer, err = NewSSHServer(cfg, srv)
 71	if err != nil {
 72		return nil, err
 73	}
 74
 75	srv.GitDaemon, err = NewGitDaemon(cfg)
 76	if err != nil {
 77		return nil, err
 78	}
 79
 80	srv.HTTPServer, err = NewHTTPServer(cfg)
 81	if err != nil {
 82		return nil, err
 83	}
 84
 85	srv.StatsServer, err = NewStatsServer(cfg)
 86	if err != nil {
 87		return nil, err
 88	}
 89
 90	return srv, nil
 91}
 92
 93// Start starts the SSH server.
 94func (s *Server) Start() error {
 95	var errg errgroup.Group
 96	errg.Go(func() error {
 97		log.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)
 98		if err := s.GitDaemon.Start(); err != ErrServerClosed {
 99			return err
100		}
101		return nil
102	})
103	errg.Go(func() error {
104		log.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr)
105		if err := s.HTTPServer.ListenAndServe(); err != http.ErrServerClosed {
106			return err
107		}
108		return nil
109	})
110	errg.Go(func() error {
111		log.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr)
112		if err := s.SSHServer.ListenAndServe(); err != ssh.ErrServerClosed {
113			return err
114		}
115		return nil
116	})
117	errg.Go(func() error {
118		log.Print("Starting Stats server", "addr", s.Config.Stats.ListenAddr)
119		if err := s.StatsServer.ListenAndServe(); err != http.ErrServerClosed {
120			return err
121		}
122		return nil
123	})
124	errg.Go(func() error {
125		log.Print("Starting cron scheduler")
126		s.Cron.Start()
127		return nil
128	})
129	return errg.Wait()
130}
131
132// Shutdown lets the server gracefully shutdown.
133func (s *Server) Shutdown(ctx context.Context) error {
134	var errg errgroup.Group
135	errg.Go(func() error {
136		return s.GitDaemon.Shutdown(ctx)
137	})
138	errg.Go(func() error {
139		return s.HTTPServer.Shutdown(ctx)
140	})
141	errg.Go(func() error {
142		return s.SSHServer.Shutdown(ctx)
143	})
144	errg.Go(func() error {
145		return s.StatsServer.Shutdown(ctx)
146	})
147	return errg.Wait()
148}
149
150// Close closes the SSH server.
151func (s *Server) Close() error {
152	var errg errgroup.Group
153	errg.Go(s.GitDaemon.Close)
154	errg.Go(s.HTTPServer.Close)
155	errg.Go(s.SSHServer.Close)
156	errg.Go(s.StatsServer.Close)
157	return errg.Wait()
158}