- 新增 internal/sshexec(x/crypto/ssh):远程命令通道,凭证来自 Credential(type=ssh) 解密(私钥/密码/端口/用户) - POST /api/build-nodes/:id/disk-test:写 dd(256MB, oflag=direct, 不支持自动降级含缓存并标注) + 读 dd + 清理;IP 为本机时直接本地 exec;输出解析兼容 Linux/Mac 两种 dd 格式;60s 超时保护 - BuildNode 新增 credentialId 关联 SSH 凭证 + 磁盘测速结果持久字段 (显式 column 标签修正 GORM MBps 命名) - 节点卡片新增仪表盘按钮触发测试;磁盘行显示 ↑写 ↓读 MB/s (>200 绿 SSD 级 / 50-200 黄 / <50 红),hover 看测试模式与时间 - 编辑弹窗新增 SSH 凭证下拉 - 本机实测:local-mac 节点 写 2336 MB/s · 读 1793 MB/s(direct)
77 lines
1.9 KiB
Go
77 lines
1.9 KiB
Go
// 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)
|
||
}
|
||
}
|