package config import ( "os" "path/filepath" "strings" "sync" ) var ( once sync.Once base string // resolved data base dir ) // DataDir returns the base directory for data (db.bolt, certs, logs, www, etc). // Respects DATA_DIR env (absolute or relative). Falls back to "data" relative to CWD. func DataDir() string { once.Do(func() { if d := os.Getenv("DATA_DIR"); d != "" { base = d return } // default relative to cwd wd, err := os.Getwd() if err != nil { wd = "." } base = filepath.Join(wd, "data") }) return base } // Resolve returns an absolute or relative path under the data dir for the given subpath (e.g. "db.bolt", "certs", "www"). // If the corresponding *_DIR env is set it is used as base instead. func Resolve(sub string) string { // Allow per-subdir override e.g. CERTS_DIR, WWW_DIR, LOGS_DIR, CHALLENGE_DIR key := "" switch sub { case "db.bolt": key = "DATA_DIR" case "certs", "custom_ssl": key = "CERTS_DIR" case "logs": key = "LOGS_DIR" case "letsencrypt-acme-challenge", "challenge": key = "CHALLENGE_DIR" case "www": if d := os.Getenv("WWW_DIR"); d != "" { return filepath.Join(d, "") // allow override to point directly } key = "DATA_DIR" case "html": if d := os.Getenv("HTML_DIR"); d != "" { return filepath.Join(d, "") } key = "DATA_DIR" default: key = "DATA_DIR" } dir := DataDir() if key != "" { if override := os.Getenv(key); override != "" { dir = override } } return filepath.Join(dir, sub) } // EnsureDataDirs creates the expected subdirs under the resolved data location (and www inside data by default). // Also pre-creates the acme-challenge subdir layout that lego webroot + our handler expect. func EnsureDataDirs() error { dirs := []string{ Resolve(""), Resolve("certs"), Resolve("logs"), Resolve("letsencrypt-acme-challenge"), Resolve("www"), // data/www by default } for _, d := range dirs { if err := os.MkdirAll(d, 0o755); err != nil { return err } } // full layout for http-01 challenges written by lego acme := filepath.Join(Resolve("letsencrypt-acme-challenge"), ".well-known", "acme-challenge") if err := os.MkdirAll(acme, 0o755); err != nil { return err } return nil } // ResetForTest clears the cached data dir (sync.Once) so tests can change // DATA_DIR / *_DIR envs via t.Setenv and observe fresh resolution. // Call at the start of such tests. This is the supported way to make // config testable without globals leaking across tests. func ResetForTest() { once = sync.Once{} base = "" } // IsDevelopment reports whether PROXY_MODE=development (or "dev"). // Defaults to false (production mode). // When true, all letsencrypt certs use self-signed test certs (via the cert manager's // issueTestCert path). No domain tricks (.localtest etc.) are used. In production // (default) real LE issuance is always attempted. func IsDevelopment() bool { mode := strings.ToLower(os.Getenv("PROXY_MODE")) return mode == "development" || mode == "dev" }