diff --git a/go.work.sum b/go.work.sum index 132842daf..b591aaa37 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,11 +1,45 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/wavetermdev/ssh_config v0.0.0-20240109090616-36c8da3d7376 h1:tFhJgTu7lgd+hldLfPSzDCoWUpXI8wHKR3rxq5jTLkQ= github.com/wavetermdev/ssh_config v0.0.0-20240109090616-36c8da3d7376/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/waveshell/pkg/shexec/client.go b/waveshell/pkg/shexec/client.go index 61a28454a..2c209532c 100644 --- a/waveshell/pkg/shexec/client.go +++ b/waveshell/pkg/shexec/client.go @@ -60,6 +60,18 @@ func (cw CmdWrap) Start() error { return cw.Cmd.Start() } +func (cw CmdWrap) StdinPipe() (io.WriteCloser, error) { + return cw.Cmd.StdinPipe() +} + +func (cw CmdWrap) StdoutPipe() (io.ReadCloser, error) { + return cw.Cmd.StdoutPipe() +} + +func (cw CmdWrap) StderrPipe() (io.ReadCloser, error) { + return cw.Cmd.StderrPipe() +} + type SessionWrap struct { Session *ssh.Session StartCmd string @@ -101,12 +113,35 @@ func (sw SessionWrap) Parser() (*packet.PacketParser, io.ReadCloser, io.ReadClos return packetParser, io.NopCloser(stdoutReader), io.NopCloser(stderrReader), nil } +func (sw SessionWrap) StdinPipe() (io.WriteCloser, error) { + return sw.Session.StdinPipe() +} + +func (sw SessionWrap) StdoutPipe() (io.ReadCloser, error) { + outPipe, err := sw.Session.StdoutPipe() + if err != nil { + return nil, err + } + return io.NopCloser(outPipe), nil +} + +func (sw SessionWrap) StderrPipe() (io.ReadCloser, error) { + errPipe, err := sw.Session.StderrPipe() + if err != nil { + return nil, err + } + return io.NopCloser(errPipe), nil +} + type ConnInterface interface { Kill() Wait() error Sender() (*packet.PacketSender, io.WriteCloser, error) Parser() (*packet.PacketParser, io.ReadCloser, io.ReadCloser, error) Start() error + StdinPipe() (io.WriteCloser, error) + StdoutPipe() (io.ReadCloser, error) + StderrPipe() (io.ReadCloser, error) } type ClientProc struct { @@ -175,6 +210,53 @@ func MakeClientProc(ctx context.Context, ecmd ConnInterface) (*ClientProc, *pack return cproc, cproc.InitPk, nil } +func MakeInstallProc(ctx context.Context, ecmd ConnInterface) (*ClientProc, *packet.InitPacketType, error) { + startTs := time.Now() + sender, inputWriter, err := ecmd.Sender() + if err != nil { + return nil, nil, err + } + packetParser, stdoutReader, stderrReader, err := ecmd.Parser() + if err != nil { + return nil, nil, err + } + err = ecmd.Start() + if err != nil { + return nil, nil, fmt.Errorf("running local client: %w", err) + } + cproc := &ClientProc{ + Cmd: ecmd, + StartTs: startTs, + StdinWriter: inputWriter, + StdoutReader: stdoutReader, + StderrReader: stderrReader, + Input: sender, + Output: packetParser, + } + + var pk packet.PacketType + select { + case pk = <-packetParser.MainCh: + case <-ctx.Done(): + cproc.Close() + return nil, nil, ctx.Err() + } + + if pk != nil { + if pk.GetType() != packet.InitPacketStr { + cproc.Close() + return nil, nil, fmt.Errorf("invalid packet received from mshell client: %s", packet.AsString(pk)) + } + initPk := pk.(*packet.InitPacketType) + cproc.InitPk = initPk + } + if cproc.InitPk == nil { + cproc.Close() + return nil, nil, fmt.Errorf("no init packet received from mshell client") + } + return cproc, cproc.InitPk, nil +} + func (cproc *ClientProc) Close() { if cproc.Input != nil { cproc.Input.Close() diff --git a/wavesrv/go.mod b/wavesrv/go.mod index a9864debd..ff98355d5 100644 --- a/wavesrv/go.mod +++ b/wavesrv/go.mod @@ -6,13 +6,14 @@ require ( github.com/alessio/shellescape v1.4.1 github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 github.com/creack/pty v1.1.18 - github.com/kevinburke/ssh_config v1.2.0 github.com/golang-migrate/migrate/v4 v4.16.2 github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/jmoiron/sqlx v1.3.5 + github.com/kevinburke/ssh_config v1.2.0 github.com/mattn/go-sqlite3 v1.14.16 + github.com/pkg/sftp v1.13.6 github.com/sashabaranov/go-openai v1.9.0 github.com/sawka/txwrap v0.1.2 github.com/wavetermdev/waveterm/waveshell v0.0.0 @@ -26,8 +27,10 @@ require ( github.com/google/go-github/v57 v57.0.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/kr/fs v0.1.0 // indirect go.uber.org/atomic v1.7.0 // indirect ) replace github.com/wavetermdev/waveterm/waveshell => ../waveshell + replace github.com/kevinburke/ssh_config => github.com/wavetermdev/ssh_config v0.0.0-20240109090616-36c8da3d7376 diff --git a/wavesrv/go.sum b/wavesrv/go.sum index 4f3cde78d..140a88898 100644 --- a/wavesrv/go.sum +++ b/wavesrv/go.sum @@ -29,6 +29,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -36,6 +38,8 @@ github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= @@ -44,21 +48,56 @@ github.com/sashabaranov/go-openai v1.9.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOg github.com/sawka/txwrap v0.1.2 h1:v8xS0Z1LE7/6vMZA81PYihI+0TSR6Zm1MalzzBIuXKc= github.com/sawka/txwrap v0.1.2/go.mod h1:T3nlw2gVpuolo6/XEetvBbk1oMXnY978YmBFy1UyHvw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/wavetermdev/ssh_config v0.0.0-20240109090616-36c8da3d7376/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= diff --git a/wavesrv/pkg/remote/remote.go b/wavesrv/pkg/remote/remote.go index e9b24f6cd..138aedfab 100644 --- a/wavesrv/pkg/remote/remote.go +++ b/wavesrv/pkg/remote/remote.go @@ -14,7 +14,9 @@ import ( "os" "os/exec" "path" + "path/filepath" "regexp" + "runtime" "strconv" "strings" "sync" @@ -25,6 +27,7 @@ import ( "github.com/armon/circbuf" "github.com/creack/pty" "github.com/google/uuid" + "github.com/pkg/sftp" "github.com/wavetermdev/waveterm/waveshell/pkg/base" "github.com/wavetermdev/waveterm/waveshell/pkg/packet" "github.com/wavetermdev/waveterm/waveshell/pkg/server" @@ -88,6 +91,9 @@ const WaveshellServerRunOnlyFmt = ` [%PINGPACKET%] mshell-[%VERSION%] --server ` +const InitCommand = ` +printf "\n##N{\"type\": \"init\", \"notfound\": true, \"uname\": \"%s|%s\"}\n" "$(uname -s)" "$(uname -m)"; +` func MakeLocalMShellCommandStr(isSudo bool) (string, error) { mshellPath, err := scbase.LocalMShellBinaryPath() @@ -144,6 +150,7 @@ type pendingStateKey struct { // remove once ssh library is stabilized type Launcher interface { Launch(*MShellProc, bool) + RunInstall(*MShellProc) } type MShellProc struct { @@ -175,6 +182,7 @@ type MShellProc struct { RunningCmds map[base.CommandKey]RunCmdType PendingStateCmds map[pendingStateKey]base.CommandKey // key=[remoteinstance name] launcher Launcher // for conditional launch method based on ssh library in use. remove once ssh library is stabilized + Client *ssh.Client } type RunCmdType struct { @@ -201,6 +209,10 @@ func (msh *MShellProc) Launch(interactive bool) { msh.launcher.Launch(msh, interactive) } +func (msh *MShellProc) RunInstall() { + msh.launcher.RunInstall(msh) +} + func (msh *MShellProc) GetStatus() string { msh.Lock.Lock() defer msh.Lock.Unlock() @@ -1057,7 +1069,197 @@ func (msh *MShellProc) WaitAndSendPassword(pw string) { } } -func (msh *MShellProc) RunInstall() { +func (NewLauncher) RunInstall(msh *MShellProc) { + remoteCopy := msh.GetRemoteCopy() + if remoteCopy.Archived { + msh.WriteToPtyBuffer("*error: cannot install on archived remote\n") + return + } + baseStatus := msh.GetStatus() + if baseStatus == StatusConnecting || baseStatus == StatusConnected { + msh.WriteToPtyBuffer("*error: cannot install on remote that is connected/connecting, disconnect to install\n") + return + } + curStatus := msh.GetInstallStatus() + if curStatus == StatusConnecting { + msh.WriteToPtyBuffer("*error: cannot install on remote that is already trying to install, cancel current install to try again\n") + return + } + + msh.WriteToPtyBuffer("installing mshell %s to %s...\n", scbase.MShellVersion, remoteCopy.RemoteCanonicalName) + clientCtx, clientCancelFn := context.WithCancel(context.Background()) + defer clientCancelFn() + msh.WithLock(func() { + msh.InstallErr = nil + msh.InstallStatus = StatusConnecting + msh.InstallCancelFn = clientCancelFn + go msh.NotifyRemoteUpdate() + }) + + if msh.Remote.IsLocal() { + srcBinPath, err := scbase.MShellBinaryPath(base.MShellVersion, runtime.GOOS, runtime.GOARCH) + if err != nil { + statusErr := fmt.Errorf("ssh cannot create session: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + homeDir, err := os.UserHomeDir() + if err != nil { + statusErr := fmt.Errorf("ssh cannot create session: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + newBinName := fmt.Sprintf("mshell-%s", semver.MajorMinor(scbase.MShellVersion)) + dstBinPath := filepath.Join(homeDir, ".mshell", newBinName) + + srcBinFile, err := os.Open(srcBinPath) + if err != nil { + statusErr := fmt.Errorf("ssh cannot create session: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + defer srcBinFile.Close() + // destination file should be handled manually - not closed with defer + dstBinFile, err := os.OpenFile(dstBinPath, os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + statusErr := fmt.Errorf("ssh cannot create session: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + _, err = io.Copy(dstBinFile, srcBinFile) + if err != nil { + statusErr := fmt.Errorf("ssh cannot create session: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + dstBinFile.Close() + return + } + err = dstBinFile.Close() + if err != nil { + statusErr := fmt.Errorf("ssh cannot create session: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + + } else { + session, err := msh.Client.NewSession() + defer session.Close() + if err != nil { + statusErr := fmt.Errorf("ssh cannot create session: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + cproc, initPk, err := shexec.MakeInstallProc(clientCtx, shexec.SessionWrap{Session: session, StartCmd: InitCommand}) //TODO + if err == context.Canceled { + msh.WriteToPtyBuffer("*install canceled\n") + msh.WithLock(func() { + msh.InstallStatus = StatusDisconnected + go msh.NotifyRemoteUpdate() + }) + return + } + if err != nil { + statusErr := fmt.Errorf("cannot create init packet: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + defer cproc.Close() + goos, goarch, err := shexec.DetectGoArch(initPk.UName) + if err != nil { + statusErr := fmt.Errorf("cannot determine architecture: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + srcBinPath, err := scbase.MShellBinaryPath(base.MShellVersion, goos, goarch) + if err != nil { + statusErr := fmt.Errorf("cannot find source binary: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + fileClient, err := sftp.NewClient(msh.Client) + if err != nil { + statusErr := fmt.Errorf("cannot estabish sftp connection: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + homeDir, err := fileClient.Getwd() + if err != nil { + statusErr := fmt.Errorf("cannot determine remote home directory: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + newBinName := fmt.Sprintf("mshell-%s", semver.MajorMinor(scbase.MShellVersion)) + dstBinPath := filepath.Join(homeDir, ".mshell", newBinName) + + srcBinFile, err := os.Open(srcBinPath) + if err != nil { + statusErr := fmt.Errorf("cannot find source executable: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + defer srcBinFile.Close() + // destination file should be handled manually - not closed with defer + dstBinFile, err := fileClient.OpenFile(dstBinPath, os.O_RDWR|os.O_CREATE) + if err != nil { + statusErr := fmt.Errorf("cannot create destination executable: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + err = dstBinFile.Chmod(0755) + if err != nil { + statusErr := fmt.Errorf("cannot set executable permissions: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + _, err = io.Copy(dstBinFile, srcBinFile) + if err != nil { + statusErr := fmt.Errorf("unable to copy to new waveshell executable: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + dstBinFile.Close() + return + } + err = dstBinFile.Close() + if err != nil { + statusErr := fmt.Errorf("unable to save waveshell executable: %w", err) + msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error()) + msh.setInstallErrorStatus(statusErr) + return + } + } + var connectMode string + msh.WithLock(func() { + msh.InstallStatus = StatusDisconnected + msh.InstallCancelFn = nil + msh.NeedsMShellUpgrade = false + msh.Status = StatusDisconnected + msh.Err = nil + connectMode = msh.Remote.ConnectMode + }) + msh.WriteToPtyBuffer("successfully installed mshell %s to ~/.mshell\n", scbase.MShellVersion) + go msh.NotifyRemoteUpdate() + if connectMode == sstore.ConnectModeStartup || connectMode == sstore.ConnectModeAuto { + // the install was successful, and we don't have a manual connect mode, try to connect + go msh.Launch(true) + } + return +} + +func (LegacyLauncher) RunInstall(msh *MShellProc) { remoteCopy := msh.GetRemoteCopy() if remoteCopy.Archived { msh.WriteToPtyBuffer("*error: cannot install on archived remote\n") @@ -1381,6 +1583,7 @@ func (NewLauncher) Launch(msh *MShellProc, interactive bool) { msh.setErrorStatus(statusErr) return } + msh.Client = client var session *ssh.Session session, err = client.NewSession() if err != nil {