Files
Olares/cli/pkg/core/connector/system.go

597 lines
14 KiB
Go

package connector
import (
"bufio"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"os/user"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/beclab/Olares/cli/pkg/core/common"
"github.com/beclab/Olares/cli/pkg/core/util"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/disk"
"github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/mem"
)
type UbuntuVersion string
type DebianVersion string
func (u UbuntuVersion) String() string {
switch u {
case Ubuntu20:
return "20."
case Ubuntu22:
return "22."
case Ubuntu24:
return "24."
}
return ""
}
func (d DebianVersion) String() string {
switch d {
case Debian11:
return "11"
case Debian12:
return "12"
case Debian13:
return "13"
}
return ""
}
const (
Ubuntu20 UbuntuVersion = "20."
Ubuntu22 UbuntuVersion = "22."
Ubuntu24 UbuntuVersion = "24."
Ubuntu25 UbuntuVersion = "25."
Debian9 DebianVersion = "9"
Debian10 DebianVersion = "10"
Debian11 DebianVersion = "11"
Debian12 DebianVersion = "12"
Debian13 DebianVersion = "13"
)
type Systems interface {
IsSupport() error
IsWindows() bool
IsDarwin() bool
IsWsl() bool
IsPve() bool
IsPveLxc() bool
IsPveOrPveLxc() bool
IsRaspbian() bool
IsLinux() bool
IsUbuntu() bool
IsDebian() bool
GetDebianVersionCode() string
IsUbuntuVersionEqual(ver UbuntuVersion) bool
IsDebianVersionEqual(ver DebianVersion) bool
IsOsArchInvalid() bool
SetHostname(v string)
GetHostname() string
GetOsType() string
GetOsArch() string
GetUsername() string
GetHomeDir() string
GetOsVersion() string
GetPkgManager() string
SetNATGateway(ip string)
GetNATGateway() string
GetOsPlatformFamily() string
GetLocalIp() string
CgroupCpuEnabled() bool
CgroupMemoryEnabled() bool
GetFsType() string
GetDefaultZfsPrefixName() string
GetTotalMemory() uint64
Print()
String() string
}
type SystemInfo struct {
HostInfo *HostInfo `json:"host"`
CpuInfo *CpuInfo `json:"cpu"`
DiskInfo *DiskInfo `json:"disk"`
MemoryInfo *MemoryInfo `json:"memory"`
FsInfo *FileSystemInfo `json:"filesystem"`
CgroupInfo *CgroupInfo `json:"cgroup,omitempty"`
LocalIp string `json:"local_ip"`
NatGateway string `json:"nat_gateway"`
PkgManager string `json:"pkg_manager"`
IsOIC bool `json:"is_oic,omitempty"`
}
func (s *SystemInfo) IsSupport() error {
if !s.IsLinux() && !s.IsDarwin() && !s.IsWindows() {
return fmt.Errorf("unsupported os type '%s'", s.GetOsType())
}
if s.IsOsArchInvalid() {
return fmt.Errorf("unsupported arch '%s'", s.GetOsArch())
}
//if !s.IsUbuntu() && !s.IsDebian() {
// return fmt.Errorf("unsupported os type '%s', exit ...", s.GetOsPlatformFamily())
//}
if s.IsUbuntu() {
if !s.IsUbuntuVersionEqual(Ubuntu22) && !s.IsUbuntuVersionEqual(Ubuntu24) && !s.IsUbuntuVersionEqual(Ubuntu25) {
return fmt.Errorf("unsupported ubuntu os version '%s'", s.GetOsVersion())
}
}
if s.IsDebian() {
if !s.IsDebianVersionEqual(Debian12) && !s.IsDebianVersionEqual(Debian13) {
return fmt.Errorf("unsupported debian os version '%s'", s.GetOsVersion())
}
}
return nil
}
func (s *SystemInfo) GetLocalIp() string {
return s.LocalIp
}
func (s *SystemInfo) SetNATGateway(ip string) {
s.NatGateway = ip
}
func (s *SystemInfo) GetNATGateway() string {
return s.NatGateway
}
func (s *SystemInfo) GetHostname() string {
return s.HostInfo.HostName
}
func (s *SystemInfo) SetHostname(v string) {
s.HostInfo.HostName = v
}
func (s *SystemInfo) GetOsType() string {
return s.HostInfo.OsType
}
func (s *SystemInfo) GetOsArch() string {
return s.HostInfo.OsArch
}
func (s *SystemInfo) GetUsername() string {
return s.HostInfo.CurrentUser
}
func (s *SystemInfo) GetHomeDir() string {
return s.HostInfo.HomeDir
}
func (s *SystemInfo) IsOsArchInvalid() bool {
return strings.EqualFold(s.HostInfo.OsArch, "")
}
func (s *SystemInfo) GetOsVersion() string {
return s.HostInfo.OsVersion
}
func (s *SystemInfo) GetOsPlatformFamily() string {
return s.HostInfo.OsPlatformFamily
}
func (s *SystemInfo) String() string {
str, _ := json.Marshal(s)
return string(str)
}
func (s *SystemInfo) IsWindows() bool {
return s.HostInfo.OsType == common.Windows
}
func (s *SystemInfo) IsDarwin() bool {
return s.HostInfo.OsType == common.Darwin
}
func (s *SystemInfo) IsPve() bool {
return s.HostInfo.OsPlatform == common.PVE
}
func (s *SystemInfo) IsPveLxc() bool {
return s.HostInfo.OsPlatform == common.PVE_LXC
}
func (s *SystemInfo) IsPveOrPveLxc() bool {
return s.IsPve() || s.IsPveLxc()
}
func (s *SystemInfo) IsWsl() bool {
return s.HostInfo.OsPlatform == common.WSL && !s.IsOIC
}
func (s *SystemInfo) IsRaspbian() bool {
return s.HostInfo.OsPlatform == common.Raspbian
}
func (s *SystemInfo) IsLinux() bool {
return s.HostInfo.OsType == common.Linux
}
func (s *SystemInfo) IsUbuntu() bool {
return s.HostInfo.OsPlatformFamily == common.Ubuntu
}
func (s *SystemInfo) IsDebian() bool {
return s.HostInfo.OsPlatformFamily == common.Debian
}
func (s *SystemInfo) IsUbuntuVersionEqual(ver UbuntuVersion) bool {
return strings.Contains(s.HostInfo.OsVersion, ver.String())
}
func (s *SystemInfo) IsDebianVersionEqual(ver DebianVersion) bool {
return strings.Contains(s.HostInfo.OsVersion, ver.String())
}
func (s *SystemInfo) GetDebianVersionCode() string {
if !s.IsDebian() {
return ""
}
if strings.Contains(s.HostInfo.OsVersion, string(Debian13)) {
return "trixie"
} else if strings.Contains(s.HostInfo.OsVersion, string(Debian12)) {
return "bookworm"
} else if strings.Contains(s.HostInfo.OsVersion, string(Debian11)) {
return "bullseye"
} else if strings.Contains(s.HostInfo.OsVersion, string(Debian10)) {
return "buster"
} else if strings.Contains(s.HostInfo.OsVersion, string(Debian9)) {
return "stretch"
} else {
return "jessie"
}
}
func (s *SystemInfo) CgroupCpuEnabled() bool {
return s.CgroupInfo.CpuEnabled >= 1
}
func (s *SystemInfo) CgroupMemoryEnabled() bool {
return s.CgroupInfo.MemoryEnabled >= 1
}
func (s *SystemInfo) GetFsType() string {
return s.FsInfo.Type
}
func (s *SystemInfo) GetDefaultZfsPrefixName() string {
return s.FsInfo.DefaultZfsPrefixName
}
func (s *SystemInfo) GetTotalMemory() uint64 {
return s.MemoryInfo.Total
}
func (s *SystemInfo) GetPkgManager() string {
return s.PkgManager
}
func (s *SystemInfo) Print() {
fmt.Printf("os info, all: %s\n", s.HostInfo.OsInfo)
fmt.Printf("host info, user: %s, hostname: %s, hostid: %s, os: %s, platform: %s, platformfamily: %s, version: %s, arch: %s, localip: %s\n",
s.HostInfo.CurrentUser, s.HostInfo.HostName, s.HostInfo.HostId,
s.HostInfo.OsType, s.HostInfo.OsPlatform, s.HostInfo.OsPlatformFamily, s.HostInfo.OsVersion, s.HostInfo.OsArch, s.LocalIp)
fmt.Printf("kernel info, version: %s\n", s.HostInfo.OsKernel)
fmt.Printf("virtual info, role: %s, system: %s\n", s.HostInfo.VirtualizationRole, s.HostInfo.VirtualizationSystem)
fmt.Printf("cpu info, model: %s, logical count: %d, physical count: %d\n",
s.CpuInfo.CpuModel, s.CpuInfo.CpuLogicalCount, s.CpuInfo.CpuPhysicalCount)
fmt.Printf("disk info, total: %s, free: %s\n", util.FormatBytes(int64(s.DiskInfo.Total)), util.FormatBytes(int64(s.DiskInfo.Free)))
fmt.Printf("fs info, fs: %s, zfsmount: %s\n", s.FsInfo.Type, s.FsInfo.DefaultZfsPrefixName)
fmt.Printf("mem info, total: %s, free: %s\n", util.FormatBytes(int64(s.MemoryInfo.Total)), util.FormatBytes(int64(s.MemoryInfo.Free)))
fmt.Printf("cgroup info, cpu: %d, mem: %d\n", s.CgroupInfo.CpuEnabled, s.CgroupInfo.MemoryEnabled)
fmt.Printf("oic: %t\n", s.IsOIC)
fmt.Printf("in wsl: %t\n", s.IsWsl())
}
func GetSystemInfo() *SystemInfo {
var si = new(SystemInfo)
si.HostInfo = getHost()
si.CpuInfo = getCpu()
si.DiskInfo = getDisk()
si.MemoryInfo = getMem()
si.FsInfo = getFs()
localIP, err := util.GetLocalIP()
if err != nil {
panic(errors.Wrap(err, "failed to get local ip"))
}
si.LocalIp = localIP.String()
if si.IsLinux() {
si.CgroupInfo = getCGroups()
}
switch si.GetOsPlatformFamily() {
case common.Ubuntu, common.Debian:
si.PkgManager = "apt-get"
case common.Fedora:
si.PkgManager = "dnf"
case common.CentOs, common.RHEL:
si.PkgManager = "yum"
default:
si.PkgManager = "apt-get"
}
return si
}
type HostInfo struct {
HostName string `json:"hostname"`
HostId string `json:"hostid"`
OsType string `json:"os_type"`
OsPlatform string `json:"os_platform"`
OsPlatformFamily string `json:"os_platform_family"`
OsVersion string `json:"os_version"`
OsArch string `json:"os_arch"`
VirtualizationRole string `json:"virtualization_role"`
VirtualizationSystem string `json:"virtualization_system"`
OsKernel string `json:"os_kernel"`
OsInfo string `json:"os_info"`
CurrentUser string `json:"current_user"`
HomeDir string `json:"home_dir"`
}
func getHost() *HostInfo {
hostInfo, err := host.Info()
if err != nil {
panic(err)
}
cmd := exec.Command("sh", "-c", "echo $(uname -a) |tr -d '\\n'")
output, _ := cmd.Output()
// if err != nil {
// panic(err)
// }
var _osType = hostInfo.OS
var _osPlatform = hostInfo.Platform
var _osPlatformFamily = hostInfo.PlatformFamily
var _osKernel = hostInfo.KernelVersion
u, _ := user.Current()
return &HostInfo{
HostName: hostInfo.Hostname,
HostId: hostInfo.HostID,
OsType: _osType, // darwin linux windows
OsPlatform: formatOsPlatform(_osType, _osPlatform, _osKernel), // darwin linux wsl raspbian pve
OsPlatformFamily: formatOsPlatformFamily(_osPlatform, _osPlatformFamily),
OsVersion: hostInfo.PlatformVersion,
OsArch: ArchAlias(hostInfo.KernelArch),
VirtualizationRole: hostInfo.VirtualizationRole,
VirtualizationSystem: hostInfo.VirtualizationSystem,
OsKernel: hostInfo.KernelVersion,
OsInfo: string(output),
CurrentUser: u.Username,
HomeDir: u.HomeDir,
}
}
func formatOsPlatform(osType, osPlatform, osKernel string) string {
if osType == common.Darwin {
return common.Darwin
}
if osPlatform == common.Raspbian {
return common.Raspbian
}
if strings.Contains(osKernel, "pve") {
if lxc := os.Getenv("container"); lxc == "lxc" {
return common.PVE_LXC
}
return common.PVE
}
if strings.Contains(osKernel, "-WSL") {
return common.WSL
}
return common.Linux
}
func formatOsPlatformFamily(osPlatform, osPlatformFamily string) string {
if osPlatform == common.Darwin {
return common.Darwin
}
if osPlatform == common.Raspbian {
return osPlatformFamily
}
return osPlatform
}
type CpuInfo struct {
CpuModel string `json:"cpu_model"`
CpuLogicalCount int `json:"cpu_logical_count"`
CpuPhysicalCount int `json:"cpu_physical_count"`
}
func getCpu() *CpuInfo {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cpuInfo, _ := cpu.InfoWithContext(ctx)
cpuLogicalCount, _ := cpu.CountsWithContext(ctx, true)
cpuPhysicalCount, _ := cpu.CountsWithContext(ctx, false)
var cpuModel = ""
if cpuInfo != nil && len(cpuInfo) > 0 {
cpuModel = cpuInfo[0].ModelName
}
return &CpuInfo{
CpuModel: cpuModel,
CpuLogicalCount: cpuLogicalCount,
CpuPhysicalCount: cpuPhysicalCount,
}
}
type DiskInfo struct {
Total uint64 `json:"total"`
Free uint64 `json:"free"`
}
func getDisk() *DiskInfo {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
usageInfo, _ := disk.UsageWithContext(ctx, "/")
var total uint64
var free uint64
if usageInfo != nil {
total = usageInfo.Total
free = usageInfo.Free
}
return &DiskInfo{
Total: total,
Free: free,
}
}
type MemoryInfo struct {
Total uint64 `json:"total"`
Free uint64 `json:"free"`
}
func getMem() *MemoryInfo {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
memInfo, _ := mem.VirtualMemoryWithContext(ctx)
var total uint64
var free uint64
if memInfo != nil {
total = memInfo.Total
free = memInfo.Free
}
return &MemoryInfo{
Total: total,
Free: free,
}
}
type FileSystemInfo struct {
Type string `json:"type"`
DefaultZfsPrefixName string `json:"default_zfs_prefix_name"`
}
func getFs() *FileSystemInfo {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var fsType = "overlayfs"
var zfsPrefixName = ""
ps, _ := disk.PartitionsWithContext(ctx, true)
if ps != nil && len(ps) > 0 {
for _, p := range ps {
if p.Mountpoint == "/var/lib" && p.Fstype == "zfs" {
fsType = "zfs"
zfsPrefixName = p.Device
break
}
}
}
return &FileSystemInfo{
Type: fsType,
DefaultZfsPrefixName: zfsPrefixName,
}
}
type CgroupInfo struct {
CpuEnabled int `json:"cpu_enabled"`
MemoryEnabled int `json:"memory_enabled"`
}
func getCGroups() *CgroupInfo {
file, err := os.Open("/proc/cgroups")
if err != nil {
return nil
}
defer file.Close()
var cpuEnabled int64
var memoryEnabled int64
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "#") {
continue
}
fields := strings.Fields(line)
if len(fields) < 4 {
continue
}
switch fields[0] {
case "cpu":
cpuEnabled, _ = strconv.ParseInt(fields[3], 10, 64)
case "memory":
memoryEnabled, _ = strconv.ParseInt(fields[3], 10, 64)
default:
continue
}
}
if err := scanner.Err(); err != nil {
panic(err)
}
return &CgroupInfo{
CpuEnabled: int(cpuEnabled),
MemoryEnabled: int(memoryEnabled),
}
}
func ArchAlias(arch string) string {
switch arch {
case "aarch64", "armv7l", "arm64", "arm":
return "arm64"
case "x86_64", "amd64":
fallthrough
case "ppc64le":
fallthrough
case "s390x":
return "amd64"
default:
return ""
}
}