Some checks failed
Build & Publish Docker Image / build-and-push (push) Failing after 11s
170 lines
4.1 KiB
Go
170 lines
4.1 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type RateLimitConfig struct {
|
|
UnauthMaxReqs int
|
|
UnauthWindow time.Duration
|
|
UnauthMaxFails int
|
|
UnauthBanDur time.Duration
|
|
AuthMaxReqs int
|
|
AuthWindow time.Duration
|
|
AuthMaxFails int
|
|
AuthBanDur time.Duration
|
|
}
|
|
|
|
type R2Config struct {
|
|
AccountID string
|
|
AccessKeyID string
|
|
SecretAccessKey string
|
|
BucketName string
|
|
Region string
|
|
Endpoint string
|
|
}
|
|
|
|
type Config struct {
|
|
Port string
|
|
DSN string
|
|
Database string
|
|
APIKey string
|
|
AdminKey string
|
|
MaxOpenConns int
|
|
MaxIdleConns int
|
|
ConnMaxLifetime int
|
|
UpdatesEnabled bool
|
|
UseS3CompatibleStorage bool
|
|
RateLimit RateLimitConfig
|
|
R2 R2Config
|
|
}
|
|
|
|
var (
|
|
instance *Config
|
|
once sync.Once
|
|
)
|
|
|
|
func Load() *Config {
|
|
once.Do(func() { instance = load() })
|
|
return instance
|
|
}
|
|
|
|
func load() *Config {
|
|
port := os.Getenv("PORT")
|
|
if port == "" {
|
|
port = "8080"
|
|
}
|
|
|
|
raw := os.Getenv("API_KEY")
|
|
var apiKey string
|
|
for _, k := range strings.Split(raw, ",") {
|
|
k = strings.TrimSpace(k)
|
|
if k != "" {
|
|
apiKey = k
|
|
break
|
|
}
|
|
}
|
|
|
|
raw = os.Getenv("ADMIN_KEY")
|
|
var adminKey string
|
|
for _, k := range strings.Split(raw, ",") {
|
|
k = strings.TrimSpace(k)
|
|
if k != "" {
|
|
adminKey = k
|
|
break
|
|
}
|
|
}
|
|
|
|
maxOpenConns, err := strconv.Atoi(os.Getenv("DB_MAX_OPEN_CONNS"))
|
|
if err != nil {
|
|
maxOpenConns = 30
|
|
}
|
|
maxIdleConns, err := strconv.Atoi(os.Getenv("DB_MAX_IDLE_CONNS"))
|
|
if err != nil {
|
|
maxIdleConns = 5
|
|
}
|
|
connMaxLifetime, err := strconv.Atoi(os.Getenv("DB_CONN_MAX_LIFETIME"))
|
|
if err != nil {
|
|
connMaxLifetime = 5
|
|
}
|
|
|
|
dbName := os.Getenv("DATABASE_NAME")
|
|
if dbName == "" {
|
|
panic("DATABASE_NAME environment variable is required")
|
|
}
|
|
dbNameRegex := regexp.MustCompile("^[a-zA-Z0-9_]+$")
|
|
// Test the regex against the dbName, otherwise panic to prevent potential SQL injection
|
|
validDbName, err := regexp.Match(dbNameRegex.String(), []byte(dbName))
|
|
if err != nil {
|
|
panic("failed to validate database name: " + err.Error())
|
|
}
|
|
if !validDbName {
|
|
panic("invalid database name: must match regex " + dbNameRegex.String())
|
|
}
|
|
|
|
if os.Getenv("DB_DSN") == "" {
|
|
panic("DB_DSN environment variable is required")
|
|
}
|
|
|
|
return &Config{
|
|
Port: port,
|
|
DSN: os.Getenv("DB_DSN"),
|
|
Database: dbName,
|
|
APIKey: apiKey,
|
|
AdminKey: adminKey,
|
|
MaxOpenConns: maxOpenConns,
|
|
MaxIdleConns: maxIdleConns,
|
|
ConnMaxLifetime: connMaxLifetime,
|
|
UpdatesEnabled: strings.ToLower(strings.TrimSpace(os.Getenv("UPDATES_ENABLED"))) == "true",
|
|
UseS3CompatibleStorage: strings.ToLower(strings.TrimSpace(os.Getenv("USE_S3_COMPATIBLE_STORAGE"))) == "true",
|
|
R2: R2Config{
|
|
AccountID: os.Getenv("CF_ACCOUNT_ID"),
|
|
AccessKeyID: os.Getenv("CF_R2_ACCESS_KEY_ID"),
|
|
SecretAccessKey: os.Getenv("CF_R2_SECRET_ACCESS_KEY"),
|
|
BucketName: os.Getenv("CF_R2_BUCKET_NAME"),
|
|
Region: envString("CF_R2_REGION", "auto"),
|
|
Endpoint: os.Getenv("CF_R2_ENDPOINT"),
|
|
},
|
|
RateLimit: RateLimitConfig{
|
|
UnauthMaxReqs: envInt("RL_UNAUTH_MAX_REQS", 10),
|
|
UnauthWindow: envDuration("RL_UNAUTH_WINDOW", 5*time.Minute),
|
|
UnauthMaxFails: envInt("RL_UNAUTH_MAX_FAILS", 5),
|
|
UnauthBanDur: envDuration("RL_UNAUTH_BAN_DUR", 15*time.Minute),
|
|
AuthMaxReqs: envInt("RL_AUTH_MAX_REQS", 100),
|
|
AuthWindow: envDuration("RL_AUTH_WINDOW", time.Minute),
|
|
AuthMaxFails: envInt("RL_AUTH_MAX_FAILS", 20),
|
|
AuthBanDur: envDuration("RL_AUTH_BAN_DUR", 5*time.Minute),
|
|
},
|
|
}
|
|
}
|
|
|
|
func envString(key, fallback string) string {
|
|
if s := os.Getenv(key); s != "" {
|
|
return s
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envInt(key string, fallback int) int {
|
|
if s := os.Getenv(key); s != "" {
|
|
if n, err := strconv.Atoi(s); err == nil {
|
|
return n
|
|
}
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envDuration(key string, fallback time.Duration) time.Duration {
|
|
if s := os.Getenv(key); s != "" {
|
|
if d, err := time.ParseDuration(s); err == nil {
|
|
return d
|
|
}
|
|
}
|
|
return fallback
|
|
}
|