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}