clonetool/transport.go

70 lines
2.2 KiB
Go

package main
import (
"fmt"
"os"
"os/exec"
"strings"
)
// shellQuote wraps s in single quotes so it survives the remote shell that
// OpenSSH hands its non-option arguments to (ssh joins them itself; it does
// not exec the remote command with a distinct argv the way os/exec does).
func shellQuote(s string) string {
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
}
func shellJoin(args []string) string {
parts := make([]string, len(args))
for i, a := range args {
parts[i] = shellQuote(a)
}
return strings.Join(parts, " ")
}
// sshCommand builds an *exec.Cmd that runs remoteArgs on host via ssh.
// batchMode disables interactive prompts (used for the push/pull direction
// probe, so an unreachable/unauthenticated attempt fails fast instead of
// hanging); it is left off for the manager's own control connections so a
// password prompt still works when the user is present at a terminal.
func sshCommand(sshBin string, extraOpts []string, batchMode bool, connectTimeoutSec int, user, host string, remoteArgs []string) *exec.Cmd {
args := []string{"-o", "StrictHostKeyChecking=accept-new"}
if batchMode {
args = append(args, "-o", "BatchMode=yes")
}
if connectTimeoutSec > 0 {
args = append(args, "-o", fmt.Sprintf("ConnectTimeout=%d", connectTimeoutSec))
}
for _, o := range extraOpts {
args = append(args, "-o", o)
}
userHost := host
if user != "" {
userHost = user + "@" + host
}
args = append(args, userHost, shellJoin(remoteArgs))
return exec.Command(sshBin, args...)
}
// selfExe is the path to the running clonetool binary — used to re-exec it
// as a local agent and to stream it to a remote host for deployment.
func selfExe() string {
self, err := os.Executable()
if err != nil {
return os.Args[0]
}
return self
}
// localAgentCommand re-execs this same binary as an agent, for a spec with
// no host part.
func localAgentCommand(agentArgs []string) *exec.Cmd {
return exec.Command(selfExe(), agentArgs...)
}
// sudoLocalCommand is localAgentCommand wrapped in an interactive sudo: the
// manager still owns the terminal, so a local password prompt works.
func sudoLocalCommand(agentArgs []string) *exec.Cmd {
return exec.Command("sudo", append([]string{"--", selfExe()}, agentArgs...)...)
}