package native import ( "fmt" "os" "os/exec" "path/filepath" "strings" "time" "git.sechpoint.app/customer-engineering/wallarm/internal/state" ) const ( BaseDir = "/opt/fw" DeployTo = "/opt/wallarm" // setup.sh hardcodes this, deploy here then move ) func installerURL() string { if u := os.Getenv("WALLARM_INSTALLER_URL"); u != "" { return u } return "https://storage.googleapis.com/meganode_storage/6.12/wallarm-6.12.5.x86_64-glibc.sh" } func InstallNode(node state.Node, apiToken, apiHost, labels string) error { if apiToken == "" { return fmt.Errorf("API token required") } _ = labels instanceDir := filepath.Join(BaseDir, node.Name, "wallarm") installerPath := filepath.Join(BaseDir, "wallarm-aio.sh") // 1. Prepare clean /opt/wallarm for this deployment os.RemoveAll(DeployTo) os.MkdirAll(DeployTo, 0755) // 2. Kill any existing process on the target port, then start per-instance NGINX port := "80" if idx := strings.LastIndex(node.Address, ":"); idx != -1 { port = node.Address[idx+1:] } killPort(port) copyNginx(DeployTo) startNginx(DeployTo, node, port) // 3. Download AIO once 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) } // 4. Extract AIO to /opt/wallarm fmt.Printf("[%s] Extracting...\n", node.Name) cmd := exec.Command("bash", installerPath, "--noexec", "--keep", "--target", DeployTo, "--noprogress", "--accept") if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("extract: %w\n%s", err, string(out)) } // 5. Fix: recreate env.list in background while setup.sh runs done := make(chan struct{}) go func() { defer close(done) for { select { case <-done: return default: os.WriteFile(filepath.Join(DeployTo, "env.list"), []byte("# wallarm env\n"), 0644) time.Sleep(500 * time.Millisecond) } } }() // 6. Run setup.sh fmt.Printf("[%s] Running setup...\n", node.Name) cmd = exec.Command("bash", filepath.Join(DeployTo, "setup.sh"), "--batch", "--token", apiToken, "--cloud", cloudFromHost(apiHost), "--custom-ngx-build", ) cmd.Dir = DeployTo logFile, _ := os.Create(filepath.Join(DeployTo, "install.log")) if logFile != nil { cmd.Stdout = logFile cmd.Stderr = logFile } runErr := cmd.Run() close(done) // stop env.list keeper if logFile != nil { logFile.Close() } if runErr != nil { if data, _ := os.ReadFile(filepath.Join(DeployTo, "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) } // 6. Move /opt/wallarm → /opt/fw/{name}/wallarm os.MkdirAll(BaseDir+"/"+node.Name, 0755) os.RemoveAll(instanceDir) if err := os.Rename(DeployTo, instanceDir); err != nil { return fmt.Errorf("move to instance dir: %w", err) } fmt.Printf("[%s] Done. Installed to %s\n", node.Name, instanceDir) return nil } func copyNginx(dir string) { dst := filepath.Join(dir, "nginx", "sbin", "nginx") if _, err := os.Stat(dst); err == nil { return } os.MkdirAll(filepath.Dir(dst), 0755) if path, err := exec.LookPath("nginx"); err == nil { exec.Command("cp", path, dst).Run() return } for _, pm := range [][]string{ {"apt-get", "install", "-y", "-qq", "nginx"}, {"yum", "install", "-y", "-q", "nginx"}, } { if _, err := exec.LookPath(pm[0]); err == nil { exec.Command(pm[0], pm[1:]...).Run() if path, err := exec.LookPath("nginx"); err == nil { exec.Command("cp", path, dst).Run() return } } } } func cloudFromHost(host string) string { if strings.Contains(host, "us1") { return "US" } return "EU" } func CreateNodesDir() error { return os.MkdirAll(BaseDir, 0755) } func GenerateSystemdTemplate() error { return nil } func RemoveNode(name string) error { return nil } func Status(name string) (string, error) { return "", nil } func startNginx(dir string, node state.Node, port string) { nginxDir := filepath.Join(dir, "nginx") confDir := filepath.Join(nginxDir, "conf") os.MkdirAll(confDir, 0755) confPath := filepath.Join(confDir, "nginx.conf") os.WriteFile(confPath, []byte(fmt.Sprintf(` worker_processes auto; pid %s/nginx.pid; error_log %s/error.log; events { worker_connections 10240; } http { access_log %s/access.log; server { listen %s; server_name _; location /health { return 200; } } } `, nginxDir, nginxDir, nginxDir, port)), 0644) bin := filepath.Join(nginxDir, "sbin", "nginx") cmd := exec.Command(bin, "-c", confPath, "-p", nginxDir) cmd.Dir = nginxDir if out, err := cmd.CombinedOutput(); err != nil { fmt.Printf("[%s] NGINX start: %v\n%s\n", node.Name, err, string(out)) } else { fmt.Printf("[%s] NGINX started on port %s\n", node.Name, port) } } func killPort(port string) { // Find and kill any process listening on the target port out, _ := exec.Command("fuser", "-k", port+"/tcp").CombinedOutput() if len(out) > 0 { fmt.Printf(" Killed existing process on port %s\n", port) } }