Crowdsec installer works?

This commit is contained in:
Owen 2025-02-19 21:42:42 -05:00
parent fd11fb81d6
commit 5f95500b6f
No known key found for this signature in database
GPG key ID: 8271FDFFD9E0CCBD
4 changed files with 165 additions and 57 deletions

View file

@ -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
}

View file

@ -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:

View file

@ -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
}

View file

@ -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()))
}