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/sqlite"
 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		sb, err := sqlite.NewSqliteBackend(cfg.DataPath)
 43		if err != nil {
 44			logger.Fatal(err)
 45		}
 46
 47		// Add the initial admin keys to the list of admins.
 48		sb.AdditionalAdmins = cfg.InitialAdminKeys
 49		cfg = cfg.WithBackend(sb)
 50
 51		// Create internal key.
 52		_, err = keygen.NewWithWrite(
 53			filepath.Join(cfg.DataPath, cfg.SSH.InternalKeyPath),
 54			nil,
 55			keygen.Ed25519,
 56		)
 57		if err != nil {
 58			return nil, err
 59		}
 60	}
 61
 62	srv := &Server{
 63		Cron:    cron.NewCronScheduler(),
 64		Config:  cfg,
 65		Backend: cfg.Backend,
 66	}
 67
 68	// Add cron jobs.
 69	srv.Cron.AddFunc(jobSpecs["mirror"], mirrorJob(cfg.Backend))
 70
 71	srv.SSHServer, err = NewSSHServer(cfg, srv)
 72	if err != nil {
 73		return nil, err
 74	}
 75
 76	srv.GitDaemon, err = NewGitDaemon(cfg)
 77	if err != nil {
 78		return nil, err
 79	}
 80
 81	srv.HTTPServer, err = NewHTTPServer(cfg)
 82	if err != nil {
 83		return nil, err
 84	}
 85
 86	srv.StatsServer, err = NewStatsServer(cfg)
 87	if err != nil {
 88		return nil, err
 89	}
 90
 91	return srv, nil
 92}
 93
 94// Start starts the SSH server.
 95func (s *Server) Start() error {
 96	var errg errgroup.Group
 97	errg.Go(func() error {
 98		log.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)
 99		if err := s.GitDaemon.Start(); err != ErrServerClosed {
100			return err
101		}
102		return nil
103	})
104	errg.Go(func() error {
105		log.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr)
106		if err := s.HTTPServer.ListenAndServe(); err != http.ErrServerClosed {
107			return err
108		}
109		return nil
110	})
111	errg.Go(func() error {
112		log.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr)
113		if err := s.SSHServer.ListenAndServe(); err != ssh.ErrServerClosed {
114			return err
115		}
116		return nil
117	})
118	errg.Go(func() error {
119		log.Print("Starting Stats server", "addr", s.Config.Stats.ListenAddr)
120		if err := s.StatsServer.ListenAndServe(); err != http.ErrServerClosed {
121			return err
122		}
123		return nil
124	})
125	errg.Go(func() error {
126		log.Print("Starting cron scheduler")
127		s.Cron.Start()
128		return nil
129	})
130	return errg.Wait()
131}
132
133// Shutdown lets the server gracefully shutdown.
134func (s *Server) Shutdown(ctx context.Context) error {
135	var errg errgroup.Group
136	errg.Go(func() error {
137		return s.GitDaemon.Shutdown(ctx)
138	})
139	errg.Go(func() error {
140		return s.HTTPServer.Shutdown(ctx)
141	})
142	errg.Go(func() error {
143		return s.SSHServer.Shutdown(ctx)
144	})
145	errg.Go(func() error {
146		return s.StatsServer.Shutdown(ctx)
147	})
148	return errg.Wait()
149}
150
151// Close closes the SSH server.
152func (s *Server) Close() error {
153	var errg errgroup.Group
154	errg.Go(s.GitDaemon.Close)
155	errg.Go(s.HTTPServer.Close)
156	errg.Go(s.SSHServer.Close)
157	errg.Go(s.StatsServer.Close)
158	return errg.Wait()
159}