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
46 srv := &Server{
47 Config: cfg,
48 Backend: cfg.Backend,
49 }
50 srv.SSHServer, err = NewSSHServer(cfg)
51 if err != nil {
52 return nil, err
53 }
54
55 srv.GitDaemon, err = NewGitDaemon(cfg)
56 if err != nil {
57 return nil, err
58 }
59
60 srv.HTTPServer, err = NewHTTPServer(cfg)
61 if err != nil {
62 return nil, err
63 }
64
65 return srv, nil
66}
67
68// Start starts the SSH server.
69func (s *Server) Start() error {
70 var errg errgroup.Group
71 errg.Go(func() error {
72 log.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)
73 if err := s.GitDaemon.Start(); err != ErrServerClosed {
74 return err
75 }
76 return nil
77 })
78 errg.Go(func() error {
79 log.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr)
80 if err := s.HTTPServer.ListenAndServe(); err != http.ErrServerClosed {
81 return err
82 }
83 return nil
84 })
85 errg.Go(func() error {
86 log.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr)
87 if err := s.SSHServer.ListenAndServe(); err != ssh.ErrServerClosed {
88 return err
89 }
90 return nil
91 })
92 return errg.Wait()
93}
94
95// Shutdown lets the server gracefully shutdown.
96func (s *Server) Shutdown(ctx context.Context) error {
97 var errg errgroup.Group
98 errg.Go(func() error {
99 return s.GitDaemon.Shutdown(ctx)
100 })
101 errg.Go(func() error {
102 return s.HTTPServer.Shutdown(ctx)
103 })
104 errg.Go(func() error {
105 return s.SSHServer.Shutdown(ctx)
106 })
107 return errg.Wait()
108}
109
110// Close closes the SSH server.
111func (s *Server) Close() error {
112 var errg errgroup.Group
113 errg.Go(s.GitDaemon.Close)
114 errg.Go(s.HTTPServer.Close)
115 errg.Go(s.SSHServer.Close)
116 return errg.Wait()
117}