// Package shared provides validation, connectivity, and system detection // utilities ported from the bash wallarm-lib.sh library. package shared import ( "fmt" "os" "os/exec" "runtime" "strconv" "strings" ) // ─── System Detection ──────────────────────────────────────────────── // InitSystem returns the detected init system: systemd, openrc, sysvinit, upstart, or unknown. func InitSystem() string { if runtime.GOOS == "darwin" { return "darwin" } if _, err := exec.LookPath("systemctl"); err == nil { return "systemd" } if _, err := os.Stat("/sbin/openrc-run"); err == nil { return "openrc" } if _, err := os.Stat("/etc/init.d"); err == nil { return "sysvinit" } if _, err := os.Stat("/sbin/upstart"); err == nil { return "upstart" } return "unknown" } // OSInfo returns (os_id, version_id) from /etc/os-release. func OSInfo() (id, version string) { data, err := os.ReadFile("/etc/os-release") if err != nil { return strings.ToLower(runtime.GOOS), "unknown" } for _, line := range strings.Split(string(data), "\n") { if strings.HasPrefix(line, "ID=") { id = strings.Trim(strings.TrimPrefix(line, "ID="), `"`) } if strings.HasPrefix(line, "VERSION_ID=") { version = strings.Trim(strings.TrimPrefix(line, "VERSION_ID="), `"`) } } if id == "" { id = strings.ToLower(runtime.GOOS) } return id, version } // Arch returns the normalized architecture: x86_64, aarch64, or armhf. func Arch() string { switch runtime.GOARCH { case "amd64": return "x86_64" case "arm64": return "aarch64" case "arm": return "armhf" default: return runtime.GOARCH } } // ─── Validation ────────────────────────────────────────────────────── // ValidateIP checks whether s is a valid IPv4 address. func ValidateIP(s string) error { parts := strings.Split(s, ".") if len(parts) != 4 { return fmt.Errorf("invalid IPv4: %s", s) } for _, p := range parts { n, err := strconv.Atoi(p) if err != nil || n < 0 || n > 255 { return fmt.Errorf("invalid IPv4 octet: %s", p) } } return nil } // ValidateCIDR checks whether s is a valid IPv4 address with optional /prefix. func ValidateCIDR(s string) error { ip := s prefix := "" if idx := strings.IndexByte(s, '/'); idx != -1 { ip, prefix = s[:idx], s[idx+1:] } if err := ValidateIP(ip); err != nil { return err } if prefix != "" { n, err := strconv.Atoi(prefix) if err != nil || n < 0 || n > 32 { return fmt.Errorf("invalid CIDR prefix: %s", prefix) } } return nil } // CommandExists returns true if cmd is in PATH or in common system directories. func CommandExists(cmd string) bool { if _, err := exec.LookPath(cmd); err == nil { return true } for _, dir := range []string{"/usr/sbin", "/sbin", "/usr/local/sbin", "/usr/bin", "/bin", "/usr/local/bin"} { if _, err := os.Stat(dir + "/" + cmd); err == nil { return true } } return false } // ─── Resource Checks ───────────────────────────────────────────────── // FreeDiskMB returns available disk space in MB for the given path. func FreeDiskMB(path string) (int64, error) { cmd := exec.Command("df", "-k", path) out, err := cmd.Output() if err != nil { return 0, err } lines := strings.Split(strings.TrimSpace(string(out)), "\n") if len(lines) < 2 { return 0, fmt.Errorf("unexpected df output") } fields := strings.Fields(lines[1]) if len(fields) < 4 { return 0, fmt.Errorf("unexpected df fields") } kb, err := strconv.ParseInt(fields[3], 10, 64) if err != nil { return 0, err } return kb / 1024, nil // convert KB to MB } // FreeMemoryMB returns available memory in MB. func FreeMemoryMB() (int64, error) { cmd := exec.Command("free", "-m") out, err := cmd.Output() if err != nil { return 0, err } lines := strings.Split(strings.TrimSpace(string(out)), "\n") if len(lines) < 2 { return 0, fmt.Errorf("unexpected free output") } fields := strings.Fields(lines[1]) if len(fields) < 2 { return 0, fmt.Errorf("unexpected free fields") } return strconv.ParseInt(fields[1], 10, 64) } // ─── Connectivity ──────────────────────────────────────────────────── // TCPConnect tests whether host:port accepts a TCP connection. func TCPConnect(host string, port int) bool { cmd := exec.Command("timeout", "5", "bash", "-c", fmt.Sprintf("echo >/dev/tcp/%s/%d 2>/dev/null", host, port)) return cmd.Run() == nil } // HTTPHead returns true if the URL returns a successful status. func HTTPHead(url string) bool { cmd := exec.Command("curl", "-fsSL", "--connect-timeout", "10", url) return cmd.Run() == nil }