Logs
Patchset ps-105
refactor(tui): use vaxis
Eric Bower
cmd/vaxis/ssh/main.go
+7
-0
go.mod
+1
-1
pico/ssh_vaxis.go
+126
-0
shared/senpai.go
+11
-2
tuivax/ui.go
+30
-0
refactor(tui): use vaxis
While we really enjoyed the charm stack for our ssh apps, we are at a point where we want to reduce our overall dependencies for our SSH apps. With charm we have: - `crypto/ssh` - `gliberlabs/ssh` - `charmbracelet/ssh` - `charmbracelet/wish` There's a lot that can go wrong here and we have seen quite a bit of thrashing within these libraries that required us to make moderate changes when upgrading. We also enjoyed bubbletea/lipgloss but we are at the point where we would like to switch to `vaxis` since it is more inline with our design ethos. We are basically going to replace 5 go packages with 1 and we are starting with the TUI.
cmd/vaxis/ssh/main.go
link
+7
-0
+7
-0
go.mod
link
+1
-1
+1
-1
1diff --git a/go.mod b/go.mod
2index 22d6c79..42579e7 100644
3--- a/go.mod
4+++ b/go.mod
5@@ -26,6 +26,7 @@ replace git.sr.ht/~rockorager/vaxis => github.com/antoniomika/vaxis v0.0.0-20250
6
7 require (
8 git.sr.ht/~delthas/senpai v0.3.1-0.20240425235039-206be659439e
9+ git.sr.ht/~rockorager/vaxis v0.10.3
10 github.com/alecthomas/chroma/v2 v2.14.0
11 github.com/antoniomika/syncmap v1.0.0
12 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
13@@ -79,7 +80,6 @@ require (
14 codeberg.org/emersion/go-scfg v0.1.0 // indirect
15 dario.cat/mergo v1.0.0 // indirect
16 filippo.io/edwards25519 v1.1.0 // indirect
17- git.sr.ht/~rockorager/vaxis v0.10.3 // indirect
18 github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
19 github.com/DavidGamba/go-getoptions v0.31.0 // indirect
20 github.com/Masterminds/goutils v1.1.1 // indirect
pico/ssh_vaxis.go
link
+126
-0
+126
-0
1diff --git a/pico/ssh_vaxis.go b/pico/ssh_vaxis.go
2new file mode 100644
3index 0000000..37fee51
4--- /dev/null
5+++ b/pico/ssh_vaxis.go
6@@ -0,0 +1,126 @@
7+package pico
8+
9+import (
10+ "context"
11+ "fmt"
12+ "os"
13+ "os/signal"
14+ "syscall"
15+ "time"
16+
17+ "git.sr.ht/~rockorager/vaxis"
18+ "github.com/charmbracelet/promwish"
19+ "github.com/charmbracelet/ssh"
20+ "github.com/charmbracelet/wish"
21+ "github.com/picosh/pico/db/postgres"
22+ "github.com/picosh/pico/shared"
23+ "github.com/picosh/pico/tuivax"
24+ wsh "github.com/picosh/pico/wish"
25+ "github.com/picosh/send/auth"
26+ "github.com/picosh/send/list"
27+ "github.com/picosh/send/pipe"
28+ wishrsync "github.com/picosh/send/protocols/rsync"
29+ "github.com/picosh/send/protocols/scp"
30+ "github.com/picosh/send/protocols/sftp"
31+ "github.com/picosh/send/proxy"
32+ "github.com/picosh/utils"
33+)
34+
35+func createRouterVaxis(cfg *shared.ConfigSite, handler *UploadHandler, cliHandler *CliHandler) proxy.Router {
36+ return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
37+ return []wish.Middleware{
38+ pipe.Middleware(handler, ""),
39+ list.Middleware(handler),
40+ scp.Middleware(handler),
41+ wishrsync.Middleware(handler),
42+ auth.Middleware(handler),
43+ wsh.PtyMdw(createTui()),
44+ WishMiddleware(cliHandler),
45+ wsh.LogMiddleware(handler.GetLogger()),
46+ }
47+ }
48+}
49+
50+func withProxyVaxis(cfg *shared.ConfigSite, handler *UploadHandler, cliHandler *CliHandler, otherMiddleware ...wish.Middleware) ssh.Option {
51+ return func(server *ssh.Server) error {
52+ err := sftp.SSHOption(handler)(server)
53+ if err != nil {
54+ return err
55+ }
56+
57+ return proxy.WithProxy(createRouterVaxis(cfg, handler, cliHandler), otherMiddleware...)(server)
58+ }
59+}
60+
61+func createTui() wish.Middleware {
62+ return func(next ssh.Handler) ssh.Handler {
63+ return func(sesh ssh.Session) {
64+ vty, err := shared.NewVConsole(sesh)
65+ if err != nil {
66+ panic(err)
67+ }
68+ opts := vaxis.Options{
69+ WithConsole: vty,
70+ }
71+ tuivax.NewTui(opts)
72+ }
73+ }
74+}
75+
76+func StartSshServerVaxis() {
77+ host := utils.GetEnv("PICO_HOST", "0.0.0.0")
78+ port := utils.GetEnv("PICO_SSH_PORT", "2222")
79+ promPort := utils.GetEnv("PICO_PROM_PORT", "9222")
80+ cfg := NewConfigSite()
81+ logger := cfg.Logger
82+ dbpool := postgres.NewDB(cfg.DbURL, cfg.Logger)
83+ defer dbpool.Close()
84+
85+ handler := NewUploadHandler(
86+ dbpool,
87+ cfg,
88+ )
89+ cliHandler := &CliHandler{
90+ Logger: logger,
91+ DBPool: dbpool,
92+ }
93+
94+ sshAuth := shared.NewSshAuthHandler(dbpool, logger)
95+ s, err := wish.NewServer(
96+ wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
97+ wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
98+ wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
99+ sshAuth.PubkeyAuthHandler(ctx, key)
100+ return true
101+ }),
102+ withProxyVaxis(
103+ cfg,
104+ handler,
105+ cliHandler,
106+ promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "pico-ssh"),
107+ ),
108+ )
109+ if err != nil {
110+ logger.Error(err.Error())
111+ return
112+ }
113+
114+ done := make(chan os.Signal, 1)
115+ signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
116+ logger.Info("starting SSH server on", "host", host, "port", port)
117+ go func() {
118+ if err = s.ListenAndServe(); err != nil {
119+ logger.Error("serve", "err", err.Error())
120+ os.Exit(1)
121+ }
122+ }()
123+
124+ <-done
125+ logger.Info("stopping SSH server")
126+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
127+ defer func() { cancel() }()
128+ if err := s.Shutdown(ctx); err != nil {
129+ logger.Error("shutdown", "err", err.Error())
130+ os.Exit(1)
131+ }
132+}
tuivax/ui.go
link
+30
-0
+30
-0
1diff --git a/tuivax/ui.go b/tuivax/ui.go
2new file mode 100644
3index 0000000..538b789
4--- /dev/null
5+++ b/tuivax/ui.go
6@@ -0,0 +1,30 @@
7+package tuivax
8+
9+import (
10+ "git.sr.ht/~rockorager/vaxis"
11+)
12+
13+func NewTui(opts vaxis.Options) {
14+ vx, err := vaxis.New(opts)
15+ if err != nil {
16+ panic(err)
17+ }
18+ defer vx.Close()
19+ for ev := range vx.Events() {
20+ switch ev := ev.(type) {
21+ case vaxis.Key:
22+ switch ev.String() {
23+ case "Ctrl+c":
24+ return
25+ case "q":
26+ return
27+ case "Escape":
28+ return
29+ }
30+ }
31+ win := vx.Window()
32+ win.Clear()
33+ win.Print(vaxis.Segment{Text: "Hello, World!"})
34+ vx.Render()
35+ }
36+}