mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-22 19:55:37 +02:00
Crowdsec installer works?
This commit is contained in:
parent
fd11fb81d6
commit
5f95500b6f
4 changed files with 165 additions and 57 deletions
|
@ -333,3 +333,61 @@ func replaceInFile(filepath, oldStr, newStr string) error {
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration
|
- ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration
|
||||||
- ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
|
- ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
|
||||||
|
- ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func installCrowdsec(config Config) error {
|
func installCrowdsec(config Config) error {
|
||||||
|
@ -59,70 +58,30 @@ func installCrowdsec(config Config) error {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := retrieveBouncerKey(config); err != nil {
|
if err := CheckAndAddTraefikLogVolume("docker-compose.yml"); err != nil {
|
||||||
return fmt.Errorf("bouncer key retrieval failed: %v", err)
|
fmt.Printf("Error checking and adding Traefik log volume: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if err := startContainers(); err != nil {
|
if err := startContainers(); err != nil {
|
||||||
// return fmt.Errorf("failed to start containers: %v", err)
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify that the container is running if not keep waiting for 10 more seconds then return an error
|
// get API key
|
||||||
count := 0
|
apiKey, err := GetCrowdSecAPIKey()
|
||||||
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()
|
|
||||||
if err != nil {
|
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
|
if err := restartContainer("traefik"); err != nil {
|
||||||
lines := strings.Split(string(output), "\n")
|
return fmt.Errorf("failed to restart containers: %v", err)
|
||||||
for _, line := range lines {
|
|
||||||
if strings.Contains(line, "key:") {
|
|
||||||
config.TraefikBouncerKey = strings.TrimSpace(strings.Split(line, ":")[1])
|
|
||||||
fmt.Println("Bouncer key:", config.TraefikBouncerKey)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,3 +95,27 @@ func checkIsCrowdsecInstalledInCompose() bool {
|
||||||
// Check for crowdsec service
|
// Check for crowdsec service
|
||||||
return bytes.Contains(content, []byte("crowdsec:"))
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -4,14 +4,16 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"io"
|
"time"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"bytes"
|
||||||
"text/template"
|
"text/template"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
@ -590,6 +592,42 @@ func startContainers() error {
|
||||||
return nil
|
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 {
|
func copyFile(src, dst string) error {
|
||||||
source, err := os.Open(src)
|
source, err := os.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -613,4 +651,32 @@ func moveFile(src, dst string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.Remove(src)
|
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()))
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue