252 lines
8.7 KiB
Go
252 lines
8.7 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
Server ServerConfig `envconfig:"server"`
|
|
Database DatabaseConfig `envconfig:"database"`
|
|
GITEA GITEAConfig `envconfig:"gitea"`
|
|
Auth AuthConfig `envconfig:"auth"`
|
|
Logging LoggingConfig `envconfig:"logging"`
|
|
BACKBEAT BackbeatConfig `envconfig:"backbeat"`
|
|
Docker DockerConfig `envconfig:"docker"`
|
|
N8N N8NConfig `envconfig:"n8n"`
|
|
OpenTelemetry OpenTelemetryConfig `envconfig:"opentelemetry"`
|
|
Composer ComposerConfig `envconfig:"composer"`
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
ListenAddr string `envconfig:"LISTEN_ADDR" default:":8080"`
|
|
ReadTimeout time.Duration `envconfig:"READ_TIMEOUT" default:"30s"`
|
|
WriteTimeout time.Duration `envconfig:"WRITE_TIMEOUT" default:"30s"`
|
|
ShutdownTimeout time.Duration `envconfig:"SHUTDOWN_TIMEOUT" default:"30s"`
|
|
AllowedOrigins []string `envconfig:"ALLOWED_ORIGINS" default:"http://localhost:3000,http://localhost:8080"`
|
|
AllowedOriginsFile string `envconfig:"ALLOWED_ORIGINS_FILE"`
|
|
}
|
|
|
|
type DatabaseConfig struct {
|
|
Host string `envconfig:"DB_HOST" default:"localhost"`
|
|
Port int `envconfig:"DB_PORT" default:"5432"`
|
|
Database string `envconfig:"DB_NAME" default:"whoosh"`
|
|
Username string `envconfig:"DB_USER" default:"whoosh"`
|
|
Password string `envconfig:"DB_PASSWORD"`
|
|
PasswordFile string `envconfig:"DB_PASSWORD_FILE"`
|
|
SSLMode string `envconfig:"DB_SSL_MODE" default:"disable"`
|
|
URL string `envconfig:"DB_URL"`
|
|
AutoMigrate bool `envconfig:"DB_AUTO_MIGRATE" default:"false"`
|
|
MaxOpenConns int `envconfig:"DB_MAX_OPEN_CONNS" default:"25"`
|
|
MaxIdleConns int `envconfig:"DB_MAX_IDLE_CONNS" default:"5"`
|
|
}
|
|
|
|
|
|
type GITEAConfig struct {
|
|
BaseURL string `envconfig:"BASE_URL" required:"true"`
|
|
Token string `envconfig:"TOKEN"`
|
|
TokenFile string `envconfig:"TOKEN_FILE"`
|
|
WebhookPath string `envconfig:"WEBHOOK_PATH" default:"/webhooks/gitea"`
|
|
WebhookToken string `envconfig:"WEBHOOK_TOKEN"`
|
|
WebhookTokenFile string `envconfig:"WEBHOOK_TOKEN_FILE"`
|
|
|
|
// Fetch hardening options
|
|
EagerFilter bool `envconfig:"EAGER_FILTER" default:"true"` // Pre-filter by labels at API level
|
|
FullRescan bool `envconfig:"FULL_RESCAN" default:"false"` // Ignore since parameter for full rescan
|
|
DebugURLs bool `envconfig:"DEBUG_URLS" default:"false"` // Log exact URLs being used
|
|
MaxRetries int `envconfig:"MAX_RETRIES" default:"3"` // Maximum retry attempts
|
|
RetryDelay time.Duration `envconfig:"RETRY_DELAY" default:"2s"` // Delay between retries
|
|
}
|
|
|
|
type AuthConfig struct {
|
|
JWTSecret string `envconfig:"JWT_SECRET"`
|
|
JWTSecretFile string `envconfig:"JWT_SECRET_FILE"`
|
|
JWTExpiry time.Duration `envconfig:"JWT_EXPIRY" default:"24h"`
|
|
ServiceTokens []string `envconfig:"SERVICE_TOKENS"`
|
|
ServiceTokensFile string `envconfig:"SERVICE_TOKENS_FILE"`
|
|
}
|
|
|
|
type LoggingConfig struct {
|
|
Level string `envconfig:"LEVEL" default:"info"`
|
|
Environment string `envconfig:"ENVIRONMENT" default:"production"`
|
|
}
|
|
|
|
type BackbeatConfig struct {
|
|
Enabled bool `envconfig:"ENABLED" default:"true"`
|
|
ClusterID string `envconfig:"CLUSTER_ID" default:"chorus-production"`
|
|
AgentID string `envconfig:"AGENT_ID" default:"whoosh"`
|
|
NATSUrl string `envconfig:"NATS_URL" default:"nats://backbeat-nats:4222"`
|
|
}
|
|
|
|
type DockerConfig struct {
|
|
Enabled bool `envconfig:"ENABLED" default:"true"`
|
|
Host string `envconfig:"HOST" default:"unix:///var/run/docker.sock"`
|
|
}
|
|
|
|
type N8NConfig struct {
|
|
BaseURL string `envconfig:"BASE_URL" default:"https://n8n.home.deepblack.cloud"`
|
|
}
|
|
|
|
type OpenTelemetryConfig struct {
|
|
Enabled bool `envconfig:"ENABLED" default:"true"`
|
|
ServiceName string `envconfig:"SERVICE_NAME" default:"whoosh"`
|
|
ServiceVersion string `envconfig:"SERVICE_VERSION" default:"0.1.7"`
|
|
Environment string `envconfig:"ENVIRONMENT" default:"production"`
|
|
JaegerEndpoint string `envconfig:"JAEGER_ENDPOINT" default:"http://localhost:14268/api/traces"`
|
|
SampleRate float64 `envconfig:"SAMPLE_RATE" default:"1.0"`
|
|
}
|
|
|
|
type ComposerConfig struct {
|
|
// Feature flags for experimental features
|
|
EnableLLMClassification bool `envconfig:"ENABLE_LLM_CLASSIFICATION" default:"false"`
|
|
EnableLLMSkillAnalysis bool `envconfig:"ENABLE_LLM_SKILL_ANALYSIS" default:"false"`
|
|
EnableLLMTeamMatching bool `envconfig:"ENABLE_LLM_TEAM_MATCHING" default:"false"`
|
|
|
|
// Analysis features
|
|
EnableComplexityAnalysis bool `envconfig:"ENABLE_COMPLEXITY_ANALYSIS" default:"true"`
|
|
EnableRiskAssessment bool `envconfig:"ENABLE_RISK_ASSESSMENT" default:"true"`
|
|
EnableAlternativeOptions bool `envconfig:"ENABLE_ALTERNATIVE_OPTIONS" default:"false"`
|
|
|
|
// Debug and monitoring
|
|
EnableAnalysisLogging bool `envconfig:"ENABLE_ANALYSIS_LOGGING" default:"true"`
|
|
EnablePerformanceMetrics bool `envconfig:"ENABLE_PERFORMANCE_METRICS" default:"true"`
|
|
EnableFailsafeFallback bool `envconfig:"ENABLE_FAILSAFE_FALLBACK" default:"true"`
|
|
|
|
// LLM model configuration
|
|
ClassificationModel string `envconfig:"CLASSIFICATION_MODEL" default:"llama3.1:8b"`
|
|
SkillAnalysisModel string `envconfig:"SKILL_ANALYSIS_MODEL" default:"llama3.1:8b"`
|
|
MatchingModel string `envconfig:"MATCHING_MODEL" default:"llama3.1:8b"`
|
|
|
|
// Performance settings
|
|
AnalysisTimeoutSecs int `envconfig:"ANALYSIS_TIMEOUT_SECS" default:"60"`
|
|
SkillMatchThreshold float64 `envconfig:"SKILL_MATCH_THRESHOLD" default:"0.6"`
|
|
}
|
|
|
|
func readSecretFile(filePath string) (string, error) {
|
|
if filePath == "" {
|
|
return "", nil
|
|
}
|
|
|
|
content, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read secret file %s: %w", filePath, err)
|
|
}
|
|
|
|
return strings.TrimSpace(string(content)), nil
|
|
}
|
|
|
|
func (c *Config) loadSecrets() error {
|
|
// Load database password from file if specified
|
|
if c.Database.PasswordFile != "" {
|
|
password, err := readSecretFile(c.Database.PasswordFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Database.Password = password
|
|
}
|
|
|
|
|
|
// Load GITEA token from file if specified
|
|
if c.GITEA.TokenFile != "" {
|
|
token, err := readSecretFile(c.GITEA.TokenFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.GITEA.Token = token
|
|
}
|
|
|
|
// Load GITEA webhook token from file if specified
|
|
if c.GITEA.WebhookTokenFile != "" {
|
|
token, err := readSecretFile(c.GITEA.WebhookTokenFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.GITEA.WebhookToken = token
|
|
}
|
|
|
|
// Load JWT secret from file if specified
|
|
if c.Auth.JWTSecretFile != "" {
|
|
secret, err := readSecretFile(c.Auth.JWTSecretFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Auth.JWTSecret = secret
|
|
}
|
|
|
|
// Load service tokens from file if specified
|
|
if c.Auth.ServiceTokensFile != "" {
|
|
tokens, err := readSecretFile(c.Auth.ServiceTokensFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Auth.ServiceTokens = strings.Split(tokens, ",")
|
|
// Trim whitespace from each token
|
|
for i, token := range c.Auth.ServiceTokens {
|
|
c.Auth.ServiceTokens[i] = strings.TrimSpace(token)
|
|
}
|
|
}
|
|
|
|
// Load allowed origins from file if specified
|
|
if c.Server.AllowedOriginsFile != "" {
|
|
origins, err := readSecretFile(c.Server.AllowedOriginsFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Server.AllowedOrigins = strings.Split(origins, ",")
|
|
// Trim whitespace from each origin
|
|
for i, origin := range c.Server.AllowedOrigins {
|
|
c.Server.AllowedOrigins[i] = strings.TrimSpace(origin)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) Validate() error {
|
|
// Load secrets from files first
|
|
if err := c.loadSecrets(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Validate required database password
|
|
if c.Database.Password == "" {
|
|
return fmt.Errorf("database password is required (set WHOOSH_DATABASE_DB_PASSWORD or WHOOSH_DATABASE_DB_PASSWORD_FILE)")
|
|
}
|
|
|
|
// Build database URL if not provided
|
|
if c.Database.URL == "" {
|
|
c.Database.URL = fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s",
|
|
url.QueryEscape(c.Database.Username),
|
|
url.QueryEscape(c.Database.Password),
|
|
c.Database.Host,
|
|
c.Database.Port,
|
|
url.QueryEscape(c.Database.Database),
|
|
c.Database.SSLMode,
|
|
)
|
|
}
|
|
|
|
if c.GITEA.BaseURL == "" {
|
|
return fmt.Errorf("GITEA base URL is required")
|
|
}
|
|
|
|
if c.GITEA.Token == "" {
|
|
return fmt.Errorf("GITEA token is required (set WHOOSH_GITEA_TOKEN or WHOOSH_GITEA_TOKEN_FILE)")
|
|
}
|
|
|
|
if c.GITEA.WebhookToken == "" {
|
|
return fmt.Errorf("GITEA webhook token is required (set WHOOSH_GITEA_WEBHOOK_TOKEN or WHOOSH_GITEA_WEBHOOK_TOKEN_FILE)")
|
|
}
|
|
|
|
if c.Auth.JWTSecret == "" {
|
|
return fmt.Errorf("JWT secret is required (set WHOOSH_AUTH_JWT_SECRET or WHOOSH_AUTH_JWT_SECRET_FILE)")
|
|
}
|
|
|
|
if len(c.Auth.ServiceTokens) == 0 {
|
|
return fmt.Errorf("at least one service token is required (set WHOOSH_AUTH_SERVICE_TOKENS or WHOOSH_AUTH_SERVICE_TOKENS_FILE)")
|
|
}
|
|
|
|
return nil
|
|
} |