114 lines
3.5 KiB
Go
114 lines
3.5 KiB
Go
package services
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"lan-manager/server/config"
|
|
"lan-manager/server/models"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
func GetSSHInfo(ip string, port int, user, pass string) (*models.SSHInfoResult, error) {
|
|
if port <= 0 {
|
|
port = 22
|
|
}
|
|
cfg := &ssh.ClientConfig{
|
|
User: user,
|
|
Auth: []ssh.AuthMethod{
|
|
ssh.Password(pass),
|
|
},
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
Timeout: time.Duration(config.Load().SSHTimeout) * time.Second,
|
|
}
|
|
|
|
client, err := ssh.Dial("tcp", ip+":"+strconv.Itoa(port), cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer client.Close()
|
|
|
|
result := &models.SSHInfoResult{}
|
|
|
|
// Hostname
|
|
if out, err := runSSHCommand(client, "hostname"); err == nil {
|
|
result.Hostname = strings.TrimSpace(out)
|
|
}
|
|
|
|
// OS Version
|
|
if out, err := runSSHCommand(client, "cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2 | tr -d '\"'"); err == nil && out != "" {
|
|
result.OsVersion = strings.TrimSpace(out)
|
|
} else if out, err := runSSHCommand(client, "uname -sr"); err == nil {
|
|
result.OsVersion = strings.TrimSpace(out)
|
|
}
|
|
|
|
// CPU (two samples from /proc/stat for accurate real-time usage)
|
|
cpuScript := `c1=$(awk '/^cpu /{print $2+$4,$2+$4+$5}' /proc/stat) && sleep 1 && c2=$(awk '/^cpu /{print $2+$4,$2+$4+$5}' /proc/stat) && awk 'BEGIN{split("'"$c1"'", a, " "); split("'"$c2"'", b, " "); if(b[2]-a[2]>0) printf "%.1f", 100*(b[1]-a[1])/(b[2]-a[2]); else print "0.0"}'`
|
|
if out, err := runSSHCommand(client, cpuScript); err == nil && out != "" {
|
|
cpuUsage := strings.TrimSpace(out)
|
|
if cpuUsage == "" {
|
|
cpuUsage = "0.0"
|
|
}
|
|
cores := ""
|
|
if cout, err := runSSHCommand(client, "nproc"); err == nil {
|
|
cores = strings.TrimSpace(cout)
|
|
}
|
|
result.CPU = cpuUsage + "%"
|
|
if cores != "" {
|
|
result.CPU += " (" + cores + " cores)"
|
|
}
|
|
result.RawCPUInfo = result.CPU
|
|
}
|
|
|
|
// Memory
|
|
if out, err := runSSHCommand(client, "free -m | awk 'NR==2{printf \"%.1f/%.1f (%.0f%%)\", $3/1024,$2/1024,$3*100/$2 }'"); err == nil && out != "" {
|
|
result.Memory = strings.TrimSpace(out)
|
|
result.RawMemoryInfo = result.Memory
|
|
}
|
|
|
|
// Disk (exclude virtual filesystems, keep all real partitions)
|
|
// Use df -hP to prevent long filesystem names from wrapping to next line
|
|
if out, err := runSSHCommand(client, "df -hP | awk 'NR>1 && $1 !~ /^(tmpfs|devtmpfs|squashfs|overlay|efivarfs|tracefs|securityfs|pstore|bpf|configfs|fusectl|mqueue|hugetlbfs|rpc_pipefs|nfsd|binfmt_misc|autofs|devpts|proc|sysfs|cgroup|cgroup2|ramfs)$/ {printf \"%s %s/%s (%s) \", $6,$3,$2,$5}'"); err == nil && out != "" {
|
|
result.Disk = strings.TrimSpace(out)
|
|
result.RawDiskInfo = result.Disk
|
|
}
|
|
|
|
// Uptime
|
|
if out, err := runSSHCommand(client, "uptime -p 2>/dev/null || uptime | awk -F',' '{print $1}'"); err == nil {
|
|
result.Uptime = strings.TrimSpace(out)
|
|
}
|
|
|
|
// Listen ports
|
|
if out, err := runSSHCommand(client, "ss -tlnp | awk 'NR>1 {print $4}' | sed 's/.*://' | sort -n | uniq"); err == nil && out != "" {
|
|
ports := []int{}
|
|
scanner := bufio.NewScanner(strings.NewReader(out))
|
|
for scanner.Scan() {
|
|
p := strings.TrimSpace(scanner.Text())
|
|
if p == "" {
|
|
continue
|
|
}
|
|
if pi, err := strconv.Atoi(p); err == nil {
|
|
ports = append(ports, pi)
|
|
}
|
|
}
|
|
result.ListenPorts = ports
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func runSSHCommand(client *ssh.Client, cmd string) (string, error) {
|
|
session, err := client.NewSession()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer session.Close()
|
|
var b bytes.Buffer
|
|
session.Stdout = &b
|
|
err = session.Run(cmd)
|
|
return b.String(), err
|
|
}
|