clonetool/transport.go
2026-09-05 23:02:29 +02:00

58 lines
1.7 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...)
}
// localAgentCommand re-execs this same binary as an agent, for a spec with
// no host part.
func localAgentCommand(remoteArgs []string) *exec.Cmd {
self, err := os.Executable()
if err != nil {
self = os.Args[0]
}
return exec.Command(self, remoteArgs...)
}