From 5f95500b6f77d3da6dfb27cc189217adc7d0a884 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 19 Feb 2025 21:42:42 -0500 Subject: [PATCH] Crowdsec installer works? --- install/config.go | 58 +++++++++++++++++++ install/config/docker-compose.yml | 1 + install/crowdsec.go | 95 +++++++++++++------------------ install/main.go | 68 +++++++++++++++++++++- 4 files changed, 165 insertions(+), 57 deletions(-) diff --git a/install/config.go b/install/config.go index f87bb1a..c31149d 100644 --- a/install/config.go +++ b/install/config.go @@ -333,3 +333,61 @@ func replaceInFile(filepath, oldStr, newStr string) error { return nil } + +func CheckAndAddTraefikLogVolume(composePath string) error { + // Read the docker-compose.yml file + data, err := os.ReadFile(composePath) + if err != nil { + return fmt.Errorf("error reading compose file: %w", err) + } + + // Parse YAML into a generic map + var compose map[string]interface{} + if err := yaml.Unmarshal(data, &compose); err != nil { + return fmt.Errorf("error parsing compose file: %w", err) + } + + // Get services section + services, ok := compose["services"].(map[string]interface{}) + if !ok { + return fmt.Errorf("services section not found or invalid") + } + + // Get traefik service + traefik, ok := services["traefik"].(map[string]interface{}) + if !ok { + return fmt.Errorf("traefik service not found or invalid") + } + + // Check volumes + logVolume := "./config/traefik/logs:/var/log/traefik" + var volumes []interface{} + + if existingVolumes, ok := traefik["volumes"].([]interface{}); ok { + // Check if volume already exists + for _, v := range existingVolumes { + if v.(string) == logVolume { + fmt.Println("Traefik log volume is already configured") + return nil + } + } + volumes = existingVolumes + } + + // Add new volume + volumes = append(volumes, logVolume) + traefik["volumes"] = volumes + + // Write updated config back to file + newData, err := MarshalYAMLWithIndent(compose, 2) + if err != nil { + return fmt.Errorf("error marshaling updated compose file: %w", err) + } + + if err := os.WriteFile(composePath, newData, 0644); err != nil { + return fmt.Errorf("error writing updated compose file: %w", err) + } + + fmt.Println("Added traefik log volume and created logs directory") + return nil +} diff --git a/install/config/docker-compose.yml b/install/config/docker-compose.yml index 42604ab..f6ce789 100644 --- a/install/config/docker-compose.yml +++ b/install/config/docker-compose.yml @@ -52,6 +52,7 @@ services: volumes: - ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration - ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates + - ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs networks: default: diff --git a/install/crowdsec.go b/install/crowdsec.go index 6d25a63..d98b2ac 100644 --- a/install/crowdsec.go +++ b/install/crowdsec.go @@ -6,7 +6,6 @@ import ( "os" "os/exec" "strings" - "time" ) func installCrowdsec(config Config) error { @@ -59,70 +58,30 @@ func installCrowdsec(config Config) error { os.Exit(1) } - if err := retrieveBouncerKey(config); err != nil { - return fmt.Errorf("bouncer key retrieval failed: %v", err) + if err := CheckAndAddTraefikLogVolume("docker-compose.yml"); err != nil { + fmt.Printf("Error checking and adding Traefik log volume: %v\n", err) + os.Exit(1) } - // if err := startContainers(); err != nil { - // return fmt.Errorf("failed to start containers: %v", err) - // } - - return nil -} - -func retrieveBouncerKey(config Config) error { - - fmt.Println("Retrieving bouncer key. Please be patient...") - - // Start crowdsec container - cmd := exec.Command("docker", "compose", "up", "-d", "crowdsec") - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to start crowdsec: %v", err) + if err := startContainers(); err != nil { + return fmt.Errorf("failed to start containers: %v", err) } - // verify that the container is running if not keep waiting for 10 more seconds then return an error - count := 0 - for { - cmd := exec.Command("docker", "inspect", "-f", "{{.State.Running}}", "crowdsec") - output, err := cmd.Output() - if err != nil { - return fmt.Errorf("failed to inspect crowdsec container: %v", err) - } - if strings.TrimSpace(string(output)) == "true" { - break - } - time.Sleep(10 * time.Second) - count++ - - if count > 4 { - return fmt.Errorf("crowdsec container is not running") - } - } - - // Get bouncer key - output, err := exec.Command("docker", "exec", "crowdsec", "cscli", "bouncers", "add", "traefik-bouncer").Output() + // get API key + apiKey, err := GetCrowdSecAPIKey() if err != nil { - return fmt.Errorf("failed to get bouncer key: %v", err) + return fmt.Errorf("failed to get API key: %v", err) + } + config.TraefikBouncerKey = apiKey + + if err := replaceInFile("config/traefik/dynamic_config.yml", "PUT_YOUR_BOUNCER_KEY_HERE_OR_IT_WILL_NOT_WORK", config.TraefikBouncerKey); err != nil { + return fmt.Errorf("failed to replace bouncer key: %v", err) } - // Parse key from output - lines := strings.Split(string(output), "\n") - for _, line := range lines { - if strings.Contains(line, "key:") { - config.TraefikBouncerKey = strings.TrimSpace(strings.Split(line, ":")[1]) - fmt.Println("Bouncer key:", config.TraefikBouncerKey) - break - } + if err := restartContainer("traefik"); err != nil { + return fmt.Errorf("failed to restart containers: %v", err) } - // Stop crowdsec container - cmd = exec.Command("docker", "compose", "down") - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to stop crowdsec: %v", err) - } - - fmt.Println("Bouncer key retrieved successfully.") - return nil } @@ -136,3 +95,27 @@ func checkIsCrowdsecInstalledInCompose() bool { // Check for crowdsec service return bytes.Contains(content, []byte("crowdsec:")) } + +func GetCrowdSecAPIKey() (string, error) { + // First, ensure the container is running + if err := waitForContainer("crowdsec"); err != nil { + return "", fmt.Errorf("waiting for container: %w", err) + } + + // Execute the command to get the API key + cmd := exec.Command("docker", "exec", "crowdsec", "cscli", "bouncers", "add", "traefik-bouncer", "-o", "raw") + var out bytes.Buffer + cmd.Stdout = &out + + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("executing command: %w", err) + } + + // Trim any whitespace from the output + apiKey := strings.TrimSpace(out.String()) + if apiKey == "" { + return "", fmt.Errorf("empty API key returned") + } + + return apiKey, nil +} diff --git a/install/main.go b/install/main.go index bca1910..9064b4f 100644 --- a/install/main.go +++ b/install/main.go @@ -4,14 +4,16 @@ import ( "bufio" "embed" "fmt" + "io" "io/fs" "os" - "io" + "time" "os/exec" "path/filepath" "runtime" "strings" "syscall" + "bytes" "text/template" "unicode" @@ -590,6 +592,42 @@ func startContainers() error { return nil } +func restartContainer(container string) error { + fmt.Printf("Restarting %s container...\n", container) + + // Check which docker compose command is available + var useNewStyle bool + checkCmd := exec.Command("docker", "compose", "version") + if err := checkCmd.Run(); err == nil { + useNewStyle = true + } else { + // Check if docker-compose (old style) is available + checkCmd = exec.Command("docker-compose", "version") + if err := checkCmd.Run(); err != nil { + return fmt.Errorf("neither 'docker compose' nor 'docker-compose' command is available: %v", err) + } + } + + // Helper function to execute docker compose commands + executeCommand := func(args ...string) error { + var cmd *exec.Cmd + if useNewStyle { + cmd = exec.Command("docker", append([]string{"compose"}, args...)...) + } else { + cmd = exec.Command("docker-compose", args...) + } + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() + } + + if err := executeCommand("-f", "docker-compose.yml", "restart", container); err != nil { + return fmt.Errorf("failed to restart %s container: %v", container, err) + } + + return nil +} + func copyFile(src, dst string) error { source, err := os.Open(src) if err != nil { @@ -613,4 +651,32 @@ func moveFile(src, dst string) error { } return os.Remove(src) +} + +func waitForContainer(containerName string) error { + maxAttempts := 30 + retryInterval := time.Second * 2 + + for attempt := 0; attempt < maxAttempts; attempt++ { + // Check if container is running + cmd := exec.Command("docker", "container", "inspect", "-f", "{{.State.Running}}", containerName) + var out bytes.Buffer + cmd.Stdout = &out + + if err := cmd.Run(); err != nil { + // If the container doesn't exist or there's another error, wait and retry + time.Sleep(retryInterval) + continue + } + + isRunning := strings.TrimSpace(out.String()) == "true" + if isRunning { + return nil + } + + // Container exists but isn't running yet, wait and retry + time.Sleep(retryInterval) + } + + return fmt.Errorf("container %s did not start within %v seconds", containerName, maxAttempts*int(retryInterval.Seconds())) } \ No newline at end of file