- commit
- 72d930b
- parent
- 2c4edfe
- author
- Antonio Mika
- date
- 2025-03-11 16:45:10 -0400 EDT
More updates
17 files changed,
+353,
-374
+17,
-18
1@@ -5,7 +5,6 @@ import (
2 "text/tabwriter"
3 "time"
4
5- "github.com/charmbracelet/wish"
6 "github.com/picosh/pico/db"
7 "github.com/picosh/pico/pssh"
8 "github.com/picosh/pico/shared"
9@@ -80,11 +79,10 @@ func WishMiddleware(dbpool db.DB, cfg *shared.ConfigSite) pssh.SSHServerMiddlewa
10 post.Data.Attempts,
11 )
12 }
13- writer.Flush()
14- return
15+ return writer.Flush()
16 } else if cmd == "rm" {
17 filename := args[1]
18- wish.Printf(sesh, "removing digest post %s\n", filename)
19+ fmt.Fprintf(sesh, "removing digest post %s\n", filename)
20 write := false
21 if len(args) > 2 {
22 writeRaw := args[2]
23@@ -95,41 +93,42 @@ func WishMiddleware(dbpool db.DB, cfg *shared.ConfigSite) pssh.SSHServerMiddlewa
24
25 post, err := dbpool.FindPostWithFilename(filename, user.ID, "feeds")
26 if err != nil {
27- wish.Errorln(sesh, err)
28- return
29+ fmt.Fprintln(sesh.Stderr(), err)
30+ return err
31 }
32 if write {
33 err = dbpool.RemovePosts([]string{post.ID})
34 if err != nil {
35- wish.Errorln(sesh, err)
36+ fmt.Fprintln(sesh.Stderr(), err)
37 }
38 }
39- wish.Printf(sesh, "digest post removed %s\n", filename)
40+ fmt.Fprintf(sesh, "digest post removed %s\n", filename)
41 if !write {
42- wish.Println(sesh, "WARNING: *must* append with `--write` for the changes to persist.")
43+ fmt.Fprintln(sesh, "WARNING: *must* append with `--write` for the changes to persist.")
44 }
45- return
46+ return err
47 } else if cmd == "run" {
48 if len(args) < 2 {
49- wish.Errorln(sesh, "must provide filename of post to run")
50- return
51+ err := fmt.Errorf("must provide filename of post to run")
52+ fmt.Fprintln(sesh.Stderr(), err)
53+ return err
54 }
55 filename := args[1]
56 post, err := dbpool.FindPostWithFilename(filename, user.ID, "feeds")
57 if err != nil {
58- wish.Errorln(sesh, err)
59- return
60+ fmt.Fprintln(sesh.Stderr(), err)
61+ return err
62 }
63- wish.Printf(sesh, "running feed post: %s\n", filename)
64+ fmt.Fprintf(sesh, "running feed post: %s\n", filename)
65 fetcher := NewFetcher(dbpool, cfg)
66 err = fetcher.RunPost(logger, user, post, true)
67 if err != nil {
68- wish.Errorln(sesh, err)
69+ fmt.Fprintln(sesh.Stderr(), err)
70 }
71- return
72+ return err
73 }
74
75- next(sesh)
76+ return next(sesh)
77 }
78 }
79 }
+3,
-3
1@@ -8,9 +8,9 @@ import (
2 "strings"
3 "time"
4
5- "github.com/charmbracelet/ssh"
6 "github.com/picosh/pico/db"
7 "github.com/picosh/pico/filehandlers"
8+ "github.com/picosh/pico/pssh"
9 "github.com/picosh/pico/shared"
10 "github.com/picosh/utils"
11 )
12@@ -20,7 +20,7 @@ type FeedHooks struct {
13 Db db.DB
14 }
15
16-func (p *FeedHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData) (bool, error) {
17+func (p *FeedHooks) FileValidate(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) (bool, error) {
18 if !utils.IsTextFile(string(data.Text)) {
19 err := fmt.Errorf(
20 "WARNING: (%s) invalid file must be plain text (utf-8), skipping",
21@@ -73,7 +73,7 @@ func (p *FeedHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData)
22 return true, nil
23 }
24
25-func (p *FeedHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData) error {
26+func (p *FeedHooks) FileMeta(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) error {
27 if data.Data.LastDigest == nil {
28 now := time.Now()
29 // let it run on the next loop
+48,
-63
1@@ -2,71 +2,34 @@ package feeds
2
3 import (
4 "context"
5- "fmt"
6 "os"
7 "os/signal"
8 "syscall"
9- "time"
10
11- "github.com/charmbracelet/promwish"
12- "github.com/charmbracelet/ssh"
13- "github.com/charmbracelet/wish"
14 "github.com/picosh/pico/db/postgres"
15 "github.com/picosh/pico/filehandlers"
16+ "github.com/picosh/pico/pssh"
17 "github.com/picosh/pico/shared"
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/rsync"
24 "github.com/picosh/send/protocols/scp"
25 "github.com/picosh/send/protocols/sftp"
26- "github.com/picosh/send/proxy"
27 "github.com/picosh/utils"
28+ "golang.org/x/crypto/ssh"
29 )
30
31-func createRouter(handler *filehandlers.FileHandlerRouter) proxy.Router {
32- return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
33- return []wish.Middleware{
34- pipe.Middleware(handler, ".txt"),
35- list.Middleware(handler),
36- scp.Middleware(handler),
37- wishrsync.Middleware(handler),
38- auth.Middleware(handler),
39- wsh.PtyMdw(wsh.DeprecatedNotice()),
40- WishMiddleware(handler.DBPool, handler.Cfg),
41- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool),
42- }
43- }
44-}
45-
46-func withProxy(handler *filehandlers.FileHandlerRouter, otherMiddleware ...wish.Middleware) ssh.Option {
47- return func(server *ssh.Server) error {
48- err := sftp.SSHOption(handler)(server)
49- if err != nil {
50- return err
51- }
52-
53- newSubsystemHandlers := map[string]ssh.SubsystemHandler{}
54-
55- for name, subsystemHandler := range server.SubsystemHandlers {
56- newSubsystemHandlers[name] = func(s ssh.Session) {
57- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool)(ssh.Handler(subsystemHandler))(s)
58- }
59- }
60-
61- server.SubsystemHandlers = newSubsystemHandlers
62-
63- return proxy.WithProxy(createRouter(handler), otherMiddleware...)(server)
64- }
65-}
66-
67 func StartSshServer() {
68 host := utils.GetEnv("LISTS_HOST", "0.0.0.0")
69 port := utils.GetEnv("LISTS_SSH_PORT", "2222")
70- promPort := utils.GetEnv("LISTS_PROM_PORT", "9222")
71+ // promPort := utils.GetEnv("LISTS_PROM_PORT", "9222")
72 cfg := NewConfigSite("feeds-ssh")
73 logger := cfg.Logger
74+
75+ ctx, cancel := context.WithCancel(context.Background())
76+ defer cancel()
77+
78 dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
79 defer dbh.Close()
80
81@@ -81,34 +44,56 @@ func StartSshServer() {
82 handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
83
84 sshAuth := shared.NewSshAuthHandler(dbh, logger)
85- s, err := wish.NewServer(
86- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
87- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
88- wish.WithPublicKeyAuth(sshAuth.PubkeyAuthHandler),
89- withProxy(
90- handler,
91- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "feeds-ssh"),
92- ),
93- )
94+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
95+ ListenAddr: "localhost:2222",
96+ ServerConfig: &ssh.ServerConfig{
97+ PublicKeyCallback: sshAuth.PubkeyAuthHandler,
98+ },
99+ Middleware: []pssh.SSHServerMiddleware{
100+ pipe.Middleware(handler, ".txt"),
101+ list.Middleware(handler),
102+ scp.Middleware(handler),
103+ rsync.Middleware(handler),
104+ auth.Middleware(handler),
105+ pssh.PtyMdw(pssh.DeprecatedNotice()),
106+ pssh.LogMiddleware(handler, dbh),
107+ },
108+ SubsystemMiddleware: []pssh.SSHServerMiddleware{
109+ sftp.Middleware(handler),
110+ pssh.LogMiddleware(handler, dbh),
111+ },
112+ })
113+
114+ pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
115+ if err != nil {
116+ logger.Error("failed to read private key file", "error", err)
117+ return
118+ }
119+
120+ signer, err := ssh.ParsePrivateKey(pemBytes)
121 if err != nil {
122- logger.Error(err.Error())
123+ logger.Error("failed to parse private key", "error", err)
124 return
125 }
126
127+ server.Config.AddHostKey(signer)
128+
129 done := make(chan os.Signal, 1)
130+
131 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
132 logger.Info("Starting SSH server", "host", host, "port", port)
133 go func() {
134- if err = s.ListenAndServe(); err != nil {
135- logger.Error(err.Error())
136+ if err = server.ListenAndServe(); err != nil {
137+ logger.Error("serve", "err", err.Error())
138+ os.Exit(1)
139 }
140 }()
141
142- <-done
143- logger.Info("Stopping SSH server")
144- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
145- defer func() { cancel() }()
146- if err := s.Shutdown(ctx); err != nil {
147- logger.Error(err.Error())
148+ exit := func() {
149+ logger.Info("stopping ssh server")
150+ cancel()
151 }
152+
153+ <-done
154+ exit()
155 }
M
go.mod
+0,
-1
1@@ -29,7 +29,6 @@ require (
2 github.com/antoniomika/syncmap v1.0.0
3 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
4 github.com/charmbracelet/lipgloss v1.0.0
5- github.com/charmbracelet/promwish v0.7.0
6 github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef
7 github.com/charmbracelet/wish v1.4.6
8 github.com/containerd/console v1.0.4
M
go.sum
+0,
-2
1@@ -160,8 +160,6 @@ github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O
2 github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
3 github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
4 github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
5-github.com/charmbracelet/promwish v0.7.0 h1:oaMH+ey6W4DDIv1xucS8jL1ik/Q46qxjNXlh6XxEm+s=
6-github.com/charmbracelet/promwish v0.7.0/go.mod h1:WbRJN9irg8LmsBU8G2rFF8md9O3rSg63qrnqquP/+cs=
7 github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef h1:dNZwn4is5svUd+sQEGsrXtp7VwD2ipYaCkKMzcpAEIE=
8 github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef/go.mod h1:hg+I6gvlMl16nS9ZzQNgBIrrCasGwEw0QiLsDcP01Ko=
9 github.com/charmbracelet/wish v1.4.6 h1:27WRqMTUmyFoZASoaAaEe78Je7LTU4VqyoBxnl4d9XA=
+3,
-3
1@@ -7,9 +7,9 @@ import (
2 "time"
3
4 "github.com/araddon/dateparse"
5- "github.com/charmbracelet/ssh"
6 "github.com/picosh/pico/db"
7 "github.com/picosh/pico/filehandlers"
8+ "github.com/picosh/pico/pssh"
9 "github.com/picosh/pico/shared"
10 "github.com/picosh/utils"
11 )
12@@ -21,7 +21,7 @@ type FileHooks struct {
13 Db db.DB
14 }
15
16-func (p *FileHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData) (bool, error) {
17+func (p *FileHooks) FileValidate(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) (bool, error) {
18 if !utils.IsTextFile(string(data.Text)) {
19 err := fmt.Errorf(
20 "ERROR: (%s) invalid file must be plain text (utf-8), skipping",
21@@ -42,7 +42,7 @@ func (p *FileHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData)
22 return true, nil
23 }
24
25-func (p *FileHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData) error {
26+func (p *FileHooks) FileMeta(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) error {
27 data.Title = utils.ToUpper(data.Slug)
28 // we want the slug to be the filename for pastes
29 data.Slug = data.Filename
+48,
-57
1@@ -2,64 +2,34 @@ package pastes
2
3 import (
4 "context"
5- "fmt"
6 "os"
7 "os/signal"
8 "syscall"
9- "time"
10
11- "github.com/charmbracelet/promwish"
12- "github.com/charmbracelet/ssh"
13- "github.com/charmbracelet/wish"
14 "github.com/picosh/pico/db/postgres"
15 "github.com/picosh/pico/filehandlers"
16+ "github.com/picosh/pico/pssh"
17 "github.com/picosh/pico/shared"
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/rsync"
24 "github.com/picosh/send/protocols/scp"
25- "github.com/picosh/send/proxy"
26+ "github.com/picosh/send/protocols/sftp"
27 "github.com/picosh/utils"
28+ "golang.org/x/crypto/ssh"
29 )
30
31-func createRouter(handler *filehandlers.FileHandlerRouter) proxy.Router {
32- return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
33- return []wish.Middleware{
34- pipe.Middleware(handler, ""),
35- list.Middleware(handler),
36- scp.Middleware(handler),
37- wishrsync.Middleware(handler),
38- auth.Middleware(handler),
39- wsh.PtyMdw(wsh.DeprecatedNotice()),
40- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool),
41- }
42- }
43-}
44-
45-func withProxy(handler *filehandlers.FileHandlerRouter, otherMiddleware ...wish.Middleware) ssh.Option {
46- return func(server *ssh.Server) error {
47- newSubsystemHandlers := map[string]ssh.SubsystemHandler{}
48-
49- for name, subsystemHandlers := range server.SubsystemHandlers {
50- newSubsystemHandlers[name] = func(s ssh.Session) {
51- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool)(ssh.Handler(subsystemHandlers))
52- }
53- }
54-
55- server.SubsystemHandlers = newSubsystemHandlers
56-
57- return proxy.WithProxy(createRouter(handler), otherMiddleware...)(server)
58- }
59-}
60-
61 func StartSshServer() {
62 host := utils.GetEnv("PASTES_HOST", "0.0.0.0")
63 port := utils.GetEnv("PASTES_SSH_PORT", "2222")
64- promPort := utils.GetEnv("PASTES_PROM_PORT", "9222")
65+ // promPort := utils.GetEnv("PASTES_PROM_PORT", "9222")
66 cfg := NewConfigSite("pastes-ssh")
67 logger := cfg.Logger
68+
69+ ctx, cancel := context.WithCancel(context.Background())
70+ defer cancel()
71+
72 dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
73 defer dbh.Close()
74 hooks := &FileHooks{
75@@ -72,34 +42,55 @@ func StartSshServer() {
76 }
77 handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
78 sshAuth := shared.NewSshAuthHandler(dbh, logger)
79- s, err := wish.NewServer(
80- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
81- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
82- wish.WithPublicKeyAuth(sshAuth.PubkeyAuthHandler),
83- withProxy(
84- handler,
85- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "pastes-ssh"),
86- ),
87- )
88+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
89+ ListenAddr: "localhost:2222",
90+ ServerConfig: &ssh.ServerConfig{
91+ PublicKeyCallback: sshAuth.PubkeyAuthHandler,
92+ },
93+ Middleware: []pssh.SSHServerMiddleware{
94+ pipe.Middleware(handler, ""),
95+ list.Middleware(handler),
96+ scp.Middleware(handler),
97+ rsync.Middleware(handler),
98+ auth.Middleware(handler),
99+ pssh.PtyMdw(pssh.DeprecatedNotice()),
100+ pssh.LogMiddleware(handler, dbh),
101+ },
102+ SubsystemMiddleware: []pssh.SSHServerMiddleware{
103+ sftp.Middleware(handler),
104+ pssh.LogMiddleware(handler, dbh),
105+ },
106+ })
107+
108+ pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
109 if err != nil {
110- logger.Error(err.Error())
111+ logger.Error("failed to read private key file", "error", err)
112 return
113 }
114
115+ signer, err := ssh.ParsePrivateKey(pemBytes)
116+ if err != nil {
117+ logger.Error("failed to parse private key", "error", err)
118+ return
119+ }
120+
121+ server.Config.AddHostKey(signer)
122+
123 done := make(chan os.Signal, 1)
124 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
125 logger.Info("Starting SSH server", "host", host, "port", port)
126 go func() {
127- if err = s.ListenAndServe(); err != nil {
128- logger.Error(err.Error())
129+ if err = server.ListenAndServe(); err != nil {
130+ logger.Error("serve", "err", err.Error())
131+ os.Exit(1)
132 }
133 }()
134
135- <-done
136- logger.Info("Stopping SSH server")
137- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
138- defer func() { cancel() }()
139- if err := s.Shutdown(ctx); err != nil {
140- logger.Error(err.Error())
141+ exit := func() {
142+ logger.Info("stopping ssh server")
143+ cancel()
144 }
145+
146+ <-done
147+ exit()
148 }
+0,
-1
1@@ -48,7 +48,6 @@ func StartSshServer(cfg *PgsConfig, killCh chan error) {
2 PublicKeyCallback: sshAuth.PubkeyAuthHandler,
3 },
4 Middleware: []pssh.SSHServerMiddleware{
5- sftp.Middleware(handler),
6 pipe.Middleware(handler, ""),
7 list.Middleware(handler),
8 scp.Middleware(handler),
+31,
-31
1@@ -8,16 +8,15 @@ import (
2 "log/slog"
3 "strings"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/charmbracelet/wish"
7 "github.com/picosh/pico/db"
8+ "github.com/picosh/pico/pssh"
9 "github.com/picosh/pico/shared"
10 "github.com/picosh/utils"
11
12 pipeLogger "github.com/picosh/utils/pipe/log"
13 )
14
15-func getUser(s ssh.Session, dbpool db.DB) (*db.User, error) {
16+func getUser(s *pssh.SSHServerConnSession, dbpool db.DB) (*db.User, error) {
17 if s.PublicKey() == nil {
18 return nil, fmt.Errorf("key not found")
19 }
20@@ -38,7 +37,7 @@ func getUser(s ssh.Session, dbpool db.DB) (*db.User, error) {
21
22 type Cmd struct {
23 User *db.User
24- SshSession ssh.Session
25+ SshSession *pssh.SSHServerConnSession
26 Session utils.CmdSession
27 Log *slog.Logger
28 Dbpool db.DB
29@@ -77,7 +76,7 @@ func (c *Cmd) logs(ctx context.Context) error {
30 user := utils.AnyToStr(parsedData, "user")
31 userId := utils.AnyToStr(parsedData, "userId")
32 if user == c.User.Name || userId == c.User.ID {
33- wish.Println(c.SshSession, line)
34+ fmt.Fprintln(c.SshSession, line)
35 }
36 }
37 return scanner.Err()
38@@ -88,34 +87,34 @@ type CliHandler struct {
39 Logger *slog.Logger
40 }
41
42-func WishMiddleware(handler *CliHandler) wish.Middleware {
43+func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
44 dbpool := handler.DBPool
45 log := handler.Logger
46
47- return func(next ssh.Handler) ssh.Handler {
48- return func(sesh ssh.Session) {
49+ return func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
50+ return func(sesh *pssh.SSHServerConnSession) error {
51 args := sesh.Command()
52 if len(args) == 0 {
53- next(sesh)
54- return
55+ return next(sesh)
56 }
57
58 user, err := getUser(sesh, dbpool)
59 if err != nil {
60- wish.Errorf(sesh, "detected ssh command: %s\n", args)
61+ fmt.Fprintf(sesh.Stderr(), "detected ssh command: %s\n", args)
62 s := fmt.Errorf("error: you need to create an account before using the remote cli: %w", err)
63- wish.Fatalln(sesh, s)
64- return
65+ sesh.Fatal(s)
66+ return s
67 }
68
69 if len(args) > 0 && args[0] == "chat" {
70 _, _, hasPty := sesh.Pty()
71 if !hasPty {
72- wish.Fatalln(
73- sesh,
74- "In order to render chat you need to enable PTY with the `ssh -t` flag",
75+ err := fmt.Errorf(
76+ "in order to render chat you need to enable PTY with the `ssh -t` flag",
77 )
78- return
79+
80+ sesh.Fatal(err)
81+ return err
82 }
83
84 ff, err := dbpool.FindFeatureForUser(user.ID, "plus")
85@@ -124,29 +123,30 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
86 ff, err = dbpool.FindFeatureForUser(user.ID, "bouncer")
87 if err != nil {
88 handler.Logger.Error("Unable to find bouncer feature flag", "err", err, "user", user, "command", args)
89- wish.Fatalln(sesh, "Unable to find plus or bouncer feature flag")
90- return
91+ sesh.Fatal(err)
92+ return err
93 }
94 }
95
96 if ff == nil {
97- wish.Fatalln(sesh, "Unable to find plus or bouncer feature flag")
98- return
99+ err = fmt.Errorf("unable to find plus or bouncer feature flag")
100+ sesh.Fatal(err)
101+ return err
102 }
103
104 pass, err := dbpool.UpsertToken(user.ID, "pico-chat")
105 if err != nil {
106- wish.Fatalln(sesh, err)
107- return
108+ sesh.Fatal(err)
109+ return err
110 }
111 app, err := shared.NewSenpaiApp(sesh, user.Name, pass)
112 if err != nil {
113- wish.Fatalln(sesh, err)
114- return
115+ sesh.Fatal(err)
116+ return err
117 }
118 app.Run()
119 app.Close()
120- return
121+ return err
122 }
123
124 opts := Cmd{
125@@ -162,20 +162,20 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
126 if len(args) == 1 {
127 if cmd == "help" {
128 opts.help()
129- return
130+ return nil
131 } else if cmd == "logs" {
132 err = opts.logs(sesh.Context())
133 if err != nil {
134- wish.Fatalln(sesh, err)
135+ sesh.Fatal(err)
136 }
137- return
138+ return nil
139 } else {
140 next(sesh)
141- return
142+ return nil
143 }
144 }
145
146- next(sesh)
147+ return next(sesh)
148 }
149 }
150 }
+62,
-68
1@@ -2,73 +2,37 @@ 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/pssh"
17 "github.com/picosh/pico/shared"
18 "github.com/picosh/pico/tui"
19- wsh "github.com/picosh/pico/wish"
20 "github.com/picosh/send/auth"
21 "github.com/picosh/send/list"
22 "github.com/picosh/send/pipe"
23- wishrsync "github.com/picosh/send/protocols/rsync"
24+ "github.com/picosh/send/protocols/rsync"
25 "github.com/picosh/send/protocols/scp"
26 "github.com/picosh/send/protocols/sftp"
27- "github.com/picosh/send/proxy"
28 "github.com/picosh/utils"
29+ "golang.org/x/crypto/ssh"
30 )
31
32-func createRouterVaxis(cfg *shared.ConfigSite, handler *UploadHandler, cliHandler *CliHandler) proxy.Router {
33- return func(sh ssh.Handler, sesh ssh.Session) []wish.Middleware {
34- shrd := &tui.SharedModel{
35- Session: sesh,
36- Cfg: cfg,
37- Dbpool: handler.DBPool,
38- Logger: cfg.Logger,
39- }
40- return []wish.Middleware{
41- pipe.Middleware(handler, ""),
42- list.Middleware(handler),
43- scp.Middleware(handler),
44- wishrsync.Middleware(handler),
45- auth.Middleware(handler),
46- wsh.PtyMdw(createTui(shrd)),
47- WishMiddleware(cliHandler),
48- wsh.LogMiddleware(handler.GetLogger(sesh), handler.DBPool),
49- }
50- }
51-}
52-
53-func withProxyVaxis(cfg *shared.ConfigSite, handler *UploadHandler, cliHandler *CliHandler, otherMiddleware ...wish.Middleware) ssh.Option {
54- return func(server *ssh.Server) error {
55- err := sftp.SSHOption(handler)(server)
56- if err != nil {
57- return err
58- }
59-
60- return proxy.WithProxy(createRouterVaxis(cfg, handler, cliHandler), otherMiddleware...)(server)
61- }
62-}
63-
64-func createTui(shrd *tui.SharedModel) wish.Middleware {
65- return func(next ssh.Handler) ssh.Handler {
66- return func(sesh ssh.Session) {
67+func createTui(shrd *tui.SharedModel) pssh.SSHServerMiddleware {
68+ return func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
69+ return func(sesh *pssh.SSHServerConnSession) error {
70 vty, err := shared.NewVConsole(sesh)
71 if err != nil {
72- panic(err)
73+ return err
74 }
75 opts := vaxis.Options{
76 WithConsole: vty,
77 }
78 tui.NewTui(opts, shrd)
79+ return nil
80 }
81 }
82 }
83@@ -76,9 +40,13 @@ func createTui(shrd *tui.SharedModel) wish.Middleware {
84 func StartSshServer() {
85 host := utils.GetEnv("PICO_HOST", "0.0.0.0")
86 port := utils.GetEnv("PICO_SSH_PORT", "2222")
87- promPort := utils.GetEnv("PICO_PROM_PORT", "9222")
88+ // promPort := utils.GetEnv("PICO_PROM_PORT", "9222")
89 cfg := NewConfigSite("pico-ssh")
90 logger := cfg.Logger
91+
92+ ctx, cancel := context.WithCancel(context.Background())
93+ defer cancel()
94+
95 dbpool := postgres.NewDB(cfg.DbURL, cfg.Logger)
96 defer dbpool.Close()
97
98@@ -86,47 +54,73 @@ func StartSshServer() {
99 dbpool,
100 cfg,
101 )
102+
103 cliHandler := &CliHandler{
104 Logger: logger,
105 DBPool: dbpool,
106 }
107
108 sshAuth := shared.NewSshAuthHandler(dbpool, logger)
109- s, err := wish.NewServer(
110- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
111- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
112- wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
113- sshAuth.PubkeyAuthHandler(ctx, key)
114- return true
115- }),
116- withProxyVaxis(
117- cfg,
118- handler,
119- cliHandler,
120- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "pico-ssh"),
121- ),
122- )
123+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
124+ ListenAddr: "localhost:2222",
125+ ServerConfig: &ssh.ServerConfig{
126+ PublicKeyCallback: sshAuth.PubkeyAuthHandler,
127+ },
128+ Middleware: []pssh.SSHServerMiddleware{
129+ pipe.Middleware(handler, ""),
130+ list.Middleware(handler),
131+ scp.Middleware(handler),
132+ rsync.Middleware(handler),
133+ auth.Middleware(handler),
134+ func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
135+ return func(sesh *pssh.SSHServerConnSession) error {
136+ shrd := &tui.SharedModel{
137+ Session: sesh,
138+ Cfg: cfg,
139+ Dbpool: handler.DBPool,
140+ Logger: cfg.Logger,
141+ }
142+ return pssh.PtyMdw(createTui(shrd))(next)(sesh)
143+ }
144+ },
145+ WishMiddleware(cliHandler),
146+ pssh.LogMiddleware(handler, dbpool),
147+ },
148+ SubsystemMiddleware: []pssh.SSHServerMiddleware{
149+ sftp.Middleware(handler),
150+ pssh.LogMiddleware(handler, dbpool),
151+ },
152+ })
153+
154+ pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
155 if err != nil {
156- logger.Error(err.Error())
157+ logger.Error("failed to read private key file", "error", err)
158 return
159 }
160
161+ signer, err := ssh.ParsePrivateKey(pemBytes)
162+ if err != nil {
163+ logger.Error("failed to parse private key", "error", err)
164+ return
165+ }
166+
167+ server.Config.AddHostKey(signer)
168+
169 done := make(chan os.Signal, 1)
170 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
171 logger.Info("starting SSH server on", "host", host, "port", port)
172 go func() {
173- if err = s.ListenAndServe(); err != nil {
174+ if err = server.ListenAndServe(); err != nil {
175 logger.Error("serve", "err", err.Error())
176 os.Exit(1)
177 }
178 }()
179
180- <-done
181- logger.Info("stopping SSH server")
182- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
183- defer func() { cancel() }()
184- if err := s.Shutdown(ctx); err != nil {
185- logger.Error("shutdown", "err", err.Error())
186- os.Exit(1)
187+ exit := func() {
188+ logger.Info("stopping ssh server")
189+ cancel()
190 }
191+
192+ <-done
193+ exit()
194 }
+20,
-23
1@@ -13,8 +13,6 @@ import (
2 "time"
3
4 "github.com/antoniomika/syncmap"
5- "github.com/charmbracelet/ssh"
6- "github.com/charmbracelet/wish"
7 "github.com/google/uuid"
8 "github.com/picosh/pico/db"
9 "github.com/picosh/pico/pssh"
10@@ -23,7 +21,7 @@ import (
11 gossh "golang.org/x/crypto/ssh"
12 )
13
14-func flagSet(cmdName string, sesh ssh.Session) *flag.FlagSet {
15+func flagSet(cmdName string, sesh *pssh.SSHServerConnSession) *flag.FlagSet {
16 cmd := flag.NewFlagSet(cmdName, flag.ContinueOnError)
17 cmd.SetOutput(sesh)
18 cmd.Usage = func() {
19@@ -102,6 +100,9 @@ type CliHandler struct {
20 Access *syncmap.Map[string, []string]
21 }
22
23+func (h *CliHandler) GetLogger(s *pssh.SSHServerConnSession) *slog.Logger {
24+}
25+
26 func toSshCmd(cfg *shared.ConfigSite) string {
27 port := ""
28 if cfg.PortOverride != "22" {
29@@ -120,7 +121,7 @@ func parseArgList(arg string) []string {
30 }
31
32 // checkAccess checks if the user has access to a topic based on an access list.
33-func checkAccess(accessList []string, userName string, sesh ssh.Session) bool {
34+func checkAccess(accessList []string, userName string, sesh *pssh.SSHServerConnSession) bool {
35 for _, acc := range accessList {
36 if acc == userName {
37 return true
38@@ -192,10 +193,7 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
39 } else if cmd == "ls" {
40 if userName == "public" {
41 err := fmt.Errorf("access denied")
42- fmt.Fprintln(sesh.Stderr(), err)
43- fmt.Fprintf(sesh.Stderr(), "\r")
44- _ = sesh.Exit(1)
45- _ = sesh.Close()
46+ sesh.Fatal(err)
47 return err
48 }
49
50@@ -275,8 +273,7 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
51 _, _ = sesh.Write([]byte(outputData))
52 }
53
54- next(sesh)
55- return
56+ return next(sesh)
57 }
58
59 topic := ""
60@@ -308,7 +305,7 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
61 clean := pubCmd.Bool("c", false, "Don't send status messages")
62
63 if !flagCheck(pubCmd, topic, cmdArgs) {
64- return
65+ return err
66 }
67
68 if pubCmd.NArg() == 1 && topic == "" {
69@@ -384,7 +381,7 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
70 }
71
72 if !*clean {
73- wish.Printf(
74+ fmt.Fprintf(
75 sesh,
76 "subscribe to this channel:\n ssh %s sub %s%s\n",
77 toSshCmd(handler.Cfg),
78@@ -456,7 +453,7 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
79 cancel()
80
81 if !*clean {
82- wish.Fatalln(sesh, "timeout reached, exiting ...")
83+ sesh.Fatal(fmt.Errorf("timeout reached, exiting ..."))
84 } else {
85 err = sesh.Exit(1)
86 if err != nil {
87@@ -506,7 +503,7 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
88 }
89
90 if err != nil && !*clean {
91- wish.Errorln(sesh, err)
92+ fmt.Fprintln(sesh.Stderr(), err)
93 }
94 } else if cmd == "sub" {
95 subCmd := flagSet("sub", sesh)
96@@ -516,7 +513,7 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
97 clean := subCmd.Bool("c", false, "Don't send status messages")
98
99 if !flagCheck(subCmd, topic, cmdArgs) {
100- return
101+ return err
102 }
103
104 if subCmd.NArg() == 1 && topic == "" {
105@@ -571,8 +568,8 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
106 } else if !*public {
107 name = toTopic(userName, withoutUser)
108 } else {
109- wish.Errorln(sesh, "access denied")
110- return
111+ fmt.Fprintln(sesh.Stderr(), "access denied")
112+ return err
113 }
114 }
115
116@@ -587,7 +584,7 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
117 )
118
119 if err != nil && !*clean {
120- wish.Errorln(sesh, err)
121+ fmt.Fprintln(sesh.Stderr(), err)
122 }
123 } else if cmd == "pipe" {
124 pipeCmd := flagSet("pipe", sesh)
125@@ -597,7 +594,7 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
126 clean := pipeCmd.Bool("c", false, "Don't send status messages")
127
128 if !flagCheck(pipeCmd, topic, cmdArgs) {
129- return
130+ return err
131 }
132
133 if pipeCmd.NArg() == 1 && topic == "" {
134@@ -665,7 +662,7 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
135 }
136
137 if isCreator && !*clean {
138- wish.Printf(
139+ fmt.Fprintf(
140 sesh,
141 "subscribe to this topic:\n ssh %s sub %s%s\n",
142 toSshCmd(handler.Cfg),
143@@ -685,15 +682,15 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
144 )
145
146 if readErr != nil && !*clean {
147- wish.Errorln(sesh, "error reading from pipe", readErr)
148+ fmt.Fprintln(sesh.Stderr(), "error reading from pipe", readErr)
149 }
150
151 if writeErr != nil && !*clean {
152- wish.Errorln(sesh, "error writing to pipe", writeErr)
153+ fmt.Fprintln(sesh.Stderr(), "error writing to pipe", writeErr)
154 }
155 }
156
157- next(sesh)
158+ return next(sesh)
159 }
160 }
161 }
+36,
-28
1@@ -2,30 +2,30 @@ package pipe
2
3 import (
4 "context"
5- "fmt"
6 "os"
7 "os/signal"
8 "syscall"
9- "time"
10
11 "github.com/antoniomika/syncmap"
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/pssh"
17 "github.com/picosh/pico/shared"
18- wsh "github.com/picosh/pico/wish"
19 psub "github.com/picosh/pubsub"
20 "github.com/picosh/utils"
21+ "golang.org/x/crypto/ssh"
22 )
23
24 func StartSshServer() {
25 host := utils.GetEnv("PIPE_HOST", "0.0.0.0")
26 port := utils.GetEnv("PIPE_SSH_PORT", "2222")
27 portOverride := utils.GetEnv("PIPE_SSH_PORT_OVERRIDE", port)
28- promPort := utils.GetEnv("PIPE_PROM_PORT", "9222")
29+ // promPort := utils.GetEnv("PIPE_PROM_PORT", "9222")
30 cfg := NewConfigSite("pipe-ssh")
31 logger := cfg.Logger
32+
33+ ctx, cancel := context.WithCancel(context.Background())
34+ defer cancel()
35+
36 dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
37 defer dbh.Close()
38
39@@ -43,38 +43,46 @@ func StartSshServer() {
40 }
41
42 sshAuth := shared.NewSshAuthHandler(dbh, logger)
43- s, err := wish.NewServer(
44- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
45- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
46- wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
47- sshAuth.PubkeyAuthHandler(ctx, key)
48- return true
49- }),
50- wish.WithMiddleware(
51+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
52+ ListenAddr: "localhost:2222",
53+ ServerConfig: &ssh.ServerConfig{
54+ PublicKeyCallback: sshAuth.PubkeyAuthHandler,
55+ },
56+ Middleware: []pssh.SSHServerMiddleware{
57 WishMiddleware(handler),
58- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "pipe-ssh"),
59- wsh.LogMiddleware(logger, dbh),
60- ),
61- )
62+ pssh.LogMiddleware(handler, dbh),
63+ },
64+ })
65+
66+ pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
67 if err != nil {
68- logger.Error("wish server", "err", err.Error())
69+ logger.Error("failed to read private key file", "error", err)
70 return
71 }
72
73+ signer, err := ssh.ParsePrivateKey(pemBytes)
74+ if err != nil {
75+ logger.Error("failed to parse private key", "error", err)
76+ return
77+ }
78+
79+ server.Config.AddHostKey(signer)
80+
81 done := make(chan os.Signal, 1)
82 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
83 logger.Info("Starting SSH server", "host", host, "port", port)
84 go func() {
85- if err = s.ListenAndServe(); err != nil {
86- logger.Error("listen", "err", err.Error())
87+ if err = server.ListenAndServe(); err != nil {
88+ logger.Error("serve", "err", err.Error())
89+ os.Exit(1)
90 }
91 }()
92
93- <-done
94- logger.Info("Stopping SSH server")
95- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
96- defer func() { cancel() }()
97- if err := s.Shutdown(ctx); err != nil {
98- logger.Error("shutdown", "err", err.Error())
99+ exit := func() {
100+ logger.Info("stopping ssh server")
101+ cancel()
102 }
103+
104+ <-done
105+ exit()
106 }
+3,
-3
1@@ -6,9 +6,9 @@ import (
2
3 "slices"
4
5- "github.com/charmbracelet/ssh"
6 "github.com/picosh/pico/db"
7 "github.com/picosh/pico/filehandlers"
8+ "github.com/picosh/pico/pssh"
9 "github.com/picosh/pico/shared"
10 "github.com/picosh/utils"
11 pipeUtil "github.com/picosh/utils/pipe"
12@@ -20,7 +20,7 @@ type MarkdownHooks struct {
13 Pipe *pipeUtil.ReconnectReadWriteCloser
14 }
15
16-func (p *MarkdownHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData) (bool, error) {
17+func (p *MarkdownHooks) FileValidate(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) (bool, error) {
18 if !utils.IsTextFile(data.Text) {
19 err := fmt.Errorf(
20 "ERROR: (%s) invalid file must be plain text (utf-8), skipping",
21@@ -57,7 +57,7 @@ func (p *MarkdownHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaD
22 return true, nil
23 }
24
25-func (p *MarkdownHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData) error {
26+func (p *MarkdownHooks) FileMeta(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) error {
27 parsedText, err := shared.ParseText(data.Text)
28 if err != nil {
29 return fmt.Errorf("%s: %w", data.Filename, err)
+47,
-62
1@@ -2,72 +2,36 @@ package prose
2
3 import (
4 "context"
5- "fmt"
6 "os"
7 "os/signal"
8 "syscall"
9- "time"
10
11- "github.com/charmbracelet/promwish"
12- "github.com/charmbracelet/ssh"
13- "github.com/charmbracelet/wish"
14 "github.com/picosh/pico/db/postgres"
15 "github.com/picosh/pico/filehandlers"
16 uploadimgs "github.com/picosh/pico/filehandlers/imgs"
17+ "github.com/picosh/pico/pssh"
18 "github.com/picosh/pico/shared"
19 "github.com/picosh/pico/shared/storage"
20- wsh "github.com/picosh/pico/wish"
21 "github.com/picosh/send/auth"
22 "github.com/picosh/send/list"
23 "github.com/picosh/send/pipe"
24- wishrsync "github.com/picosh/send/protocols/rsync"
25+ "github.com/picosh/send/protocols/rsync"
26 "github.com/picosh/send/protocols/scp"
27 "github.com/picosh/send/protocols/sftp"
28- "github.com/picosh/send/proxy"
29 "github.com/picosh/utils"
30+ "golang.org/x/crypto/ssh"
31 )
32
33-func createRouter(handler *filehandlers.FileHandlerRouter) proxy.Router {
34- return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
35- return []wish.Middleware{
36- pipe.Middleware(handler, ".md"),
37- list.Middleware(handler),
38- scp.Middleware(handler),
39- wishrsync.Middleware(handler),
40- auth.Middleware(handler),
41- wsh.PtyMdw(wsh.DeprecatedNotice()),
42- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool),
43- }
44- }
45-}
46-
47-func withProxy(handler *filehandlers.FileHandlerRouter, otherMiddleware ...wish.Middleware) ssh.Option {
48- return func(server *ssh.Server) error {
49- err := sftp.SSHOption(handler)(server)
50- if err != nil {
51- return err
52- }
53-
54- newSubsystemHandlers := map[string]ssh.SubsystemHandler{}
55-
56- for name, subsystemHandler := range server.SubsystemHandlers {
57- newSubsystemHandlers[name] = func(s ssh.Session) {
58- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool)(ssh.Handler(subsystemHandler))(s)
59- }
60- }
61-
62- server.SubsystemHandlers = newSubsystemHandlers
63-
64- return proxy.WithProxy(createRouter(handler), otherMiddleware...)(server)
65- }
66-}
67-
68 func StartSshServer() {
69 host := utils.GetEnv("PROSE_HOST", "0.0.0.0")
70 port := utils.GetEnv("PROSE_SSH_PORT", "2222")
71- promPort := utils.GetEnv("PROSE_PROM_PORT", "9222")
72+ // promPort := utils.GetEnv("PROSE_PROM_PORT", "9222")
73 cfg := NewConfigSite("prose-ssh")
74 logger := cfg.Logger
75+
76+ ctx, cancel := context.WithCancel(context.Background())
77+ defer cancel()
78+
79 dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
80 defer dbh.Close()
81
82@@ -97,34 +61,55 @@ func StartSshServer() {
83 handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
84
85 sshAuth := shared.NewSshAuthHandler(dbh, logger)
86- s, err := wish.NewServer(
87- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
88- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
89- wish.WithPublicKeyAuth(sshAuth.PubkeyAuthHandler),
90- withProxy(
91- handler,
92- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "prose-ssh"),
93- ),
94- )
95+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
96+ ListenAddr: "localhost:2222",
97+ ServerConfig: &ssh.ServerConfig{
98+ PublicKeyCallback: sshAuth.PubkeyAuthHandler,
99+ },
100+ Middleware: []pssh.SSHServerMiddleware{
101+ pipe.Middleware(handler, ".md"),
102+ list.Middleware(handler),
103+ scp.Middleware(handler),
104+ rsync.Middleware(handler),
105+ auth.Middleware(handler),
106+ pssh.PtyMdw(pssh.DeprecatedNotice()),
107+ pssh.LogMiddleware(handler, dbh),
108+ },
109+ SubsystemMiddleware: []pssh.SSHServerMiddleware{
110+ sftp.Middleware(handler),
111+ pssh.LogMiddleware(handler, dbh),
112+ },
113+ })
114+
115+ pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
116 if err != nil {
117- logger.Error("wish server", "err", err.Error())
118+ logger.Error("failed to read private key file", "error", err)
119 return
120 }
121
122+ signer, err := ssh.ParsePrivateKey(pemBytes)
123+ if err != nil {
124+ logger.Error("failed to parse private key", "error", err)
125+ return
126+ }
127+
128+ server.Config.AddHostKey(signer)
129+
130 done := make(chan os.Signal, 1)
131 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
132 logger.Info("Starting SSH server", "host", host, "port", port)
133 go func() {
134- if err = s.ListenAndServe(); err != nil {
135- logger.Error(err.Error())
136+ if err = server.ListenAndServe(); err != nil {
137+ logger.Error("serve", "err", err.Error())
138+ os.Exit(1)
139 }
140 }()
141
142- <-done
143- logger.Info("Stopping SSH server")
144- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
145- defer func() { cancel() }()
146- if err := s.Shutdown(ctx); err != nil {
147- logger.Error(err.Error())
148+ exit := func() {
149+ logger.Info("stopping ssh server")
150+ cancel()
151 }
152+
153+ <-done
154+ exit()
155 }
+22,
-2
1@@ -3,8 +3,10 @@ package pssh
2 import (
3 "context"
4 "errors"
5+ "fmt"
6 "log/slog"
7 "net"
8+ "os"
9 "strings"
10 "sync"
11 "time"
12@@ -119,20 +121,38 @@ func (s *SSHServerConnSession) Exit(code int) error {
13 return err
14 }
15
16+func (s *SSHServerConnSession) Fatal(err error) {
17+ fmt.Fprintln(s.Stderr(), err)
18+ fmt.Fprintf(s.Stderr(), "\r")
19+ _ = s.Exit(1)
20+ _ = s.Close()
21+}
22+
23 type Window struct {
24- Width int
25- Height int
26+ Width int
27+ Height int
28+ HeightPixels int
29+ WidthPixels int
30 }
31
32 type Pty struct {
33 Term string
34 Window Window
35+ Slave os.File
36 }
37
38 func (s *SSHServerConnSession) Pty() (Pty, <-chan Window, bool) {
39 return Pty{}, nil, false
40 }
41
42+func (p Pty) Resize(width, height int) error {
43+ return nil
44+}
45+
46+func (p Pty) Name() string {
47+ return ""
48+}
49+
50 var _ context.Context = &SSHServerConnSession{}
51
52 func (sc *SSHServerConn) Handle(chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) error {
1@@ -6,8 +6,8 @@ import (
2 "sync"
3
4 "git.sr.ht/~delthas/senpai"
5- "github.com/charmbracelet/ssh"
6 "github.com/containerd/console"
7+ "github.com/picosh/pico/pssh"
8 )
9
10 type consoleData struct {
11@@ -16,13 +16,13 @@ type consoleData struct {
12 }
13
14 type VConsole struct {
15- ssh.Session
16- pty ssh.Pty
17+ Session *pssh.SSHServerConnSession
18+ pty pssh.Pty
19
20 sizeEnableOnce sync.Once
21
22 windowMu sync.Mutex
23- currentWindow ssh.Window
24+ currentWindow pssh.Window
25
26 readReq chan []byte
27 dataChan chan consoleData
28@@ -94,7 +94,7 @@ func (v *VConsole) DisableEcho() error {
29 }
30
31 func (v *VConsole) Reset() error {
32- _, err := v.Write([]byte("\033[?25h\033[0 q\033[34h\033[?25h\033[39;49m\033[m^O\033[H\033[J\033[?1049l\033[?1l\033>\033[?1000l\033[?1002l\033[?1003l\033[?1006l\033[?2004l"))
33+ _, err := v.Session.Write([]byte("\033[?25h\033[0 q\033[34h\033[?25h\033[39;49m\033[m^O\033[H\033[J\033[?1049l\033[?1l\033>\033[?1000l\033[?1002l\033[?1003l\033[?1006l\033[?2004l"))
34 return err
35 }
36
37@@ -123,7 +123,11 @@ func (v *VConsole) Close() error {
38 return err
39 }
40
41-func NewVConsole(sesh ssh.Session) (*VConsole, error) {
42+func (v *VConsole) Write(p []byte) (int, error) {
43+ return v.Session.Write(p)
44+}
45+
46+func NewVConsole(sesh *pssh.SSHServerConnSession) (*VConsole, error) {
47 pty, win, ok := sesh.Pty()
48 if !ok {
49 return nil, fmt.Errorf("PTY not found")
50@@ -178,7 +182,7 @@ func NewVConsole(sesh ssh.Session) (*VConsole, error) {
51 return vty, nil
52 }
53
54-func NewSenpaiApp(sesh ssh.Session, username, pass string) (*senpai.App, error) {
55+func NewSenpaiApp(sesh *pssh.SSHServerConnSession, username, pass string) (*senpai.App, error) {
56 vty, err := NewVConsole(sesh)
57 if err != nil {
58 slog.Error("PTY not found")
+2,
-2
1@@ -9,8 +9,8 @@ import (
2 "git.sr.ht/~rockorager/vaxis"
3 "git.sr.ht/~rockorager/vaxis/vxfw"
4 "git.sr.ht/~rockorager/vaxis/vxfw/richtext"
5- "github.com/charmbracelet/ssh"
6 "github.com/picosh/pico/db"
7+ "github.com/picosh/pico/pssh"
8 "github.com/picosh/pico/shared"
9 "github.com/picosh/utils"
10 )
11@@ -19,7 +19,7 @@ var HOME = "dash"
12
13 type SharedModel struct {
14 Logger *slog.Logger
15- Session ssh.Session
16+ Session *pssh.SSHServerConnSession
17 Cfg *shared.ConfigSite
18 Dbpool db.DB
19 User *db.User