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/sqlite"
13 "github.com/charmbracelet/soft-serve/server/config"
14 "github.com/charmbracelet/soft-serve/server/cron"
15 "github.com/charmbracelet/ssh"
16 "golang.org/x/sync/errgroup"
17)
18
19var (
20 logger = log.WithPrefix("server")
21)
22
23// Server is the Soft Serve server.
24type Server struct {
25 SSHServer *SSHServer
26 GitDaemon *GitDaemon
27 HTTPServer *HTTPServer
28 StatsServer *StatsServer
29 Cron *cron.CronScheduler
30 Config *config.Config
31 Backend backend.Backend
32}
33
34// NewServer returns a new *ssh.Server configured to serve Soft Serve. The SSH
35// server key-pair will be created if none exists. An initial admin SSH public
36// key can be provided with authKey. If authKey is provided, access will be
37// restricted to that key. If authKey is not provided, the server will be
38// publicly writable until configured otherwise by cloning the `config` repo.
39func NewServer(cfg *config.Config) (*Server, error) {
40 var err error
41 if cfg.Backend == nil {
42 sb, err := sqlite.NewSqliteBackend(cfg)
43 if err != nil {
44 logger.Fatal(err)
45 }
46
47 // Add the initial admin keys to the list of admins.
48 sb.AdditionalAdmins = cfg.InitialAdminKeys
49 cfg = cfg.WithBackend(sb)
50
51 // Create internal key.
52 _, err = keygen.NewWithWrite(
53 filepath.Join(cfg.DataPath, cfg.SSH.InternalKeyPath),
54 nil,
55 keygen.Ed25519,
56 )
57 if err != nil {
58 return nil, err
59 }
60
61 // Create client key.
62 _, err = keygen.NewWithWrite(
63 filepath.Join(cfg.DataPath, cfg.SSH.ClientKeyPath),
64 nil,
65 keygen.Ed25519,
66 )
67 if err != nil {
68 return nil, err
69 }
70 }
71
72 srv := &Server{
73 Cron: cron.NewCronScheduler(),
74 Config: cfg,
75 Backend: cfg.Backend,
76 }
77
78 // Add cron jobs.
79 srv.Cron.AddFunc(jobSpecs["mirror"], mirrorJob(cfg))
80
81 srv.SSHServer, err = NewSSHServer(cfg, srv)
82 if err != nil {
83 return nil, err
84 }
85
86 srv.GitDaemon, err = NewGitDaemon(cfg)
87 if err != nil {
88 return nil, err
89 }
90
91 srv.HTTPServer, err = NewHTTPServer(cfg)
92 if err != nil {
93 return nil, err
94 }
95
96 srv.StatsServer, err = NewStatsServer(cfg)
97 if err != nil {
98 return nil, err
99 }
100
101 return srv, nil
102}
103
104// Start starts the SSH server.
105func (s *Server) Start() error {
106 var errg errgroup.Group
107 errg.Go(func() error {
108 log.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)
109 if err := s.GitDaemon.Start(); err != ErrServerClosed {
110 return err
111 }
112 return nil
113 })
114 errg.Go(func() error {
115 log.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr)
116 if err := s.HTTPServer.ListenAndServe(); err != http.ErrServerClosed {
117 return err
118 }
119 return nil
120 })
121 errg.Go(func() error {
122 log.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr)
123 if err := s.SSHServer.ListenAndServe(); err != ssh.ErrServerClosed {
124 return err
125 }
126 return nil
127 })
128 errg.Go(func() error {
129 log.Print("Starting Stats server", "addr", s.Config.Stats.ListenAddr)
130 if err := s.StatsServer.ListenAndServe(); err != http.ErrServerClosed {
131 return err
132 }
133 return nil
134 })
135 errg.Go(func() error {
136 log.Print("Starting cron scheduler")
137 s.Cron.Start()
138 return nil
139 })
140 return errg.Wait()
141}
142
143// Shutdown lets the server gracefully shutdown.
144func (s *Server) Shutdown(ctx context.Context) error {
145 var errg errgroup.Group
146 errg.Go(func() error {
147 return s.GitDaemon.Shutdown(ctx)
148 })
149 errg.Go(func() error {
150 return s.HTTPServer.Shutdown(ctx)
151 })
152 errg.Go(func() error {
153 return s.SSHServer.Shutdown(ctx)
154 })
155 errg.Go(func() error {
156 return s.StatsServer.Shutdown(ctx)
157 })
158 return errg.Wait()
159}
160
161// Close closes the SSH server.
162func (s *Server) Close() error {
163 var errg errgroup.Group
164 errg.Go(s.GitDaemon.Close)
165 errg.Go(s.HTTPServer.Close)
166 errg.Go(s.SSHServer.Close)
167 errg.Go(s.StatsServer.Close)
168 return errg.Wait()
169}