refactoring for versioned mshell binaries on remotes

This commit is contained in:
sawka 2022-09-26 13:02:34 -07:00
parent 4550e18b6b
commit b5c67b6260
7 changed files with 90 additions and 40 deletions

5
go.mod
View File

@ -10,4 +10,7 @@ require (
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
)
require github.com/Masterminds/semver/v3 v3.1.1 // indirect
require (
github.com/Masterminds/semver/v3 v3.1.1 // indirect
golang.org/x/mod v0.5.1 // indirect
)

2
go.sum
View File

@ -8,5 +8,7 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -417,14 +417,14 @@ func handleInstall() (int, error) {
if !base.ValidGoArch(goos, goarch) {
return 1, fmt.Errorf("invalid arch '%s' passed to mshell --install", fullArch)
}
optName := base.GoArchOptFile(goos, goarch)
optName := base.GoArchOptFile(base.MShellVersion, goos, goarch)
_, err = os.Stat(optName)
if err != nil {
return 1, fmt.Errorf("cannot install mshell to remote host, cannot read '%s': %w", optName, err)
}
opts.OptName = optName
}
err = shexec.RunInstallSSHCommand(opts)
err = shexec.RunInstallFromOpts(opts)
if err != nil {
return 1, err
}

View File

@ -19,6 +19,7 @@ import (
"sync"
"github.com/google/uuid"
"golang.org/x/mod/semver"
)
const HomeVarName = "HOME"
@ -26,10 +27,12 @@ const DefaultMShellHome = "~/.mshell"
const DefaultMShellName = "mshell"
const MShellPathVarName = "MSHELL_PATH"
const MShellHomeVarName = "MSHELL_HOME"
const MShellInstallBinVarName = "MSHELL_INSTALLBIN_PATH"
const SSHCommandVarName = "SSH_COMMAND"
const SessionsDirBaseName = "sessions"
const MShellVersion = "v0.1.0"
const RemoteIdFile = "remoteid"
const DefaultMShellInstallBinDir = "/opt/mshell/bin"
var sessionDirCache = make(map[string]string)
var baseLock = &sync.Mutex{}
@ -229,8 +232,17 @@ func ValidGoArch(goos string, goarch string) bool {
return (goos == "darwin" || goos == "linux") && (goarch == "amd64" || goarch == "arm64")
}
func GoArchOptFile(goos string, goarch string) string {
return fmt.Sprintf("/opt/mshell/bin/mshell.%s.%s", goos, goarch)
func GoArchOptFile(version string, goos string, goarch string) string {
installBinDir := os.Getenv(MShellInstallBinVarName)
if installBinDir == "" {
installBinDir = DefaultMShellInstallBinDir
}
versionStr := semver.MajorMinor(version)
if versionStr == "" {
versionStr = "unknown"
}
binBaseName := fmt.Sprintf("mshell-%s-%s.%s", versionStr, goos, goarch)
return fmt.Sprintf(path.Join(installBinDir, binBaseName))
}
func GetRemoteId() (string, error) {

View File

@ -9,6 +9,7 @@ import (
"github.com/scripthaus-dev/mshell/pkg/base"
"github.com/scripthaus-dev/mshell/pkg/packet"
"golang.org/x/mod/semver"
)
// TODO - track buffer sizes for sending input
@ -72,11 +73,11 @@ func MakeClientProc(ctx context.Context, ecmd *exec.Cmd) (*ClientProc, string, e
initPk := pk.(*packet.InitPacketType)
if initPk.NotFound {
cproc.Close()
return nil, initPk.UName, fmt.Errorf("mshell command not found on local server")
return nil, initPk.UName, fmt.Errorf("mshell-%s command not found on local server", semver.MajorMinor(base.MShellVersion))
}
if initPk.Version != base.MShellVersion {
if semver.MajorMinor(initPk.Version) != semver.MajorMinor(base.MShellVersion) {
cproc.Close()
return nil, initPk.UName, fmt.Errorf("invalid remote mshell version '%s', must be %s", initPk.Version, base.MShellVersion)
return nil, initPk.UName, fmt.Errorf("invalid remote mshell version '%s', must be '=%s'", initPk.Version, semver.MajorMinor(base.MShellVersion))
}
cproc.InitPk = initPk
}

View File

@ -27,6 +27,7 @@ import (
"github.com/scripthaus-dev/mshell/pkg/cirfile"
"github.com/scripthaus-dev/mshell/pkg/mpio"
"github.com/scripthaus-dev/mshell/pkg/packet"
"golang.org/x/mod/semver"
"golang.org/x/sys/unix"
)
@ -45,29 +46,37 @@ const MaxMaxPtySize = 100 * 1024 * 1024
const GetStateTimeout = 5 * time.Second
const ClientCommand = `
const ClientCommandFmt = `
PATH=$PATH:~/.mshell;
which mshell > /dev/null;
if [[ "$?" -ne 0 ]]
then
printf "\n##N{\"type\": \"init\", \"notfound\": true, \"uname\": \"%s|%s\"}\n" "$(uname -s)" "$(uname -m)"
else
mshell --single
mshell-[%VERSION%] --single
fi
`
const InstallCommand = `
func MakeClientCommandStr() string {
return strings.ReplaceAll(ClientCommandFmt, "[%VERSION%]", semver.MajorMinor(base.MShellVersion))
}
const InstallCommandFmt = `
printf "\n##N{\"type\": \"init\", \"notfound\": true, \"uname\": \"%s|%s\"}\n" "$(uname -s)" "$(uname -m)";
mkdir -p ~/.mshell/;
cat > ~/.mshell/mshell.temp;
if [[ -s ~/.mshell/mshell.temp ]]
then
mv ~/.mshell/mshell.temp ~/.mshell/mshell;
chmod a+x ~/.mshell/mshell;
~/.mshell/mshell --single --version
mv ~/.mshell/mshell.temp ~/.mshell/mshell-[%VERSION%];
chmod a+x ~/.mshell/mshell-[%VERSION%];
~/.mshell/mshell-[%VERSION%] --single --version
fi
`
func MakeInstallCommandStr() string {
return strings.ReplaceAll(InstallCommandFmt, "[%VERSION%]", semver.MajorMinor(base.MShellVersion))
}
const RunCommandFmt = `%s`
const RunSudoCommandFmt = `sudo -n -C %d bash /dev/fd/%d`
const RunSudoPasswordCommandFmt = `cat /dev/fd/%d | sudo -k -S -C %d bash -c "echo '[from-mshell]'; exec %d>&-; bash /dev/fd/%d < /dev/fd/%d"`
@ -389,6 +398,7 @@ type InstallOpts struct {
ArchStr string
OptName string
Detect bool
CmdPty *os.File
}
type ClientOpts struct {
@ -408,7 +418,8 @@ func (opts SSHOpts) MakeSSHInstallCmd() (*exec.Cmd, error) {
if opts.SSHHost == "" {
return nil, fmt.Errorf("no ssh host provided, can only install to a remote host")
}
return opts.MakeSSHExecCmd(InstallCommand), nil
cmdStr := MakeInstallCommandStr()
return opts.MakeSSHExecCmd(cmdStr), nil
}
func (opts SSHOpts) MakeMShellServerCmd() (*exec.Cmd, error) {
@ -434,7 +445,8 @@ func (opts SSHOpts) MakeMShellSingleCmd(fromServer bool) (*exec.Cmd, error) {
}
return ecmd, nil
}
return opts.MakeSSHExecCmd(ClientCommand), nil
cmdStr := MakeClientCommandStr()
return opts.MakeSSHExecCmd(cmdStr), nil
}
func (opts SSHOpts) MakeSSHExecCmd(remoteCommand string) *exec.Cmd {
@ -632,12 +644,7 @@ func sendOptFile(input io.WriteCloser, optName string) error {
return nil
}
func RunInstallSSHCommand(opts *InstallOpts) error {
tryDetect := opts.Detect
ecmd, err := opts.SSHOpts.MakeSSHInstallCmd()
if err != nil {
return err
}
func RunInstallFromCmd(ecmd *exec.Cmd, tryDetect bool, optName string, msgFn func(string)) error {
inputWriter, err := ecmd.StdinPipe()
if err != nil {
return fmt.Errorf("creating stdin pipe: %v", err)
@ -653,8 +660,11 @@ func RunInstallSSHCommand(opts *InstallOpts) error {
go func() {
io.Copy(os.Stderr, stderrReader)
}()
if opts.OptName != "" {
sendOptFile(inputWriter, opts.OptName)
if optName != "" {
err = sendOptFile(inputWriter, optName)
if err != nil {
return fmt.Errorf("cannot send mshell binary: %v", err)
}
}
packetParser := packet.MakePacketParser(stdoutReader)
err = ecmd.Start()
@ -677,15 +687,19 @@ func RunInstallSSHCommand(opts *InstallOpts) error {
if err != nil {
return fmt.Errorf("arch cannot be detected (might be incompatible with mshell): %w", err)
}
fmt.Printf("mshell detected remote architecture as '%s.%s'\n", goos, goarch)
optName := base.GoArchOptFile(goos, goarch)
sendOptFile(inputWriter, optName)
msgStr := fmt.Sprintf("mshell detected remote architecture as '%s.%s'\n", goos, goarch)
msgFn(msgStr)
optName := base.GoArchOptFile(base.MShellVersion, goos, goarch)
fmt.Printf("optname %s\n", optName)
err = sendOptFile(inputWriter, optName)
if err != nil {
return fmt.Errorf("cannot send mshell binary: %v", err)
}
continue
}
if pk.GetType() == packet.InitPacketStr && !firstInit {
initPacket := pk.(*packet.InitPacketType)
if initPacket.Version == base.MShellVersion {
fmt.Printf("mshell %s, installed successfully at %s:~/.mshell/mshell\n", initPacket.Version, opts.SSHOpts.SSHHost)
return nil
}
return fmt.Errorf("invalid version '%s' received from client, expecting '%s'", initPacket.Version, base.MShellVersion)
@ -700,6 +714,23 @@ func RunInstallSSHCommand(opts *InstallOpts) error {
return fmt.Errorf("did not receive version string from client, install not successful")
}
func RunInstallFromOpts(opts *InstallOpts) error {
ecmd, err := opts.SSHOpts.MakeSSHInstallCmd()
if err != nil {
return err
}
msgFn := func(str string) {
fmt.Printf("%s", str)
}
err = RunInstallFromCmd(ecmd, opts.Detect, opts.OptName, msgFn)
if err != nil {
return err
}
mmVersion := semver.MajorMinor(base.MShellVersion)
fmt.Printf("mshell installed successfully at %s:~/.mshell/mshell%s\n", opts.SSHOpts.SSHHost, mmVersion)
return nil
}
func HasDupStdin(fds []packet.RemoteFd) bool {
for _, rfd := range fds {
if rfd.Read && rfd.DupStdin {
@ -764,22 +795,23 @@ func RunClientSSHCommandAndWait(runPacket *packet.RunPacketType, fdContext FdCon
}
if pk.GetType() == packet.InitPacketStr {
initPk := pk.(*packet.InitPacketType)
mmVersion := semver.MajorMinor(base.MShellVersion)
if initPk.NotFound {
if sshOpts.SSHHost == "" {
return nil, fmt.Errorf("mshell command not found on local server")
return nil, fmt.Errorf("mshell-%s command not found on local server", mmVersion)
}
if initPk.UName == "" {
return nil, fmt.Errorf("mshell command not found on remote server, no uname detected")
return nil, fmt.Errorf("mshell-%s command not found on remote server, no uname detected", mmVersion)
}
goos, goarch, err := DetectGoArch(initPk.UName)
if err != nil {
return nil, fmt.Errorf("mshell command not found on remote server, architecture cannot be detected (might be incompatible with mshell): %w", err)
return nil, fmt.Errorf("mshell-%s command not found on remote server, architecture cannot be detected (might be incompatible with mshell): %w", mmVersion, err)
}
sshOptsStr := sshOpts.MakeMShellSSHOpts()
return nil, fmt.Errorf("mshell command not found on remote server, can install with 'mshell --install %s %s.%s'", sshOptsStr, goos, goarch)
return nil, fmt.Errorf("mshell-%s command not found on remote server, can install with 'mshell --install %s %s.%s'", mmVersion, sshOptsStr, goos, goarch)
}
if initPk.Version != base.MShellVersion {
return nil, fmt.Errorf("invalid remote mshell version '%s', must be %s", initPk.Version, base.MShellVersion)
if semver.MajorMinor(initPk.Version) != semver.MajorMinor(base.MShellVersion) {
return nil, fmt.Errorf("invalid remote mshell version '%s', must be '=%s'", initPk.Version, semver.MajorMinor(base.MShellVersion))
}
versionOk = true
if debug {

View File

@ -1,16 +1,16 @@
```bash
# @scripthaus command build
go build -ldflags="-s -w" -o /Users/mike/.mshell/mshell main-mshell.go
go build -ldflags="-s -w" -o /Users/mike/.mshell/mshell-v0.1 main-mshell.go
```
```bash
# @scripthaus command fullbuild
go build -ldflags="-s -w" -o /Users/mike/.mshell/mshell main-mshell.go
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o /opt/mshell/bin/mshell.linux.amd64 main-mshell.go
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o /opt/mshell/bin/mshell.linux.arm64 main-mshell.go
GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o /opt/mshell/bin/mshell.darwin.amd64 main-mshell.go
GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o /opt/mshell/bin/mshell.darwin.arm64 main-mshell.go
go build -ldflags="-s -w" -o /Users/mike/.mshell/mshell-v0.1 main-mshell.go
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o /opt/mshell/bin/mshell-v0.1-linux.amd64 main-mshell.go
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o /opt/mshell/bin/mshell-v0.1-linux.arm64 main-mshell.go
GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o /opt/mshell/bin/mshell-v0.1-darwin.amd64 main-mshell.go
GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o /opt/mshell/bin/mshell-v0.1-darwin.arm64 main-mshell.go
```