diff --git a/install/crowdsec.go b/install/crowdsec.go index c75dccf32..8dff42d99 100644 --- a/install/crowdsec.go +++ b/install/crowdsec.go @@ -6,12 +6,13 @@ import ( "log" "os" "os/exec" + "path/filepath" "strings" "gopkg.in/yaml.v3" ) -func installCrowdsec(config Config) error { +func installCrowdsec(config Config, installDir string) error { if err := stopContainers(config.InstallationContainerType); err != nil { return fmt.Errorf("failed to stop containers: %v", err) @@ -40,6 +41,8 @@ func installCrowdsec(config Config) error { os.Exit(1) } + setupTraefikLogRotate(installDir) + 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) @@ -208,3 +211,69 @@ func CheckAndAddCrowdsecDependency(composePath string) error { fmt.Println("Added dependency of crowdsec to traefik") return nil } + +// setupTraefikLogRotate writes a logrotate config for the Traefik access log +// that CrowdSec depends on. This is only needed when CrowdSec is installed +// because the default Pangolin install does not enable Traefik access logs. +// +// copytruncate is used so Traefik does not need to be restarted or sent a +// signal after rotation — it keeps writing to the same file descriptor while +// the rotated copy is made and the original is truncated in place. +func setupTraefikLogRotate(installDir string) { + const logrotateDir = "/etc/logrotate.d" + const logrotateFile = "/etc/logrotate.d/pangolin-traefik" + + logPath := filepath.Join(installDir, "config/traefik/logs/access.log") + + if os.Geteuid() != 0 { + fmt.Println("\n[logrotate] Skipping automatic logrotate setup: not running as root.") + fmt.Println("[logrotate] To prevent unbounded growth of the Traefik access log used by CrowdSec,") + fmt.Println("[logrotate] create the file /etc/logrotate.d/pangolin-traefik manually with:") + printLogrotateConfig(logPath) + return + } + + config := fmt.Sprintf(`# Logrotate config for Traefik access logs used by CrowdSec. +# Generated by the Pangolin installer. Safe to edit. +%s { + daily + rotate 7 + compress + delaycompress + missingok + notifempty + copytruncate +} +`, logPath) + + if err := os.MkdirAll(logrotateDir, 0755); err != nil { + fmt.Printf("[logrotate] Warning: could not create %s: %v\n", logrotateDir, err) + return + } + + if err := os.WriteFile(logrotateFile, []byte(config), 0644); err != nil { + fmt.Printf("[logrotate] Warning: could not write %s: %v\n", logrotateFile, err) + fmt.Println("[logrotate] Set it up manually:") + printLogrotateConfig(logPath) + return + } + + fmt.Printf("[logrotate] Wrote logrotate config to %s\n", logrotateFile) + fmt.Println("[logrotate] Traefik access logs will be rotated daily, keeping 7 compressed copies.") +} + +// printLogrotateConfig prints a logrotate config block to stdout so users can +// set it up manually when the installer cannot write to /etc. +func printLogrotateConfig(logPath string) { + fmt.Printf(` + %s { + daily + rotate 7 + compress + delaycompress + missingok + notifempty + copytruncate + } +`, logPath) +} diff --git a/install/main.go b/install/main.go index a38d78fc6..13e506d06 100644 --- a/install/main.go +++ b/install/main.go @@ -259,7 +259,7 @@ func main() { } config.DoCrowdsecInstall = true - err := installCrowdsec(config) + err := installCrowdsec(config, installDir) if err != nil { fmt.Printf("Error installing CrowdSec: %v\n", err) return