package cilogs
import (
"context"
"fmt"
"log/slog"
"github.com/charmbracelet/ssh"
"github.com/charmbracelet/wish"
tea "github.com/charmbracelet/wish/bubbletea"
)
type Config struct {
ListenAddr string
HostKeyPath string
}
type Server struct {
config Config
spindles *Spindles
logger *slog.Logger
}
func New(cfg Config, spindles *Spindles, logger *slog.Logger) *Server {
return &Server{config: cfg, spindles: spindles, logger: logger}
}
func (s *Server) ListenAndServe(ctx context.Context) error {
opts := []ssh.Option{
wish.WithAddress(s.config.ListenAddr),
wish.WithMiddleware(
tea.Middleware(s.teaHandler),
requirePty,
),
}
if s.config.HostKeyPath != "" {
opts = append(opts, wish.WithHostKeyPath(s.config.HostKeyPath))
}
srv, err := wish.NewServer(opts...)
if err != nil {
return err
}
go func() {
<-ctx.Done()
s.logger.Info("shutting down SSH log server")
srv.Close()
}()
s.logger.Info("SSH log server listening", "address", s.config.ListenAddr)
if err := srv.ListenAndServe(); err != ssh.ErrServerClosed {
return err
}
s.logger.Info("SSH log server stopped")
return nil
}
// requirePty is a middleware that rejects connections without a PTY and tells the user to pass -t.
func requirePty(next ssh.Handler) ssh.Handler {
return func(sess ssh.Session) {
_, _, ok := sess.Pty()
if !ok {
fmt.Fprintf(sess.Stderr(), "error: no terminal allocated\nhint: use `ssh -t` to force PTY allocation\n")
sess.Exit(1)
return
}
next(sess)
}
}