diff --git a/db/migrations/000001_init.up.sql b/db/migrations/000001_init.up.sql index 20fd6ca9..7630007d 100644 --- a/db/migrations/000001_init.up.sql +++ b/db/migrations/000001_init.up.sql @@ -88,6 +88,7 @@ CREATE TABLE remote ( remoteuser varchar(50) NOT NULL, remotehost varchar(200) NOT NULL, connectmode varchar(20) NOT NULL, + autoinstall boolean NOT NULL, initpk json NOT NULL, sshopts json NOT NULL, remoteopts json NOT NULL, diff --git a/go.mod b/go.mod index d4a4489f..c928ff76 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect go.uber.org/atomic v1.7.0 // indirect + golang.org/x/mod v0.5.1 // indirect golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect mvdan.cc/sh/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index 7465dfaa..97dd6f71 100644 --- a/go.sum +++ b/go.sum @@ -1192,6 +1192,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index 405cf070..0f9f9ab6 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -575,8 +575,8 @@ func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } return sstore.ModelUpdate{ Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("show all remote info"), - InfoLines: splitLinesForInfo(buf.String()), + InfoTitle: fmt.Sprintf("show all remote info"), + RemoteShowAll: true, }, }, nil } @@ -1322,3 +1322,46 @@ func formatTermOpts(termOpts sstore.TermOpts) string { } return rtnStr } + +type ColMeta struct { + Title string + MinCols int + MaxCols int +} + +func toInterfaceArr(sarr []string) []interface{} { + rtn := make([]interface{}, len(sarr)) + for idx, s := range sarr { + rtn[idx] = s + } + return rtn +} + +func formatTextTable(totalCols int, data [][]string, colMeta []ColMeta) []string { + numCols := len(colMeta) + maxColLen := make([]int, len(colMeta)) + for i, cm := range colMeta { + maxColLen[i] = cm.MinCols + } + for _, row := range data { + for i := 0; i < numCols && i < len(row); i++ { + dlen := len(row[i]) + if dlen > maxColLen[i] { + maxColLen[i] = dlen + } + } + } + fmtStr := "" + for idx, clen := range maxColLen { + if idx != 0 { + fmtStr += " " + } + fmtStr += fmt.Sprintf("%%%ds", clen) + } + var rtn []string + for _, row := range data { + sval := fmt.Sprintf(fmtStr, toInterfaceArr(row)...) + rtn = append(rtn, sval) + } + return rtn +} diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index 809524a1..bb328d84 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -24,6 +24,7 @@ import ( "github.com/scripthaus-dev/mshell/pkg/shexec" "github.com/scripthaus-dev/sh2-server/pkg/scpacket" "github.com/scripthaus-dev/sh2-server/pkg/sstore" + "golang.org/x/mod/semver" ) const RemoteTypeMShell = "mshell" @@ -34,6 +35,9 @@ const RemoteTermRows = 8 const RemoteTermCols = 80 const PtyReadBufSize = 100 +const MShellVersion = "v0.1.0" +const MShellVersionConstraint = "^0.1" + const MShellServerCommand = ` PATH=$PATH:~/.mshell; which mshell > /dev/null; @@ -53,6 +57,12 @@ const ( StatusError = "error" ) +func init() { + if MShellVersion != base.MShellVersion { + panic(fmt.Sprintf("sh2-server mshell version must match '%s' vs '%s'", MShellVersion, base.MShellVersion)) + } +} + var GlobalStore *Store type Store struct { @@ -73,6 +83,7 @@ type MShellProc struct { ControllingPty *os.File PtyBuffer *circbuf.Buffer MakeClientCancelFn context.CancelFunc + NeedsMShellUpgrade bool RunningCmds []base.CommandKey } @@ -88,6 +99,7 @@ type RemoteRuntimeState struct { ErrorStr string `json:"errorstr,omitempty"` DefaultState *sstore.RemoteState `json:"defaultstate"` ConnectMode string `json:"connectmode"` + AutoInstall bool `json:"autoinstall"` Archived bool `json:"archived"` RemoteIdx int64 `json:"remoteidx"` UName string `json:"uname"` @@ -349,6 +361,7 @@ func (msh *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState { PhysicalId: msh.Remote.PhysicalId, Status: msh.Status, ConnectMode: msh.Remote.ConnectMode, + AutoInstall: msh.Remote.AutoInstall, Archived: msh.Remote.Archived, RemoteIdx: msh.Remote.RemoteIdx, UName: msh.UName, @@ -649,17 +662,25 @@ func (msh *MShellProc) Launch() { go msh.NotifyRemoteUpdate() }) cproc, uname, err := shexec.MakeClientProc(makeClientCtx, ecmd) + var mshellVersion string msh.WithLock(func() { msh.UName = uname msh.MakeClientCancelFn = nil if cproc != nil && cproc.InitPk != nil { msh.Remote.InitPk = cproc.InitPk + mshellVersion = cproc.InitPk.Version + } + if semver.Compare(mshellVersion, MShellVersion) < 0 { + msh.NeedsMShellUpgrade = true } // no notify here, because we'll call notify in either case below }) if err == context.Canceled { err = fmt.Errorf("forced disconnection") } + if semver.MajorMinor(mshellVersion) != semver.MajorMinor(MShellVersion) { + err = fmt.Errorf("mshell version is not compatible current=%s remote=%s", MShellVersion, mshellVersion) + } if err != nil { msh.setErrorStatus(err) msh.WriteToPtyBuffer("*error connecting to remote (uname=%q): %v\n", msh.UName, err) diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index f3e96fab..143286c8 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -488,6 +488,7 @@ type RemoteType struct { RemoteUser string `json:"remoteuser"` RemoteHost string `json:"remotehost"` ConnectMode string `json:"connectmode"` + AutoInstall bool `json:"autoinstall"` InitPk *packet.InitPacketType `json:"inipk"` SSHOpts *SSHOpts `json:"sshopts"` RemoteOpts *RemoteOptsType `json:"remoteopts"` @@ -530,6 +531,7 @@ func (r *RemoteType) ToMap() map[string]interface{} { rtn["remoteuser"] = r.RemoteUser rtn["remotehost"] = r.RemoteHost rtn["connectmode"] = r.ConnectMode + rtn["autoinstall"] = r.AutoInstall rtn["initpk"] = quickJson(r.InitPk) rtn["sshopts"] = quickJson(r.SSHOpts) rtn["remoteopts"] = quickJson(r.RemoteOpts) @@ -553,6 +555,7 @@ func RemoteFromMap(m map[string]interface{}) *RemoteType { quickSetStr(&r.RemoteUser, m, "remoteuser") quickSetStr(&r.RemoteHost, m, "remotehost") quickSetStr(&r.ConnectMode, m, "connectmode") + quickSetBool(&r.AutoInstall, m, "autoinstall") quickSetJson(&r.InitPk, m, "initpk") quickSetJson(&r.SSHOpts, m, "sshopts") quickSetJson(&r.RemoteOpts, m, "remoteopts") diff --git a/pkg/sstore/updatebus.go b/pkg/sstore/updatebus.go index 7942739a..01a6bd7f 100644 --- a/pkg/sstore/updatebus.go +++ b/pkg/sstore/updatebus.go @@ -88,6 +88,7 @@ type InfoMsgType struct { InfoLines []string `json:"infolines,omitempty"` TimeoutMs int64 `json:"timeoutms,omitempty"` PtyRemoteId string `json:"ptyremoteid,omitempty"` + RemoteShowAll bool `json:"remoteshowall,omitempty"` } type HistoryInfoType struct {