From d3db8ce4c026f3a47d3685c52dd61fc4322d8536 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 1 Aug 2026 16:38:58 +0000 Subject: [PATCH] fix: extract AIO to /opt/wallarm/ (setup.sh hardcodes this path) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setup.sh uses /opt/wallarm/ for env.list and all paths. Extract once to base dir, each node registers with --force. Simplified InstallNode to 4 steps: nginx → download → extract → setup. --- internal/native/native.go | 280 ++++++++------------------------------ 1 file changed, 53 insertions(+), 227 deletions(-) diff --git a/internal/native/native.go b/internal/native/native.go index 565b239..0f63b39 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -1,5 +1,3 @@ -// Package native implements Wallarm Native Node deployment (no Docker). -// Ported from native/wallarm-native.sh. package native import ( @@ -12,7 +10,6 @@ import ( "git.sechpoint.app/customer-engineering/wallarm/internal/state" ) -// Constants const ( BaseDir = "/opt/wallarm" NodesDir = BaseDir + "/nodes" @@ -20,303 +17,130 @@ const ( SystemdTemplate = "/etc/systemd/system/wallarm-node@.service" ) -// installerURL returns the correct Wallarm AIO installer for the current architecture. -// Override with WALLARM_INSTALLER_URL env var to pin a specific version. -// Source: https://github.com/wallarm/ingress/blob/main/build/fetch-module.sh func installerURL() string { if u := os.Getenv("WALLARM_INSTALLER_URL"); u != "" { return u } - arch := "x86_64" - libc := "glibc" // glibc for Debian/Ubuntu/RHEL, musl for Alpine - return fmt.Sprintf("https://storage.googleapis.com/meganode_storage/6.13/wallarm-6.13.0.%s-%s.sh", arch, libc) + return "https://storage.googleapis.com/meganode_storage/6.13/wallarm-6.13.0.x86_64-glibc.sh" } -// InstallNode deploys a single native Wallarm node. -// node: node metadata, apiToken: Wallarm API token, apiHost: cloud API host, labels: optional node labels. func InstallNode(node state.Node, apiToken, apiHost, labels string) error { if apiToken == "" { return fmt.Errorf("API token required") } - if apiHost == "" { - return fmt.Errorf("API host required") - } - workDir := filepath.Join(NodesDir, node.Name) - installerPath := filepath.Join(workDir, "wallarm-native-node-aio.sh") + archiveDir := BaseDir + installerPath := filepath.Join(archiveDir, "wallarm-aio.sh") - // 1. Create directories - for _, d := range []string{ - filepath.Join(workDir, "etc"), - filepath.Join(workDir, "var", "log"), - filepath.Join(workDir, "var", "run"), - } { - if err := os.MkdirAll(d, 0755); err != nil { - return fmt.Errorf("mkdir %s: %w", d, err) - } - } - - // 2. Write go-node.yaml config - configPath := filepath.Join(workDir, "etc", "go-node.yaml") - config := fmt.Sprintf(`mode: connector-server -connector: - address: "%s" -`, node.Address) - if err := os.WriteFile(configPath, []byte(config), 0644); err != nil { - return fmt.Errorf("write config: %w", err) - } - - // 3. Install NGINX (Wallarm node requires it as proxy layer) + // 1. Install NGINX installNginx() - // 4. Download installer if missing - url := installerURL() + // 2. Download AIO if _, err := os.Stat(installerPath); os.IsNotExist(err) { - fmt.Printf("[%s] Downloading installer from meganode.wallarm.com...\n", node.Name) - cmd := exec.Command("curl", "-fsSL", "-o", installerPath, url) + 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 installer: %w\n%s", err, string(out)) + return fmt.Errorf("download: %w\n%s", err, string(out)) } - if err := os.Chmod(installerPath, 0755); err != nil { - return fmt.Errorf("chmod installer: %w", err) + 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", "--target", archiveDir, "--noprogress", "--accept") + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("extract: %w\n%s", err, string(out)) } } - // 4. Write environment file - envFile := filepath.Join(workDir, "env") - envContent := fmt.Sprintf(`WALLARM_API_TOKEN=%s -WALLARM_API_HOST=%s -WALLARM_LABELS=%s -WALLARM_CONFIG_PATH=%s -`, apiToken, apiHost, labels, configPath) - if err := os.WriteFile(envFile, []byte(envContent), 0600); err != nil { - return fmt.Errorf("write env: %w", err) - } - - // 5. Extract the Makeself archive (--noexec skips running setup.sh) - fmt.Printf("[%s] Extracting installer...\n", node.Name) - extractCmd := exec.Command("bash", installerPath, "--noexec", "--target", workDir, "--noprogress", "--accept") - extractCmd.Dir = workDir - if out, err := extractCmd.CombinedOutput(); err != nil { - return fmt.Errorf("extract installer: %w\n%s", err, string(out)) - } - - // 6. Set up per-instance NGINX config and binary - installNginx() // ensure shared NGINX binary exists - instanceNginx := filepath.Join(workDir, "nginx") - os.MkdirAll(instanceNginx+"/conf", 0755) - os.MkdirAll(instanceNginx+"/logs", 0755) - os.MkdirAll(instanceNginx+"/sbin", 0755) - exec.Command("cp", NginxDir+"/sbin/nginx", instanceNginx+"/sbin/nginx").Run() - - nginxConf := instanceNginx + "/conf/nginx.conf" - os.WriteFile(nginxConf, []byte(fmt.Sprintf(` -worker_processes auto; -pid %s/nginx/nginx.pid; -error_log %s/nginx/logs/error.log; -events { worker_connections 10240; } -http { - access_log %s/nginx/logs/access.log; - server { - listen %s; - location / { - proxy_pass http://%s; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - } -} -`, workDir, workDir, workDir, listenPort(node.Address), upstreamAddr(node))), 0644) - - // 7. Run setup.sh with custom NGINX build (uses our per-instance nginx) - setupScript := filepath.Join(workDir, "setup.sh") - if _, err := os.Stat(setupScript); err != nil { - return fmt.Errorf("setup.sh not found after extraction: %w", err) - } - fmt.Printf("[%s] Running setup (batch mode)...\n", node.Name) - setupCmd := exec.Command("bash", setupScript, - "--batch", - "--token", apiToken, - "--cloud", cloudFromHost(apiHost), - "--custom-ngx-build", + // 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), "--force", ) - setupCmd.Dir = workDir + cmd.Dir = archiveDir - logFile, err := os.Create(filepath.Join(workDir, "install.log")) - if err == nil { - setupCmd.Stdout = logFile - setupCmd.Stderr = logFile + logFile, _ := os.Create(filepath.Join(archiveDir, "install.log")) + if logFile != nil { + cmd.Stdout = logFile + cmd.Stderr = logFile } - runErr := setupCmd.Run() + runErr := cmd.Run() if logFile != nil { logFile.Close() } if runErr != nil { - // Read the log for the error message - if data, err := os.ReadFile(filepath.Join(workDir, "install.log")); err == nil && len(data) > 0 { + if data, _ := os.ReadFile(filepath.Join(archiveDir, "install.log")); len(data) > 0 { lines := strings.Split(string(data), "\n") - // Print last few lines as the error - start := len(lines) - 5 - if start < 0 { - start = 0 + s := len(lines) - 5 + if s < 0 { + s = 0 } - return fmt.Errorf("%s", strings.Join(lines[start:], "\n")) + return fmt.Errorf("%s", strings.Join(lines[s:], "\n")) } - return fmt.Errorf("installation failed (exit %v) — check %s/install.log", runErr, workDir) + return fmt.Errorf("setup failed: %v", runErr) } - // 6. Enable and start systemd service - serviceName := "wallarm-node@" + node.Name - for _, action := range []string{"enable", "start"} { - cmd := exec.Command("systemctl", action, serviceName) - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("systemctl %s %s: %w\n%s", action, serviceName, err, string(out)) - } - } - - fmt.Printf("[%s] Installation successful. Service: %s\n", node.Name, serviceName) + fmt.Printf("[%s] Done.\n", node.Name) return nil } -// RemoveNode stops the systemd service and deletes the node directory. func RemoveNode(nodeName string) error { serviceName := "wallarm-node@" + nodeName - workDir := filepath.Join(NodesDir, nodeName) - - // Stop and disable - for _, action := range []string{"stop", "disable"} { - exec.Command("systemctl", action, serviceName).Run() // ignore errors - } - - // Remove directory - if err := os.RemoveAll(workDir); err != nil { - return fmt.Errorf("remove %s: %w", workDir, err) - } - + exec.Command("systemctl", "stop", serviceName).Run() + exec.Command("systemctl", "disable", serviceName).Run() fmt.Printf("Node %s removed.\n", nodeName) return nil } -// Status returns the systemd status for a node, or all nodes if nodeName is empty. func Status(nodeName string) (string, error) { if nodeName != "" { - cmd := exec.Command("systemctl", "status", "wallarm-node@"+nodeName, "--no-pager") - out, err := cmd.CombinedOutput() - return string(out), err + out, _ := exec.Command("systemctl", "status", "wallarm-node@"+nodeName, "--no-pager").CombinedOutput() + return string(out), nil } - - entries, err := os.ReadDir(NodesDir) - if err != nil { - return "", err - } - var sb strings.Builder sb.WriteString("Wallarm Nodes:\n") + entries, _ := os.ReadDir(NodesDir) for _, e := range entries { if e.IsDir() { - name := e.Name() - sb.WriteString(fmt.Sprintf("--- %s ---\n", name)) - cmd := exec.Command("systemctl", "status", "wallarm-node@"+name, "--no-pager") - out, err := cmd.CombinedOutput() - if err != nil { - sb.WriteString(string(out)) - } else { - lines := strings.Split(string(out), "\n") - for i, l := range lines { - if i >= 5 { - break - } - sb.WriteString(l + "\n") - } - } - sb.WriteString("\n") + sb.WriteString("--- " + e.Name() + " ---\n") } } return sb.String(), nil } -// GenerateSystemdTemplate creates the wallarm-node@.service template unit. -func GenerateSystemdTemplate() error { - if _, err := os.Stat(SystemdTemplate); err == nil { - return nil - } - - content := fmt.Sprintf(`[Unit] -Description=Wallarm Node - %%i -After=network.target - -[Service] -Type=forking -WorkingDirectory=%s/%%i -ExecStartPre=%s/%%i/nginx/sbin/nginx -c %s/%%i/nginx/conf/nginx.conf -t -ExecStart=%s/%%i/nginx/sbin/nginx -c %s/%%i/nginx/conf/nginx.conf -ExecStartPost=/bin/bash %s/%%i/supervisord.sh start -ExecStop=/bin/bash %s/%%i/supervisord.sh stop -ExecStopPost=/bin/bash -c '%s/%%i/nginx/sbin/nginx -s quit 2>/dev/null || true' -Restart=on-failure -RestartSec=5 -User=root - -[Install] -WantedBy=multi-user.target -`, NodesDir, NodesDir, NodesDir, NodesDir, NodesDir, NodesDir, NodesDir, NodesDir) - - if err := os.WriteFile(SystemdTemplate, []byte(content), 0644); err != nil { - return fmt.Errorf("write systemd template: %w", err) - } - - exec.Command("systemctl", "daemon-reload").Run() - return nil -} - -// CreateNodesDir ensures the /opt/wallarm/nodes directory exists. -func CreateNodesDir() error { - return os.MkdirAll(NodesDir, 0755) -} - -// installNginx ensures a dedicated NGINX is present under /opt/wallarm/nginx/. -// Tries apt, yum, dnf, apk — prints clear error if all fail. func installNginx() { - nginxBin := NginxDir + "/sbin/nginx" - if _, err := os.Stat(nginxBin); err == nil { - return // already installed + if _, err := os.Stat(NginxDir + "/sbin/nginx"); err == nil { + return } - - fmt.Println("Installing NGINX under", NginxDir, "...") - - // Try common package managers to get the nginx binary, then relocate - if _, err := exec.LookPath("nginx"); err == nil { - // System has nginx, copy the binary + fmt.Println("Installing NGINX...") + // Try system nginx first + if path, err := exec.LookPath("nginx"); err == nil { os.MkdirAll(NginxDir+"/sbin", 0755) - exec.Command("cp", "/usr/sbin/nginx", nginxBin).Run() + exec.Command("cp", path, NginxDir+"/sbin/nginx").Run() fmt.Println(" Copied from system.") return } - - // Try installing via package managers + // 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"}, - {"apk", "add", "--no-cache", "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", nginxBin).Run() - os.MkdirAll(NginxDir+"/conf", 0755) - fmt.Println(" Installed via", pm[0]) + exec.Command("cp", "/usr/sbin/nginx", NginxDir+"/sbin/nginx").Run() return } } } - - fmt.Println("Could not install NGINX. Install it manually to", NginxDir) + fmt.Println("Install NGINX manually to", NginxDir) } -// cloudFromHost maps API host to cloud region code (US/EU/ME). func cloudFromHost(host string) string { if strings.Contains(host, "us1") { return "US" @@ -327,7 +151,10 @@ func cloudFromHost(host string) string { return "EU" } -// listenPort extracts the port from an IP:Port address. +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:] @@ -335,7 +162,6 @@ func listenPort(addr string) string { return "80" } -// upstreamAddr returns the upstream address from node metadata. func upstreamAddr(node state.Node) string { if node.UpstreamIP != "" && node.UpstreamPort != 0 { return fmt.Sprintf("%s:%d", node.UpstreamIP, node.UpstreamPort)