package main import ( "RenewCMS/api" validator "RenewCMS/api/pkg/validator" "RenewCMS/internal/infrastructure/config" "context" "errors" "fmt" "log" "net" "net/http" "os" "os/signal" "slices" "syscall" "time" "github.com/joho/godotenv" ) var version = "dev" const ( readHeaderTimeout = 10 * time.Second readTimeout = 30 * time.Second writeTimeout = 60 * time.Second idleTimeout = 120 * time.Second shutdownTimeout = 10 * time.Second ) var possibleEnvFileLocations = []string{".env", "../.env"} var requiredEnvVars = []string{ "PUBLIC_URL", "JWT_SECRET", "SMTP_LOGIN", "SMTP_SENDER", "SMTP_PASSWORD", "SMTP_HOST", "SMTP_PORT", } var envDefaults = map[string]string{ "ENVIRONMENT": "production", "PORT": "8080", "DB_FILE": "./data/renewcms.db", config.EnvDatabaseDriver: config.DefaultDatabaseDriver, "DB_PORT": config.DefaultDatabasePort, "DB_TLS": config.DefaultDatabaseTLS, "DB_MAX_OPEN_CONNS": config.DefaultMaxOpenConns, "DB_MAX_IDLE_CONNS": config.DefaultMaxIdleConns, "DB_CONN_MAX_LIFETIME_SECONDS": config.DefaultConnMaxLifetimeSeconds, "UPLOAD_DIR": "./data/uploads", "AUTH_RATE_LIMIT_MAX_ATTEMPTS": "5", "AUTH_RATE_LIMIT_WINDOW_SECONDS": "60", "TRUSTED_PROXIES": "", "UPDATE_FEED_URL": "https://git.floriansylvain.fr", "RENEWCMS_CONTAINER": "false", } const versionFlag = "--version" func main() { if len(os.Args) == 2 && os.Args[1] == versionFlag { fmt.Println(version) return } if len(os.Args) > 1 { os.Exit(runCommand(os.Args[1:], os.Stdout, os.Stderr)) } if err := run(); err != nil { log.Fatal(err) } } func run() error { initEnvVariables() settings, err := config.NewSettingsFromEnv() if err != nil { return err } settings.Version = version container, closeDb := api.InitContainer(settings) defer closeDatabase(closeDb) validator.InitValidator() server := newServer(api.InitRoutes(container, settings), settings) return serveUntilStopped(server, settings, container.RecordRunningInfoUseCase.Record, container.PurgeScheduler.Run) } func initEnvVariables() { loadEnvFile() applyEnvDefaults() ensureRequiredEnvVars() } func loadEnvFile() { for _, envLocation := range possibleEnvFileLocations { if godotenv.Load(envLocation) == nil { return } } log.Println("WARNING: Could not load any .env file") } func applyEnvDefaults() { for envVar, value := range envDefaults { if _, isSet := os.LookupEnv(envVar); !isSet { _ = os.Setenv(envVar, value) } } } func ensureRequiredEnvVars() { required := slices.Concat(requiredEnvVars, config.DatabaseRequiredEnvVars(os.Getenv(config.EnvDatabaseDriver))) if err := config.MissingEnvVarError(required, os.LookupEnv); err != nil { panic(err.Error()) } } func closeDatabase(closeDb func() error) { if err := closeDb(); err != nil { log.Println("error closing database:", err) } } func newServer(handler http.Handler, settings config.Settings) *http.Server { return &http.Server{ Addr: ":" + settings.Port, Handler: handler, ReadHeaderTimeout: readHeaderTimeout, ReadTimeout: readTimeout, WriteTimeout: writeTimeout, IdleTimeout: idleTimeout, } } func serveUntilStopped(server *http.Server, settings config.Settings, recordRunning func() error, runScheduler func(context.Context)) error { stopSignal, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) schedulerDone := make(chan struct{}) go func() { defer close(schedulerDone) runScheduler(stopSignal) }() defer func() { stop() <-schedulerDone }() serverErr := make(chan error, 1) go listen(server, serverErr, settings, recordRunning) select { case err := <-serverErr: return err case <-stopSignal.Done(): return shutdown(server) } } func listen(server *http.Server, serverErr chan<- error, settings config.Settings, recordRunning func() error) { listener, err := net.Listen("tcp", server.Addr) if err != nil { serverErr <- err return } if err := recordRunning(); err != nil { log.Printf("WARNING: could not write the running-version file: %v", err) } log.Printf("RenewCMS %s (%s) listening on %s, public URL %s", version, settings.Environment, server.Addr, settings.PublicURL) err = server.Serve(listener) if errors.Is(err, http.ErrServerClosed) { err = nil } serverErr <- err } func shutdown(server *http.Server) error { log.Println("shutting down...") ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) defer cancel() return server.Shutdown(ctx) }