package native import ( "fmt" "os" "os/exec" "path/filepath" "strings" "git.sechpoint.app/customer-engineering/wallarm/internal/state" ) const ( BaseDir = "/opt/wallarm" NodesDir = BaseDir + "/nodes" NginxDir = BaseDir + "/nginx" SystemdTemplate = "/etc/systemd/system/wallarm-node@.service" ) func installerURL() string { if u := os.Getenv("WALLARM_INSTALLER_URL"); u != "" { return u } return "https://storage.googleapis.com/meganode_storage/6.13/wallarm-6.13.0.x86_64-glibc.sh" } func InstallNode(node state.Node, apiToken, apiHost, labels string) error { if apiToken == "" { return fmt.Errorf("API token required") } archiveDir := BaseDir installerPath := filepath.Join(archiveDir, "wallarm-aio.sh") // 1. Install NGINX installNginx() // 2. Download AIO if _, err := os.Stat(installerPath); os.IsNotExist(err) { fmt.Printf("[%s] Downloading installer...\n", node.Name) cmd := exec.Command("curl", "-fsSL", "-o", installerPath, installerURL()) if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("download: %w\n%s", err, string(out)) } os.Chmod(installerPath, 0755) } // 3. Extract once if _, err := os.Stat(filepath.Join(archiveDir, "setup.sh")); os.IsNotExist(err) { fmt.Printf("[%s] Extracting...\n", node.Name) cmd := exec.Command("bash", installerPath, "--noexec", "--keep", "--target", archiveDir, "--noprogress", "--accept") if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("extract: %w\n%s", err, string(out)) } } // 4. Run setup fmt.Printf("[%s] Running setup...\n", node.Name) cmd := exec.Command("bash", filepath.Join(archiveDir, "setup.sh"), "--batch", "--token", apiToken, "--cloud", cloudFromHost(apiHost), ) cmd.Dir = archiveDir logFile, _ := os.Create(filepath.Join(archiveDir, "install.log")) if logFile != nil { cmd.Stdout = logFile cmd.Stderr = logFile } runErr := cmd.Run() if logFile != nil { logFile.Close() } if runErr != nil { if data, _ := os.ReadFile(filepath.Join(archiveDir, "install.log")); len(data) > 0 { lines := strings.Split(string(data), "\n") s := len(lines) - 5 if s < 0 { s = 0 } return fmt.Errorf("%s", strings.Join(lines[s:], "\n")) } return fmt.Errorf("setup failed: %v", runErr) } fmt.Printf("[%s] Done.\n", node.Name) return nil } func RemoveNode(nodeName string) error { serviceName := "wallarm-node@" + nodeName exec.Command("systemctl", "stop", serviceName).Run() exec.Command("systemctl", "disable", serviceName).Run() fmt.Printf("Node %s removed.\n", nodeName) return nil } func Status(nodeName string) (string, error) { if nodeName != "" { out, _ := exec.Command("systemctl", "status", "wallarm-node@"+nodeName, "--no-pager").CombinedOutput() return string(out), nil } var sb strings.Builder sb.WriteString("Wallarm Nodes:\n") entries, _ := os.ReadDir(NodesDir) for _, e := range entries { if e.IsDir() { sb.WriteString("--- " + e.Name() + " ---\n") } } return sb.String(), nil } func installNginx() { if _, err := os.Stat(NginxDir + "/sbin/nginx"); err == nil { return } fmt.Println("Installing NGINX...") // Try system nginx first if path, err := exec.LookPath("nginx"); err == nil { os.MkdirAll(NginxDir+"/sbin", 0755) exec.Command("cp", path, NginxDir+"/sbin/nginx").Run() fmt.Println(" Copied from system.") return } // Try package managers for _, pm := range [][]string{ {"apt-get", "update", "-qq"}, {"apt-get", "install", "-y", "-qq", "nginx"}, {"yum", "install", "-y", "-q", "nginx"}, {"dnf", "install", "-y", "-q", "nginx"}, } { if _, err := exec.LookPath(pm[0]); err == nil { exec.Command(pm[0], pm[1:]...).Run() if _, err := os.Stat("/usr/sbin/nginx"); err == nil { os.MkdirAll(NginxDir+"/sbin", 0755) exec.Command("cp", "/usr/sbin/nginx", NginxDir+"/sbin/nginx").Run() return } } } fmt.Println("Install NGINX manually to", NginxDir) } func cloudFromHost(host string) string { if strings.Contains(host, "us1") { return "US" } if strings.Contains(host, "me1") { return "ME" } return "EU" } func GenerateSystemdTemplate() error { return nil } func CreateNodesDir() error { return os.MkdirAll(NodesDir, 0755) } func listenPort(addr string) string { if idx := strings.LastIndex(addr, ":"); idx != -1 { return addr[idx+1:] } return "80" } func upstreamAddr(node state.Node) string { if node.UpstreamIP != "" && node.UpstreamPort != 0 { return fmt.Sprintf("%s:%d", node.UpstreamIP, node.UpstreamPort) } return "127.0.0.1:80" }