package config import ( "log/slog" "os" "path/filepath" "strings" ) type Config struct { Port string DataDir string DatabasePath string JWTSecret string BaseURL string AllowRegistration bool LogLevel string Debug bool DemoMode bool // Optional SMTP environment variables (can also be overridden/configured in DB system_settings) SMTPHost string SMTPPort string SMTPUser string SMTPPass string SMTPFrom string SMTPTLS bool // SMTPSkipVerify accepts self-signed or mismatching certificates (internal mail servers) SMTPSkipVerify bool // Legal notices (Impressum, Datenschutz): an external URL and/or Markdown text, shown even before login Legal LegalTexts } type LegalTexts struct { ImprintURL, ImprintContent string PrivacyURL, PrivacyContent string } // legalContent reads Markdown from NAME or, for longer texts, from the file named in NAME_FILE. func legalContent(name string) string { if v := strings.TrimSpace(os.Getenv(name)); v != "" { return v } if path := strings.TrimSpace(os.Getenv(name + "_FILE")); path != "" { data, err := os.ReadFile(path) if err != nil { slog.Warn("Rechtstext konnte nicht gelesen werden", "variable", name+"_FILE", "error", err) return "" } return strings.TrimSpace(string(data)) } return "" } // legalURL accepts only http(s) links, so a misconfiguration cannot inject javascript: URLs. func legalURL(name string) string { v := strings.TrimSpace(os.Getenv(name)) if v == "" { return "" } if !strings.HasPrefix(v, "https://") && !strings.HasPrefix(v, "http://") { slog.Warn("Rechtstext-URL ignoriert: nur http(s) erlaubt", "variable", name) return "" } return v } func Load() *Config { port := getEnv("PORT", "8080") dataDir := getEnv("DATA_DIR", "./data") dbURL := getEnv("DATABASE_URL", "") dbPath := filepath.Join(dataDir, "mindsift.db") if strings.HasPrefix(dbURL, "sqlite://") { dbPath = strings.TrimPrefix(dbURL, "sqlite://") } else if dbURL != "" && !strings.HasPrefix(dbURL, "postgres") { dbPath = dbURL } jwtSecret := getEnv("JWT_SECRET", "mindsift-default-selfhosted-jwt-secret-key-2026") baseURL := getEnv("BASE_URL", "http://localhost:"+port) allowReg := strings.ToLower(getEnv("ALLOW_REGISTRATION", "false")) == "true" debugVal := strings.ToLower(getEnv("DEBUG", "false")) debug := debugVal == "true" || debugVal == "1" || debugVal == "yes" || debugVal == "on" logLevel := strings.ToLower(getEnv("LOG_LEVEL", "info")) if debug { logLevel = "debug" } demoVal := strings.ToLower(getEnv("MINDA_DEMO", getEnv("MINDSIFT_DEMO", getEnv("DEMO_MODE", "false")))) demoMode := demoVal == "true" || demoVal == "1" || demoVal == "yes" || demoVal == "on" smtpHost := getEnv("SMTP_HOST", "") smtpPort := getEnv("SMTP_PORT", "587") smtpUser := getEnv("SMTP_USER", "") smtpPass := getEnv("SMTP_PASS", "") smtpFrom := getEnv("SMTP_FROM", "") smtpTLS := strings.ToLower(getEnv("SMTP_TLS", "true")) != "false" smtpSkipVerify := strings.ToLower(getEnv("SMTP_SKIP_VERIFY", "false")) == "true" return &Config{ Port: port, DataDir: dataDir, DatabasePath: dbPath, JWTSecret: jwtSecret, BaseURL: baseURL, AllowRegistration: allowReg, LogLevel: logLevel, Debug: debug, DemoMode: demoMode, SMTPHost: smtpHost, SMTPPort: smtpPort, SMTPUser: smtpUser, SMTPPass: smtpPass, SMTPFrom: smtpFrom, SMTPTLS: smtpTLS, SMTPSkipVerify: smtpSkipVerify, Legal: LegalTexts{ ImprintURL: legalURL("LEGAL_IMPRINT_URL"), ImprintContent: legalContent("LEGAL_IMPRINT_CONTENT"), PrivacyURL: legalURL("LEGAL_PRIVACY_URL"), PrivacyContent: legalContent("LEGAL_PRIVACY_CONTENT"), }, } } // SlogLevel parses Config.LogLevel into a standard slog.Level. func (c *Config) SlogLevel() slog.Level { switch strings.ToLower(strings.TrimSpace(c.LogLevel)) { case "debug": return slog.LevelDebug case "warn", "warning": return slog.LevelWarn case "error": return slog.LevelError default: return slog.LevelInfo } } func getEnv(key, fallback string) string { if val, ok := os.LookupEnv(key); ok && strings.TrimSpace(val) != "" { return strings.TrimSpace(val) } return fallback }