refactor(config): rename default Garmin token-store dir to .garmin

Shorter, more conventional dotdir name than garmin-tokenstores. Restore
the "next to DBPath" join that got dropped when the default was
inlined via getEnvDefault -- an explicit GARMIN_TOKENSTORE still wins
verbatim, but the implicit default must still resolve relative to the
DB file's directory, not the process's CWD.

internal/garmin/client.go's GARMIN_TOKENSTORE env var is now always
set (TokenStorePath can no longer be empty), so the conditional that
only appended it when non-empty is dead code.
This commit is contained in:
2026-07-26 21:25:39 +02:00
parent a12fe3e810
commit 77f9588c2d
5 changed files with 73 additions and 34 deletions

View File

@@ -7,7 +7,6 @@ package config
import (
"fmt"
"log"
"os"
"path/filepath"
"strconv"
@@ -29,7 +28,7 @@ type Config struct {
// GarminTokenStoreRoot is the root directory under which each user's
// Garmin session cache lives (one subdirectory per user id, e.g.
// "<root>/3"). Read from the GARMIN_TOKENSTORE env var; if unset, it
// defaults to a "garmin-tokenstores" directory next to DBPath so every
// defaults to a ".garmin" directory next to DBPath so every
// deployment gets per-user isolation automatically -- multi-tenant
// operation always relies on this being a real, distinct-per-user path
// (see api.Server.garminFor), so it can never be silently left empty.
@@ -69,11 +68,12 @@ type Config struct {
// Load reads configuration from environment variables, applying defaults
// for anything optional. Returns an error if a required variable is unset.
func Load() (Config, error) {
dbPath := getEnvDefault("GENIUSRUN_DB_PATH", "geniusrun.db")
cfg := Config{
Addr: getEnvDefault("GENIUSRUN_ADDR", ":8080"),
DBPath: getEnvDefault("GENIUSRUN_DB_PATH", "geniusrun.db"),
DBPath: dbPath,
GarminPythonPath: getEnvDefault("GARMIN_WRAPPER_PYTHON", "python3"),
GarminTokenStoreRoot: os.Getenv("GARMIN_TOKENSTORE"),
GarminTokenStoreRoot: getEnvDefault("GARMIN_TOKENSTORE", filepath.Join(filepath.Dir(dbPath), ".garmin")),
MinConfidence: getEnvFloat("GENIUSRUN_MIN_CONFIDENCE", 0.6),
LogLevel: getEnvDefault("GENIUSRUN_LOG_LEVEL", "info"),
BackendURL: strings.TrimRight(os.Getenv("GENIUSRUN_BACKEND_URL"), "/"),
@@ -84,15 +84,11 @@ func Load() (Config, error) {
SessionDuration: getEnvDuration("GENIUSRUN_SESSION_DURATION", 720*time.Hour),
}
if cfg.GarminTokenStoreRoot == "" {
cfg.GarminTokenStoreRoot = filepath.Join(filepath.Dir(cfg.DBPath), "garmin-tokenstores")
log.Printf("GARMIN_TOKENSTORE not set, defaulting to %q", cfg.GarminTokenStoreRoot)
}
if cfg.BackendURL == "" {
return cfg, fmt.Errorf("GENIUSRUN_BACKEND_URL is required (e.g. https://geniusrun.example.com)")
}
cfg.FrontendURL = strings.TrimRight(getEnvDefault("GENIUSRUN_FRONTEND_URL", cfg.BackendURL), "/")
if cfg.OIDCIssuerURL == "" {
return cfg, fmt.Errorf("GENIUSRUN_OIDC_ISSUER_URL is required")
}
@@ -102,12 +98,13 @@ func Load() (Config, error) {
if cfg.OIDCClientSecret == "" {
return cfg, fmt.Errorf("GENIUSRUN_OIDC_CLIENT_SECRET is required")
}
cfg.OIDCRedirectURL = cfg.BackendURL + "/api/session/callback"
sessionSecret := os.Getenv("GENIUSRUN_SESSION_SECRET")
if len(sessionSecret) < 32 {
return cfg, fmt.Errorf("GENIUSRUN_SESSION_SECRET is required and must be at least 32 characters")
}
cfg.SessionSecret = []byte(sessionSecret)
cfg.OIDCRedirectURL = cfg.BackendURL + "/api/session/callback"
cfg.SessionSecure = strings.HasPrefix(cfg.BackendURL, "https://")
return cfg, nil

View File

@@ -88,7 +88,7 @@ func TestLoad_GarminTokenStoreRootDefaultsNextToDBPath(t *testing.T) {
if err != nil {
t.Fatalf("Load: %v", err)
}
want := filepath.Join("/var/lib/geniusrun", "garmin-tokenstores")
want := filepath.Join("/var/lib/geniusrun", ".garmin")
if cfg.GarminTokenStoreRoot != want {
t.Errorf("GarminTokenStoreRoot = %q, want %q", cfg.GarminTokenStoreRoot, want)
}