// Package sshexec 远程命令执行通道(构建节点 SSH 操作)。 package sshexec import ( "fmt" "net" "time" "golang.org/x/crypto/ssh" ) // Config SSH 连接参数(来自 SSH 凭证内容 JSON) type Config struct { Host string // IP Port int // 默认 22 User string Password string PrivateKey string // PEM 私钥(与 Password 二选一) } // Run 执行单条远程命令,返回合并输出;timeout 为整条命令的硬上限 func Run(cfg Config, cmd string, timeout time.Duration) (string, error) { if cfg.Port == 0 { cfg.Port = 22 } if cfg.User == "" { cfg.User = "root" } var authMethods []ssh.AuthMethod if cfg.PrivateKey != "" { signer, err := ssh.ParsePrivateKey([]byte(cfg.PrivateKey)) if err != nil { return "", fmt.Errorf("私钥解析失败: %v", err) } authMethods = append(authMethods, ssh.PublicKeys(signer)) } if cfg.Password != "" { authMethods = append(authMethods, ssh.Password(cfg.Password)) } if len(authMethods) == 0 { return "", fmt.Errorf("未提供密码或私钥") } client, err := ssh.Dial("tcp", net.JoinHostPort(cfg.Host, fmt.Sprint(cfg.Port)), &ssh.ClientConfig{ User: cfg.User, Auth: authMethods, Timeout: 10 * time.Second, HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 内网构建节点场景 }) if err != nil { return "", fmt.Errorf("SSH 连接失败: %v", err) } defer client.Close() session, err := client.NewSession() if err != nil { return "", err } defer session.Close() done := make(chan struct{}) var output []byte var runErr error go func() { defer close(done) output, runErr = session.CombinedOutput(cmd) }() select { case <-done: return string(output), runErr case <-time.After(timeout): _ = session.Close() return string(output), fmt.Errorf("命令执行超时(%s)", timeout) } }