diff --git a/README.md b/README.md index fc089c7..4636176 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ orchard list vms ### Accessing Virtual Machines +#### SSH + To SSH into a VM use the `orchard ssh` command: ```shell @@ -40,6 +42,20 @@ orchard ssh vm ventura-base You can specify the `--username` and `--password` flags to specify the username/password pair to SSH. By default, `admin`/`admin` is used. +You can also execute remote commands instead of spawning a login shell, similarly to the OpenSSH's `ssh` command: + +```shell +orchard ssh vm ventura-base "uname -a" +``` + +You can execute scripts remotely this way, by telling the remote command-line interpreter to read from the standard input and using the redirection operator as follows: + +```shell +orchard ssh vm ventura-base "bash -s" < script.sh +``` + +#### VNC + Similar to `ssh` command, you can use `vnc` command to open Screen Sharing into a remote VM: ```shell diff --git a/internal/command/ssh/vm.go b/internal/command/ssh/vm.go index d2e3809..34f1b72 100644 --- a/internal/command/ssh/vm.go +++ b/internal/command/ssh/vm.go @@ -22,9 +22,9 @@ var wait uint16 func newSSHVMCommand() *cobra.Command { command := &cobra.Command{ - Use: "vm VM_NAME", + Use: "vm VM_NAME [COMMAND]", Short: "SSH into the VM", - Args: cobra.ExactArgs(1), + Args: cobra.RangeArgs(1, 2), RunE: runSSHVM, } @@ -39,8 +39,16 @@ func newSSHVMCommand() *cobra.Command { } func runSSHVM(cmd *cobra.Command, args []string) error { + // Required NAME argument name := args[0] + // Optional [COMMAND] argument + var command string + + if len(args) > 1 { + command = args[1] + } + client, err := client.New() if err != nil { return err @@ -70,6 +78,9 @@ func runSSHVM(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("%w: failed to establish an SSH connection: %v", ErrFailed, err) } + defer func() { + _ = sshConn.Close() + }() sshClient := ssh.NewClient(sshConn, chans, reqs) @@ -77,6 +88,17 @@ func runSSHVM(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("%w: failed to open an SSH session: %v", ErrFailed, err) } + defer func() { + _ = sshSess.Close() + }() + + if command != "" { + sshSess.Stdout = os.Stdout + sshSess.Stderr = os.Stderr + sshSess.Stdin = os.Stdin + + return sshSess.Run(command) + } // Switch controlling terminal into raw mode, // otherwise ANSI escape sequences that allow @@ -109,7 +131,6 @@ func runSSHVM(cmd *cobra.Command, args []string) error { go func() { _, _ = io.Copy(sshSessStdinPipe, os.Stdin) _ = sshSessStdinPipe.Close() - _ = sshSess.Close() }() // Periodically adjust remote terminal size @@ -176,7 +197,7 @@ func ChooseUsernameAndPassword( } // Fall back - fmt.Println("no credentials specified or found, trying default admin:admin credentials...") + _, _ = fmt.Fprintf(os.Stderr, "no credentials specified or found, trying default admin:admin credentials...") return "admin", "admin" }