diff --git a/CHANGELOG.md b/CHANGELOG.md index c6298d25..009047e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## Changelog +### [13.1.0](https://kaos.sh/ek/13.1.0) + +- `[env]` Fixed compatibility with Windows +- `[support]` Add basic support of collecting info on Windows +- `[support/apps]` Added Windows support +- `[support/network]` Added Windows support + ### [13.0.0](https://kaos.sh/ek/13.0.0) > [!CAUTION] diff --git a/ek.go b/ek.go index 9c5ecce9..bf3c4a27 100644 --- a/ek.go +++ b/ek.go @@ -21,7 +21,7 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // // VERSION is current ek package version -const VERSION = "13.0.0" +const VERSION = "13.1.0" // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/env/env.go b/env/env.go index a302a441..df9b5fd5 100644 --- a/env/env.go +++ b/env/env.go @@ -1,6 +1,3 @@ -//go:build !windows -// +build !windows - // Package env provides methods for working with environment variables package env @@ -15,7 +12,6 @@ import ( "os" "strconv" "strings" - "syscall" ) // ////////////////////////////////////////////////////////////////////////////////// // @@ -51,19 +47,6 @@ func Get() Env { return env } -// Which find full path to some app -func Which(name string) string { - paths := Get().Path() - - for _, path := range paths { - if syscall.Access(path+"/"+name, syscall.F_OK) == nil { - return path + "/" + name - } - } - - return "" -} - // ////////////////////////////////////////////////////////////////////////////////// // // Get returns environment variable value diff --git a/env/env_unix.go b/env/env_unix.go new file mode 100644 index 00000000..2d9374df --- /dev/null +++ b/env/env_unix.go @@ -0,0 +1,29 @@ +//go:build !windows +// +build !windows + +// Package env provides methods for working with environment variables +package env + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2024 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import "syscall" + +// ////////////////////////////////////////////////////////////////////////////////// // + +// Which find full path to some app +func Which(name string) string { + paths := Get().Path() + + for _, path := range paths { + if syscall.Access(path+"/"+name, syscall.F_OK) == nil { + return path + "/" + name + } + } + + return "" +} diff --git a/env/env_windows.go b/env/env_windows.go index 66b85f4f..f95ae8d2 100644 --- a/env/env_windows.go +++ b/env/env_windows.go @@ -8,73 +8,9 @@ package env // // // ////////////////////////////////////////////////////////////////////////////////// // -// ❗ Env is map with environment values -type Env map[string]string - -// ❗ Variable is environment variable for lazy reading -type Variable struct { - key string - value string - isRead bool -} - -// ////////////////////////////////////////////////////////////////////////////////// // - -// ❗ Var creates new environment variable struct -func Var(name string) *Variable { - panic("UNSUPPORTED") -} - -// ❗ Get return key-value map with environment values -func Get() Env { - panic("UNSUPPORTED") -} - // ❗ Which find full path to some app func Which(name string) string { panic("UNSUPPORTED") } // ////////////////////////////////////////////////////////////////////////////////// // - -// ❗ Get returns environment variable value -func (v *Variable) Get() string { - panic("UNSUPPORTED") -} - -// ❗ Is returns true if environment variable value is equal to given one -func (v *Variable) Is(value string) bool { - panic("UNSUPPORTED") -} - -// ❗ String returns environment variable value as string -func (v *Variable) String() string { - panic("UNSUPPORTED") -} - -// ❗ Reset resets reading state of variable -func (v *Variable) Reset() *Variable { - panic("UNSUPPORTED") -} - -// ////////////////////////////////////////////////////////////////////////////////// // - -// ❗ Path return path as string slice -func (e Env) Path() []string { - panic("UNSUPPORTED") -} - -// ❗ GetS return environment variable value as string -func (e Env) GetS(name string) string { - panic("UNSUPPORTED") -} - -// ❗ GetI return environment variable value as int -func (e Env) GetI(name string) int { - panic("UNSUPPORTED") -} - -// ❗ GetF return environment variable value as float -func (e Env) GetF(name string) float64 { - panic("UNSUPPORTED") -} diff --git a/go.mod b/go.mod index 139ae8ac..51fab7a0 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/essentialkaos/check v1.4.0 github.com/essentialkaos/depsy v1.3.0 github.com/essentialkaos/go-linenoise/v3 v3.6.0 - golang.org/x/crypto v0.24.0 + golang.org/x/crypto v0.25.0 golang.org/x/sys v0.22.0 ) diff --git a/go.sum b/go.sum index 80b4fb74..b0ed1b35 100644 --- a/go.sum +++ b/go.sum @@ -13,7 +13,7 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/support/apps/apps.go b/support/apps/apps.go index a8ab9545..00cf2424 100644 --- a/support/apps/apps.go +++ b/support/apps/apps.go @@ -1,6 +1,3 @@ -//go:build !windows -// +build !windows - // Package apps provides methods for obtaining version information about various tools package apps diff --git a/support/apps/apps_windows.go b/support/apps/apps_windows.go deleted file mode 100644 index 640d527e..00000000 --- a/support/apps/apps_windows.go +++ /dev/null @@ -1,85 +0,0 @@ -// Package apps provides methods for obtaining version information about various tools -package apps - -// ////////////////////////////////////////////////////////////////////////////////// // -// // -// Copyright (c) 2024 ESSENTIAL KAOS // -// Apache License, Version 2.0 // -// // -// ////////////////////////////////////////////////////////////////////////////////// // - -import ( - "github.com/essentialkaos/ek/v13/support" -) - -// ////////////////////////////////////////////////////////////////////////////////// // - -// ❗ Golang extracts version info from go command output -func Golang() support.App { - panic("UNSUPPORTED") -} - -// ❗ GCC extracts version info from gcc command output -func GCC() support.App { - panic("UNSUPPORTED") -} - -// ❗ Clang extracts version info from clang command output -func Clang() support.App { - panic("UNSUPPORTED") -} - -// ❗ Python3 extracts version info from python 3.x command output -func Python3() support.App { - panic("UNSUPPORTED") -} - -// ❗ Java extracts version info from java command output -func Java() support.App { - panic("UNSUPPORTED") -} - -// ❗ Groovy extracts version info from groovy command output -func Groovy() support.App { - panic("UNSUPPORTED") -} - -// ❗ Ruby extracts version info from ruby command output -func Ruby() support.App { - panic("UNSUPPORTED") -} - -// ❗ NodeJS extracts version info from node command output -func NodeJS() support.App { - panic("UNSUPPORTED") -} - -// ❗ Rust extracts version info from rust command output -func Rust() support.App { - panic("UNSUPPORTED") -} - -// ❗ PHP extracts version info from php command output -func PHP() support.App { - panic("UNSUPPORTED") -} - -// ❗ Bash extracts version info from bash command output -func Bash() support.App { - panic("UNSUPPORTED") -} - -// ❗ Git extracts version info from git command output -func Git() support.App { - panic("UNSUPPORTED") -} - -// ❗ Mercurial extracts version info from hg command output -func Mercurial() support.App { - panic("UNSUPPORTED") -} - -// ❗ SVN extracts version info from svn command output -func SVN() support.App { - panic("UNSUPPORTED") -} diff --git a/support/fs/fs_windows.go b/support/fs/fs_windows.go index 92a294b0..3dc1489c 100644 --- a/support/fs/fs_windows.go +++ b/support/fs/fs_windows.go @@ -14,7 +14,7 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // -// ❗ Collect collects info about filesystem +// Collect collects info about filesystem func Collect() []support.FSInfo { - panic("UNSUPPORTED") + return nil } diff --git a/support/network/ip-resolver.go b/support/network/ip-resolver.go new file mode 100644 index 00000000..8ca8341e --- /dev/null +++ b/support/network/ip-resolver.go @@ -0,0 +1,72 @@ +// Package network provides methods for collecting information about machine network +package network + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2024 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "net" + "strings" + + "github.com/essentialkaos/ek/v13/req" + "github.com/essentialkaos/ek/v13/strutil" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +// resolvePublicIP resolves public IP using IP resolver +func resolvePublicIP(resolverURL string) string { + resp, err := req.Request{ + URL: resolverURL, + AutoDiscard: true, + }.Get() + + if err != nil { + return "" + } + + var ip string + + if strings.HasSuffix(resolverURL, "/cdn-cgi/trace") { + ip = extractIPFromCloudflareTrace(resp.String()) + } else { + ip = resp.String() + } + + if isValidIP(ip) { + return ip + } + + return "" +} + +// extractIPFromCloudflareTrace extracts public IP from Cloudflare trace +// response +func extractIPFromCloudflareTrace(data string) string { + for i := 0; i < 16; i++ { + f := strutil.ReadField(data, i, false, '\n') + + if f == "" { + break + } + + n := strutil.ReadField(f, 0, false, '=') + + if n != "ip" { + continue + } + + return strutil.ReadField(f, 1, false, '=') + } + + return "" +} + +// isValidIP returns if given IP is valid +func isValidIP(ip string) bool { + return ip != "" && net.ParseIP(ip) != nil +} diff --git a/support/network/network.go b/support/network/network.go index 54b3b5ef..d862b5b6 100644 --- a/support/network/network.go +++ b/support/network/network.go @@ -12,15 +12,11 @@ package network // ////////////////////////////////////////////////////////////////////////////////// // import ( - "net" "os" - "strings" "github.com/essentialkaos/ek/v13/netutil" - "github.com/essentialkaos/ek/v13/req" "github.com/essentialkaos/ek/v13/sliceutil" "github.com/essentialkaos/ek/v13/sortutil" - "github.com/essentialkaos/ek/v13/strutil" "github.com/essentialkaos/ek/v13/support" ) @@ -66,56 +62,3 @@ func cleanIPList(ips []string) []string { return result } - -// resolvePublicIP resolves public IP using IP resolver -func resolvePublicIP(resolverURL string) string { - resp, err := req.Request{ - URL: resolverURL, - AutoDiscard: true, - }.Get() - - if err != nil { - return "" - } - - var ip string - - if strings.HasSuffix(resolverURL, "/cdn-cgi/trace") { - ip = extractIPFromCloudflareTrace(resp.String()) - } else { - ip = resp.String() - } - - if isValidIP(ip) { - return ip - } - - return "" -} - -// extractIPFromCloudflareTrace extracts public IP from Cloudflare trace -// response -func extractIPFromCloudflareTrace(data string) string { - for i := 0; i < 16; i++ { - f := strutil.ReadField(data, i, false, '\n') - - if f == "" { - break - } - - n := strutil.ReadField(f, 0, false, '=') - - if n != "ip" { - continue - } - - return strutil.ReadField(f, 1, false, '=') - } - - return "" -} - -// isValidIP returns if given IP is valid -func isValidIP(ip string) bool { - return ip != "" && net.ParseIP(ip) != nil -} diff --git a/support/network/network_windows.go b/support/network/network_windows.go index 6840d4c3..609aa74e 100644 --- a/support/network/network_windows.go +++ b/support/network/network_windows.go @@ -10,11 +10,25 @@ package network import ( "github.com/essentialkaos/ek/v13/support" + + "golang.org/x/sys/windows" ) // ////////////////////////////////////////////////////////////////////////////////// // -// ❗ Collect collects network info +// Collect collects network info func Collect(ipResolverURL ...string) *support.NetworkInfo { - panic("UNSUPPORTED") + info := &support.NetworkInfo{} + + info.Hostname, _ = windows.ComputerName() + + if len(ipResolverURL) != 0 { + info.PublicIP = resolvePublicIP(ipResolverURL[0]) + } + + if info.Hostname == "" && info.PublicIP == "" { + return nil + } + + return info } diff --git a/support/pkgs/pkgs_windows.go b/support/pkgs/pkgs_windows.go index cc64dbd4..0654423e 100644 --- a/support/pkgs/pkgs_windows.go +++ b/support/pkgs/pkgs_windows.go @@ -14,7 +14,7 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // -// ❗ Collect collect info about packages +// Collect collect info about packages func Collect(pkgs ...string) []support.Pkg { - panic("UNSUPPORTED") + return nil } diff --git a/support/services/services_windows.go b/support/services/services_windows.go index ea68cfd0..658f57e4 100644 --- a/support/services/services_windows.go +++ b/support/services/services_windows.go @@ -12,7 +12,7 @@ import "github.com/essentialkaos/ek/v13/support" // ////////////////////////////////////////////////////////////////////////////////// // -// ❗ Collect collect info about services +// Collect collect info about services func Collect(services ...string) []support.Service { - panic("UNSUPPORTED") + return nil } diff --git a/support/support.go b/support/support.go index a342e86e..f87ea875 100644 --- a/support/support.go +++ b/support/support.go @@ -58,6 +58,23 @@ package support // // // ////////////////////////////////////////////////////////////////////////////////// // +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "runtime/debug" + "strings" + + "github.com/essentialkaos/ek/v13/fmtc" + "github.com/essentialkaos/ek/v13/fmtutil" + "github.com/essentialkaos/ek/v13/hash" + "github.com/essentialkaos/ek/v13/mathutil" + "github.com/essentialkaos/ek/v13/strutil" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + type CheckStatus string const ( @@ -184,3 +201,548 @@ type Check struct { Title string `json:"title"` Message string `json:"message,omitempty"` } + +// ////////////////////////////////////////////////////////////////////////////////// // + +// Collect collects basic info about system +func Collect(app, ver string) *Info { + bin, _ := os.Executable() + + if bin != "" { + bin = filepath.Base(bin) + } + + info := &Info{ + Name: app, + Version: ver, + Binary: bin, + } + + info.appendBuildInfo() + info.appendOSInfo() + info.appendSystemInfo() + + return info +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// WithDeps adds information about dependencies +func (i *Info) WithDeps(deps []Dep) *Info { + if i == nil { + return nil + } + + if len(deps) > 0 { + i.Deps = deps + } + + return i +} + +// WithRevision adds git revision +func (i *Info) WithRevision(rev string) *Info { + if i == nil { + return nil + } + + if rev != "" { + i.Build.GitSHA = rev + return i + } + + i.Build.GitSHA = extractGitRevFromBuildInfo() + + return i +} + +// WithPackages adds information about packages +func (i *Info) WithPackages(pkgs []Pkg) *Info { + if i == nil { + return nil + } + + i.Pkgs = append(i.Pkgs, pkgs...) + + return i +} + +// WithServices adds information about services +func (i *Info) WithServices(services []Service) *Info { + if i == nil { + return nil + } + + i.Services = append(i.Services, services...) + + return i +} + +// WithPackages adds information about system apps +func (i *Info) WithApps(apps ...App) *Info { + if i == nil { + return nil + } + + i.Apps = append(i.Apps, apps...) + + return i +} + +// WithChecks adds information custom checks +func (i *Info) WithChecks(check ...Check) *Info { + if i == nil { + return nil + } + + i.Checks = append(i.Checks, check...) + + return i +} + +// WithEnvVars adds information with environment variables +func (i *Info) WithEnvVars(vars ...string) *Info { + if i == nil { + return nil + } + + for _, k := range vars { + if k == "" { + continue + } + + v := os.Getenv(k) + + if v != "" { + i.Env = append(i.Env, EnvVar{k, v}) + } + } + + return i +} + +// WithNetwork adds information about the network +func (i *Info) WithNetwork(info *NetworkInfo) *Info { + if i == nil { + return nil + } + + i.Network = info + + return i +} + +// WithFS adds file system information +func (i *Info) WithFS(info []FSInfo) *Info { + if i == nil { + return nil + } + + i.FS = info + + return i +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// Print prints support info +func (i *Info) Print() { + if i == nil { + return + } + + fmtutil.SeparatorTitleColorTag = "{s-}" + fmtutil.SeparatorFullscreen = false + fmtutil.SeparatorColorTag = "{s-}" + fmtutil.SeparatorSize = 80 + + i.printAppInfo() + i.printOSInfo() + i.printNetworkInfo() + i.printFSInfo() + i.printEnvVars() + i.printPackagesInfo() + i.printServicesInfo() + i.printAppsInfo() + i.printChecksInfo() + i.printDependencies() + + fmtutil.Separator(false) +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// appendBuildInfo appends build info +func (i *Info) appendBuildInfo() { + i.Build = &BuildInfo{ + GoVersion: strings.TrimPrefix(runtime.Version(), "go"), + GoArch: runtime.GOARCH, + GoOS: runtime.GOOS, + } + + bin, _ := os.Executable() + binSHA := hash.FileHash(bin) + + i.Build.BinSHA = strutil.Head(binSHA, 7) +} + +// printAppInfo prints info about app +func (i *Info) printAppInfo() { + fmtutil.Separator(false, "APPLICATION INFO") + + name := i.Name + + if strings.ToLower(i.Name) != strings.ToLower(i.Binary) { + name += fmtc.Sprintf(" {s-}(%s){!}", i.Binary) + } + + format(7, true, + "Name", name, + "Version", i.Version, + ) + + if i.Build == nil { + return + } + + format(7, false, + "Go", fmtc.Sprintf("%s {s}(%s/%s){!}", i.Build.GoVersion, i.Build.GoOS, i.Build.GoArch), + "Git SHA", i.Build.GitSHA+getHashColorBullet(i.Build.GitSHA), + "Bin SHA", i.Build.BinSHA+getHashColorBullet(i.Build.BinSHA), + ) +} + +// printOSInfo prints info about OS and system +func (i *Info) printOSInfo() { + if i.OS != nil { + fmtutil.Separator(false, "OS INFO") + + format(12, true, + "Name", strutil.Q(i.OS.coloredName, i.OS.Name), + "Pretty Name", strutil.Q(i.OS.coloredPrettyName, i.OS.PrettyName), + "Version", i.OS.Version, + "ID", i.OS.ID, + "ID Like", i.OS.IDLike, + "Version ID", i.OS.VersionID, + "Version Code", i.OS.VersionCode, + "Platform ID", i.OS.PlatformID, + "CPE", i.OS.CPE, + ) + } else if i.System != nil { + fmtutil.Separator(false, "SYSTEM INFO") + + format(12, true, + "Name", i.System.Name, + "Arch", i.System.Arch, + "Kernel", i.System.Kernel, + ) + } + + if i.System != nil { + format(12, true, + "Arch", i.System.Arch, + "Kernel", i.System.Kernel, + ) + } + + if i.System.ContainerEngine != "" { + fmtc.NewLine() + switch i.System.ContainerEngine { + case "docker": + format(12, true, "Container", "Yes (Docker)") + case "docker+runsc": + format(12, true, "Container", "Yes (Docker+gVisor)") + case "podman": + format(12, true, "Container", "Yes (Podman)") + case "lxc": + format(12, true, "Container", "Yes (LXC)") + case "yandex": + format(12, true, "Container", "Yes (Yandex Serverless)") + } + } +} + +// printEnvVars prints environment variables +func (i *Info) printEnvVars() { + if len(i.Env) == 0 { + return + } + + fmtutil.Separator(false, "ENVIRONMENT VARIABLES") + + size := getMaxKeySize(i.Env) + + for _, ev := range i.Env { + format(size, true, ev.Key, ev.Value) + } +} + +// printPackagesInfo prints info about packages +func (i *Info) printPackagesInfo() { + if len(i.Pkgs) == 0 { + return + } + + fmtutil.Separator(false, "PACKAGES") + + size := getMaxAppNameSize(i.Pkgs) + + for _, p := range i.Pkgs { + if p.Name == "" { + continue + } + + format(size, true, p.Name, p.Version) + } +} + +// printServicesInfo prints services info +func (i *Info) printServicesInfo() { + if len(i.Services) == 0 { + return + } + + fmtutil.Separator(false, "SERVICES") + + size := getMaxServiceNameSize(i.Services) + + for _, s := range i.Services { + var status string + + switch s.Status { + case STATUS_WORKS: + status = "{g}works{!}" + case STATUS_STOPPED: + status = "{s}stopped{!}" + } + + if s.IsEnabled { + status += " {s-}(enabled){!}" + } + + format(size, true, s.Name, fmtc.Sprint(status)) + } +} + +// printAppsInfo prints info about applications +func (i *Info) printAppsInfo() { + if len(i.Apps) == 0 { + return + } + + fmtutil.Separator(false, "APPLICATIONS") + + size := getMaxAppNameSize(i.Apps) + + for _, a := range i.Apps { + if a.Name == "" { + continue + } + + v := a.Version + + v = strings.ReplaceAll(v, "(", "{s}(") + v = strings.ReplaceAll(v, ")", "){!}") + + format(size, true, a.Name, fmtc.Sprint(v)) + } +} + +// printChecksInfo prints checks info +func (i *Info) printChecksInfo() { + if len(i.Checks) == 0 { + return + } + + fmtutil.Separator(false, "CHECKS") + + for _, c := range i.Checks { + if c.Title == "" { + continue + } + + switch c.Status { + case CHECK_OK: + fmtc.Print(" {g}✔ {!}") + case CHECK_SKIP: + fmtc.Print(" {s-}✔ {!}") + case CHECK_WARN: + fmtc.Print(" {y}✖ {!}") + case CHECK_ERROR: + fmtc.Print(" {r}✖ {!}") + } + + fmtc.Printf(" {*}%s{!}", c.Title) + + if c.Message == "" { + fmtc.NewLine() + continue + } + + switch c.Status { + case CHECK_OK, CHECK_SKIP: + fmtc.Printf(" {s}— {&}%s{!}\n", c.Message) + case CHECK_WARN: + fmtc.Printf(" {s}— {y}{&}%s{!}\n", c.Message) + case CHECK_ERROR: + fmtc.Printf(" {s}— {r}{&}%s{!}\n", c.Message) + } + } +} + +// printNetworkInfo prints network info +func (i *Info) printNetworkInfo() { + if i.Network == nil { + return + } + + fmtutil.Separator(false, "NETWORK") + + format(0, false, + "Hostname", i.Network.Hostname, + "Public IP", i.Network.PublicIP, + "IP v4", strings.Join(i.Network.IPv4, " "), + "IP v6", strings.Join(i.Network.IPv6, " "), + ) +} + +// printFSInfo prints filesystem info +func (i *Info) printFSInfo() { + if len(i.FS) == 0 { + return + } + + fmtutil.Separator(false, "FILESYSTEM") + + size := getMaxDeviceNameSize(i.FS) + + for _, m := range i.FS { + if m.Path == "" || m.Device == "" { + continue + } + + format(size, false, m.Device, fmtc.Sprintf( + "%s {s}(%s){!} %s{s}/{!}%s {s-}(%s){!}", + m.Path, m.Type, fmtutil.PrettySize(m.Used), + fmtutil.PrettySize(m.Used+m.Free), + fmtutil.PrettyPerc(mathutil.Perc(m.Used, m.Used+m.Free)), + )) + } +} + +// printDependencies prints used dependencies +func (i *Info) printDependencies() { + if len(i.Deps) == 0 { + return + } + + fmtutil.Separator(false, "DEPENDENCIES") + + for _, dep := range i.Deps { + switch dep.Extra { + case "": + fmtc.Printf(" {s}%8s{!} %s\n", dep.Version, dep.Path) + default: + fmtc.Printf(" {s}%8s{!} %s {s-}(%s){!}\n", dep.Version, dep.Path, dep.Extra) + } + } +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// format formats and prints records +func format(size int, printEmpty bool, records ...string) { + if size <= 0 { + for i := 0; i < len(records); i += 2 { + if records[i+1] == "" && !printEmpty { + continue + } + + size = mathutil.Max(size, len(records[i])) + } + } + + size++ + + for i := 0; i < len(records); i += 2 { + name, value := records[i]+":", records[i+1] + + if value == "" && printEmpty { + fm := fmt.Sprintf(" {*}%%-%ds{!} {s-}—{!}\n", size) + fmtc.Printf(fm, name) + } else if value != "" { + fm := fmt.Sprintf(" {*}%%-%ds{!} %%s\n", size) + fmtc.Printf(fm, name, value) + } + } +} + +// extractGitRevFromBuildInfo extracts git SHA from embedded build info +func extractGitRevFromBuildInfo() string { + info, ok := debug.ReadBuildInfo() + + if !ok { + return "" + } + + for _, s := range info.Settings { + if s.Key == "vcs.revision" && len(s.Value) > 7 { + return s.Value[:7] + } + } + + return "" +} + +// getHashColorBullet return bullet with color from hash +func getHashColorBullet(v string) string { + if v == "" || fmtc.DisableColors || !fmtc.IsTrueColorSupported() { + return "" + } + + return fmtc.Sprintf(" {#" + strutil.Head(v, 6) + "}● {!}") +} + +// getMaxKeySize returns max key size +func getMaxKeySize(vars []EnvVar) int { + var size int + + for _, ev := range vars { + size = mathutil.Max(size, len(ev.Key)) + } + + return size +} + +// getMaxAppNameSize returns max package name size +func getMaxAppNameSize(apps []App) int { + var size int + + for _, p := range apps { + size = mathutil.Max(size, len(p.Name)) + } + + return size +} + +// getMaxServiceNameSize returns max package name size +func getMaxServiceNameSize(apps []Service) int { + var size int + + for _, s := range apps { + size = mathutil.Max(size, len(s.Name)) + } + + return size +} + +// getMaxDeviceNameSize returns max device name size +func getMaxDeviceNameSize(mounts []FSInfo) int { + var size int + + for _, m := range mounts { + size = mathutil.Max(size, len(m.Device)) + } + + return size +} diff --git a/support/support_darwin.go b/support/support_darwin.go index f734cd6a..a643e94b 100644 --- a/support/support_darwin.go +++ b/support/support_darwin.go @@ -40,8 +40,3 @@ func (i *Info) appendOSInfo() { Build: osInfo.Build, } } - -// appendPackagesInfo appends packages info -func (i *Info) appendPackagesInfo() { - return -} diff --git a/support/support_nix.go b/support/support_nix.go deleted file mode 100644 index f97ae451..00000000 --- a/support/support_nix.go +++ /dev/null @@ -1,571 +0,0 @@ -//go:build !windows -// +build !windows - -package support - -// ////////////////////////////////////////////////////////////////////////////////// // -// // -// Copyright (c) 2024 ESSENTIAL KAOS // -// Apache License, Version 2.0 // -// // -// ////////////////////////////////////////////////////////////////////////////////// // - -import ( - "fmt" - "os" - "runtime" - "runtime/debug" - "strings" - - "github.com/essentialkaos/ek/v13/fmtc" - "github.com/essentialkaos/ek/v13/fmtutil" - "github.com/essentialkaos/ek/v13/hash" - "github.com/essentialkaos/ek/v13/mathutil" - "github.com/essentialkaos/ek/v13/path" - "github.com/essentialkaos/ek/v13/strutil" -) - -// ////////////////////////////////////////////////////////////////////////////////// // - -// Collect collects basic info about system -func Collect(app, ver string) *Info { - bin, _ := os.Executable() - - if bin != "" { - bin = path.Base(bin) - } - - info := &Info{ - Name: app, - Version: ver, - Binary: bin, - } - - info.appendBuildInfo() - info.appendOSInfo() - info.appendSystemInfo() - - return info -} - -// ////////////////////////////////////////////////////////////////////////////////// // - -// WithDeps adds information about dependencies -func (i *Info) WithDeps(deps []Dep) *Info { - if i == nil { - return nil - } - - if len(deps) > 0 { - i.Deps = deps - } - - return i -} - -// WithRevision adds git revision -func (i *Info) WithRevision(rev string) *Info { - if i == nil { - return nil - } - - if rev != "" { - i.Build.GitSHA = rev - return i - } - - i.Build.GitSHA = extractGitRevFromBuildInfo() - - return i -} - -// WithPackages adds information about packages -func (i *Info) WithPackages(pkgs []Pkg) *Info { - if i == nil { - return nil - } - - i.Pkgs = append(i.Pkgs, pkgs...) - - return i -} - -// WithServices adds information about services -func (i *Info) WithServices(services []Service) *Info { - if i == nil { - return nil - } - - i.Services = append(i.Services, services...) - - return i -} - -// WithPackages adds information about system apps -func (i *Info) WithApps(apps ...App) *Info { - if i == nil { - return nil - } - - i.Apps = append(i.Apps, apps...) - - return i -} - -// WithChecks adds information custom checks -func (i *Info) WithChecks(check ...Check) *Info { - if i == nil { - return nil - } - - i.Checks = append(i.Checks, check...) - - return i -} - -// WithEnvVars adds information with environment variables -func (i *Info) WithEnvVars(vars ...string) *Info { - if i == nil { - return nil - } - - for _, k := range vars { - if k == "" { - continue - } - - v := os.Getenv(k) - - if v != "" { - i.Env = append(i.Env, EnvVar{k, v}) - } - } - - return i -} - -// WithNetwork adds information about the network -func (i *Info) WithNetwork(info *NetworkInfo) *Info { - if i == nil { - return nil - } - - i.Network = info - - return i -} - -// WithFS adds file system information -func (i *Info) WithFS(info []FSInfo) *Info { - if i == nil { - return nil - } - - i.FS = info - - return i -} - -// ////////////////////////////////////////////////////////////////////////////////// // - -// Print prints support info -func (i *Info) Print() { - if i == nil { - return - } - - fmtutil.SeparatorTitleColorTag = "{s-}" - fmtutil.SeparatorFullscreen = false - fmtutil.SeparatorColorTag = "{s-}" - fmtutil.SeparatorSize = 80 - - i.printAppInfo() - i.printOSInfo() - i.printNetworkInfo() - i.printFSInfo() - i.printEnvVars() - i.printPackagesInfo() - i.printServicesInfo() - i.printAppsInfo() - i.printChecksInfo() - i.printDependencies() - - fmtutil.Separator(false) -} - -// ////////////////////////////////////////////////////////////////////////////////// // - -// appendBuildInfo appends build info -func (i *Info) appendBuildInfo() { - i.Build = &BuildInfo{ - GoVersion: strings.TrimPrefix(runtime.Version(), "go"), - GoArch: runtime.GOARCH, - GoOS: runtime.GOOS, - } - - bin, _ := os.Executable() - binSHA := hash.FileHash(bin) - - i.Build.BinSHA = strutil.Head(binSHA, 7) -} - -// printAppInfo prints info about app -func (i *Info) printAppInfo() { - fmtutil.Separator(false, "APPLICATION INFO") - - name := i.Name - - if strings.ToLower(i.Name) != strings.ToLower(i.Binary) { - name += fmtc.Sprintf(" {s-}(%s){!}", i.Binary) - } - - format(7, true, - "Name", name, - "Version", i.Version, - ) - - if i.Build == nil { - return - } - - format(7, false, - "Go", fmtc.Sprintf("%s {s}(%s/%s){!}", i.Build.GoVersion, i.Build.GoOS, i.Build.GoArch), - "Git SHA", i.Build.GitSHA+getHashColorBullet(i.Build.GitSHA), - "Bin SHA", i.Build.BinSHA+getHashColorBullet(i.Build.BinSHA), - ) -} - -// printOSInfo prints info about OS and system -func (i *Info) printOSInfo() { - if i.OS != nil { - fmtutil.Separator(false, "OS INFO") - - format(12, true, - "Name", i.OS.coloredName, - "Pretty Name", i.OS.coloredPrettyName, - "Version", i.OS.Version, - "ID", i.OS.ID, - "ID Like", i.OS.IDLike, - "Version ID", i.OS.VersionID, - "Version Code", i.OS.VersionCode, - "Platform ID", i.OS.PlatformID, - "CPE", i.OS.CPE, - ) - } else if i.System != nil { - fmtutil.Separator(false, "SYSTEM INFO") - - format(12, true, - "Name", i.System.Name, - "Arch", i.System.Arch, - "Kernel", i.System.Kernel, - ) - } - - if i.System != nil { - format(12, true, - "Arch", i.System.Arch, - "Kernel", i.System.Kernel, - ) - } - - if i.System.ContainerEngine != "" { - fmtc.NewLine() - switch i.System.ContainerEngine { - case "docker": - format(12, true, "Container", "Yes (Docker)") - case "docker+runsc": - format(12, true, "Container", "Yes (Docker+gVisor)") - case "podman": - format(12, true, "Container", "Yes (Podman)") - case "lxc": - format(12, true, "Container", "Yes (LXC)") - case "yandex": - format(12, true, "Container", "Yes (Yandex Serverless)") - } - } -} - -// printEnvVars prints environment variables -func (i *Info) printEnvVars() { - if len(i.Env) == 0 { - return - } - - fmtutil.Separator(false, "ENVIRONMENT VARIABLES") - - size := getMaxKeySize(i.Env) - - for _, ev := range i.Env { - format(size, true, ev.Key, ev.Value) - } -} - -// printPackagesInfo prints info about packages -func (i *Info) printPackagesInfo() { - if len(i.Pkgs) == 0 { - return - } - - fmtutil.Separator(false, "PACKAGES") - - size := getMaxAppNameSize(i.Pkgs) - - for _, p := range i.Pkgs { - if p.Name == "" { - continue - } - - format(size, true, p.Name, p.Version) - } -} - -// printServicesInfo prints services info -func (i *Info) printServicesInfo() { - if len(i.Services) == 0 { - return - } - - fmtutil.Separator(false, "SERVICES") - - size := getMaxServiceNameSize(i.Services) - - for _, s := range i.Services { - var status string - - switch s.Status { - case STATUS_WORKS: - status = "{g}works{!}" - case STATUS_STOPPED: - status = "{s}stopped{!}" - } - - if s.IsEnabled { - status += " {s-}(enabled){!}" - } - - format(size, true, s.Name, fmtc.Sprint(status)) - } -} - -// printAppsInfo prints info about applications -func (i *Info) printAppsInfo() { - if len(i.Apps) == 0 { - return - } - - fmtutil.Separator(false, "APPLICATIONS") - - size := getMaxAppNameSize(i.Apps) - - for _, a := range i.Apps { - if a.Name == "" { - continue - } - - v := a.Version - - v = strings.ReplaceAll(v, "(", "{s}(") - v = strings.ReplaceAll(v, ")", "){!}") - - format(size, true, a.Name, fmtc.Sprint(v)) - } -} - -// printChecksInfo prints checks info -func (i *Info) printChecksInfo() { - if len(i.Checks) == 0 { - return - } - - fmtutil.Separator(false, "CHECKS") - - for _, c := range i.Checks { - if c.Title == "" { - continue - } - - switch c.Status { - case CHECK_OK: - fmtc.Print(" {g}✔ {!}") - case CHECK_SKIP: - fmtc.Print(" {s-}✔ {!}") - case CHECK_WARN: - fmtc.Print(" {y}✖ {!}") - case CHECK_ERROR: - fmtc.Print(" {r}✖ {!}") - } - - fmtc.Printf(" {*}%s{!}", c.Title) - - if c.Message == "" { - fmtc.NewLine() - continue - } - - switch c.Status { - case CHECK_OK, CHECK_SKIP: - fmtc.Printf(" {s}— {&}%s{!}\n", c.Message) - case CHECK_WARN: - fmtc.Printf(" {s}— {y}{&}%s{!}\n", c.Message) - case CHECK_ERROR: - fmtc.Printf(" {s}— {r}{&}%s{!}\n", c.Message) - } - } -} - -// printNetworkInfo prints network info -func (i *Info) printNetworkInfo() { - if i.Network == nil { - return - } - - fmtutil.Separator(false, "NETWORK") - - format(0, false, - "Hostname", i.Network.Hostname, - "Public IP", i.Network.PublicIP, - "IP v4", strings.Join(i.Network.IPv4, " "), - "IP v6", strings.Join(i.Network.IPv6, " "), - ) -} - -// printFSInfo prints filesystem info -func (i *Info) printFSInfo() { - if len(i.FS) == 0 { - return - } - - fmtutil.Separator(false, "FILESYSTEM") - - size := getMaxDeviceNameSize(i.FS) - - for _, m := range i.FS { - if m.Path == "" || m.Device == "" { - continue - } - - format(size, false, m.Device, fmtc.Sprintf( - "%s {s}(%s){!} %s{s}/{!}%s {s-}(%s){!}", - m.Path, m.Type, fmtutil.PrettySize(m.Used), - fmtutil.PrettySize(m.Used+m.Free), - fmtutil.PrettyPerc(mathutil.Perc(m.Used, m.Used+m.Free)), - )) - } -} - -// printDependencies prints used dependencies -func (i *Info) printDependencies() { - if len(i.Deps) == 0 { - return - } - - fmtutil.Separator(false, "DEPENDENCIES") - - for _, dep := range i.Deps { - switch dep.Extra { - case "": - fmtc.Printf(" {s}%8s{!} %s\n", dep.Version, dep.Path) - default: - fmtc.Printf(" {s}%8s{!} %s {s-}(%s){!}\n", dep.Version, dep.Path, dep.Extra) - } - } -} - -// ////////////////////////////////////////////////////////////////////////////////// // - -// format formats and prints records -func format(size int, printEmpty bool, records ...string) { - if size <= 0 { - for i := 0; i < len(records); i += 2 { - if records[i+1] == "" && !printEmpty { - continue - } - - size = mathutil.Max(size, len(records[i])) - } - } - - size++ - - for i := 0; i < len(records); i += 2 { - name, value := records[i]+":", records[i+1] - - if value == "" && printEmpty { - fm := fmt.Sprintf(" {*}%%-%ds{!} {s-}—{!}\n", size) - fmtc.Printf(fm, name) - } else if value != "" { - fm := fmt.Sprintf(" {*}%%-%ds{!} %%s\n", size) - fmtc.Printf(fm, name, value) - } - } -} - -// extractGitRevFromBuildInfo extracts git SHA from embedded build info -func extractGitRevFromBuildInfo() string { - info, ok := debug.ReadBuildInfo() - - if !ok { - return "" - } - - for _, s := range info.Settings { - if s.Key == "vcs.revision" && len(s.Value) > 7 { - return s.Value[:7] - } - } - - return "" -} - -// getHashColorBullet return bullet with color from hash -func getHashColorBullet(v string) string { - if v == "" || fmtc.DisableColors || !fmtc.IsTrueColorSupported() { - return "" - } - - return fmtc.Sprintf(" {#" + strutil.Head(v, 6) + "}● {!}") -} - -// getMaxKeySize returns max key size -func getMaxKeySize(vars []EnvVar) int { - var size int - - for _, ev := range vars { - size = mathutil.Max(size, len(ev.Key)) - } - - return size -} - -// getMaxAppNameSize returns max package name size -func getMaxAppNameSize(apps []App) int { - var size int - - for _, p := range apps { - size = mathutil.Max(size, len(p.Name)) - } - - return size -} - -// getMaxServiceNameSize returns max package name size -func getMaxServiceNameSize(apps []Service) int { - var size int - - for _, s := range apps { - size = mathutil.Max(size, len(s.Name)) - } - - return size -} - -// getMaxDeviceNameSize returns max device name size -func getMaxDeviceNameSize(mounts []FSInfo) int { - var size int - - for _, m := range mounts { - size = mathutil.Max(size, len(m.Device)) - } - - return size -} diff --git a/support/support_windows.go b/support/support_windows.go index 127b91f6..9140a669 100644 --- a/support/support_windows.go +++ b/support/support_windows.go @@ -7,61 +7,90 @@ package support // // // ////////////////////////////////////////////////////////////////////////////////// // -// ❗ Collect collects basic info about system -func Collect(app, ver string) *Info { - panic("UNSUPPORTED") -} +import ( + "fmt" + "runtime" -// ////////////////////////////////////////////////////////////////////////////////// // + "github.com/essentialkaos/ek/v13/fmtc" -// ❗ WithDeps adds information about dependencies -func (i *Info) WithDeps(deps []Dep) *Info { - panic("UNSUPPORTED") -} + "golang.org/x/sys/windows" +) -// ❗ WithRevision adds git revision -func (i *Info) WithRevision(rev string) *Info { - panic("UNSUPPORTED") -} +// ////////////////////////////////////////////////////////////////////////////////// // -// ❗ WithPackages adds information about packages -func (i *Info) WithPackages(pkgs []Pkg) *Info { - panic("UNSUPPORTED") +// appendSystemInfo appends system info +func (i *Info) appendSystemInfo() { + i.System = &SystemInfo{ + Name: "Windows", + Arch: formatArchName(runtime.GOARCH), + Kernel: "Windows NT", + } } -// WithServices adds information about services -func (i *Info) WithServices(services []Service) *Info { - panic("UNSUPPORTED") -} +// appendOSInfo appends OS info +func (i *Info) appendOSInfo() { + major, minor, build := windows.RtlGetNtVersionNumbers() + i.OS = &OSInfo{ + Name: "Windows", + ID: "win", + Version: fmt.Sprintf("%d.%d.%d", major, minor, build), + } -// ❗ WithPackages adds information about system apps -func (i *Info) WithApps(apps ...App) *Info { - panic("UNSUPPORTED") -} + switch build { + case 22000, 22621, 22631, 26100: + i.OS.Name = "Windows 11" + case 10240, 10586, 15063, 16299, 17134, 18362, + 18363, 19041, 19042, 19043, 19044, 19045: + i.OS.Name = "Windows 10" + case 20348: + i.OS.Name = "Windows Server 2022" + case 17763: + i.OS.Name = "Windows Server 2019" + case 14393: + i.OS.Name = "Windows Server 2016" + case 9600: + i.OS.Name = "Windows 8.1 / Windows Server 2012 R2" + case 9200: + i.OS.Name = "Windows 8 / Windows Server 2012" + } -// ❗ WithChecks adds information custom checks -func (i *Info) WithChecks(check ...Check) *Info { - panic("UNSUPPORTED") -} + switch build { + // Win 11 + case 22000: + i.OS.VersionID = "24H2" + case 22621: + i.OS.VersionID = "23H2" + case 22631: + i.OS.VersionID = "22H2" -// ❗ WithEnvVars adds information with environment variables -func (i *Info) WithEnvVars(vars ...string) *Info { - panic("UNSUPPORTED") -} + // Win 10 + case 19045: + i.OS.VersionID = "22H2" + case 19044: + i.OS.VersionID = "21H2" + case 19043: + i.OS.VersionID = "21H1" + case 19042: + i.OS.VersionID = "20H2" + } -// ❗ WithNetwork adds information about the network -func (i *Info) WithNetwork(info *NetworkInfo) *Info { - panic("UNSUPPORTED") -} + if i.OS.VersionID != "" { + i.OS.PrettyName = i.OS.Name + " " + i.OS.VersionID + } else { + i.OS.PrettyName = i.OS.Name + } -// ❗ WithFS adds file system information -func (i *Info) WithFS(info []FSInfo) *Info { - panic("UNSUPPORTED") + i.OS.coloredName = fmtc.Sprintf("{c}%s{!}", i.OS.Name) + i.OS.coloredPrettyName = fmtc.Sprintf("{c}%s{!}", i.OS.PrettyName) } // ////////////////////////////////////////////////////////////////////////////////// // -// ❗ Print prints support info -func (i *Info) Print() { - panic("UNSUPPORTED") +// formatArchName formats arch name +func formatArchName(goos string) string { + if goos == "amd64" { + return "x86_64" + } + + return goos }