188 lines
4.6 KiB
Go
188 lines
4.6 KiB
Go
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)
|
|
}
|