2025-02-12 21:56:13 -05:00
package main
2025-02-09 16:14:29 -05:00
import (
"bytes"
"fmt"
2025-04-23 20:36:16 +02:00
"log"
2025-02-09 16:14:29 -05:00
"os"
"os/exec"
"strings"
2025-04-23 20:36:16 +02:00
"gopkg.in/yaml.v3"
2025-02-09 16:14:29 -05:00
)
2025-02-13 22:16:52 -05:00
func installCrowdsec ( config Config ) error {
2025-02-18 21:41:23 -05:00
if err := stopContainers ( ) ; err != nil {
return fmt . Errorf ( "failed to stop containers: %v" , err )
}
2025-02-09 16:14:29 -05:00
// Run installation steps
if err := backupConfig ( ) ; err != nil {
return fmt . Errorf ( "backup failed: %v" , err )
}
2025-02-13 22:16:52 -05:00
if err := createConfigFiles ( config ) ; err != nil {
fmt . Printf ( "Error creating config files: %v\n" , err )
os . Exit ( 1 )
2025-02-09 16:14:29 -05:00
}
2025-02-18 21:41:23 -05:00
os . MkdirAll ( "config/crowdsec/db" , 0755 )
2025-04-05 17:47:37 +02:00
os . MkdirAll ( "config/crowdsec/acquis.d" , 0755 )
2025-02-18 21:41:23 -05:00
os . MkdirAll ( "config/traefik/logs" , 0755 )
2025-02-16 11:26:45 -05:00
if err := copyDockerService ( "config/crowdsec/docker-compose.yml" , "docker-compose.yml" , "crowdsec" ) ; err != nil {
fmt . Printf ( "Error copying docker service: %v\n" , err )
os . Exit ( 1 )
}
2025-02-23 21:44:02 -05:00
if err := MergeYAML ( "config/traefik/traefik_config.yml" , "config/crowdsec/traefik_config.yml" ) ; err != nil {
2025-02-16 11:26:45 -05:00
fmt . Printf ( "Error copying entry points: %v\n" , err )
os . Exit ( 1 )
}
2025-02-23 21:44:02 -05:00
// delete the 2nd file
if err := os . Remove ( "config/crowdsec/traefik_config.yml" ) ; err != nil {
fmt . Printf ( "Error removing file: %v\n" , err )
2025-02-16 11:26:45 -05:00
os . Exit ( 1 )
}
2025-02-23 21:44:02 -05:00
if err := MergeYAML ( "config/traefik/dynamic_config.yml" , "config/crowdsec/dynamic_config.yml" ) ; err != nil {
fmt . Printf ( "Error copying entry points: %v\n" , err )
2025-02-16 11:26:45 -05:00
os . Exit ( 1 )
}
2025-02-23 21:44:02 -05:00
// delete the 2nd file
if err := os . Remove ( "config/crowdsec/dynamic_config.yml" ) ; err != nil {
fmt . Printf ( "Error removing file: %v\n" , err )
2025-02-16 11:26:45 -05:00
os . Exit ( 1 )
}
if err := os . Remove ( "config/crowdsec/docker-compose.yml" ) ; err != nil {
fmt . Printf ( "Error removing file: %v\n" , err )
os . Exit ( 1 )
}
2025-02-09 16:14:29 -05:00
2025-02-19 21:42:42 -05:00
if err := CheckAndAddTraefikLogVolume ( "docker-compose.yml" ) ; err != nil {
fmt . Printf ( "Error checking and adding Traefik log volume: %v\n" , err )
os . Exit ( 1 )
2025-02-09 16:14:29 -05:00
}
2025-04-23 20:36:16 +02:00
// check and add the service dependency of crowdsec to traefik
if err := CheckAndAddCrowdsecDependency ( "docker-compose.yml" ) ; err != nil {
fmt . Printf ( "Error adding crowdsec dependency to traefik: %v\n" , err )
os . Exit ( 1 )
}
2025-02-19 21:42:42 -05:00
if err := startContainers ( ) ; err != nil {
return fmt . Errorf ( "failed to start containers: %v" , err )
}
2025-02-18 21:41:23 -05:00
2025-02-19 21:42:42 -05:00
// get API key
apiKey , err := GetCrowdSecAPIKey ( )
2025-02-09 16:14:29 -05:00
if err != nil {
2025-02-19 21:42:42 -05:00
return fmt . Errorf ( "failed to get API key: %v" , err )
2025-02-09 16:14:29 -05:00
}
2025-02-19 21:42:42 -05:00
config . TraefikBouncerKey = apiKey
2025-02-09 16:14:29 -05:00
2025-02-19 21:42:42 -05:00
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 )
2025-02-09 16:14:29 -05:00
}
2025-02-19 21:42:42 -05:00
if err := restartContainer ( "traefik" ) ; err != nil {
return fmt . Errorf ( "failed to restart containers: %v" , err )
2025-02-18 21:41:23 -05:00
}
2025-03-03 15:43:26 -05:00
if checkIfTextInFile ( "config/traefik/dynamic_config.yml" , "PUT_YOUR_BOUNCER_KEY_HERE_OR_IT_WILL_NOT_WORK" ) {
fmt . Println ( "Failed to replace bouncer key! Please retrieve the key and replace it in the config/traefik/dynamic_config.yml file using the following command:" )
fmt . Println ( " docker exec crowdsec cscli bouncers add traefik-bouncer" )
}
2025-02-09 16:14:29 -05:00
return nil
}
2025-02-13 22:16:52 -05:00
func checkIsCrowdsecInstalledInCompose ( ) bool {
// Read docker-compose.yml
content , err := os . ReadFile ( "docker-compose.yml" )
2025-02-12 21:56:13 -05:00
if err != nil {
2025-02-13 22:16:52 -05:00
return false
2025-02-12 21:56:13 -05:00
}
2025-02-13 22:16:52 -05:00
// Check for crowdsec service
return bytes . Contains ( content , [ ] byte ( "crowdsec:" ) )
2025-02-12 21:56:13 -05:00
}
2025-02-19 21:42:42 -05:00
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
}
2025-03-03 15:43:26 -05:00
func checkIfTextInFile ( file , text string ) bool {
// Read file
content , err := os . ReadFile ( file )
if err != nil {
return false
}
// Check for text
return bytes . Contains ( content , [ ] byte ( text ) )
}
2025-04-23 20:36:16 +02:00
func CheckAndAddCrowdsecDependency ( 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" )
}
// Get dependencies
dependsOn , ok := traefik [ "depends_on" ] . ( map [ string ] interface { } )
if ok {
// Append the new block for crowdsec
dependsOn [ "crowdsec" ] = map [ string ] interface { } {
"condition" : "service_healthy" ,
}
} else {
// No dependencies exist, create it
traefik [ "depends_on" ] = map [ string ] interface { } {
"crowdsec" : map [ string ] interface { } {
"condition" : "service_healthy" ,
} ,
}
}
// Marshal the modified data back to YAML with indentation
modifiedData , err := MarshalYAMLWithIndent ( compose , 2 ) // Set indentation to 2 spaces
if err != nil {
log . Fatalf ( "error marshaling YAML: %v" , err )
}
if err := os . WriteFile ( composePath , modifiedData , 0644 ) ; err != nil {
return fmt . Errorf ( "error writing updated compose file: %w" , err )
}
fmt . Println ( "Added dependency of crowdsec to traefik" )
return nil
}