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