Read INVOICE_MEMO_YEARLY and INVOICE_MEMO_LIFETIME from the environment and pass the user pubkey in LNbits payment extra for invoice creation.
203 lines
4.7 KiB
Go
203 lines
4.7 KiB
Go
package config
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/joho/godotenv"
|
||
)
|
||
|
||
type LightningConfig struct {
|
||
Enabled bool
|
||
LNbitsURL string
|
||
LNbitsInvoiceKey string
|
||
PriceYearlySats int64
|
||
PriceLifetimeSats int64
|
||
InvoiceExpiryMins int
|
||
InvoiceMemoYearly string
|
||
InvoiceMemoLifetime string
|
||
}
|
||
|
||
type NostrConfig struct {
|
||
Relays []string
|
||
UsernameSyncEnabled bool
|
||
SyncIntervalMins int
|
||
}
|
||
|
||
type DMConfig struct {
|
||
Enabled bool
|
||
Nsec string
|
||
Kind int
|
||
MessagesFile string
|
||
}
|
||
|
||
type ExpiryConfig struct {
|
||
ReminderDays []int
|
||
GraceDays int
|
||
CronHourUTC int
|
||
}
|
||
|
||
type WebhookConfig struct {
|
||
URL string
|
||
Secret string
|
||
TimeoutSecs int
|
||
MaxRetries int
|
||
}
|
||
|
||
type Config struct {
|
||
Domain string
|
||
Port int
|
||
AdminAPIKey string
|
||
FrontendURL string
|
||
DatabasePath string
|
||
|
||
Lightning LightningConfig
|
||
Nostr NostrConfig
|
||
DM DMConfig
|
||
Expiry ExpiryConfig
|
||
Webhook WebhookConfig
|
||
|
||
LogLevel string
|
||
RateLimitPerMin int
|
||
ReservedUsernames []string
|
||
|
||
// CORS: exact origin list = FRONTEND_URL ∪ CORS_ORIGINS; loopback hosts if CORS_ALLOW_LOCALHOST.
|
||
CORSExtraOrigins []string
|
||
CORSAllowLocalhost bool
|
||
CORSAllowCredentials bool
|
||
}
|
||
|
||
func Load() (*Config, error) {
|
||
_ = godotenv.Load()
|
||
|
||
c := &Config{
|
||
Domain: env("DOMAIN", ""),
|
||
Port: envInt("PORT", 8080),
|
||
AdminAPIKey: env("ADMIN_API_KEY", ""),
|
||
FrontendURL: env("FRONTEND_URL", ""),
|
||
DatabasePath: env("DATABASE_PATH", ".data/nip05.db"),
|
||
Lightning: LightningConfig{
|
||
Enabled: envBool("LIGHTNING_ENABLED", true),
|
||
LNbitsURL: env("LNBITS_URL", ""),
|
||
LNbitsInvoiceKey: env("LNBITS_INVOICE_KEY", ""),
|
||
PriceYearlySats: int64(envInt("PRICE_YEARLY_SATS", 1000)),
|
||
PriceLifetimeSats: int64(envInt("PRICE_LIFETIME_SATS", 10000)),
|
||
InvoiceExpiryMins: envInt("INVOICE_EXPIRY_MINUTES", 30),
|
||
InvoiceMemoYearly: env("INVOICE_MEMO_YEARLY", "Noderunners Relay yearly Access"),
|
||
InvoiceMemoLifetime: env("INVOICE_MEMO_LIFETIME", "Noderunners Relay lifetime Access"),
|
||
},
|
||
Nostr: NostrConfig{
|
||
Relays: csv(env("RELAYS", "")),
|
||
UsernameSyncEnabled: envBool("USERNAME_SYNC_ENABLED", true),
|
||
SyncIntervalMins: envInt("SYNC_INTERVAL_MINUTES", 15),
|
||
},
|
||
DM: DMConfig{
|
||
Enabled: envBool("DM_ENABLED", true),
|
||
Nsec: env("DM_NSEC", ""),
|
||
Kind: envInt("DM_KIND", 1059),
|
||
MessagesFile: env("MESSAGES_FILE", "messages.yaml"),
|
||
},
|
||
Expiry: ExpiryConfig{
|
||
ReminderDays: csvInt(env("EXPIRY_REMINDER_DAYS", "7")),
|
||
GraceDays: envInt("USERNAME_GRACE_DAYS", 30),
|
||
CronHourUTC: envInt("EXPIRY_CRON_HOUR_UTC", 9),
|
||
},
|
||
Webhook: WebhookConfig{
|
||
URL: env("WEBHOOK_URL", ""),
|
||
Secret: env("WEBHOOK_SECRET", ""),
|
||
TimeoutSecs: envInt("WEBHOOK_TIMEOUT_SECONDS", 10),
|
||
MaxRetries: envInt("WEBHOOK_MAX_RETRIES", 5),
|
||
},
|
||
LogLevel: env("LOG_LEVEL", "info"),
|
||
RateLimitPerMin: envInt("RATE_LIMIT_PER_MIN", 30),
|
||
ReservedUsernames: csv(env("RESERVED_USERNAMES", "")),
|
||
CORSExtraOrigins: csv(env("CORS_ORIGINS", "")),
|
||
CORSAllowLocalhost: envBool("CORS_ALLOW_LOCALHOST", true),
|
||
CORSAllowCredentials: envBool("CORS_ALLOW_CREDENTIALS", false),
|
||
}
|
||
|
||
if err := Validate(c); err != nil {
|
||
return nil, err
|
||
}
|
||
return c, nil
|
||
}
|
||
|
||
func env(key, def string) string {
|
||
if v, ok := os.LookupEnv(key); ok && v != "" {
|
||
return v
|
||
}
|
||
return def
|
||
}
|
||
|
||
func envInt(key string, def int) int {
|
||
if v, ok := os.LookupEnv(key); ok && v != "" {
|
||
if n, err := strconv.Atoi(v); err == nil {
|
||
return n
|
||
}
|
||
}
|
||
return def
|
||
}
|
||
|
||
func envBool(key string, def bool) bool {
|
||
if v, ok := os.LookupEnv(key); ok && v != "" {
|
||
switch strings.ToLower(v) {
|
||
case "1", "true", "yes", "on":
|
||
return true
|
||
case "0", "false", "no", "off":
|
||
return false
|
||
}
|
||
}
|
||
return def
|
||
}
|
||
|
||
func csv(v string) []string {
|
||
if v == "" {
|
||
return nil
|
||
}
|
||
parts := strings.Split(v, ",")
|
||
out := make([]string, 0, len(parts))
|
||
for _, p := range parts {
|
||
p = strings.TrimSpace(p)
|
||
if p != "" {
|
||
out = append(out, p)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
func csvInt(v string) []int {
|
||
parts := csv(v)
|
||
out := make([]int, 0, len(parts))
|
||
for _, p := range parts {
|
||
n, err := strconv.Atoi(p)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
out = append(out, n)
|
||
}
|
||
return out
|
||
}
|
||
|
||
func (c *Config) Addr() string { return fmt.Sprintf(":%d", c.Port) }
|
||
|
||
// CORSExactOrigins lists allowed browser Origins for exact match (before loopback wildcard).
|
||
func (c *Config) CORSExactOrigins() []string {
|
||
seen := make(map[string]bool)
|
||
out := make([]string, 0, 4+len(c.CORSExtraOrigins))
|
||
add := func(s string) {
|
||
s = strings.TrimSpace(s)
|
||
if s == "" || seen[s] {
|
||
return
|
||
}
|
||
seen[s] = true
|
||
out = append(out, s)
|
||
}
|
||
add(c.FrontendURL)
|
||
for _, o := range c.CORSExtraOrigins {
|
||
add(o)
|
||
}
|
||
return out
|
||
}
|