package infra import ( "fmt" "os/exec" "strings" "time" ) // SSHTunnelOpen abre un tunel SSH (local port forwarding) en background. // Mapea localPort en la maquina local a remoteHost:remotePort a traves del servidor SSH. // Retorna el PID del proceso ssh para cerrarlo despues con SSHTunnelClose. func SSHTunnelOpen(conn SSHConn, localPort int, remoteHost string, remotePort int) (int, error) { if remoteHost == "" { remoteHost = "localhost" } forward := fmt.Sprintf("%d:%s:%d", localPort, remoteHost, remotePort) args := conn.sshArgs() args = append(args, "-o", "BatchMode=yes") args = append(args, "-o", "ExitOnForwardFailure=yes") args = append(args, "-N", "-f", "-L", forward, conn.destination()) cmd := exec.Command("ssh", args...) out, err := cmd.CombinedOutput() if err != nil { return 0, fmt.Errorf("ssh tunnel %s: %s", forward, strings.TrimSpace(string(out))) } // ssh -f se detach, buscar el PID del proceso time.Sleep(200 * time.Millisecond) pidCmd := exec.Command("sh", "-c", fmt.Sprintf("ps aux | grep '[s]sh.*-L %s' | awk '{print $2}' | head -1", forward)) pidOut, err := pidCmd.Output() if err != nil || strings.TrimSpace(string(pidOut)) == "" { return 0, fmt.Errorf("ssh tunnel started but could not find PID") } var pid int _, err = fmt.Sscanf(strings.TrimSpace(string(pidOut)), "%d", &pid) if err != nil { return 0, fmt.Errorf("ssh tunnel: invalid PID %q", strings.TrimSpace(string(pidOut))) } return pid, nil }