78 lines
2.8 KiB
Go
78 lines
2.8 KiB
Go
package stress
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"auto-check/pkg/sshclient"
|
||
|
||
"golang.org/x/crypto/ssh"
|
||
)
|
||
|
||
// ============================
|
||
// Docker 环境检测与特权执行(压测统一在容器中执行,工具预装在镜像里)
|
||
// ============================
|
||
|
||
// DockerInfo Docker 运行环境信息
|
||
type DockerInfo struct {
|
||
Version string // Docker 版本号(如 24.0.7)
|
||
Available bool // 是否可用
|
||
}
|
||
|
||
// IsDockerAvailable 检测远程 Docker 是否可用(飞牛 NAS 默认已安装)。
|
||
// 非 root 用户(如 fnOS admin)无 docker.sock 权限,需通过 sudo 执行。
|
||
func IsDockerAvailable(client *ssh.Client, sudoPwd string) DockerInfo {
|
||
out, err := runPrivileged(client, "docker version --format '{{.Server.Version}}' 2>/dev/null", sudoPwd)
|
||
if err != nil || strings.TrimSpace(out) == "" {
|
||
return DockerInfo{Available: false}
|
||
}
|
||
return DockerInfo{
|
||
Version: strings.TrimSpace(out),
|
||
Available: true,
|
||
}
|
||
}
|
||
|
||
// runPrivileged 以特权执行命令:root 直接执行;非 root 用 sudo,
|
||
// 若提供了 sudo 密码则通过 stdin 喂入(无需 NOPASSWD 配置)。
|
||
// fnOS 的 admin 用户无 docker.sock 权限,docker 命令均需走此路径。
|
||
func runPrivileged(client *ssh.Client, cmd, sudoPwd string) (string, error) {
|
||
// 已是 root 则直接执行
|
||
if out, err := sshclient.RunCommand(client, "id -u"); err == nil && strings.TrimSpace(out) == "0" {
|
||
return sshclient.RunCommand(client, cmd)
|
||
}
|
||
// 非 root:尝试 sudo(-p '' 静默密码提示,避免提示文本污染命令输出)
|
||
if sudoPwd != "" {
|
||
// echo 密码 | sudo -S 执行;整条命令用双引号包裹,避免命令内部单引号嵌套冲突。
|
||
// 仅对命令内的双引号转义(docker 命令均不含双引号,安全)。
|
||
quoted := strings.ReplaceAll(singleLine(cmd), `"`, `\"`)
|
||
wrapped := fmt.Sprintf("echo %s | sudo -S -p '' -- sh -c \"%s\"", shellQuote(sudoPwd), quoted)
|
||
return sshclient.RunCommand(client, wrapped)
|
||
}
|
||
// 无密码:先尝试免密 sudo (-n),失败则回退普通 sudo(会提示需要密码)
|
||
quoted := strings.ReplaceAll(singleLine(cmd), `"`, `\"`)
|
||
if out, err := sshclient.RunCommand(client, "sudo -n -p '' -- sh -c \""+quoted+"\""); err == nil {
|
||
return out, nil
|
||
}
|
||
return sshclient.RunCommand(client, "sudo -- sh -c \""+quoted+"\"")
|
||
}
|
||
|
||
// shellQuote 转义单引号,避免密码破坏命令结构
|
||
func shellQuote(s string) string {
|
||
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
|
||
}
|
||
|
||
// singleLine 去掉换行,保证能塞进 sh -c '...'
|
||
func singleLine(s string) string {
|
||
return strings.ReplaceAll(strings.ReplaceAll(s, "\n", " "), "\r", "")
|
||
}
|
||
|
||
// truncate 截断过长的输出,便于日志展示
|
||
func truncate(s string, n int) string {
|
||
// 去掉 ANSI/多余空行,只保留前 n 个字符
|
||
s = strings.TrimSpace(s)
|
||
if len([]rune(s)) > n {
|
||
return string([]rune(s)[:n]) + "...(truncated)"
|
||
}
|
||
return s
|
||
}
|