240 lines
6.6 KiB
Go
240 lines
6.6 KiB
Go
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. Load Wallarm NGINX module + register node
|
|
fmt.Printf("[%s] Configuring NGINX module...\n", node.Name)
|
|
exec.Command("bash", filepath.Join(DeployTo, "pick-module.sh")).Run()
|
|
|
|
fmt.Printf("[%s] Registering node...\n", node.Name)
|
|
fmt.Printf("[%s] Registering node...\n", node.Name)
|
|
registerCmd := exec.Command("bash", "-c",
|
|
fmt.Sprintf("source %s/env.list 2>/dev/null; %s/register-node job:register -token '%s' -host '%s'",
|
|
DeployTo, DeployTo, apiToken, apiHost))
|
|
registerCmd.Dir = DeployTo
|
|
|
|
// Run with explicit timeout
|
|
type result struct {
|
|
out []byte
|
|
err error
|
|
}
|
|
ch := make(chan result, 1)
|
|
go func() {
|
|
out, err := registerCmd.CombinedOutput()
|
|
ch <- result{out, err}
|
|
}()
|
|
select {
|
|
case r := <-ch:
|
|
if r.err != nil {
|
|
return fmt.Errorf("register: %w\n%s", r.err, string(r.out))
|
|
}
|
|
case <-time.After(60 * time.Second):
|
|
registerCmd.Process.Kill()
|
|
return fmt.Errorf("registration timed out")
|
|
}
|
|
|
|
// 7. Stop our temporary NGINX, start Wallarm via supervisord
|
|
exec.Command("pkill", "-f", filepath.Join(DeployTo, "nginx")).Run()
|
|
supervisorCmd := exec.Command("bash", filepath.Join(DeployTo, "supervisord.sh"), "start")
|
|
supervisorCmd.Dir = DeployTo
|
|
supervisorCmd.Run()
|
|
fmt.Printf("[%s] Services started.\n", node.Name)
|
|
|
|
// 8. Move /opt/wallarm → /opt/fw/{name}/wallarm
|
|
os.MkdirAll(filepath.Join(BaseDir, node.Name), 0755)
|
|
os.RemoveAll(instanceDir)
|
|
os.Rename(DeployTo, instanceDir)
|
|
|
|
// 9. Set up systemd service
|
|
GenerateSystemdTemplate()
|
|
svc := "wallarm-node@" + node.Name
|
|
exec.Command("systemctl", "enable", svc).Run()
|
|
exec.Command("systemctl", "start", svc).Run()
|
|
fmt.Printf("[%s] Done. Installed to %s (systemctl status %s)\n", node.Name, instanceDir, svc)
|
|
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 {
|
|
tmpl := `/etc/systemd/system/wallarm-node@.service`
|
|
if _, err := os.Stat(tmpl); err == nil {
|
|
return nil // already exists
|
|
}
|
|
content := fmt.Sprintf(`[Unit]
|
|
Description=Wallarm Node - %%i
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=forking
|
|
WorkingDirectory=%s/%%i/wallarm
|
|
ExecStart=/bin/bash %s/%%i/wallarm/supervisord.sh start
|
|
ExecStop=/bin/bash %s/%%i/wallarm/supervisord.sh stop
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
User=root
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
`, BaseDir, BaseDir, BaseDir)
|
|
os.WriteFile(tmpl, []byte(content), 0644)
|
|
exec.Command("systemctl", "daemon-reload").Run()
|
|
return nil
|
|
}
|
|
|
|
func RemoveNode(name string) error {
|
|
exec.Command("systemctl", "stop", "wallarm-node@"+name).Run()
|
|
exec.Command("systemctl", "disable", "wallarm-node@"+name).Run()
|
|
os.RemoveAll(filepath.Join(BaseDir, name))
|
|
return nil
|
|
}
|
|
|
|
func Status(name string) (string, error) {
|
|
out, _ := exec.Command("systemctl", "status", "wallarm-node@"+name, "--no-pager").CombinedOutput()
|
|
return string(out), 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)
|
|
}
|
|
}
|