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...)...) }