diff --git a/install/config.go b/install/config.go new file mode 100644 index 0000000..fa10fa5 --- /dev/null +++ b/install/config.go @@ -0,0 +1,216 @@ +package main + +import ( + "bytes" + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +// TraefikConfig represents the structure of the main Traefik configuration +type TraefikConfig struct { + Experimental struct { + Plugins struct { + Badger struct { + Version string `yaml:"version"` + } `yaml:"badger"` + } `yaml:"plugins"` + } `yaml:"experimental"` + CertificatesResolvers struct { + LetsEncrypt struct { + Acme struct { + Email string `yaml:"email"` + } `yaml:"acme"` + } `yaml:"letsencrypt"` + } `yaml:"certificatesResolvers"` +} + +// DynamicConfig represents the structure of the dynamic configuration +type DynamicConfig struct { + HTTP struct { + Routers map[string]struct { + Rule string `yaml:"rule"` + } `yaml:"routers"` + } `yaml:"http"` +} + +// ConfigValues holds the extracted configuration values +type ConfigValues struct { + DashboardDomain string + LetsEncryptEmail string + BadgerVersion string +} + +// ReadTraefikConfig reads and extracts values from Traefik configuration files +func ReadTraefikConfig(mainConfigPath, dynamicConfigPath string) (*ConfigValues, error) { + // Read main config file + mainConfigData, err := os.ReadFile(mainConfigPath) + if err != nil { + return nil, fmt.Errorf("error reading main config file: %w", err) + } + + var mainConfig TraefikConfig + if err := yaml.Unmarshal(mainConfigData, &mainConfig); err != nil { + return nil, fmt.Errorf("error parsing main config file: %w", err) + } + + // Read dynamic config file + dynamicConfigData, err := os.ReadFile(dynamicConfigPath) + if err != nil { + return nil, fmt.Errorf("error reading dynamic config file: %w", err) + } + + var dynamicConfig DynamicConfig + if err := yaml.Unmarshal(dynamicConfigData, &dynamicConfig); err != nil { + return nil, fmt.Errorf("error parsing dynamic config file: %w", err) + } + + // Extract values + values := &ConfigValues{ + BadgerVersion: mainConfig.Experimental.Plugins.Badger.Version, + LetsEncryptEmail: mainConfig.CertificatesResolvers.LetsEncrypt.Acme.Email, + } + + // Extract DashboardDomain from router rules + // Look for it in the main router rules + for _, router := range dynamicConfig.HTTP.Routers { + if router.Rule != "" { + // Extract domain from Host(`mydomain.com`) + if domain := extractDomainFromRule(router.Rule); domain != "" { + values.DashboardDomain = domain + break + } + } + } + + return values, nil +} + +// extractDomainFromRule extracts the domain from a router rule +func extractDomainFromRule(rule string) string { + // Look for the Host(`mydomain.com`) pattern + if start := findPattern(rule, "Host(`"); start != -1 { + end := findPattern(rule[start:], "`)") + if end != -1 { + return rule[start+6 : start+end] + } + } + return "" +} + +// findPattern finds the start of a pattern in a string +func findPattern(s, pattern string) int { + return bytes.Index([]byte(s), []byte(pattern)) +} + +type Volume string +type Port string +type Expose string + +type HealthCheck struct { + Test []string `yaml:"test,omitempty"` + Interval string `yaml:"interval,omitempty"` + Timeout string `yaml:"timeout,omitempty"` + Retries int `yaml:"retries,omitempty"` +} + +type DependsOnCondition struct { + Condition string `yaml:"condition,omitempty"` +} + +type Service struct { + Image string `yaml:"image,omitempty"` + ContainerName string `yaml:"container_name,omitempty"` + Environment map[string]string `yaml:"environment,omitempty"` + HealthCheck *HealthCheck `yaml:"healthcheck,omitempty"` + DependsOn map[string]DependsOnCondition `yaml:"depends_on,omitempty"` + Labels []string `yaml:"labels,omitempty"` + Volumes []Volume `yaml:"volumes,omitempty"` + Ports []Port `yaml:"ports,omitempty"` + Expose []Expose `yaml:"expose,omitempty"` + Restart string `yaml:"restart,omitempty"` + Command interface{} `yaml:"command,omitempty"` + NetworkMode string `yaml:"network_mode,omitempty"` + CapAdd []string `yaml:"cap_add,omitempty"` +} + +type Network struct { + Driver string `yaml:"driver,omitempty"` + Name string `yaml:"name,omitempty"` +} + +type DockerConfig struct { + Version string `yaml:"version,omitempty"` + Services map[string]Service `yaml:"services"` + Networks map[string]Network `yaml:"networks,omitempty"` +} + +func AddCrowdSecService(configPath string) error { + // Read existing config + data, err := os.ReadFile(configPath) + if err != nil { + return err + } + + // Parse existing config + var config DockerConfig + if err := yaml.Unmarshal(data, &config); err != nil { + return err + } + + // Create CrowdSec service + crowdsecService := Service{ + Image: "crowdsecurity/crowdsec:latest", + ContainerName: "crowdsec", + Environment: map[string]string{ + "GID": "${GID-1000}", + "COLLECTIONS": "crowdsecurity/traefik crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules", + "ENROLL_INSTANCE_NAME": "pangolin-crowdsec", + "PARSERS": "crowdsecurity/whitelists", + "ACQUIRE_FILES": "/var/log/traefik/*.log", + "ENROLL_TAGS": "docker", + }, + HealthCheck: &HealthCheck{ + Test: []string{"CMD", "cscli", "capi", "status"}, + }, + DependsOn: map[string]DependsOnCondition{ + "gerbil": {}, + }, + Labels: []string{"traefik.enable=false"}, + Volumes: []Volume{ + "./config/crowdsec:/etc/crowdsec", + "./config/crowdsec/db:/var/lib/crowdsec/data", + "./config/crowdsec_logs/auth.log:/var/log/auth.log:ro", + "./config/crowdsec_logs/syslog:/var/log/syslog:ro", + "./config/crowdsec_logs:/var/log", + "./config/traefik/logs:/var/log/traefik", + }, + Ports: []Port{ + "9090:9090", + "6060:6060", + }, + Expose: []Expose{ + "9090", + "6060", + "7422", + }, + Restart: "unless-stopped", + Command: "-t", + } + + // Add CrowdSec service to config + if config.Services == nil { + config.Services = make(map[string]Service) + } + config.Services["crowdsec"] = crowdsecService + + // Marshal config with better formatting + yamlData, err := yaml.Marshal(&config) + if err != nil { + return err + } + + // Write config back to file + return os.WriteFile(configPath, yamlData, 0644) +} diff --git a/install/fs/config.yml b/install/config/config.yml similarity index 100% rename from install/fs/config.yml rename to install/config/config.yml diff --git a/install/crowdsec/acquis.yaml b/install/config/crowdsec/acquis.yaml similarity index 100% rename from install/crowdsec/acquis.yaml rename to install/config/crowdsec/acquis.yaml diff --git a/install/crowdsec/config.yaml b/install/config/crowdsec/config.yaml similarity index 100% rename from install/crowdsec/config.yaml rename to install/config/crowdsec/config.yaml diff --git a/install/crowdsec/dynamic_config.yml b/install/config/crowdsec/dynamic_config.yml similarity index 92% rename from install/crowdsec/dynamic_config.yml rename to install/config/crowdsec/dynamic_config.yml index 9175b14..d255697 100644 --- a/install/crowdsec/dynamic_config.yml +++ b/install/config/crowdsec/dynamic_config.yml @@ -56,7 +56,7 @@ http: routers: # HTTP to HTTPS redirect router main-app-router-redirect: - rule: "Host(`{{.DomainName}}`)" # Dynamic Domain Name + rule: "Host(`{{.DashboardDomain}}`)" # Dynamic Domain Name service: next-service entryPoints: - web @@ -65,7 +65,7 @@ http: # Next.js router (handles everything except API and WebSocket paths) next-router: - rule: "Host(`{{.DomainName}}`) && !PathPrefix(`/api/v1`)" # Dynamic Domain Name + rule: "Host(`{{.DashboardDomain}}`) && !PathPrefix(`/api/v1`)" # Dynamic Domain Name service: next-service entryPoints: - websecure @@ -76,7 +76,7 @@ http: # API router (handles /api/v1 paths) api-router: - rule: "Host(`{{.DomainName}}`) && PathPrefix(`/api/v1`)" # Dynamic Domain Name + rule: "Host(`{{.DashboardDomain}}`) && PathPrefix(`/api/v1`)" # Dynamic Domain Name service: api-service entryPoints: - websecure @@ -87,7 +87,7 @@ http: # WebSocket router ws-router: - rule: "Host(`{{.DomainName}}`)" # Dynamic Domain Name + rule: "Host(`{{.DashboardDomain}}`)" # Dynamic Domain Name service: api-service entryPoints: - websecure diff --git a/install/crowdsec/local_api_credentials.yaml b/install/config/crowdsec/local_api_credentials.yaml similarity index 100% rename from install/crowdsec/local_api_credentials.yaml rename to install/config/crowdsec/local_api_credentials.yaml diff --git a/install/crowdsec/profiles.yaml b/install/config/crowdsec/profiles.yaml similarity index 100% rename from install/crowdsec/profiles.yaml rename to install/config/crowdsec/profiles.yaml diff --git a/install/crowdsec/traefik_config.yml b/install/config/crowdsec/traefik_config.yml similarity index 100% rename from install/crowdsec/traefik_config.yml rename to install/config/crowdsec/traefik_config.yml diff --git a/install/fs/docker-compose.yml b/install/config/docker-compose.yml similarity index 97% rename from install/fs/docker-compose.yml rename to install/config/docker-compose.yml index ea673eb..42604ab 100644 --- a/install/fs/docker-compose.yml +++ b/install/config/docker-compose.yml @@ -10,7 +10,6 @@ services: interval: "3s" timeout: "3s" retries: 5 - {{if .InstallGerbil}} gerbil: image: fosrl/gerbil:{{.GerbilVersion}} @@ -34,15 +33,13 @@ services: - 443:443 # Port for traefik because of the network_mode - 80:80 # Port for traefik because of the network_mode {{end}} - traefik: image: traefik:v3.3.3 container_name: traefik restart: unless-stopped {{if .InstallGerbil}} network_mode: service:gerbil # Ports appear on the gerbil service -{{end}} -{{if not .InstallGerbil}} +{{end}}{{if not .InstallGerbil}} ports: - 443:443 - 80:80 diff --git a/install/fs/traefik/dynamic_config.yml b/install/config/traefik/dynamic_config.yml similarity index 100% rename from install/fs/traefik/dynamic_config.yml rename to install/config/traefik/dynamic_config.yml diff --git a/install/fs/traefik/traefik_config.yml b/install/config/traefik/traefik_config.yml similarity index 100% rename from install/fs/traefik/traefik_config.yml rename to install/config/traefik/traefik_config.yml diff --git a/install/crowdsec.go b/install/crowdsec.go index 187fea8..02e9c71 100644 --- a/install/crowdsec.go +++ b/install/crowdsec.go @@ -2,45 +2,26 @@ package main import ( "bytes" - "embed" "fmt" - "html/template" - "io" - "io/fs" "os" "os/exec" - "path/filepath" "strings" "time" ) -//go:embed crowdsec/* -var configCrowdsecFiles embed.FS - -// DockerContainer represents a Docker container -type DockerContainer struct { - NetworkSettings struct { - Networks map[string]struct { - IPAddress string `json:"IPAddress"` - } `json:"Networks"` - } `json:"NetworkSettings"` -} - -func installCrowdsec() error { - // Create configuration - config := &Config{} - +func installCrowdsec(config Config) error { // Run installation steps if err := backupConfig(); err != nil { return fmt.Errorf("backup failed: %v", err) } - if err := modifyDockerCompose(); err != nil { - return fmt.Errorf("docker-compose modification failed: %v", err) + if err := AddCrowdSecService("docker-compose.yml"); err != nil { + return fmt.Errorf("crowdsec service addition failed: %v", err) } - if err := createCrowdsecFiles(*config); err != nil { - return fmt.Errorf("config file creation failed: %v", err) + if err := createConfigFiles(config); err != nil { + fmt.Printf("Error creating config files: %v\n", err) + os.Exit(1) } moveFile("config/crowdsec/traefik_config.yaml", "config/traefik/traefik_config.yaml") @@ -50,14 +31,6 @@ func installCrowdsec() error { return fmt.Errorf("bouncer key retrieval failed: %v", err) } - if err := deployStack(); err != nil { - return fmt.Errorf("deployment failed: %v", err) - } - - if err := verifyDeployment(); err != nil { - return fmt.Errorf("verification failed: %v", err) - } - return nil } @@ -80,33 +53,7 @@ func backupConfig() error { return nil } -func modifyDockerCompose() error { - // Read existing docker-compose.yml - content, err := os.ReadFile("docker-compose.yml") - if err != nil { - return fmt.Errorf("failed to read docker-compose.yml: %v", err) - } - - // Verify required services exist - requiredServices := []string{"services:", "pangolin:", "gerbil:", "traefik:"} - for _, service := range requiredServices { - if !bytes.Contains(content, []byte(service)) { - return fmt.Errorf("required service %s not found in docker-compose.yml", service) - } - } - - // Add crowdsec service - modified := addCrowdsecService(string(content)) - - // Write modified content - if err := os.WriteFile("docker-compose.yml", []byte(modified), 0644); err != nil { - return fmt.Errorf("failed to write modified docker-compose.yml: %v", err) - } - - return nil -} - -func retrieveBouncerKey(config *Config) error { +func retrieveBouncerKey(config Config) error { // Start crowdsec container cmd := exec.Command("docker", "compose", "up", "-d", "crowdsec") if err := cmd.Run(); err != nil { @@ -114,8 +61,24 @@ func retrieveBouncerKey(config *Config) error { } defer exec.Command("docker", "compose", "down").Run() - // Wait for container to start - time.Sleep(10 * time.Second) + // 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() @@ -135,181 +98,13 @@ func retrieveBouncerKey(config *Config) error { return nil } -func deployStack() error { - cmd := exec.Command("docker", "compose", "up", "-d") - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to deploy stack: %v", err) - } - - fmt.Println("Stack deployed. Waiting 2 minutes for services to initialize...") - time.Sleep(2 * time.Minute) - return nil -} - -func verifyDeployment() error { - resp, err := exec.Command("curl", "-s", "http://localhost:6060/metrics").Output() +func checkIsCrowdsecInstalledInCompose() bool { + // Read docker-compose.yml + content, err := os.ReadFile("docker-compose.yml") if err != nil { - return fmt.Errorf("failed to get metrics: %v", err) + return false } - if !bytes.Contains(resp, []byte("appsec")) { - return fmt.Errorf("appsec metrics not found in response") - } - - return nil -} - -func copyFile(src, dst string) error { - source, err := os.Open(src) - if err != nil { - return err - } - defer source.Close() - - destination, err := os.Create(dst) - if err != nil { - return err - } - defer destination.Close() - - _, err = io.Copy(destination, source) - return err -} - -func moveFile(src, dst string) error { - if err := copyFile(src, dst); err != nil { - return err - } - - return os.Remove(src) -} - -func addCrowdsecService(content string) string { - // Implementation of adding crowdsec service to docker-compose.yml - // This would involve string manipulation or template rendering - // The actual implementation would depend on how you want to structure the docker-compose modifications - return content + ` - crowdsec: - image: crowdsecurity/crowdsec:latest - container_name: crowdsec - environment: - GID: "${GID-1000}" - COLLECTIONS: crowdsecurity/traefik crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules - ENROLL_INSTANCE_NAME: "pangolin-crowdsec" - PARSERS: crowdsecurity/whitelists - ACQUIRE_FILES: "/var/log/traefik/*.log" - ENROLL_TAGS: docker - healthcheck: - test: ["CMD", "cscli", "capi", "status"] - depends_on: - - gerbil - labels: - - "traefik.enable=false" - volumes: - - ./config/crowdsec:/etc/crowdsec - - ./config/crowdsec/db:/var/lib/crowdsec/data - - ./config/crowdsec_logs/auth.log:/var/log/auth.log:ro - - ./config/crowdsec_logs/syslog:/var/log/syslog:ro - - ./config/crowdsec_logs:/var/log - - ./config/traefik/logs:/var/log/traefik - ports: - - 9090:9090 - - 6060:6060 - expose: - - 9090 - - 6060 - - 7422 - restart: unless-stopped - command: -t` -} - -func createCrowdsecFiles(config Config) error { - // Walk through all embedded files - err := fs.WalkDir(configCrowdsecFiles, "crowdsec", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - // Skip the root fs directory itself - if path == "fs" { - return nil - } - - // Get the relative path by removing the "fs/" prefix - relPath := strings.TrimPrefix(path, "fs/") - - // skip .DS_Store - if strings.Contains(relPath, ".DS_Store") { - return nil - } - - // Create the full output path under "config/" - outPath := filepath.Join("config", relPath) - - if d.IsDir() { - // Create directory - if err := os.MkdirAll(outPath, 0755); err != nil { - return fmt.Errorf("failed to create directory %s: %v", outPath, err) - } - return nil - } - - // Read the template file - content, err := configFiles.ReadFile(path) - if err != nil { - return fmt.Errorf("failed to read %s: %v", path, err) - } - - // Parse template - tmpl, err := template.New(d.Name()).Parse(string(content)) - if err != nil { - return fmt.Errorf("failed to parse template %s: %v", path, err) - } - - // Ensure parent directory exists - if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil { - return fmt.Errorf("failed to create parent directory for %s: %v", outPath, err) - } - - // Create output file - outFile, err := os.Create(outPath) - if err != nil { - return fmt.Errorf("failed to create %s: %v", outPath, err) - } - defer outFile.Close() - - // Execute template - if err := tmpl.Execute(outFile, config); err != nil { - return fmt.Errorf("failed to execute template %s: %v", path, err) - } - - return nil - }) - - if err != nil { - return fmt.Errorf("error walking config files: %v", err) - } - - // get the current directory - dir, err := os.Getwd() - if err != nil { - return fmt.Errorf("failed to get current directory: %v", err) - } - - sourcePath := filepath.Join(dir, "config/docker-compose.yml") - destPath := filepath.Join(dir, "docker-compose.yml") - - // Check if source file exists - if _, err := os.Stat(sourcePath); err != nil { - return fmt.Errorf("source docker-compose.yml not found: %v", err) - } - - // Try to move the file - err = os.Rename(sourcePath, destPath) - if err != nil { - return fmt.Errorf("failed to move docker-compose.yml from %s to %s: %v", - sourcePath, destPath, err) - } - - return nil + // Check for crowdsec service + return bytes.Contains(content, []byte("crowdsec:")) } diff --git a/install/go.mod b/install/go.mod index 85cf49e..536ac2d 100644 --- a/install/go.mod +++ b/install/go.mod @@ -5,4 +5,5 @@ go 1.23.0 require ( golang.org/x/sys v0.29.0 // indirect golang.org/x/term v0.28.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/install/go.sum b/install/go.sum index f05f63b..3316e03 100644 --- a/install/go.sum +++ b/install/go.sum @@ -2,3 +2,6 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/install/main.go b/install/main.go index d5bf5e1..4f7c7df 100644 --- a/install/main.go +++ b/install/main.go @@ -6,6 +6,7 @@ import ( "fmt" "io/fs" "os" + "io" "os/exec" "path/filepath" "runtime" @@ -24,7 +25,7 @@ func loadVersions(config *Config) { config.BadgerVersion = "replaceme" } -//go:embed fs/* +//go:embed config/* var configFiles embed.FS type Config struct { @@ -46,6 +47,7 @@ type Config struct { EmailNoReply string InstallGerbil bool TraefikBouncerKey string + DoCrowdsecInstall bool } func main() { @@ -57,9 +59,12 @@ func main() { os.Exit(1) } + var config Config + config.DoCrowdsecInstall = false + // check if there is already a config file if _, err := os.Stat("config/config.yml"); err != nil { - config := collectUserInput(reader) + config = collectUserInput(reader) loadVersions(&config) @@ -68,18 +73,53 @@ func main() { os.Exit(1) } + moveFile("config/docker-compose.yml", "docker-compose.yml") + if !isDockerInstalled() && runtime.GOOS == "linux" { if readBool(reader, "Docker is not installed. Would you like to install it?", true) { installDocker() } } + + fmt.Println("\n=== Starting installation ===") + + if isDockerInstalled() { + if readBool(reader, "Would you like to install and start the containers?", true) { + pullAndStartContainers() + } + } } else { - fmt.Println("Config file already exists... skipping configuration") + fmt.Println("Looks like you already installed, so I am going to do the setup...") } - if isDockerInstalled() { - if readBool(reader, "Would you like to install and start the containers?", true) { - pullAndStartContainers() + if !checkIsCrowdsecInstalledInCompose() { + fmt.Println("\n=== Crowdsec Install ===") + // check if crowdsec is installed + if readBool(reader, "Would you like to install Crowdsec?", true) { + + if config.DashboardDomain == "" { + traefikConfig, err := ReadTraefikConfig("config/traefik/traefik_config.yml", "config/traefik/dynamic_config.yml") + if err != nil { + fmt.Printf("Error reading config: %v\n", err) + return + } + config.DashboardDomain = traefikConfig.DashboardDomain + config.LetsEncryptEmail = traefikConfig.LetsEncryptEmail + config.BadgerVersion = traefikConfig.BadgerVersion + + // print the values and check if they are right + fmt.Println("Detected values:") + fmt.Printf("Dashboard Domain: %s\n", config.DashboardDomain) + fmt.Printf("Let's Encrypt Email: %s\n", config.LetsEncryptEmail) + fmt.Printf("Badger Version: %s\n", config.BadgerVersion) + + if !readBool(reader, "Are these values correct?", true) { + config = collectUserInput(reader) + } + } + + config.DoCrowdsecInstall = true + installCrowdsec(config) } } @@ -137,6 +177,11 @@ func readInt(reader *bufio.Reader, prompt string, defaultValue int) int { return value } +func isDockerFilePresent() bool { + _, err := os.Stat("docker-compose.yml") + return !os.IsNotExist(err) +} + func collectUserInput(reader *bufio.Reader) Config { config := Config{} @@ -262,31 +307,33 @@ func createConfigFiles(config Config) error { os.MkdirAll("config/logs", 0755) // Walk through all embedded files - err := fs.WalkDir(configFiles, "fs", func(path string, d fs.DirEntry, err error) error { + err := fs.WalkDir(configFiles, "config", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } // Skip the root fs directory itself - if path == "fs" { + if path == "config" { return nil } - // Get the relative path by removing the "fs/" prefix - relPath := strings.TrimPrefix(path, "fs/") + if !config.DoCrowdsecInstall && strings.Contains(path, "crowdsec") { + return nil + } + + if config.DoCrowdsecInstall && !strings.Contains(path, "crowdsec") { + return nil + } // skip .DS_Store - if strings.Contains(relPath, ".DS_Store") { + if strings.Contains(path, ".DS_Store") { return nil } - // Create the full output path under "config/" - outPath := filepath.Join("config", relPath) - if d.IsDir() { // Create directory - if err := os.MkdirAll(outPath, 0755); err != nil { - return fmt.Errorf("failed to create directory %s: %v", outPath, err) + if err := os.MkdirAll(path, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %v", path, err) } return nil } @@ -304,14 +351,14 @@ func createConfigFiles(config Config) error { } // Ensure parent directory exists - if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil { - return fmt.Errorf("failed to create parent directory for %s: %v", outPath, err) + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return fmt.Errorf("failed to create parent directory for %s: %v", path, err) } // Create output file - outFile, err := os.Create(outPath) + outFile, err := os.Create(path) if err != nil { - return fmt.Errorf("failed to create %s: %v", outPath, err) + return fmt.Errorf("failed to create %s: %v", path, err) } defer outFile.Close() @@ -327,30 +374,10 @@ func createConfigFiles(config Config) error { return fmt.Errorf("error walking config files: %v", err) } - // get the current directory - dir, err := os.Getwd() - if err != nil { - return fmt.Errorf("failed to get current directory: %v", err) - } - - sourcePath := filepath.Join(dir, "config/docker-compose.yml") - destPath := filepath.Join(dir, "docker-compose.yml") - - // Check if source file exists - if _, err := os.Stat(sourcePath); err != nil { - return fmt.Errorf("source docker-compose.yml not found: %v", err) - } - - // Try to move the file - err = os.Rename(sourcePath, destPath) - if err != nil { - return fmt.Errorf("failed to move docker-compose.yml from %s to %s: %v", - sourcePath, destPath, err) - } - return nil } + func installDocker() error { // Detect Linux distribution cmd := exec.Command("cat", "/etc/os-release") @@ -491,3 +518,28 @@ func pullAndStartContainers() error { return nil } + +func copyFile(src, dst string) error { + source, err := os.Open(src) + if err != nil { + return err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return err + } + defer destination.Close() + + _, err = io.Copy(destination, source) + return err +} + +func moveFile(src, dst string) error { + if err := copyFile(src, dst); err != nil { + return err + } + + return os.Remove(src) +} \ No newline at end of file