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