github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/ssh/ssh.go (about) 1 package ssh 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "os/exec" 8 9 "github.com/mutagen-io/mutagen/pkg/platform" 10 ) 11 12 // CompressionFlag returns a flag that can be passed to scp or ssh to enable 13 // compression. Note that while SSH does have a CompressionLevel configuration 14 // option, this only applies to SSHv1. SSHv2 defaults to a DEFLATE level of 6, 15 // which is what we want anyway. 16 func CompressionFlag() string { 17 return "-C" 18 } 19 20 // ConnectTimeoutFlag returns a flag that can be passed to scp or ssh to limit 21 // connection time. The provided timeout is in seconds. The timeout must be 22 // greater than 0, otherwise this function will panic. 23 func ConnectTimeoutFlag(timeout uint64) string { 24 // Validate the timeout. 25 if timeout < 1 { 26 panic("invalid timeout value") 27 } 28 29 // Format the flag. 30 return fmt.Sprintf("-oConnectTimeout=%d", timeout) 31 } 32 33 // ServerAliveFlags returns a set of flags that can be passed to scp or ssh to 34 // enable use of server alive messages. The provided interval is in seconds. 35 // Both the interval and count must be greater than 0, otherwise this function 36 // will panic. 37 func ServerAliveFlags(interval, countMax int) []string { 38 // Validate the interval and count. 39 if interval < 1 { 40 panic("invalid interval value") 41 } else if countMax < 1 { 42 panic("invalid count value") 43 } 44 45 // Format the flags. 46 return []string{ 47 fmt.Sprintf("-oServerAliveInterval=%d", interval), 48 fmt.Sprintf("-oServerAliveCountMax=%d", countMax), 49 } 50 } 51 52 // sshCommandPath returns the full path to use for invoking ssh. It will use the 53 // MUTAGEN_SSH_PATH environment variable if provided, otherwise falling back to 54 // a platform-specific implementation. 55 func sshCommandPath() (string, error) { 56 // If MUTAGEN_SSH_PATH is specified, then use it to perform the lookup. 57 if searchPath := os.Getenv("MUTAGEN_SSH_PATH"); searchPath != "" { 58 return platform.FindCommand("ssh", []string{searchPath}) 59 } 60 61 // Otherwise fall back to the platform-specific implementation. 62 return sshCommandPathForPlatform() 63 } 64 65 // SSHCommand prepares (but does not start) an SSH command with the specified 66 // arguments and scoped to lifetime of the provided context. 67 func SSHCommand(ctx context.Context, args ...string) (*exec.Cmd, error) { 68 // Identify the command name or path. 69 nameOrPath, err := sshCommandPath() 70 if err != nil { 71 return nil, fmt.Errorf("unable to identify 'ssh' command: %w", err) 72 } 73 74 // Create the command. 75 return exec.CommandContext(ctx, nameOrPath, args...), nil 76 } 77 78 // scpCommandPath returns the full path to use for invoking scp. It will use the 79 // MUTAGEN_SSH_PATH environment variable if provided, otherwise falling back to 80 // a platform-specific implementation. 81 func scpCommandPath() (string, error) { 82 // If MUTAGEN_SSH_PATH is specified, then use it to perform the lookup. 83 if searchPath := os.Getenv("MUTAGEN_SSH_PATH"); searchPath != "" { 84 return platform.FindCommand("scp", []string{searchPath}) 85 } 86 87 // Otherwise fall back to the platform-specific implementation. 88 return scpCommandPathForPlatform() 89 } 90 91 // SCPCommand prepares (but does not start) an SCP command with the specified 92 // arguments and scoped to lifetime of the provided context. 93 func SCPCommand(ctx context.Context, args ...string) (*exec.Cmd, error) { 94 // Identify the command name or path. 95 nameOrPath, err := scpCommandPath() 96 if err != nil { 97 return nil, fmt.Errorf("unable to identify 'scp' command: %w", err) 98 } 99 100 // Create the command. 101 return exec.CommandContext(ctx, nameOrPath, args...), nil 102 }