Initial commit: CarrotAssistant backend (Go + Gin + SQLite, OpenAI-compatible agent runtime)
This commit is contained in:
108
internal/config/config.go
Normal file
108
internal/config/config.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Package config loads runtime configuration for the CarrotAssistant backend.
|
||||
//
|
||||
// Configuration can be supplied via environment variables (preferred for
|
||||
// containers) and falls back to sensible defaults suitable for local
|
||||
// development. Secrets such as the master key MUST be provided in
|
||||
// production through the environment.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config holds all runtime configuration values.
|
||||
type Config struct {
|
||||
// HTTPAddr is the address the API server listens on.
|
||||
HTTPAddr string
|
||||
// DataDir is the root directory for persistent files (sqlite db,
|
||||
// generated skill markdown, etc.).
|
||||
DataDir string
|
||||
// DBPath is the resolved SQLite database file path.
|
||||
DBPath string
|
||||
// SkillsDir is the root directory where skill markdown files live,
|
||||
// organised as <SkillsDir>/<app_slug>/<skill_slug>.md.
|
||||
SkillsDir string
|
||||
// MasterKey is used to encrypt secrets at rest (model api keys). It
|
||||
// must be 32 bytes; if a shorter value is supplied it is padded, if a
|
||||
// longer one is supplied it is truncated. Provide a strong value in
|
||||
// production via the MASTER_KEY env var.
|
||||
MasterKey string
|
||||
// JWTSecret signs admin session tokens.
|
||||
JWTSecret string
|
||||
// AdminBootstrapUser, if set and no admin user exists, is created on
|
||||
// first run. Useful for fresh deployments.
|
||||
AdminBootstrapUser string
|
||||
AdminBootstrapPassword string
|
||||
// DefaultAdminUser / DefaultAdminPassword are used when bootstrapping
|
||||
// is requested and no explicit credentials are configured.
|
||||
DefaultAdminUser string
|
||||
DefaultAdminPassword string
|
||||
}
|
||||
|
||||
// Load reads configuration from environment variables with defaults.
|
||||
func Load() (*Config, error) {
|
||||
c := &Config{
|
||||
HTTPAddr: env("HTTP_ADDR", ":8080"),
|
||||
DataDir: env("DATA_DIR", "./data"),
|
||||
MasterKey: env("MASTER_KEY", ""),
|
||||
JWTSecret: env("JWT_SECRET", ""),
|
||||
AdminBootstrapUser: env("ADMIN_BOOTSTRAP_USER", ""),
|
||||
AdminBootstrapPassword: env("ADMIN_BOOTSTRAP_PASSWORD", ""),
|
||||
DefaultAdminUser: env("DEFAULT_ADMIN_USER", "admin"),
|
||||
DefaultAdminPassword: env("DEFAULT_ADMIN_PASSWORD", "changeme"),
|
||||
}
|
||||
|
||||
// Resolve derived paths to absolute form for stable behaviour in the
|
||||
// file loader and the HTTP handler layer.
|
||||
dataDir, err := filepath.Abs(c.DataDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve data dir: %w", err)
|
||||
}
|
||||
c.DataDir = dataDir
|
||||
c.DBPath = filepath.Join(dataDir, "carrot.db")
|
||||
c.SkillsDir = filepath.Join(dataDir, "skills")
|
||||
|
||||
for _, dir := range []string{c.DataDir, c.SkillsDir} {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return nil, fmt.Errorf("mkdir %s: %w", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
// In development we allow empty secrets and substitute a deterministic
|
||||
// value so the server still runs. A warning is printed to make the
|
||||
// insecure state obvious; production should always set these.
|
||||
if c.MasterKey == "" {
|
||||
c.MasterKey = "carrot-dev-master-key-please-change"
|
||||
}
|
||||
if c.JWTSecret == "" {
|
||||
c.JWTSecret = "carrot-dev-jwt-secret-please-change"
|
||||
}
|
||||
if len(c.MasterKey) > 32 {
|
||||
c.MasterKey = c.MasterKey[:32]
|
||||
}
|
||||
if len(c.MasterKey) < 32 {
|
||||
c.MasterKey = padKey(c.MasterKey, 32)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// env returns the value of the named env var, or fallback when unset/empty.
|
||||
func env(key, fallback string) string {
|
||||
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// padKey right-pads a short master key to the requested length. This only
|
||||
// exists so the dev defaults work; production must supply a real 32-byte key.
|
||||
func padKey(k string, n int) string {
|
||||
if len(k) >= n {
|
||||
return k[:n]
|
||||
}
|
||||
return k + strings.Repeat("0", n-len(k))
|
||||
}
|
||||
Reference in New Issue
Block a user