post inicial
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// systemInfoTool reúne datos básicos del sistema (§46, §49, §50, §51) leyendo
|
||||
// directamente /proc, /etc/os-release y el PATH: no ejecuta comandos, así
|
||||
// que es seguro en cualquier modo de ejecución.
|
||||
type systemInfoTool struct{}
|
||||
|
||||
func (systemInfoTool) Name() string { return "system_info" }
|
||||
func (systemInfoTool) Description() string {
|
||||
return "Report OS, kernel, architecture, hostname, CPU, memory, uptime, privileges (uid/euid, sudo/doas), init system, package managers, mounted filesystems and network interfaces. Read-only."
|
||||
}
|
||||
func (systemInfoTool) Schema() map[string]any { return obj(nil, map[string]any{}) }
|
||||
|
||||
func (systemInfoTool) Run(_ context.Context, _ *Env, raw json.RawMessage) Result {
|
||||
var none struct{}
|
||||
if err := decode(raw, &none); err != nil {
|
||||
return fail("system_info", err)
|
||||
}
|
||||
return Result{OK: true, ExitCode: 0, Summary: "system_info", Output: CollectSystemInfo()}
|
||||
}
|
||||
|
||||
// CollectSystemInfo produce un informe textual. Los datos que no se pueden
|
||||
// leer aparecen como UNKNOWN en lugar de suponerse.
|
||||
func CollectSystemInfo() string {
|
||||
var b strings.Builder
|
||||
kv := func(k, v string) {
|
||||
if v == "" {
|
||||
v = "UNKNOWN"
|
||||
}
|
||||
fmt.Fprintf(&b, "%s: %s\n", k, v)
|
||||
}
|
||||
|
||||
osr := readKeyValues("/etc/os-release")
|
||||
kv("os", osr["PRETTY_NAME"])
|
||||
kv("os_id", strings.TrimSpace(osr["ID"]+" "+osr["ID_LIKE"]))
|
||||
kv("os_version", osr["VERSION_ID"])
|
||||
kv("kernel", readTrim("/proc/sys/kernel/osrelease"))
|
||||
kv("architecture", runtime.GOARCH)
|
||||
h, _ := os.Hostname()
|
||||
kv("hostname", h)
|
||||
|
||||
cpuModel, cpus := "", 0
|
||||
if f, err := os.Open("/proc/cpuinfo"); err == nil {
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
line := sc.Text()
|
||||
if strings.HasPrefix(line, "processor") {
|
||||
cpus++
|
||||
}
|
||||
if cpuModel == "" && (strings.HasPrefix(line, "model name") || strings.HasPrefix(line, "Model")) {
|
||||
if _, v, ok := strings.Cut(line, ":"); ok {
|
||||
cpuModel = strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
kv("cpu_model", cpuModel)
|
||||
kv("cpu_count", fmt.Sprint(max(cpus, runtime.NumCPU())))
|
||||
|
||||
mem := readKeyValuesSep("/proc/meminfo", ":")
|
||||
kv("mem_total", mem["MemTotal"])
|
||||
kv("mem_available", mem["MemAvailable"])
|
||||
kv("swap_total", mem["SwapTotal"])
|
||||
if up := strings.Fields(readTrim("/proc/uptime")); len(up) > 0 {
|
||||
kv("uptime_seconds", up[0])
|
||||
}
|
||||
|
||||
kv("uid", fmt.Sprint(os.Getuid()))
|
||||
kv("euid", fmt.Sprint(os.Geteuid()))
|
||||
kv("running_as_root", fmt.Sprint(os.Geteuid() == 0))
|
||||
kv("escalation_tools", strings.Join(available("sudo", "doas", "su"), " "))
|
||||
|
||||
initSys := "UNKNOWN"
|
||||
if st, err := os.Stat("/run/systemd/system"); err == nil && st.IsDir() {
|
||||
initSys = "systemd"
|
||||
} else if comm := readTrim("/proc/1/comm"); comm != "" {
|
||||
initSys = comm
|
||||
}
|
||||
kv("init_system", initSys)
|
||||
kv("package_managers", strings.Join(available("apt", "apt-get", "dpkg", "dnf", "yum", "rpm", "pacman", "zypper", "apk", "snap", "flatpak"), " "))
|
||||
|
||||
b.WriteString("filesystems:\n")
|
||||
if f, err := os.Open("/proc/mounts"); err == nil {
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
p := strings.Fields(sc.Text())
|
||||
if len(p) < 3 || isPseudoFS(p[2]) {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&b, " %s on %s type %s\n", p[0], p[1], p[2])
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
b.WriteString("network_interfaces:\n")
|
||||
if ifs, err := net.Interfaces(); err == nil {
|
||||
for _, it := range ifs {
|
||||
addrs, _ := it.Addrs()
|
||||
var as []string
|
||||
for _, a := range addrs {
|
||||
as = append(as, a.String())
|
||||
}
|
||||
fmt.Fprintf(&b, " %s flags=%s mac=%s addrs=%s\n", it.Name, it.Flags, it.HardwareAddr, strings.Join(as, ","))
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// execLookPath existe para que find_program y available compartan criterio.
|
||||
func execLookPath(name string) (string, error) { return exec.LookPath(name) }
|
||||
|
||||
func available(names ...string) []string {
|
||||
var out []string
|
||||
for _, n := range names {
|
||||
if _, err := exec.LookPath(n); err == nil {
|
||||
out = append(out, n)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func isPseudoFS(t string) bool {
|
||||
switch t {
|
||||
case "proc", "sysfs", "devtmpfs", "devpts", "tmpfs", "cgroup", "cgroup2", "securityfs", "pstore", "bpf",
|
||||
"debugfs", "tracefs", "mqueue", "hugetlbfs", "configfs", "fusectl", "autofs", "binfmt_misc", "efivarfs",
|
||||
"rpc_pipefs", "nsfs", "ramfs", "selinuxfs":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func readTrim(path string) string {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(b))
|
||||
}
|
||||
|
||||
func readKeyValues(path string) map[string]string {
|
||||
m := map[string]string{}
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return m
|
||||
}
|
||||
defer f.Close()
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
if k, v, ok := strings.Cut(sc.Text(), "="); ok {
|
||||
m[k] = strings.Trim(v, `"`)
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func readKeyValuesSep(path, sep string) map[string]string {
|
||||
m := map[string]string{}
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return m
|
||||
}
|
||||
defer f.Close()
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
if k, v, ok := strings.Cut(sc.Text(), sep); ok {
|
||||
m[strings.TrimSpace(k)] = strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
Reference in New Issue
Block a user