pico

created pr with 48.1 on 2025-02-24T12:55:08Z · by c8ef7d19
added 48.2 on 2025-02-25T01:16:25Z · by c8ef7d19
1: 9033000 = 1: 9033000 refactor(tui): use vaxis
-: ------- > 2: e191f12 chore(tui): setup page navigation
changed status to accepted on 2025-03-13T14:53:20Z · by c8ef7d19
cmds
checkout latest patchset:
ssh pr.pico.sh print 48 | git am -3
checkout any patchset in a patch request:
ssh pr.pico.sh print 48.[rev] | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 48
set PR to open (enables RSS notifications):
ssh pr.pico.sh pr open 48
set PR to draft (stops RSS notifications):
ssh pr.pico.sh pr draft 48

Patchset 48.1 on 2025-02-24T12:55:08Z · commit 9033000

refactor(tui): use vaxis
Eric Bower 2025-02-24T12:47:08Z
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.
Semantic diff summary
7 added, 0 modified, 0 signature changed, 1 removed across 4 analyzed files (1 file skipped: unsupported file type)
+7 -0 cmd/vaxis/ssh/main.go #
......@@ -0,0 +1,7 @@
1+package main
2+
3+import "github.com/picosh/pico/pico"
4+
5+func main() {
6+ pico.StartSshServerVaxis()
7+}
+1 -1 go.mod #
......@@ -26,6 +26,7 @@ replace git.sr.ht/~rockorager/vaxis => github.com/antoniomika/vaxis v0.0.0-20250
2626
2727 require (
2828 git.sr.ht/~delthas/senpai v0.3.1-0.20240425235039-206be659439e
29+ git.sr.ht/~rockorager/vaxis v0.10.3
2930 github.com/alecthomas/chroma/v2 v2.14.0
3031 github.com/antoniomika/syncmap v1.0.0
3132 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
......@@ -79,7 +80,6 @@ require (
7980 codeberg.org/emersion/go-scfg v0.1.0 // indirect
8081 dario.cat/mergo v1.0.0 // indirect
8182 filippo.io/edwards25519 v1.1.0 // indirect
82- git.sr.ht/~rockorager/vaxis v0.10.3 // indirect
8383 github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
8484 github.com/DavidGamba/go-getoptions v0.31.0 // indirect
8585 github.com/Masterminds/goutils v1.1.1 // indirect
+126 -0 pico/ssh_vaxis.go #
......@@ -0,0 +1,126 @@
1+package pico
2+
3+import (
4+ "context"
5+ "fmt"
6+ "os"
7+ "os/signal"
8+ "syscall"
9+ "time"
10+
11+ "git.sr.ht/~rockorager/vaxis"
12+ "github.com/charmbracelet/promwish"
13+ "github.com/charmbracelet/ssh"
14+ "github.com/charmbracelet/wish"
15+ "github.com/picosh/pico/db/postgres"
16+ "github.com/picosh/pico/shared"
17+ "github.com/picosh/pico/tuivax"
18+ wsh "github.com/picosh/pico/wish"
19+ "github.com/picosh/send/auth"
20+ "github.com/picosh/send/list"
21+ "github.com/picosh/send/pipe"
22+ wishrsync "github.com/picosh/send/protocols/rsync"
23+ "github.com/picosh/send/protocols/scp"
24+ "github.com/picosh/send/protocols/sftp"
25+ "github.com/picosh/send/proxy"
26+ "github.com/picosh/utils"
27+)
28+
29+func createRouterVaxis(cfg *shared.ConfigSite, handler *UploadHandler, cliHandler *CliHandler) proxy.Router {
30+ return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
31+ return []wish.Middleware{
32+ pipe.Middleware(handler, ""),
33+ list.Middleware(handler),
34+ scp.Middleware(handler),
35+ wishrsync.Middleware(handler),
36+ auth.Middleware(handler),
37+ wsh.PtyMdw(createTui()),
38+ WishMiddleware(cliHandler),
39+ wsh.LogMiddleware(handler.GetLogger()),
40+ }
41+ }
42+}
43+
44+func withProxyVaxis(cfg *shared.ConfigSite, handler *UploadHandler, cliHandler *CliHandler, otherMiddleware ...wish.Middleware) ssh.Option {
45+ return func(server *ssh.Server) error {
46+ err := sftp.SSHOption(handler)(server)
47+ if err != nil {
48+ return err
49+ }
50+
51+ return proxy.WithProxy(createRouterVaxis(cfg, handler, cliHandler), otherMiddleware...)(server)
52+ }
53+}
54+
55+func createTui() wish.Middleware {
56+ return func(next ssh.Handler) ssh.Handler {
57+ return func(sesh ssh.Session) {
58+ vty, err := shared.NewVConsole(sesh)
59+ if err != nil {
60+ panic(err)
61+ }
62+ opts := vaxis.Options{
63+ WithConsole: vty,
64+ }
65+ tuivax.NewTui(opts)
66+ }
67+ }
68+}
69+
70+func StartSshServerVaxis() {
71+ host := utils.GetEnv("PICO_HOST", "0.0.0.0")
72+ port := utils.GetEnv("PICO_SSH_PORT", "2222")
73+ promPort := utils.GetEnv("PICO_PROM_PORT", "9222")
74+ cfg := NewConfigSite()
75+ logger := cfg.Logger
76+ dbpool := postgres.NewDB(cfg.DbURL, cfg.Logger)
77+ defer dbpool.Close()
78+
79+ handler := NewUploadHandler(
80+ dbpool,
81+ cfg,
82+ )
83+ cliHandler := &CliHandler{
84+ Logger: logger,
85+ DBPool: dbpool,
86+ }
87+
88+ sshAuth := shared.NewSshAuthHandler(dbpool, logger)
89+ s, err := wish.NewServer(
90+ wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
91+ wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
92+ wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
93+ sshAuth.PubkeyAuthHandler(ctx, key)
94+ return true
95+ }),
96+ withProxyVaxis(
97+ cfg,
98+ handler,
99+ cliHandler,
100+ promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "pico-ssh"),
101+ ),
102+ )
103+ if err != nil {
104+ logger.Error(err.Error())
105+ return
106+ }
107+
108+ done := make(chan os.Signal, 1)
109+ signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
110+ logger.Info("starting SSH server on", "host", host, "port", port)
111+ go func() {
112+ if err = s.ListenAndServe(); err != nil {
113+ logger.Error("serve", "err", err.Error())
114+ os.Exit(1)
115+ }
116+ }()
117+
118+ <-done
119+ logger.Info("stopping SSH server")
120+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
121+ defer func() { cancel() }()
122+ if err := s.Shutdown(ctx); err != nil {
123+ logger.Error("shutdown", "err", err.Error())
124+ os.Exit(1)
125+ }
126+}
+11 -2 shared/senpai.go #
......@@ -123,10 +123,9 @@ func (v *VConsole) Close() error {
123123 return err
124124 }
125125
126-func NewSenpaiApp(sesh ssh.Session, username, pass string) (*senpai.App, error) {
126+func NewVConsole(sesh ssh.Session) (*VConsole, error) {
127127 pty, win, ok := sesh.Pty()
128128 if !ok {
129- slog.Error("PTY not found")
130129 return nil, fmt.Errorf("PTY not found")
131130 }
132131
......@@ -176,6 +175,16 @@ func NewSenpaiApp(sesh ssh.Session, username, pass string) (*senpai.App, error)
176175 }
177176 }()
178177
178+ return vty, nil
179+}
180+
181+func NewSenpaiApp(sesh ssh.Session, username, pass string) (*senpai.App, error) {
182+ vty, err := NewVConsole(sesh)
183+ if err != nil {
184+ slog.Error("PTY not found")
185+ return nil, err
186+ }
187+
179188 senpaiCfg := senpai.Defaults()
180189 senpaiCfg.TLS = true
181190 senpaiCfg.Addr = "irc.pico.sh:6697"
+30 -0 tuivax/ui.go #
......@@ -0,0 +1,30 @@
1+package tuivax
2+
3+import (
4+ "git.sr.ht/~rockorager/vaxis"
5+)
6+
7+func NewTui(opts vaxis.Options) {
8+ vx, err := vaxis.New(opts)
9+ if err != nil {
10+ panic(err)
11+ }
12+ defer vx.Close()
13+ for ev := range vx.Events() {
14+ switch ev := ev.(type) {
15+ case vaxis.Key:
16+ switch ev.String() {
17+ case "Ctrl+c":
18+ return
19+ case "q":
20+ return
21+ case "Escape":
22+ return
23+ }
24+ }
25+ win := vx.Window()
26+ win.Clear()
27+ win.Print(vaxis.Segment{Text: "Hello, World!"})
28+ vx.Render()
29+ }
30+}
Back to top