-
Notifications
You must be signed in to change notification settings - Fork 80
/
Copy pathmain.go
128 lines (115 loc) · 3 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package main
// An example Bubble Tea server. This will put an ssh session into alt screen
// and continually print up to date terminal information.
import (
"context"
"errors"
"fmt"
"net"
"os"
"os/signal"
"syscall"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/log"
"github.com/charmbracelet/ssh"
"github.com/charmbracelet/wish"
"github.com/charmbracelet/wish/bubbletea"
"github.com/charmbracelet/wish/logging"
"github.com/muesli/termenv"
)
const (
host = "localhost"
port = "23234"
)
func main() {
s, err := wish.NewServer(
wish.WithAddress(net.JoinHostPort(host, port)),
wish.WithHostKeyPath(".ssh/id_ed25519"),
wish.WithMiddleware(
myCustomBubbleteaMiddleware(),
logging.Middleware(),
),
)
if err != nil {
log.Error("Could not start server", "error", err)
}
done := make(chan os.Signal, 1)
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
log.Info("Starting SSH server", "host", host, "port", port)
go func() {
if err = s.ListenAndServe(); err != nil && !errors.Is(err, ssh.ErrServerClosed) {
log.Error("Could not start server", "error", err)
done <- nil
}
}()
<-done
log.Info("Stopping SSH server")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer func() { cancel() }()
if err := s.Shutdown(ctx); err != nil && !errors.Is(err, ssh.ErrServerClosed) {
log.Error("Could not stop server", "error", err)
}
}
// You can write your own custom bubbletea middleware that wraps tea.Program.
// Make sure you set the program input and output to ssh.Session.
func myCustomBubbleteaMiddleware() wish.Middleware {
newProg := func(m tea.Model, opts ...tea.ProgramOption) *tea.Program {
p := tea.NewProgram(m, opts...)
go func() {
for {
<-time.After(1 * time.Second)
p.Send(timeMsg(time.Now()))
}
}()
return p
}
teaHandler := func(s ssh.Session) *tea.Program {
pty, _, active := s.Pty()
if !active {
wish.Fatalln(s, "no active terminal, skipping")
return nil
}
m := model{
term: pty.Term,
width: pty.Window.Width,
height: pty.Window.Height,
time: time.Now(),
}
return newProg(m, append(bubbletea.MakeOptions(s), tea.WithAltScreen())...)
}
return bubbletea.MiddlewareWithProgramHandler(teaHandler, termenv.ANSI256)
}
// Just a generic tea.Model to demo terminal information of ssh.
type model struct {
term string
width int
height int
time time.Time
}
type timeMsg time.Time
func (m model) Init() tea.Cmd {
return nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case timeMsg:
m.time = time.Time(msg)
case tea.WindowSizeMsg:
m.height = msg.Height
m.width = msg.Width
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() string {
s := "Your term is %s\n"
s += "Your window size is x: %d y: %d\n"
s += "Time: " + m.time.Format(time.RFC1123) + "\n\n"
s += "Press 'q' to quit\n"
return fmt.Sprintf(s, m.term, m.width, m.height)
}