Files
sub2api-cn-relay-manager/internal/config/config.go
2026-05-28 11:01:29 +08:00

167 lines
4.3 KiB
Go

package config
import (
"fmt"
"os"
"strings"
"time"
)
const (
EnvListenAddr = "SUB2API_CRM_LISTEN_ADDR"
EnvSQLiteDSN = "SUB2API_CRM_SQLITE_DSN"
EnvAdminToken = "SUB2API_CRM_ADMIN_TOKEN"
EnvAdminUsername = "SUB2API_CRM_ADMIN_USERNAME"
EnvAdminPassword = "SUB2API_CRM_ADMIN_PASSWORD"
EnvAdminSessionTTL = "SUB2API_CRM_ADMIN_SESSION_TTL"
EnvRepoRoot = "SUB2API_CRM_REPO_ROOT"
EnvReconcileWorkerEnabled = "SUB2API_CRM_RECONCILE_WORKER_ENABLED"
EnvReconcilePollInterval = "SUB2API_CRM_RECONCILE_POLL_INTERVAL"
DefaultListenAddr = ":8080"
DefaultSQLiteDSN = "file:sub2api-cn-relay-manager.db?_foreign_keys=on&_busy_timeout=5000"
DefaultAdminUsername = "admin"
DefaultAdminSessionTTL = 12 * time.Hour
DefaultReconcilePollInterval = 10 * time.Minute
)
type ServerConfig struct {
ListenAddr string
}
type DatabaseConfig struct {
SQLiteDSN string
}
type ReconcileConfig struct {
WorkerEnabled bool
PollInterval time.Duration
}
type RepositoryConfig struct {
RepoRoot string
}
type StartupConfig struct {
Server ServerConfig
Database DatabaseConfig
Repository RepositoryConfig
Reconcile ReconcileConfig
}
type AdminSessionConfig struct {
Username string
Password string
SessionTTL time.Duration
}
func LoadStartupFromEnv() (StartupConfig, error) {
return loadStartupFromLookupEnv(os.LookupEnv)
}
func loadStartupFromLookupEnv(lookup func(string) (string, bool)) (StartupConfig, error) {
reconcilePollInterval, err := readOptionalDurationEnv(lookup, EnvReconcilePollInterval, DefaultReconcilePollInterval)
if err != nil {
return StartupConfig{}, err
}
cfg := StartupConfig{
Server: ServerConfig{
ListenAddr: readOptionalEnv(lookup, EnvListenAddr, DefaultListenAddr),
},
Database: DatabaseConfig{
SQLiteDSN: readOptionalEnv(lookup, EnvSQLiteDSN, DefaultSQLiteDSN),
},
Repository: RepositoryConfig{
RepoRoot: readOptionalEnv(lookup, EnvRepoRoot, ""),
},
Reconcile: ReconcileConfig{
WorkerEnabled: readOptionalBoolEnv(lookup, EnvReconcileWorkerEnabled, false),
PollInterval: reconcilePollInterval,
},
}
return cfg, nil
}
func LoadAdminTokenFromEnv() (string, error) {
return loadAdminTokenFromLookupEnv(os.LookupEnv)
}
func LoadAdminSessionFromEnv() (AdminSessionConfig, error) {
return loadAdminSessionFromLookupEnv(os.LookupEnv)
}
func loadAdminTokenFromLookupEnv(lookup func(string) (string, bool)) (string, error) {
token := strings.TrimSpace(readRequiredEnv(lookup, EnvAdminToken))
if token == "" {
return "", fmt.Errorf("%s is required", EnvAdminToken)
}
return token, nil
}
func loadAdminSessionFromLookupEnv(lookup func(string) (string, bool)) (AdminSessionConfig, error) {
ttl, err := readOptionalDurationEnv(lookup, EnvAdminSessionTTL, DefaultAdminSessionTTL)
if err != nil {
return AdminSessionConfig{}, err
}
return AdminSessionConfig{
Username: readOptionalEnv(lookup, EnvAdminUsername, DefaultAdminUsername),
Password: strings.TrimSpace(readOptionalEnv(lookup, EnvAdminPassword, "")),
SessionTTL: ttl,
}, nil
}
func readOptionalEnv(lookup func(string) (string, bool), key string, defaultValue string) string {
value, ok := lookup(key)
if !ok {
return defaultValue
}
value = strings.TrimSpace(value)
if value == "" {
return defaultValue
}
return value
}
func readRequiredEnv(lookup func(string) (string, bool), key string) string {
value, ok := lookup(key)
if !ok {
return ""
}
return value
}
func readOptionalBoolEnv(lookup func(string) (string, bool), key string, defaultValue bool) bool {
value, ok := lookup(key)
if !ok {
return defaultValue
}
switch strings.ToLower(strings.TrimSpace(value)) {
case "1", "true", "yes", "on":
return true
case "0", "false", "no", "off", "":
return false
default:
return defaultValue
}
}
func readOptionalDurationEnv(lookup func(string) (string, bool), key string, defaultValue time.Duration) (time.Duration, error) {
value, ok := lookup(key)
if !ok || strings.TrimSpace(value) == "" {
return defaultValue, nil
}
duration, err := time.ParseDuration(strings.TrimSpace(value))
if err != nil {
return 0, fmt.Errorf("%s: parse duration: %w", key, err)
}
if duration <= 0 {
return 0, fmt.Errorf("%s: duration must be positive", key)
}
return duration, nil
}