//go:build windows package deploy import ( "encoding/json" "fmt" "os" "os/exec" "strings" ) // StartSSHForward opens a local port on the agent that forwards to remote_host:remote_port via SSH. func StartSSHForward(meta SSHForwardMeta) (string, error) { if meta.LocalPort <= 0 || meta.LocalPort > 65535 { return "", fmt.Errorf("local_port required (1-65535)") } meta.RemoteHost = strings.TrimSpace(meta.RemoteHost) if meta.RemoteHost == "" { return "", fmt.Errorf("remote_host required") } if meta.RemotePort <= 0 || meta.RemotePort > 65535 { return "", fmt.Errorf("remote_port required (1-65535)") } jump := strings.TrimSpace(meta.JumpHost) if jump == "" { jump = meta.RemoteHost } user := strings.TrimSpace(meta.SSHUser) if user == "" { user = os.Getenv("USERNAME") if user == "" { user = "Administrator" } } bind := fmt.Sprintf("127.0.0.1:%d:%s:%d", meta.LocalPort, meta.RemoteHost, meta.RemotePort) target := fmt.Sprintf("%s@%s", user, jump) var cmd *exec.Cmd if sshPath, err := exec.LookPath("ssh"); err == nil { cmd = HiddenCommand(sshPath, "-N", "-o", "StrictHostKeyChecking=no", "-o", "BatchMode=yes", "-o", "ExitOnForwardFailure=yes", "-L", bind, target, ) } else if plinkPath, err := exec.LookPath("plink"); err == nil { cmd = HiddenCommand(plinkPath, "-N", "-batch", "-L", bind, target, ) } else { return "", fmt.Errorf("OpenSSH client (ssh) or PuTTY plink not found on PATH") } if err := cmd.Start(); err != nil { return "", fmt.Errorf("failed to start ssh forward: %w", err) } metaBlob, _ := json.Marshal(meta) RegisterTunnelPID(TunnelSSHForward, cmd.Process.Pid, string(metaBlob)) return fmt.Sprintf("ssh forward pid %d — 127.0.0.1:%d → %s:%d via %s", cmd.Process.Pid, meta.LocalPort, meta.RemoteHost, meta.RemotePort, target), nil }