1package ssh
2
3import (
4 "strings"
5
6 tea "github.com/charmbracelet/bubbletea"
7 "github.com/charmbracelet/soft-serve/server/backend"
8 "github.com/charmbracelet/soft-serve/server/config"
9 "github.com/charmbracelet/soft-serve/server/errors"
10 "github.com/charmbracelet/soft-serve/server/ui"
11 "github.com/charmbracelet/soft-serve/server/ui/common"
12 "github.com/charmbracelet/ssh"
13 "github.com/charmbracelet/wish"
14 bm "github.com/charmbracelet/wish/bubbletea"
15 "github.com/muesli/termenv"
16 "github.com/prometheus/client_golang/prometheus"
17 "github.com/prometheus/client_golang/prometheus/promauto"
18)
19
20var (
21 tuiSessionCounter = promauto.NewCounterVec(prometheus.CounterOpts{
22 Namespace: "soft_serve",
23 Subsystem: "ssh",
24 Name: "tui_session_total",
25 Help: "The total number of TUI sessions",
26 }, []string{"key", "user", "repo", "term"})
27)
28
29// SessionHandler is the soft-serve bubbletea ssh session handler.
30func SessionHandler(cfg *config.Config) bm.ProgramHandler {
31 return func(s ssh.Session) *tea.Program {
32 ak := backend.MarshalAuthorizedKey(s.PublicKey())
33 pty, _, active := s.Pty()
34 if !active {
35 return nil
36 }
37
38 cmd := s.Command()
39 initialRepo := ""
40 if len(cmd) == 1 {
41 initialRepo = cmd[0]
42 auth := cfg.Backend.AccessLevelByPublicKey(initialRepo, s.PublicKey())
43 if auth < backend.ReadOnlyAccess {
44 wish.Fatalln(s, errors.ErrUnauthorized)
45 return nil
46 }
47 }
48
49 envs := &sessionEnv{s}
50 output := termenv.NewOutput(s, termenv.WithColorCache(true), termenv.WithEnvironment(envs))
51 c := common.NewCommon(s.Context(), output, pty.Window.Width, pty.Window.Height)
52 c.SetValue(common.ConfigKey, cfg)
53 m := ui.New(c, initialRepo)
54 p := tea.NewProgram(m,
55 tea.WithInput(s),
56 tea.WithOutput(s),
57 tea.WithAltScreen(),
58 tea.WithoutCatchPanics(),
59 tea.WithMouseCellMotion(),
60 )
61
62 tuiSessionCounter.WithLabelValues(ak, s.User(), initialRepo, pty.Term).Inc()
63
64 return p
65 }
66}
67
68var _ termenv.Environ = &sessionEnv{}
69
70type sessionEnv struct {
71 ssh.Session
72}
73
74func (s *sessionEnv) Environ() []string {
75 pty, _, _ := s.Pty()
76 return append(s.Session.Environ(), "TERM="+pty.Term)
77}
78
79func (s *sessionEnv) Getenv(key string) string {
80 for _, env := range s.Environ() {
81 if strings.HasPrefix(env, key+"=") {
82 return strings.TrimPrefix(env, key+"=")
83 }
84 }
85 return ""
86}