server.go

 1package server
 2
 3import (
 4	"context"
 5	"fmt"
 6	"log"
 7	"net"
 8
 9	appCfg "github.com/charmbracelet/soft-serve/config"
10	cm "github.com/charmbracelet/soft-serve/server/cmd"
11	"github.com/charmbracelet/soft-serve/server/config"
12	gm "github.com/charmbracelet/soft-serve/server/git"
13	"github.com/charmbracelet/wish"
14	bm "github.com/charmbracelet/wish/bubbletea"
15	lm "github.com/charmbracelet/wish/logging"
16	rm "github.com/charmbracelet/wish/recover"
17	"github.com/gliderlabs/ssh"
18	"github.com/muesli/termenv"
19)
20
21// Server is the Soft Serve server.
22type Server struct {
23	SSHServer *ssh.Server
24	Config    *config.Config
25	config    *appCfg.Config
26}
27
28// NewServer returns a new *ssh.Server configured to serve Soft Serve. The SSH
29// server key-pair will be created if none exists. An initial admin SSH public
30// key can be provided with authKey. If authKey is provided, access will be
31// restricted to that key. If authKey is not provided, the server will be
32// publicly writable until configured otherwise by cloning the `config` repo.
33func NewServer(cfg *config.Config) *Server {
34	ac, err := appCfg.NewConfig(cfg)
35	if err != nil {
36		log.Fatal(err)
37	}
38	mw := []wish.Middleware{
39		rm.MiddlewareWithLogger(
40			cfg.ErrorLog,
41			// BubbleTea middleware.
42			bm.MiddlewareWithProgramHandler(SessionHandler(ac), termenv.ANSI256),
43			// Command middleware must come after the git middleware.
44			cm.Middleware(ac),
45			// Git middleware.
46			gm.Middleware(cfg.RepoPath, ac),
47			// Logging middleware must be last to be executed first.
48			lm.Middleware(),
49		),
50	}
51	s, err := wish.NewServer(
52		ssh.PublicKeyAuth(ac.PublicKeyHandler),
53		ssh.KeyboardInteractiveAuth(ac.KeyboardInteractiveHandler),
54		wish.WithAddress(fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.Port)),
55		wish.WithHostKeyPath(cfg.KeyPath),
56		wish.WithMiddleware(mw...),
57	)
58	if err != nil {
59		log.Fatalln(err)
60	}
61	return &Server{
62		SSHServer: s,
63		Config:    cfg,
64		config:    ac,
65	}
66}
67
68// Reload reloads the server configuration.
69func (srv *Server) Reload() error {
70	return srv.config.Reload()
71}
72
73// Start starts the SSH server.
74func (srv *Server) Start() error {
75	if err := srv.SSHServer.ListenAndServe(); err != ssh.ErrServerClosed {
76		return err
77	}
78	return nil
79}
80
81// Serve serves the SSH server using the provided listener.
82func (srv *Server) Serve(l net.Listener) error {
83	if err := srv.SSHServer.Serve(l); err != ssh.ErrServerClosed {
84		return err
85	}
86	return nil
87}
88
89// Shutdown lets the server gracefully shutdown.
90func (srv *Server) Shutdown(ctx context.Context) error {
91	return srv.SSHServer.Shutdown(ctx)
92}
93
94// Close closes the SSH server.
95func (srv *Server) Close() error {
96	return srv.SSHServer.Close()
97}