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}