package main import ( "RenewCMS/api" validator "RenewCMS/api/pkg/validator" "RenewCMS/internal/infrastructure/config" "context" "errors" "fmt" "log" "net/http" "os" "os/signal" "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", "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", } func main() { 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) } 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() { for _, envVar := range requiredEnvVars { if _, isSet := os.LookupEnv(envVar); !isSet { panic(fmt.Sprintf("Environment variable %s is not set", envVar)) } } } 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) error { stopSignal, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() serverErr := make(chan error, 1) go listen(server, serverErr, settings) select { case err := <-serverErr: return err case <-stopSignal.Done(): return shutdown(server) } } func listen(server *http.Server, serverErr chan<- error, settings config.Settings) { log.Printf("RenewCMS %s (%s) listening on %s, public URL %s", version, settings.Environment, server.Addr, settings.PublicURL) err := server.ListenAndServe() 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) }