mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-03-02 04:02:13 +01:00
implement install command
This commit is contained in:
parent
26479f59c0
commit
afd3bdb315
132
main-mshell.go
132
main-mshell.go
@ -23,8 +23,6 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const MShellVersion = "0.1.0"
|
||||
|
||||
// in single run mode, we don't want mshell to die from signals
|
||||
// since we want the single mshell to persist even if session / main mshell
|
||||
// is terminated.
|
||||
@ -220,8 +218,14 @@ func handleSingle() {
|
||||
close(sender.SendCh)
|
||||
<-sender.DoneCh
|
||||
}()
|
||||
if len(os.Args) >= 3 && os.Args[2] == "--version" {
|
||||
initPacket := packet.MakeInitPacket()
|
||||
initPacket.Version = base.MShellVersion
|
||||
sender.SendPacket(initPacket)
|
||||
return
|
||||
}
|
||||
initPacket := packet.MakeInitPacket()
|
||||
initPacket.Version = MShellVersion
|
||||
initPacket.Version = base.MShellVersion
|
||||
sender.SendPacket(initPacket)
|
||||
var runPacket *packet.RunPacketType
|
||||
for pk := range packetParser.MainCh {
|
||||
@ -280,37 +284,70 @@ func detectOpenFds() ([]packet.RemoteFd, error) {
|
||||
return fds, nil
|
||||
}
|
||||
|
||||
func parseInstallOpts() (*shexec.InstallOpts, error) {
|
||||
opts := &shexec.InstallOpts{}
|
||||
iter := base.MakeOptsIter(os.Args[2:]) // first arg is --install
|
||||
for iter.HasNext() {
|
||||
argStr := iter.Next()
|
||||
found, err := tryParseSSHOpt(iter, &opts.SSHOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
if base.IsOption(argStr) {
|
||||
return nil, fmt.Errorf("invalid option '%s' passed to mshell --install", argStr)
|
||||
}
|
||||
opts.ArchStr = argStr
|
||||
break
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func tryParseSSHOpt(iter *base.OptsIter, sshOpts *shexec.SharedSSHOpts) (bool, error) {
|
||||
argStr := iter.Current()
|
||||
if argStr == "--ssh" {
|
||||
if !iter.IsNextPlain() {
|
||||
return false, fmt.Errorf("'--ssh [user@host]' missing host")
|
||||
}
|
||||
sshOpts.SSHHost = iter.Next()
|
||||
return true, nil
|
||||
}
|
||||
if argStr == "--ssh-opts" {
|
||||
if !iter.HasNext() {
|
||||
return false, fmt.Errorf("'--ssh-opts [options]' missing options")
|
||||
}
|
||||
sshOpts.SSHOptsStr = iter.Next()
|
||||
return true, nil
|
||||
}
|
||||
if argStr == "-i" {
|
||||
if !iter.IsNextPlain() {
|
||||
return false, fmt.Errorf("-i [identity-file]' missing file")
|
||||
}
|
||||
sshOpts.SSHIdentity = iter.Next()
|
||||
return true, nil
|
||||
}
|
||||
if argStr == "-l" {
|
||||
if !iter.IsNextPlain() {
|
||||
return false, fmt.Errorf("-l [user]' missing user")
|
||||
}
|
||||
sshOpts.SSHUser = iter.Next()
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func parseClientOpts() (*shexec.ClientOpts, error) {
|
||||
opts := &shexec.ClientOpts{}
|
||||
iter := base.MakeOptsIter(os.Args[1:])
|
||||
for iter.HasNext() {
|
||||
argStr := iter.Next()
|
||||
if argStr == "--ssh" {
|
||||
if !iter.IsNextPlain() {
|
||||
return nil, fmt.Errorf("'--ssh [user@host]' missing host")
|
||||
}
|
||||
opts.SSHHost = iter.Next()
|
||||
continue
|
||||
found, err := tryParseSSHOpt(iter, &opts.SSHOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if argStr == "--ssh-opts" {
|
||||
if !iter.HasNext() {
|
||||
return nil, fmt.Errorf("'--ssh-opts [options]' missing options")
|
||||
}
|
||||
opts.SSHOptsStr = iter.Next()
|
||||
continue
|
||||
}
|
||||
if argStr == "-i" {
|
||||
if !iter.IsNextPlain() {
|
||||
return nil, fmt.Errorf("-i [identity-file]' missing file")
|
||||
}
|
||||
opts.SSHIdentity = iter.Next()
|
||||
continue
|
||||
}
|
||||
if argStr == "-l" {
|
||||
if !iter.IsNextPlain() {
|
||||
return nil, fmt.Errorf("-l [user]' missing user")
|
||||
}
|
||||
opts.SSHUser = iter.Next()
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
if argStr == "--cwd" {
|
||||
@ -392,6 +429,36 @@ func handleClient() (int, error) {
|
||||
return donePacket.ExitCode, nil
|
||||
}
|
||||
|
||||
func handleInstall() (int, error) {
|
||||
opts, err := parseInstallOpts()
|
||||
if err != nil {
|
||||
return 1, fmt.Errorf("parsing opts: %w", err)
|
||||
}
|
||||
if opts.SSHOpts.SSHHost == "" {
|
||||
return 1, fmt.Errorf("cannot install without '--ssh user@host' option")
|
||||
}
|
||||
fullArch := opts.ArchStr
|
||||
fields := strings.SplitN(fullArch, ".", 2)
|
||||
if len(fields) != 2 {
|
||||
return 1, fmt.Errorf("invalid arch format '%s' passed to mshell --install", fullArch)
|
||||
}
|
||||
goos, goarch := fields[0], fields[1]
|
||||
if !base.ValidGoArch(goos, goarch) {
|
||||
return 1, fmt.Errorf("invalid arch '%s' passed to mshell --install", fullArch)
|
||||
}
|
||||
optName := base.GoArchOptFile(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)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func handleUsage() {
|
||||
usage := `
|
||||
Client Usage: mshell [opts] --ssh user@host -- [command]
|
||||
@ -444,7 +511,7 @@ func main() {
|
||||
handleUsage()
|
||||
return
|
||||
} else if firstArg == "--version" {
|
||||
fmt.Printf("mshell v%s\n", MShellVersion)
|
||||
fmt.Printf("mshell v%s\n", base.MShellVersion)
|
||||
return
|
||||
} else if firstArg == "--single" {
|
||||
handleSingle()
|
||||
@ -452,6 +519,13 @@ func main() {
|
||||
} else if firstArg == "--server" {
|
||||
handleServer()
|
||||
return
|
||||
} else if firstArg == "--install" {
|
||||
rtnCode, err := handleInstall()
|
||||
if err != nil {
|
||||
fmt.Printf("[error] %v\n", err)
|
||||
}
|
||||
os.Exit(rtnCode)
|
||||
return
|
||||
} else {
|
||||
rtnCode, err := handleClient()
|
||||
if err != nil {
|
||||
|
@ -30,6 +30,7 @@ const SessionsDirBaseName = ".sessions"
|
||||
const RunnerBaseName = "runner"
|
||||
const SessionDBName = "session.db"
|
||||
const ScReadyString = "scripthaus runner ready"
|
||||
const MShellVersion = "0.1.0"
|
||||
|
||||
const OSCEscError = "error"
|
||||
|
||||
@ -253,3 +254,11 @@ func ExpandHomeDir(pathStr string) string {
|
||||
}
|
||||
return path.Join(homeDir, pathStr[2:])
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -41,6 +41,13 @@ func (iter *OptsIter) Next() string {
|
||||
return rtn
|
||||
}
|
||||
|
||||
func (iter *OptsIter) Current() string {
|
||||
if iter.Pos == 0 {
|
||||
return ""
|
||||
}
|
||||
return iter.Opts[iter.Pos-1]
|
||||
}
|
||||
|
||||
func (iter *OptsIter) Rest() []string {
|
||||
return iter.Opts[iter.Pos:]
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ const FirstExtraFilesFdNum = 3
|
||||
|
||||
const ClientCommand = `
|
||||
PATH=$PATH:~/.mshell;
|
||||
which mshell2 > /dev/null;
|
||||
which mshell > /dev/null;
|
||||
if [[ "$?" -ne 0 ]]
|
||||
then
|
||||
printf "\n##N{\"type\": \"init\", \"notfound\": true, \"uname\": \"%s | %s\"}\n" "$(uname -s)" "$(uname -m)"
|
||||
@ -41,6 +41,14 @@ else
|
||||
fi
|
||||
`
|
||||
|
||||
const InstallCommand = `
|
||||
mkdir -p ~/.mshell/;
|
||||
cat > ~/.mshell/mshell.temp;
|
||||
mv ~/.mshell/mshell.temp ~/.mshell/mshell;
|
||||
chmod a+x ~/.mshell/mshell;
|
||||
~/.mshell/mshell --single --version
|
||||
`
|
||||
|
||||
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"`
|
||||
@ -227,11 +235,21 @@ func RunCommand(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecT
|
||||
}
|
||||
}
|
||||
|
||||
type SharedSSHOpts struct {
|
||||
SSHHost string
|
||||
SSHOptsStr string
|
||||
SSHIdentity string
|
||||
SSHUser string
|
||||
}
|
||||
|
||||
type InstallOpts struct {
|
||||
SSHOpts SharedSSHOpts
|
||||
ArchStr string
|
||||
OptName string
|
||||
}
|
||||
|
||||
type ClientOpts struct {
|
||||
SSHHost string
|
||||
SSHOptsStr string
|
||||
SSHIdentity string
|
||||
SSHUser string
|
||||
SSHOpts SharedSSHOpts
|
||||
Command string
|
||||
Fds []packet.RemoteFd
|
||||
Cwd string
|
||||
@ -244,46 +262,63 @@ type ClientOpts struct {
|
||||
}
|
||||
|
||||
func (opts *ClientOpts) MakeExecCmd() *exec.Cmd {
|
||||
if opts.SSHHost == "" {
|
||||
if opts.SSHOpts.SSHHost == "" {
|
||||
ecmd := exec.Command("bash", "-c", strings.TrimSpace(ClientCommand))
|
||||
return ecmd
|
||||
} else {
|
||||
var moreSSHOpts []string
|
||||
if opts.SSHIdentity != "" {
|
||||
identityOpt := fmt.Sprintf("-i %s", shellescape.Quote(opts.SSHIdentity))
|
||||
if opts.SSHOpts.SSHIdentity != "" {
|
||||
identityOpt := fmt.Sprintf("-i %s", shellescape.Quote(opts.SSHOpts.SSHIdentity))
|
||||
moreSSHOpts = append(moreSSHOpts, identityOpt)
|
||||
}
|
||||
if opts.SSHUser != "" {
|
||||
userOpt := fmt.Sprintf("-l %s", shellescape.Quote(opts.SSHUser))
|
||||
if opts.SSHOpts.SSHUser != "" {
|
||||
userOpt := fmt.Sprintf("-l %s", shellescape.Quote(opts.SSHOpts.SSHUser))
|
||||
moreSSHOpts = append(moreSSHOpts, userOpt)
|
||||
}
|
||||
remoteCommand := strings.TrimSpace(ClientCommand)
|
||||
// note that SSHOptsStr is *not* escaped
|
||||
sshCmd := fmt.Sprintf("ssh %s %s %s %s", strings.Join(moreSSHOpts, " "), opts.SSHOptsStr, shellescape.Quote(opts.SSHHost), shellescape.Quote(remoteCommand))
|
||||
sshCmd := fmt.Sprintf("ssh %s %s %s %s", strings.Join(moreSSHOpts, " "), opts.SSHOpts.SSHOptsStr, shellescape.Quote(opts.SSHOpts.SSHHost), shellescape.Quote(remoteCommand))
|
||||
ecmd := exec.Command("bash", "-c", sshCmd)
|
||||
return ecmd
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *ClientOpts) MakeInstallCommandString(goos string, goarch string) string {
|
||||
func (opts *InstallOpts) MakeExecCmd() *exec.Cmd {
|
||||
var moreSSHOpts []string
|
||||
if opts.SSHIdentity != "" {
|
||||
identityOpt := fmt.Sprintf("-i %s", shellescape.Quote(opts.SSHIdentity))
|
||||
if opts.SSHOpts.SSHIdentity != "" {
|
||||
identityOpt := fmt.Sprintf("-i %s", shellescape.Quote(opts.SSHOpts.SSHIdentity))
|
||||
moreSSHOpts = append(moreSSHOpts, identityOpt)
|
||||
}
|
||||
if opts.SSHUser != "" {
|
||||
userOpt := fmt.Sprintf("-l %s", shellescape.Quote(opts.SSHUser))
|
||||
if opts.SSHOpts.SSHUser != "" {
|
||||
userOpt := fmt.Sprintf("-l %s", shellescape.Quote(opts.SSHOpts.SSHUser))
|
||||
moreSSHOpts = append(moreSSHOpts, userOpt)
|
||||
}
|
||||
if opts.SSHOptsStr != "" {
|
||||
optsOpt := fmt.Sprintf("--ssh-opts %s", shellescape.Quote(opts.SSHOptsStr))
|
||||
// note that SSHOptsStr is *not* escaped
|
||||
installCommand := strings.TrimSpace(InstallCommand)
|
||||
sshCmd := fmt.Sprintf("ssh %s %s %s %s", strings.Join(moreSSHOpts, " "), opts.SSHOpts.SSHOptsStr, shellescape.Quote(opts.SSHOpts.SSHHost), shellescape.Quote(installCommand))
|
||||
ecmd := exec.Command("bash", "-c", sshCmd)
|
||||
return ecmd
|
||||
}
|
||||
|
||||
func (opts *ClientOpts) MakeInstallCommandString(goos string, goarch string) string {
|
||||
var moreSSHOpts []string
|
||||
if opts.SSHOpts.SSHIdentity != "" {
|
||||
identityOpt := fmt.Sprintf("-i %s", shellescape.Quote(opts.SSHOpts.SSHIdentity))
|
||||
moreSSHOpts = append(moreSSHOpts, identityOpt)
|
||||
}
|
||||
if opts.SSHOpts.SSHUser != "" {
|
||||
userOpt := fmt.Sprintf("-l %s", shellescape.Quote(opts.SSHOpts.SSHUser))
|
||||
moreSSHOpts = append(moreSSHOpts, userOpt)
|
||||
}
|
||||
if opts.SSHOpts.SSHOptsStr != "" {
|
||||
optsOpt := fmt.Sprintf("--ssh-opts %s", shellescape.Quote(opts.SSHOpts.SSHOptsStr))
|
||||
moreSSHOpts = append(moreSSHOpts, optsOpt)
|
||||
}
|
||||
if opts.SSHHost != "" {
|
||||
sshArg := fmt.Sprintf("--ssh %s", shellescape.Quote(opts.SSHHost))
|
||||
if opts.SSHOpts.SSHHost != "" {
|
||||
sshArg := fmt.Sprintf("--ssh %s", shellescape.Quote(opts.SSHOpts.SSHHost))
|
||||
moreSSHOpts = append(moreSSHOpts, sshArg)
|
||||
}
|
||||
return fmt.Sprintf("mshell --install %s %s_%s", strings.Join(moreSSHOpts, " "), goos, goarch)
|
||||
return fmt.Sprintf("mshell --install %s %s.%s", strings.Join(moreSSHOpts, " "), goos, goarch)
|
||||
}
|
||||
|
||||
func (opts *ClientOpts) MakeRunPacket() (*packet.RunPacketType, error) {
|
||||
@ -383,6 +418,55 @@ func ValidateRemoteFds(rfds []packet.RemoteFd) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func RunInstallSSHCommand(opts *InstallOpts) error {
|
||||
ecmd := opts.MakeExecCmd()
|
||||
inputWriter, err := ecmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating stdin pipe: %v", err)
|
||||
}
|
||||
stdoutReader, err := ecmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating stdout pipe: %v", err)
|
||||
}
|
||||
stderrReader, err := ecmd.StderrPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating stderr pipe: %v", err)
|
||||
}
|
||||
go func() {
|
||||
io.Copy(os.Stderr, stderrReader)
|
||||
}()
|
||||
fd, err := os.Open(opts.OptName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open '%s': %w", opts.OptName, err)
|
||||
}
|
||||
go func() {
|
||||
defer inputWriter.Close()
|
||||
io.Copy(inputWriter, fd)
|
||||
}()
|
||||
packetParser := packet.MakePacketParser(stdoutReader)
|
||||
err = ecmd.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("running ssh command: %w", err)
|
||||
}
|
||||
for pk := range packetParser.MainCh {
|
||||
if pk.GetType() == packet.InitPacketStr {
|
||||
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)
|
||||
}
|
||||
if pk.GetType() == packet.RawPacketStr {
|
||||
rawPk := pk.(*packet.RawPacketType)
|
||||
fmt.Printf("%s\n", rawPk.Data)
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("invalid response packet '%s' received from client", pk.GetType())
|
||||
}
|
||||
return fmt.Errorf("did not receive version string from client, install not successful")
|
||||
}
|
||||
|
||||
func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, error) {
|
||||
err := ValidateRemoteFds(opts.Fds)
|
||||
if err != nil {
|
||||
@ -464,8 +548,8 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er
|
||||
installCmd := opts.MakeInstallCommandString(goos, goarch)
|
||||
return nil, fmt.Errorf("mshell command not found on remote server, can install with '%s'", installCmd)
|
||||
}
|
||||
if initPk.Version != "0.1.0" {
|
||||
return nil, fmt.Errorf("invalid remote mshell version 'v%s', must be v0.1.0", initPk.Version)
|
||||
if initPk.Version != base.MShellVersion {
|
||||
return nil, fmt.Errorf("invalid remote mshell version 'v%s', must be v%s", initPk.Version, base.MShellVersion)
|
||||
}
|
||||
versionOk = true
|
||||
if opts.Debug {
|
||||
@ -509,6 +593,9 @@ func UNameStringToGoArch(uname string) (string, string, error) {
|
||||
if goarch == "" {
|
||||
return "", "", fmt.Errorf("invalid uname machine type '%s', mshell only supports aarch64 (amd64) and x86_64 (amd64)", archVal)
|
||||
}
|
||||
if !base.ValidGoArch(goos, goarch) {
|
||||
return "", "", fmt.Errorf("invalid arch detected %s.%s", goos, goarch)
|
||||
}
|
||||
return goos, goarch, nil
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user