Files
claw-email/internal/config/config.go
T
thuanleandClaude Opus 4.7 dbe5cab56e
CI / fmt (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
ci: add Gitea Actions workflow with gofmt and test jobs
Add CI workflow at .gitea/workflows/ci.yml triggered on pull_request
with two jobs: fmt (gofmt check) and test (go test ./...).

Fix gofmt issues (import ordering) in 7 files so CI passes from
the start.

Fixes #7

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 22:34:56 +07:00

131 lines
3.2 KiB
Go

package config
import (
"fmt"
"net"
"os"
"strings"
"github.com/joho/godotenv"
)
// Config holds all application configuration loaded from environment variables.
type Config struct {
// IMAP settings
IMAPHost string
IMAPPort string
IMAPUser string
IMAPPass string
// SMTP settings
SMTPHost string
SMTPPort string
SMTPUser string
SMTPPass string
// OpenClaw settings
OpenClawURL string
OpenClawAPIKey string
// Bridge settings
BridgeCallbackToken string
SystemEmail string
WhitelistEmails []string
// Optional
IMAPProxyURL string
// Server
ListenAddr string
// Public callback base URL for OpenClaw callbacks
CallbackBaseURL string
}
// Load reads .env (if present) and populates Config from environment variables.
// Returns an error if any required variable is missing.
func Load() (*Config, error) {
// Best-effort load .env — ignore error if file doesn't exist.
_ = godotenv.Load()
cfg := &Config{
IMAPHost: os.Getenv("IMAP_HOST"),
IMAPPort: os.Getenv("IMAP_PORT"),
IMAPUser: os.Getenv("IMAP_USER"),
IMAPPass: os.Getenv("IMAP_PASS"),
SMTPHost: os.Getenv("SMTP_HOST"),
SMTPPort: os.Getenv("SMTP_PORT"),
SMTPUser: os.Getenv("SMTP_USER"),
SMTPPass: os.Getenv("SMTP_PASS"),
OpenClawURL: os.Getenv("OPENCLAW_URL"),
OpenClawAPIKey: os.Getenv("OPENCLAW_API_KEY"),
BridgeCallbackToken: os.Getenv("BRIDGE_CALLBACK_TOKEN"),
SystemEmail: os.Getenv("SYSTEM_EMAIL"),
IMAPProxyURL: os.Getenv("IMAP_PROXY_URL"),
ListenAddr: os.Getenv("LISTEN_ADDR"),
CallbackBaseURL: os.Getenv("CALLBACK_BASE_URL"),
}
if raw := os.Getenv("WHITELIST_EMAILS"); raw != "" {
for _, email := range strings.Split(raw, ",") {
if trimmed := strings.TrimSpace(email); trimmed != "" {
cfg.WhitelistEmails = append(cfg.WhitelistEmails, trimmed)
}
}
}
if cfg.ListenAddr == "" {
cfg.ListenAddr = ":8080"
}
if cfg.CallbackBaseURL == "" {
cfg.CallbackBaseURL = defaultCallbackBaseURL(cfg.ListenAddr)
}
if err := cfg.validate(); err != nil {
return nil, err
}
return cfg, nil
}
func defaultCallbackBaseURL(listenAddr string) string {
host, port, err := net.SplitHostPort(listenAddr)
if err != nil || port == "" {
return "http://localhost:8080"
}
if host == "" || host == "0.0.0.0" || host == "::" {
host = "localhost"
}
return fmt.Sprintf("http://%s:%s", host, port)
}
// validate checks that all required configuration values are present.
func (c *Config) validate() error {
required := map[string]string{
"IMAP_HOST": c.IMAPHost,
"IMAP_PORT": c.IMAPPort,
"IMAP_USER": c.IMAPUser,
"IMAP_PASS": c.IMAPPass,
"SMTP_HOST": c.SMTPHost,
"SMTP_PORT": c.SMTPPort,
"SMTP_USER": c.SMTPUser,
"SMTP_PASS": c.SMTPPass,
"OPENCLAW_URL": c.OpenClawURL,
"BRIDGE_CALLBACK_TOKEN": c.BridgeCallbackToken,
"SYSTEM_EMAIL": c.SystemEmail,
}
var missing []string
for name, val := range required {
if val == "" {
missing = append(missing, name)
}
}
if len(missing) > 0 {
return fmt.Errorf("missing required config: %s", strings.Join(missing, ", "))
}
return nil
}