32ae17cc9f
Reduce .env to just REPOS_HOST_PATH (the projects root); runtime bootstrap moves to compose/defaults. Projects are the subdirectories of the single root — removed the project-directories feature (store table, service methods, /api/config/project-dirs, Settings section). Each project's forge is derived from its git remote (matched to a configured forge, else the bare host) and shown as a pill next to its name (State.Forge via scanner ForgeFor + svc.ForgeDisplay). Removed first-run .env seeding; forges + identity are managed in Settings. Added forge.HostOf. AGENT.md updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
111 lines
3.5 KiB
Go
111 lines
3.5 KiB
Go
// Package config loads GitManager's runtime configuration from the environment
|
|
// (and an optional .env file). See AGENT.md §1.5 — every setting is env-driven;
|
|
// nothing is hardcoded.
|
|
package config
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/joho/godotenv"
|
|
)
|
|
|
|
// Config is the typed application configuration.
|
|
type Config struct {
|
|
ListenAddr string // address the plain HTTP server binds to
|
|
|
|
// TLS: when HTTPSAddr and both cert/key files are set, an HTTPS listener is
|
|
// started in addition to the HTTP one. Claude Desktop's MCP connector only
|
|
// accepts https:// URLs, so /mcp must be reachable over TLS (AGENT.md §8.1).
|
|
HTTPSAddr string // address the HTTPS server binds to ("" disables TLS)
|
|
TLSCertFile string // PEM cert (e.g. an mkcert leaf trusted by the OS store)
|
|
TLSKeyFile string // PEM private key
|
|
|
|
// DBPath is the SQLite config store (forges + tokens, git identity). It lives
|
|
// on a private volume, not accessible outside the container (§1.3).
|
|
DBPath string
|
|
|
|
// ProjectsRoot is the single directory (inside the container) under which each
|
|
// project lives in its own subdirectory. This is the one thing set in .env
|
|
// (as REPOS_HOST_PATH on the host, mounted here); everything else is either a
|
|
// compose/env default or lives in the config store.
|
|
ProjectsRoot string
|
|
|
|
GitBin string // path to the git binary
|
|
|
|
ScanInterval time.Duration // scanner refresh interval
|
|
ScanMaxDepth int // max discovery depth under the root
|
|
ScanIgnore []string // directory names to skip during discovery
|
|
ScanFetchEnabled bool // allow the scanner to run `git fetch`
|
|
|
|
Dev bool // readable console logging vs structured JSON
|
|
LogFile string // optional file to also append logs to
|
|
}
|
|
|
|
// Load reads .env (if present) then the environment, applying defaults.
|
|
// A missing .env is not an error — the environment may be set another way.
|
|
func Load() (Config, error) {
|
|
_ = godotenv.Load()
|
|
|
|
c := Config{
|
|
ListenAddr: env("LISTEN_ADDR", "127.0.0.1:8080"),
|
|
DBPath: env("GITMANAGER_DB", "/data/gitmanager.db"),
|
|
ProjectsRoot: env("PROJECTS_ROOT", "/repos"),
|
|
HTTPSAddr: env("HTTPS_ADDR", ""),
|
|
TLSCertFile: env("TLS_CERT_FILE", ""),
|
|
TLSKeyFile: env("TLS_KEY_FILE", ""),
|
|
GitBin: env("GIT_BIN", "git"),
|
|
ScanMaxDepth: envInt("SCAN_MAX_DEPTH", 4),
|
|
ScanIgnore: splitList(env("SCAN_IGNORE", "node_modules,vendor,.cache")),
|
|
ScanFetchEnabled: envBool("SCAN_FETCH_ENABLED", false),
|
|
Dev: strings.EqualFold(env("APP_ENV", "dev"), "dev"),
|
|
LogFile: env("LOG_FILE", ""),
|
|
}
|
|
|
|
interval, err := time.ParseDuration(env("SCAN_INTERVAL", "30s"))
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
c.ScanInterval = interval
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func env(key, def string) string {
|
|
if v, ok := os.LookupEnv(key); ok && v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
func envInt(key string, def int) int {
|
|
if v, ok := os.LookupEnv(key); ok {
|
|
if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil {
|
|
return n
|
|
}
|
|
}
|
|
return def
|
|
}
|
|
|
|
func envBool(key string, def bool) bool {
|
|
if v, ok := os.LookupEnv(key); ok {
|
|
if b, err := strconv.ParseBool(strings.TrimSpace(v)); err == nil {
|
|
return b
|
|
}
|
|
}
|
|
return def
|
|
}
|
|
|
|
// splitList splits a comma-separated value, trimming spaces and dropping empties.
|
|
func splitList(v string) []string {
|
|
var out []string
|
|
for _, part := range strings.Split(v, ",") {
|
|
if p := strings.TrimSpace(part); p != "" {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out
|
|
}
|