// Package preflight runs system readiness checks before any deployment. // It is called on every binary start and returns a structured report. package preflight import ( "fmt" "os" "git.sechpoint.app/customer-engineering/wallarm/internal/shared" ) // Result holds the outcome of all preflight checks. type Result struct { Passed bool `json:"passed"` Checks []Check `json:"checks"` USReachable bool `json:"us_reachable"` EUReachable bool `json:"eu_reachable"` } // Check represents a single preflight check. type Check struct { Name string `json:"name"` Passed bool `json:"passed"` Detail string `json:"detail,omitempty"` Warning bool `json:"warning,omitempty"` } // Cloud endpoints (from wallarm-lib.sh) var euEndpoints = []string{ "api.wallarm.com:443", "node-data0.eu1.wallarm.com:443", "node-data1.eu1.wallarm.com:443", } var usEndpoints = []string{ "us1.api.wallarm.com:443", "node-data0.us1.wallarm.com:443", "node-data1.us1.wallarm.com:443", } // Run executes all preflight checks and returns the result. func Run() Result { r := Result{Passed: true} // 1. Root check if os.Geteuid() != 0 { r.Checks = append(r.Checks, Check{ Name: "root", Passed: false, Detail: "must run as root for package installation and system config", }) r.Passed = false } else { r.Checks = append(r.Checks, Check{Name: "root", Passed: true}) } // 2. Init system initSys := shared.InitSystem() if initSys != "systemd" { r.Checks = append(r.Checks, Check{ Name: "init", Passed: false, Detail: fmt.Sprintf("requires systemd, detected: %s", initSys), }) r.Passed = false } else { r.Checks = append(r.Checks, Check{Name: "init", Passed: true, Detail: "systemd"}) } // 3. Architecture arch := shared.Arch() supported := arch == "x86_64" || arch == "aarch64" r.Checks = append(r.Checks, Check{ Name: "arch", Passed: supported, Detail: arch, }) if !supported { r.Passed = false } // 4. OS id, ver := shared.OSInfo() r.Checks = append(r.Checks, Check{Name: "os", Passed: true, Detail: id + " " + ver}) // 5. Required commands requiredCmds := []string{"curl", "systemctl", "sed", "mkdir", "rm"} for _, cmd := range requiredCmds { ok := shared.CommandExists(cmd) r.Checks = append(r.Checks, Check{Name: "cmd:" + cmd, Passed: ok}) if !ok { r.Passed = false } } // 6. Installer reachability installerOk := shared.HTTPHead("https://meganode.wallarm.com/6.12/") r.Checks = append(r.Checks, Check{ Name: "installer_reachable", Passed: installerOk, Detail: "repo.wallarm.com", }) if !installerOk { r.Checks[len(r.Checks)-1].Warning = true } // 7. Cloud endpoints r.USReachable = checkEndpoints(usEndpoints) r.EUReachable = checkEndpoints(euEndpoints) r.Checks = append(r.Checks, Check{ Name: "cloud:US", Passed: r.USReachable, Detail: fmt.Sprintf("%d/%d reachable", countReachable(usEndpoints), len(usEndpoints)), }) r.Checks = append(r.Checks, Check{ Name: "cloud:EU", Passed: r.EUReachable, Detail: fmt.Sprintf("%d/%d reachable", countReachable(euEndpoints), len(euEndpoints)), }) if !r.USReachable && !r.EUReachable { r.Passed = false } // 8. Disk space (>= 2GB) free, err := shared.FreeDiskMB("/opt") if err == nil && free < 2048 { r.Checks = append(r.Checks, Check{ Name: "disk", Passed: false, Detail: fmt.Sprintf("%d MB free (need >= 2048 MB)", free), }) r.Passed = false } else { r.Checks = append(r.Checks, Check{Name: "disk", Passed: true, Detail: fmt.Sprintf("%d MB free", free)}) } // 9. Memory (>= 2GB, warning only) mem, err := shared.FreeMemoryMB() if err == nil && mem < 2048 { r.Checks = append(r.Checks, Check{ Name: "memory", Passed: true, Warning: true, Detail: fmt.Sprintf("%d MB (2GB+ recommended)", mem), }) } else { r.Checks = append(r.Checks, Check{Name: "memory", Passed: true, Detail: fmt.Sprintf("%d MB", mem)}) } return r } func checkEndpoints(endpoints []string) bool { for _, ep := range endpoints { host, _ := splitHostPort(ep) if shared.TCPConnect(host, 443) { return true } } return false } func countReachable(endpoints []string) int { n := 0 for _, ep := range endpoints { host, _ := splitHostPort(ep) if shared.TCPConnect(host, 443) { n++ } } return n } func splitHostPort(addr string) (string, string) { for i := len(addr) - 1; i >= 0; i-- { if addr[i] == ':' { return addr[:i], addr[i+1:] } } return addr, "" }