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}