server.go

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