config.go 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. // Package config 负责从环境变量加载并校验后端配置。
  2. //
  3. // 依据 docs/architecture.md 第四节:所有配置通过环境变量提供,
  4. // 不引入配置文件;缺失必填项时拒绝启动。
  5. //
  6. // cookie 名、有效期、bcrypt cost 等常量不在此暴露,直接写在代码里。
  7. package config
  8. import (
  9. "fmt"
  10. "os"
  11. "strconv"
  12. "strings"
  13. )
  14. // Config 是后端启动所需的全部配置。
  15. type Config struct {
  16. // 必填
  17. ListenAddr string // HAVEN_LISTEN_ADDR
  18. DBDSN string // HAVEN_DB_DSN
  19. FrontendDir string // HAVEN_FRONTEND_DIR
  20. GogsBaseURL string // HAVEN_GOGS_BASE_URL
  21. CookieDomain string // HAVEN_COOKIE_DOMAIN
  22. // 可选,带默认值
  23. CookieSecure bool // HAVEN_COOKIE_SECURE,默认 true
  24. TotpIssuer string // HAVEN_TOTP_ISSUER,默认 Haven
  25. LogLevel string // HAVEN_LOG_LEVEL,默认 info
  26. }
  27. // Load 从环境变量读取并校验配置。
  28. // 任一必填项缺失或取值非法都返回错误,由调用方拒绝启动。
  29. func Load() (Config, error) {
  30. var cfg Config
  31. var missing []string
  32. cfg.ListenAddr = strings.TrimSpace(os.Getenv("HAVEN_LISTEN_ADDR"))
  33. if cfg.ListenAddr == "" {
  34. missing = append(missing, "HAVEN_LISTEN_ADDR")
  35. }
  36. cfg.DBDSN = strings.TrimSpace(os.Getenv("HAVEN_DB_DSN"))
  37. if cfg.DBDSN == "" {
  38. missing = append(missing, "HAVEN_DB_DSN")
  39. }
  40. cfg.FrontendDir = strings.TrimSpace(os.Getenv("HAVEN_FRONTEND_DIR"))
  41. if cfg.FrontendDir == "" {
  42. missing = append(missing, "HAVEN_FRONTEND_DIR")
  43. }
  44. cfg.GogsBaseURL = strings.TrimSpace(strings.TrimSuffix(os.Getenv("HAVEN_GOGS_BASE_URL"), "/"))
  45. if cfg.GogsBaseURL == "" {
  46. missing = append(missing, "HAVEN_GOGS_BASE_URL")
  47. }
  48. cfg.CookieDomain = strings.TrimSpace(os.Getenv("HAVEN_COOKIE_DOMAIN"))
  49. if cfg.CookieDomain == "" {
  50. missing = append(missing, "HAVEN_COOKIE_DOMAIN")
  51. }
  52. if len(missing) > 0 {
  53. return Config{}, fmt.Errorf("缺少必填环境变量: %s", strings.Join(missing, ", "))
  54. }
  55. cfg.CookieSecure = true
  56. if raw := strings.TrimSpace(os.Getenv("HAVEN_COOKIE_SECURE")); raw != "" {
  57. v, err := strconv.ParseBool(raw)
  58. if err != nil {
  59. return Config{}, fmt.Errorf("HAVEN_COOKIE_SECURE 取值非法 %q,应为 true 或 false", raw)
  60. }
  61. cfg.CookieSecure = v
  62. }
  63. cfg.TotpIssuer = strings.TrimSpace(os.Getenv("HAVEN_TOTP_ISSUER"))
  64. if cfg.TotpIssuer == "" {
  65. cfg.TotpIssuer = "Haven"
  66. }
  67. cfg.LogLevel = strings.ToLower(strings.TrimSpace(os.Getenv("HAVEN_LOG_LEVEL")))
  68. if cfg.LogLevel == "" {
  69. cfg.LogLevel = "info"
  70. }
  71. switch cfg.LogLevel {
  72. case "debug", "info", "warn", "error":
  73. default:
  74. return Config{}, fmt.Errorf("HAVEN_LOG_LEVEL 取值非法 %q,应为 debug / info / warn / error", cfg.LogLevel)
  75. }
  76. return cfg, nil
  77. }
苏ICP备2026082406号
苏公网安备 待审核