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