repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

commit
17217e6
parent
72d930b
author
Antonio Mika
date
2025-03-11 18:55:15 -0400 EDT
Almost fully working
11 files changed,  +125, -171
M go.mod
M go.sum
M go.mod
+1, -15
 1@@ -29,8 +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/ssh v0.0.0-20250213143314-8712ec3ff3ef
 6-	github.com/charmbracelet/wish v1.4.6
 7 	github.com/containerd/console v1.0.4
 8 	github.com/darkweak/souin v1.7.5
 9 	github.com/darkweak/souin/plugins/souin/storages v1.7.5
10@@ -49,6 +47,7 @@ require (
11 	github.com/picosh/pobj v0.0.0-20250304201248-a9c7179aa49b
12 	github.com/picosh/pubsub v0.0.0-20241114191831-ec8f16c0eb88
13 	github.com/picosh/send v0.0.0-20250304201154-e36cd3bbbb35
14+	github.com/picosh/tunkit v0.0.0-00010101000000-000000000000
15 	github.com/picosh/utils v0.0.0-20241120033529-8ca070c09bf4
16 	github.com/pkg/sftp v1.13.7
17 	github.com/prometheus/client_golang v1.21.0-rc.0
18@@ -79,7 +78,6 @@ require (
19 	github.com/PuerkitoBio/goquery v1.10.0 // indirect
20 	github.com/RoaringBitmap/roaring v1.2.3 // indirect
21 	github.com/andybalholm/cascadia v1.3.2 // indirect
22-	github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
23 	github.com/antlabs/stl v0.0.1 // indirect
24 	github.com/antlabs/timer v0.0.11 // indirect
25 	github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
26@@ -116,20 +114,12 @@ require (
27 	github.com/caddyserver/zerossl v0.1.3 // indirect
28 	github.com/cespare/xxhash v1.1.0 // indirect
29 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
30-	github.com/charmbracelet/bubbletea v1.3.4 // indirect
31-	github.com/charmbracelet/keygen v0.5.1 // indirect
32-	github.com/charmbracelet/log v0.4.0 // indirect
33 	github.com/charmbracelet/x/ansi v0.8.0 // indirect
34-	github.com/charmbracelet/x/conpty v0.1.0 // indirect
35-	github.com/charmbracelet/x/errors v0.0.0-20250226164017-59292a315e58 // indirect
36 	github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b // indirect
37-	github.com/charmbracelet/x/term v0.2.1 // indirect
38-	github.com/charmbracelet/x/termios v0.1.1 // indirect
39 	github.com/chzyer/readline v1.5.1 // indirect
40 	github.com/coreos/go-semver v0.3.1 // indirect
41 	github.com/coreos/go-systemd/v22 v22.5.0 // indirect
42 	github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
43-	github.com/creack/pty v1.1.24 // indirect
44 	github.com/darkweak/go-esi v0.0.5 // indirect
45 	github.com/darkweak/storages/badger v0.0.8 // indirect
46 	github.com/darkweak/storages/etcd v0.0.8 // indirect
47@@ -157,7 +147,6 @@ require (
48 	github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d // indirect
49 	github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 // indirect
50 	github.com/dustin/go-humanize v1.0.1 // indirect
51-	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
52 	github.com/forPelevin/gomoji v1.2.0 // indirect
53 	github.com/gammazero/deque v0.2.1 // indirect
54 	github.com/gkampitakis/ciinfo v0.3.0 // indirect
55@@ -220,7 +209,6 @@ require (
56 	github.com/maruel/natural v1.1.1 // indirect
57 	github.com/mattn/go-colorable v0.1.13 // indirect
58 	github.com/mattn/go-isatty v0.0.20 // indirect
59-	github.com/mattn/go-localereader v0.0.1 // indirect
60 	github.com/mattn/go-runewidth v0.0.16 // indirect
61 	github.com/mattn/go-sixel v0.0.5 // indirect
62 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
63@@ -239,8 +227,6 @@ require (
64 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
65 	github.com/modern-go/reflect2 v1.0.2 // indirect
66 	github.com/mschoch/smat v0.2.0 // indirect
67-	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
68-	github.com/muesli/cancelreader v0.2.2 // indirect
69 	github.com/muesli/termenv v0.16.0 // indirect
70 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
71 	github.com/nats-io/nats.go v1.36.0 // indirect
M go.sum
+0, -31
 1@@ -60,8 +60,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
 2 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 3 github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
 4 github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
 5-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 6-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 7 github.com/antlabs/stl v0.0.1 h1:TRD3csCrjREeLhLoQ/supaoCvFhNLBTNIwuRGrDIs6Q=
 8 github.com/antlabs/stl v0.0.1/go.mod h1:wvVwP1loadLG3cRjxUxK8RL4Co5xujGaZlhbztmUEqQ=
 9 github.com/antlabs/timer v0.0.11 h1:z75oGFLeTqJHMOcWzUPBKsBbQAz4Ske3AfqJ7bsdcwU=
10@@ -152,30 +150,12 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
11 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
12 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
13 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
14-github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
15-github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo=
16-github.com/charmbracelet/keygen v0.5.1 h1:zBkkYPtmKDVTw+cwUyY6ZwGDhRxXkEp0Oxs9sqMLqxI=
17-github.com/charmbracelet/keygen v0.5.1/go.mod h1:zznJVmK/GWB6dAtjluqn2qsttiCBhA5MZSiwb80fcHw=
18 github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
19 github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
20-github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
21-github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
22-github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef h1:dNZwn4is5svUd+sQEGsrXtp7VwD2ipYaCkKMzcpAEIE=
23-github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef/go.mod h1:hg+I6gvlMl16nS9ZzQNgBIrrCasGwEw0QiLsDcP01Ko=
24-github.com/charmbracelet/wish v1.4.6 h1:27WRqMTUmyFoZASoaAaEe78Je7LTU4VqyoBxnl4d9XA=
25-github.com/charmbracelet/wish v1.4.6/go.mod h1:RRy2LFW3WQ3tlPmMMGgEeSMDVlFd5yqklGBVZWQSHmk=
26 github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
27 github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
28-github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
29-github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
30-github.com/charmbracelet/x/errors v0.0.0-20250226164017-59292a315e58 h1:UWrrJJrdFfi7Y5XNKjz8/1RtZzGbshaFEZzlI7CgJ7M=
31-github.com/charmbracelet/x/errors v0.0.0-20250226164017-59292a315e58/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
32 github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q=
33 github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
34-github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
35-github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
36-github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
37-github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
38 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
39 github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
40 github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
41@@ -208,8 +188,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0q
42 github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
43 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
44 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
45-github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
46-github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
47 github.com/darkweak/go-esi v0.0.5 h1:b9LHI8Tz46R+i6p8avKPHAIBRQUCZDebNmKm5w/Zrns=
48 github.com/darkweak/go-esi v0.0.5/go.mod h1:koCJqwum1u6mslyZuq/Phm6hfG1K3ZK5Y7jrUBTH654=
49 github.com/darkweak/souin v1.7.5 h1:drNhZc0GhSbGcugiGfcYdLDTcx3DCZW6o13wwRj5o5Y=
50@@ -294,8 +272,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
51 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
52 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
53 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
54-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
55-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
56 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
57 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
58 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
59@@ -597,8 +573,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
60 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
61 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
62 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
63-github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
64-github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
65 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
66 github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
67 github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
68@@ -656,10 +630,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
69 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
70 github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
71 github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
72-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
73-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
74-github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
75-github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
76 github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
77 github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
78 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
79@@ -1106,7 +1076,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
80 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
81 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
82 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
83-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
84 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
85 golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
86 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
M pgs/ssh.go
+8, -4
 1@@ -14,6 +14,7 @@ import (
 2 	"github.com/picosh/send/protocols/rsync"
 3 	"github.com/picosh/send/protocols/scp"
 4 	"github.com/picosh/send/protocols/sftp"
 5+	"github.com/picosh/tunkit"
 6 	"github.com/picosh/utils"
 7 	"golang.org/x/crypto/ssh"
 8 )
 9@@ -36,10 +37,10 @@ func StartSshServer(cfg *PgsConfig, killCh chan error) {
10 
11 	sshAuth := shared.NewSshAuthHandler(cfg.DB, logger)
12 
13-	// webTunnel := &tunkit.WebTunnelHandler{
14-	// 	Logger:      logger,
15-	// 	HttpHandler: createHttpHandler(cfg),
16-	// }
17+	webTunnel := &tunkit.WebTunnelHandler{
18+		Logger:      logger,
19+		HttpHandler: createHttpHandler(cfg),
20+	}
21 
22 	// Create a new SSH server
23 	server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
24@@ -61,6 +62,9 @@ func StartSshServer(cfg *PgsConfig, killCh chan error) {
25 			sftp.Middleware(handler),
26 			pssh.LogMiddleware(handler, handler.Cfg.DB),
27 		},
28+		ChannelMiddleware: map[string]pssh.SSHServerChannelMiddleware{
29+			"direct-tcpip": tunkit.LocalForwardHandler(webTunnel),
30+		},
31 	})
32 
33 	pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
M pgs/ssh_test.go
+2, -1
 1@@ -408,7 +408,8 @@ func WriteFileWithSftp(cfg *PgsConfig, conn *ssh.Client) (*os.FileInfo, error) {
 2 		cfg.Logger.Error("could not write to file", "err", err)
 3 		return nil, err
 4 	}
 5-	f.Close()
 6+
 7+	cfg.Logger.Info("closing", "err", f.Close())
 8 
 9 	// check it's there
10 	fi, err := client.Lstat("test/hello.txt")
M pgs/tunnel.go
+4, -3
 1@@ -6,9 +6,10 @@ import (
 2 	"strings"
 3 	"time"
 4 
 5-	"github.com/charmbracelet/ssh"
 6 	"github.com/picosh/pico/db"
 7+	"github.com/picosh/pico/pssh"
 8 	"github.com/picosh/pico/shared"
 9+	"golang.org/x/crypto/ssh"
10 )
11 
12 type TunnelWebRouter struct {
13@@ -33,7 +34,7 @@ func (web *TunnelWebRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
14 	web.UserRouter.ServeHTTP(w, r.WithContext(ctx))
15 }
16 
17-type CtxHttpBridge = func(ssh.Context) http.Handler
18+type CtxHttpBridge = func(*pssh.SSHServerConnSession) http.Handler
19 
20 func getInfoFromUser(user string) (string, string) {
21 	if strings.Contains(user, "__") {
22@@ -45,7 +46,7 @@ func getInfoFromUser(user string) (string, string) {
23 }
24 
25 func createHttpHandler(cfg *PgsConfig) CtxHttpBridge {
26-	return func(ctx ssh.Context) http.Handler {
27+	return func(ctx *pssh.SSHServerConnSession) http.Handler {
28 		logger := cfg.Logger
29 		asUser, subdomain := getInfoFromUser(ctx.User())
30 		log := logger.With(
M pico/file_handler.go
+4, -4
 1@@ -11,12 +11,12 @@ import (
 2 	"strings"
 3 	"time"
 4 
 5-	"github.com/charmbracelet/ssh"
 6 	"github.com/picosh/pico/db"
 7 	"github.com/picosh/pico/pssh"
 8 	"github.com/picosh/pico/shared"
 9 	sendutils "github.com/picosh/send/utils"
10 	"github.com/picosh/utils"
11+	"golang.org/x/crypto/ssh"
12 )
13 
14 type UploadHandler struct {
15@@ -168,7 +168,7 @@ func authorizedKeysDiff(keyInUse ssh.PublicKey, curKeys []KeyWithId, nextKeys []
16 	for _, nk := range nextKeys {
17 		found := false
18 		for _, ck := range curKeys {
19-			if ssh.KeysEqual(nk.Pk, ck.Pk) {
20+			if pssh.KeysEqual(nk.Pk, ck.Pk) {
21 				found = true
22 
23 				// update the comment field
24@@ -188,13 +188,13 @@ func authorizedKeysDiff(keyInUse ssh.PublicKey, curKeys []KeyWithId, nextKeys []
25 	for _, ck := range curKeys {
26 		// we never want to remove the key that's in the current ssh session
27 		// in an effort to avoid mistakenly removing their current key
28-		if ssh.KeysEqual(ck.Pk, keyInUse) {
29+		if pssh.KeysEqual(ck.Pk, keyInUse) {
30 			continue
31 		}
32 
33 		found := false
34 		for _, nk := range nextKeys {
35-			if ssh.KeysEqual(ck.Pk, nk.Pk) {
36+			if pssh.KeysEqual(ck.Pk, nk.Pk) {
37 				found = true
38 				break
39 			}
M pipe/cli.go
+2, -1
 1@@ -101,6 +101,7 @@ type CliHandler struct {
 2 }
 3 
 4 func (h *CliHandler) GetLogger(s *pssh.SSHServerConnSession) *slog.Logger {
 5+	return h.Logger
 6 }
 7 
 8 func toSshCmd(cfg *shared.ConfigSite) string {
 9@@ -453,7 +454,7 @@ func WishMiddleware(handler *CliHandler) pssh.SSHServerMiddleware {
10 							cancel()
11 
12 							if !*clean {
13-								sesh.Fatal(fmt.Errorf("timeout reached, exiting ..."))
14+								sesh.Fatal(fmt.Errorf("timeout reached, exiting"))
15 							} else {
16 								err = sesh.Exit(1)
17 								if err != nil {
M pssh/server.go
+98, -67
  1@@ -2,6 +2,7 @@ package pssh
  2 
  3 import (
  4 	"context"
  5+	"crypto/subtle"
  6 	"errors"
  7 	"fmt"
  8 	"log/slog"
  9@@ -166,75 +167,15 @@ func (sc *SSHServerConn) Handle(chans <-chan ssh.NewChannel, reqs <-chan *ssh.Re
 10 			if !ok {
 11 				return nil
 12 			}
 13-			sc.Logger.Info("new channel", "type", newChan.ChannelType(), "extraData", newChan.ExtraData())
 14-			switch newChan.ChannelType() {
 15-			case "session":
 16-				channel, requests, err := newChan.Accept()
 17-				if err != nil {
 18-					sc.Logger.Error("accept session channel", "err", err)
 19-					return err
 20-				}
 21-
 22-				go func() {
 23-					for {
 24-						select {
 25-						case <-sc.Done():
 26-							return
 27-						case req, ok := <-requests:
 28-							if !ok {
 29-								return
 30-							}
 31 
 32-							sc.Logger.Info("new session request", "type", req.Type, "wantReply", req.WantReply, "payload", req.Payload)
 33-							if req.Type == "subsystem" {
 34-								if len(sc.SSHServer.Config.SubsystemMiddleware) == 0 {
 35-									req.Reply(false, nil)
 36-									continue
 37-								}
 38-
 39-								h := func(*SSHServerConnSession) error { return nil }
 40-								for _, m := range sc.SSHServer.Config.SubsystemMiddleware {
 41-									h = m(h)
 42-								}
 43-
 44-								if err := h(&SSHServerConnSession{
 45-									Channel:       channel,
 46-									SSHServerConn: sc,
 47-								}); err != nil {
 48-									req.Reply(false, nil)
 49-									continue
 50-								}
 51-
 52-								req.Reply(true, nil)
 53-							} else if req.Type == "exec" {
 54-								if len(sc.SSHServer.Config.Middleware) == 0 {
 55-									req.Reply(false, nil)
 56-									continue
 57-								}
 58-
 59-								sesh := &SSHServerConnSession{
 60-									Channel:       channel,
 61-									SSHServerConn: sc,
 62-								}
 63-
 64-								sesh.SetValue("command", strings.Fields(string(req.Payload[4:])))
 65-
 66-								h := func(*SSHServerConnSession) error { return nil }
 67-								for _, m := range sc.SSHServer.Config.Middleware {
 68-									h = m(h)
 69-								}
 70-
 71-								if err := h(sesh); err != nil {
 72-									req.Reply(false, nil)
 73-									continue
 74-								}
 75-
 76-								req.Reply(true, nil)
 77-							}
 78-						}
 79-					}
 80-				}()
 81+			sc.Logger.Info("new channel", "type", newChan.ChannelType(), "extraData", newChan.ExtraData())
 82+			chanFunc, ok := sc.SSHServer.Config.ChannelMiddleware[newChan.ChannelType()]
 83+			if !ok {
 84+				sc.Logger.Info("no channel middleware for type", "type", newChan.ChannelType())
 85+				continue
 86 			}
 87+
 88+			go chanFunc(newChan, sc)
 89 		case req, ok := <-reqs:
 90 			if !ok {
 91 				return nil
 92@@ -271,12 +212,14 @@ func NewSSHServerConn(
 93 
 94 type SSHServerHandler func(*SSHServerConnSession) error
 95 type SSHServerMiddleware func(SSHServerHandler) SSHServerHandler
 96+type SSHServerChannelMiddleware func(ssh.NewChannel, *SSHServerConn) error
 97 
 98 type SSHServerConfig struct {
 99 	*ssh.ServerConfig
100 	ListenAddr          string
101 	Middleware          []SSHServerMiddleware
102 	SubsystemMiddleware []SSHServerMiddleware
103+	ChannelMiddleware   map[string]SSHServerChannelMiddleware
104 }
105 
106 type SSHServer struct {
107@@ -375,6 +318,84 @@ func NewSSHServer(ctx context.Context, logger *slog.Logger, config *SSHServerCon
108 		config = &SSHServerConfig{}
109 	}
110 
111+	if config.ChannelMiddleware == nil {
112+		config.ChannelMiddleware = map[string]SSHServerChannelMiddleware{}
113+	}
114+
115+	if _, ok := config.ChannelMiddleware["session"]; !ok {
116+		config.ChannelMiddleware["session"] = func(newChan ssh.NewChannel, sc *SSHServerConn) error {
117+			channel, requests, err := newChan.Accept()
118+			if err != nil {
119+				sc.Logger.Error("accept session channel", "err", err)
120+				return err
121+			}
122+
123+			for {
124+				select {
125+				case <-sc.Done():
126+					return nil
127+				case req, ok := <-requests:
128+					if !ok {
129+						return nil
130+					}
131+
132+					go func() {
133+						sc.Logger.Info("new session request", "type", req.Type, "wantReply", req.WantReply, "payload", req.Payload)
134+						if req.Type == "subsystem" {
135+							if len(sc.SSHServer.Config.SubsystemMiddleware) == 0 {
136+								req.Reply(false, nil)
137+								sc.Close()
138+								return
139+							}
140+
141+							h := func(*SSHServerConnSession) error { return nil }
142+							for _, m := range sc.SSHServer.Config.SubsystemMiddleware {
143+								h = m(h)
144+							}
145+
146+							sesh := &SSHServerConnSession{
147+								Channel:       channel,
148+								SSHServerConn: sc,
149+							}
150+
151+							if err := h(sesh); err != nil {
152+								req.Reply(false, nil)
153+								sesh.Close()
154+							}
155+
156+							req.Reply(true, nil)
157+							sesh.Close()
158+						} else if req.Type == "exec" {
159+							if len(sc.SSHServer.Config.Middleware) == 0 {
160+								req.Reply(false, nil)
161+							}
162+
163+							sesh := &SSHServerConnSession{
164+								Channel:       channel,
165+								SSHServerConn: sc,
166+							}
167+
168+							sesh.SetValue("command", strings.Fields(string(req.Payload[4:])))
169+
170+							h := func(*SSHServerConnSession) error { return nil }
171+							for _, m := range sc.SSHServer.Config.Middleware {
172+								h = m(h)
173+							}
174+
175+							if err := h(sesh); err != nil {
176+								req.Reply(false, nil)
177+								sesh.Close()
178+							}
179+
180+							req.Reply(true, nil)
181+							sesh.Close()
182+						}
183+					}()
184+				}
185+			}
186+		}
187+	}
188+
189 	server := &SSHServer{
190 		Ctx:        cancelCtx,
191 		CancelFunc: cancelFunc,
192@@ -385,3 +406,13 @@ func NewSSHServer(ctx context.Context, logger *slog.Logger, config *SSHServerCon
193 
194 	return server
195 }
196+
197+func KeysEqual(a, b ssh.PublicKey) bool {
198+	if a == nil || b == nil {
199+		return false
200+	}
201+
202+	am := a.Marshal()
203+	bm := b.Marshal()
204+	return (len(am) == len(bm) && subtle.ConstantTimeCompare(am, bm) == 1)
205+}
M shared/api.go
+1, -1
 1@@ -8,9 +8,9 @@ import (
 2 	"os"
 3 	"strings"
 4 
 5-	"github.com/charmbracelet/ssh"
 6 	"github.com/picosh/pico/db"
 7 	"github.com/picosh/utils"
 8+	"golang.org/x/crypto/ssh"
 9 )
10 
11 type SubdomainProps struct {
M shared/router.go
+5, -5
 1@@ -10,8 +10,8 @@ import (
 2 	"regexp"
 3 	"strings"
 4 
 5-	"github.com/charmbracelet/ssh"
 6 	"github.com/picosh/pico/db"
 7+	"github.com/picosh/pico/pssh"
 8 	"github.com/picosh/pico/shared/storage"
 9 )
10 
11@@ -173,12 +173,12 @@ type ctxCfg struct{}
12 
13 type CtxSubdomainKey struct{}
14 type ctxKey struct{}
15-type CtxSshKey struct{}
16+type CtxSessionKey struct{}
17 
18-func GetSshCtx(r *http.Request) (ssh.Context, error) {
19-	payload, ok := r.Context().Value(CtxSshKey{}).(ssh.Context)
20+func GetSshCtx(r *http.Request) (*pssh.SSHServerConnSession, error) {
21+	payload, ok := r.Context().Value(CtxSessionKey{}).(*pssh.SSHServerConnSession)
22 	if payload == nil || !ok {
23-		return payload, fmt.Errorf("sshCtx not set on `r.Context()` for connection")
24+		return payload, fmt.Errorf("ssh session not set on `r.Context()` for connection")
25 	}
26 	return payload, nil
27 }
D wish/pty.go
+0, -39
 1@@ -1,39 +0,0 @@
 2-package wish
 3-
 4-import (
 5-	"fmt"
 6-
 7-	"github.com/charmbracelet/ssh"
 8-	"github.com/charmbracelet/wish"
 9-)
10-
11-func SessionMessage(sesh ssh.Session, msg string) {
12-	_, _ = sesh.Write([]byte(msg + "\r\n"))
13-}
14-
15-func DeprecatedNotice() wish.Middleware {
16-	return func(next ssh.Handler) ssh.Handler {
17-		return func(sesh ssh.Session) {
18-			msg := fmt.Sprintf(
19-				"%s\n\nRun %s to access pico's TUI",
20-				"DEPRECATED",
21-				"ssh pico.sh",
22-			)
23-			SessionMessage(sesh, msg)
24-			next(sesh)
25-		}
26-	}
27-}
28-
29-func PtyMdw(mdw wish.Middleware) wish.Middleware {
30-	return func(next ssh.Handler) ssh.Handler {
31-		return func(sesh ssh.Session) {
32-			_, _, ok := sesh.Pty()
33-			if !ok {
34-				next(sesh)
35-				return
36-			}
37-			mdw(next)(sesh)
38-		}
39-	}
40-}