mirror of
https://github.com/maaslalani/slides.git
synced 2026-01-09 14:28:05 -05:00
Add ssh server functionality (#150)
* Add ssh server functionality via `wish` library This adds a `-server` flag which will spin up an ssh server that executes the slides bubbletea application upon each ssh session.
This commit is contained in:
89
internal/cmd/serve.go
Normal file
89
internal/cmd/serve.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/maaslalani/slides/internal/model"
|
||||
"github.com/maaslalani/slides/internal/navigation"
|
||||
"github.com/maaslalani/slides/internal/server"
|
||||
"github.com/muesli/coral"
|
||||
)
|
||||
|
||||
var (
|
||||
host string
|
||||
port int
|
||||
keyPath string
|
||||
err error
|
||||
fileName string
|
||||
|
||||
ServeCmd = &coral.Command{
|
||||
Use: "serve <file.md>",
|
||||
Aliases: []string{"server"},
|
||||
Short: "Start an SSH server to run slides",
|
||||
Args: coral.ArbitraryArgs,
|
||||
RunE: func(cmd *coral.Command, args []string) error {
|
||||
k := os.Getenv("SLIDES_SERVER_KEY_PATH")
|
||||
if k != "" {
|
||||
keyPath = k
|
||||
}
|
||||
h := os.Getenv("SLIDES_SERVER_HOST")
|
||||
if h != "" {
|
||||
host = h
|
||||
}
|
||||
p := os.Getenv("SLIDES_SERVER_PORT")
|
||||
if p != "" {
|
||||
port, _ = strconv.Atoi(p)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
fileName = args[0]
|
||||
}
|
||||
|
||||
presentation := model.Model{
|
||||
Page: 0,
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
FileName: fileName,
|
||||
Search: navigation.NewSearch(),
|
||||
}
|
||||
err = presentation.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, err := server.NewServer(keyPath, host, port, presentation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
done := make(chan os.Signal, 1)
|
||||
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
log.Printf("Starting Slides server on %s:%d", host, port)
|
||||
go func() {
|
||||
if err = s.Start(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}()
|
||||
|
||||
<-done
|
||||
log.Print("Stopping Slides server")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer func() { cancel() }()
|
||||
if err := s.Shutdown(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
ServeCmd.Flags().StringVar(&keyPath, "keyPath", "slides", "Server private key path")
|
||||
ServeCmd.Flags().StringVar(&host, "host", "localhost", "Server host to bind to")
|
||||
ServeCmd.Flags().IntVar(&port, "port", 53531, "Server port to bind to")
|
||||
}
|
||||
32
internal/server/middleware.go
Normal file
32
internal/server/middleware.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/wish"
|
||||
bm "github.com/charmbracelet/wish/bubbletea"
|
||||
"github.com/gliderlabs/ssh"
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
func slidesMiddleware(srv *Server) wish.Middleware {
|
||||
newProg := func(m tea.Model, opts ...tea.ProgramOption) *tea.Program {
|
||||
p := tea.NewProgram(m, opts...)
|
||||
return p
|
||||
}
|
||||
teaHandler := func(s ssh.Session) *tea.Program {
|
||||
_, _, active := s.Pty()
|
||||
if !active {
|
||||
fmt.Println("no active terminal, skipping")
|
||||
err := s.Exit(1)
|
||||
if err != nil {
|
||||
fmt.Println("Error exiting session")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return newProg(srv.presentation, tea.WithInput(s), tea.WithOutput(s), tea.WithAltScreen())
|
||||
}
|
||||
return bm.MiddlewareWithProgramHandler(teaHandler, termenv.ANSI256)
|
||||
}
|
||||
|
||||
48
internal/server/server.go
Normal file
48
internal/server/server.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/charmbracelet/wish"
|
||||
"github.com/gliderlabs/ssh"
|
||||
"github.com/maaslalani/slides/internal/model"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
host string
|
||||
port int
|
||||
srv *ssh.Server
|
||||
presentation model.Model
|
||||
}
|
||||
|
||||
// NewServer creates a new server.
|
||||
func NewServer(keyPath, host string, port int, presentation model.Model) (*Server, error) {
|
||||
s := &Server{
|
||||
host: host,
|
||||
port: port,
|
||||
presentation: presentation,
|
||||
}
|
||||
srv, err := wish.NewServer(
|
||||
wish.WithHostKeyPath(keyPath),
|
||||
wish.WithAddress(fmt.Sprintf("%s:%d", host, port)),
|
||||
wish.WithMiddleware(
|
||||
slidesMiddleware(s),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.srv = srv
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Start starts the ssh server.
|
||||
func (s *Server) Start() error {
|
||||
return s.srv.ListenAndServe()
|
||||
}
|
||||
|
||||
// Shutdown shuts down the server.
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
return s.srv.Shutdown(ctx)
|
||||
}
|
||||
Reference in New Issue
Block a user