234 lines
10 KiB
Go
234 lines
10 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
Mode string
|
|
DBPath string
|
|
SpoolRoot string
|
|
RabbitURL string
|
|
Exchange string
|
|
OutboxBatch int
|
|
AgentID string
|
|
DispatcherID string
|
|
Version string
|
|
ControlListen string
|
|
ControlToken string
|
|
CellID string
|
|
GRPCListen string
|
|
DispatcherGRPCListen string
|
|
DispatcherGRPCEndpoint string
|
|
DispatcherGRPCServerName string
|
|
DispatcherGRPCAllowedAgentIDs string
|
|
MTLSCAFile string
|
|
MTLSCertFile string
|
|
MTLSKeyFile string
|
|
MTLSServerName string
|
|
MTLSPeerCertificateFingerprints string
|
|
OSSRegion string
|
|
OSSEndpoint string
|
|
OSSBucket string
|
|
OSSAccessKeyID string
|
|
OSSAccessKeySecret string
|
|
OSSKeyPrefix string
|
|
OSSGrantTTL time.Duration
|
|
OSSMaxAssetBytes int64
|
|
StaticArtifactPath string
|
|
AgentEndpointsFile string
|
|
CallLogPath string
|
|
CallLogPhoneKey string
|
|
ARIURL string
|
|
ARIWebsocketURL string
|
|
ARIApplication string
|
|
ARIUsername string
|
|
ARIPassword string
|
|
CallTarget string
|
|
CallTrunkID string
|
|
CallCallerID string
|
|
CallMediaBind string
|
|
CallMediaPort int
|
|
CallRecordingDirectory string
|
|
CallAISnapshotPath string
|
|
CallTenantID string
|
|
CallTenantKey string
|
|
CallTaskID string
|
|
CallTaskItemID string
|
|
CallExecutionID string
|
|
}
|
|
|
|
func FromEnv() Config {
|
|
return Config{
|
|
Mode: envOr("SIP_GO_AGENT_MODE", "mock"),
|
|
DBPath: envOr("DISPATCHER_DB", "dispatcher.db"),
|
|
SpoolRoot: envOr("AGENT_SPOOL", "./spool"),
|
|
RabbitURL: os.Getenv("RABBITMQ_URL"),
|
|
Exchange: envOr("RABBITMQ_EXCHANGE", "agent-call.commands.v1"),
|
|
OutboxBatch: 50,
|
|
AgentID: envOr("AGENT_ID", "agent-local"),
|
|
DispatcherID: envOr("DISPATCHER_ID", "dispatcher-local"),
|
|
Version: envOr("AGENT_VERSION", "dev"),
|
|
ControlListen: os.Getenv("DISPATCHER_CONTROL_LISTEN"),
|
|
ControlToken: os.Getenv("DISPATCHER_CONTROL_TOKEN"),
|
|
CellID: envOr("CELL_ID", "cell-local"),
|
|
GRPCListen: os.Getenv("AGENT_GRPC_LISTEN"),
|
|
DispatcherGRPCListen: os.Getenv("DISPATCHER_GRPC_LISTEN"),
|
|
DispatcherGRPCEndpoint: os.Getenv("DISPATCHER_GRPC_ENDPOINT"),
|
|
DispatcherGRPCServerName: envOr("DISPATCHER_GRPC_SERVER_NAME", os.Getenv("MTLS_SERVER_NAME")),
|
|
DispatcherGRPCAllowedAgentIDs: os.Getenv("DISPATCHER_ALLOWED_AGENT_IDS"),
|
|
MTLSCAFile: os.Getenv("MTLS_CA_FILE"),
|
|
MTLSCertFile: os.Getenv("MTLS_CERT_FILE"),
|
|
MTLSKeyFile: os.Getenv("MTLS_KEY_FILE"),
|
|
MTLSServerName: os.Getenv("MTLS_SERVER_NAME"),
|
|
MTLSPeerCertificateFingerprints: os.Getenv("MTLS_PEER_CERT_FINGERPRINTS"),
|
|
OSSRegion: os.Getenv("DISPATCHER_OSS_REGION"),
|
|
OSSEndpoint: os.Getenv("DISPATCHER_OSS_ENDPOINT"),
|
|
OSSBucket: os.Getenv("DISPATCHER_OSS_BUCKET"),
|
|
OSSAccessKeyID: envOrSecret("DISPATCHER_OSS_ACCESS_KEY_ID", "DISPATCHER_OSS_ACCESS_KEY_ID_FILE"),
|
|
OSSAccessKeySecret: envOrSecret("DISPATCHER_OSS_ACCESS_KEY_SECRET", "DISPATCHER_OSS_ACCESS_KEY_SECRET_FILE"),
|
|
OSSKeyPrefix: envOr("DISPATCHER_OSS_KEY_PREFIX", "agent-call/recordings"),
|
|
OSSGrantTTL: time.Duration(envInt("DISPATCHER_OSS_GRANT_TTL_SECONDS", 900)) * time.Second,
|
|
OSSMaxAssetBytes: int64(envInt("DISPATCHER_OSS_MAX_ASSET_BYTES", 64<<20)),
|
|
StaticArtifactPath: os.Getenv("AGENT_STATIC_ARTIFACT"),
|
|
AgentEndpointsFile: os.Getenv("DISPATCHER_AGENT_ENDPOINTS_FILE"),
|
|
CallLogPath: os.Getenv("AGENT_CALL_BUSINESS_LOG"),
|
|
CallLogPhoneKey: os.Getenv("AGENT_CALL_PHONE_LOG_KEY"),
|
|
ARIURL: envOr("ARI_URL", "http://127.0.0.1:8088/ari"),
|
|
ARIWebsocketURL: envOr("ARI_WS_URL", "ws://127.0.0.1:8088/ari/events"),
|
|
ARIApplication: envOr("ARI_APPLICATION", "agent-call"),
|
|
ARIUsername: os.Getenv("ARI_USERNAME"),
|
|
ARIPassword: os.Getenv("ARI_PASSWORD"),
|
|
CallTarget: os.Getenv("AGENT_CALL_TARGET"),
|
|
CallTrunkID: os.Getenv("AGENT_CALL_TRUNK_ID"),
|
|
CallCallerID: os.Getenv("AGENT_CALL_CALLER_ID"),
|
|
CallMediaBind: envOr("AGENT_CALL_MEDIA_BIND", "127.0.0.1"),
|
|
CallMediaPort: envInt("AGENT_CALL_MEDIA_PORT", 12000),
|
|
CallRecordingDirectory: envOr("AGENT_CALL_RECORDING_DIR", "./recordings"),
|
|
CallAISnapshotPath: os.Getenv("AGENT_CALL_AI_SNAPSHOT"),
|
|
CallTenantID: os.Getenv("AGENT_CALL_TENANT_ID"),
|
|
CallTenantKey: os.Getenv("AGENT_CALL_TENANT_KEY"),
|
|
CallTaskID: os.Getenv("AGENT_CALL_TASK_ID"),
|
|
CallTaskItemID: os.Getenv("AGENT_CALL_TASK_ITEM_ID"),
|
|
CallExecutionID: os.Getenv("AGENT_CALL_EXECUTION_ID"),
|
|
}
|
|
}
|
|
|
|
func (c Config) Validate(role string) error {
|
|
if c.Mode != "mock" && c.Mode != "mixed" && c.Mode != "real" {
|
|
return fmt.Errorf("unsupported mode %q", c.Mode)
|
|
}
|
|
if role == "dispatcher" && strings.TrimSpace(c.DBPath) == "" {
|
|
return errors.New("dispatcher DB path is required")
|
|
}
|
|
if role == "agent" && strings.TrimSpace(c.SpoolRoot) == "" {
|
|
return errors.New("agent spool root is required")
|
|
}
|
|
if role == "agent" && strings.TrimSpace(c.CallLogPath) != "" && strings.TrimSpace(c.CallLogPhoneKey) == "" {
|
|
return errors.New("AGENT_CALL_PHONE_LOG_KEY is required when AGENT_CALL_BUSINESS_LOG is set")
|
|
}
|
|
if role == "agent" && strings.TrimSpace(c.CallLogPhoneKey) != "" && len([]byte(c.CallLogPhoneKey)) < 16 {
|
|
return errors.New("AGENT_CALL_PHONE_LOG_KEY must contain at least 16 bytes")
|
|
}
|
|
if role == "agent" && strings.TrimSpace(c.GRPCListen) != "" {
|
|
for name, value := range map[string]string{"MTLS_CA_FILE": c.MTLSCAFile, "MTLS_CERT_FILE": c.MTLSCertFile, "MTLS_KEY_FILE": c.MTLSKeyFile} {
|
|
if strings.TrimSpace(value) == "" {
|
|
return fmt.Errorf("%s is required when AGENT_GRPC_LISTEN is enabled", name)
|
|
}
|
|
}
|
|
}
|
|
if role == "agent" && c.Mode == "real" && strings.TrimSpace(c.GRPCListen) == "" {
|
|
return errors.New("real agent mode requires AGENT_GRPC_LISTEN")
|
|
}
|
|
if role == "agent" && c.Mode == "real" && strings.TrimSpace(c.StaticArtifactPath) == "" {
|
|
return errors.New("real agent mode requires AGENT_STATIC_ARTIFACT")
|
|
}
|
|
if role == "agent" && strings.TrimSpace(c.MTLSPeerCertificateFingerprints) != "" {
|
|
if _, err := ParseCertificateFingerprints(c.MTLSPeerCertificateFingerprints); err != nil {
|
|
return fmt.Errorf("invalid MTLS_PEER_CERT_FINGERPRINTS: %w", err)
|
|
}
|
|
}
|
|
if role == "dispatcher" && strings.TrimSpace(c.ControlListen) != "" && strings.TrimSpace(c.ControlToken) == "" {
|
|
return errors.New("control HTTP requires DISPATCHER_CONTROL_TOKEN")
|
|
}
|
|
if role == "agent" && strings.TrimSpace(c.DispatcherGRPCEndpoint) != "" {
|
|
for name, value := range map[string]string{"MTLS_CA_FILE": c.MTLSCAFile, "MTLS_CERT_FILE": c.MTLSCertFile, "MTLS_KEY_FILE": c.MTLSKeyFile} {
|
|
if strings.TrimSpace(value) == "" {
|
|
return fmt.Errorf("%s is required when DISPATCHER_GRPC_ENDPOINT is enabled", name)
|
|
}
|
|
}
|
|
}
|
|
if role == "dispatcher" && strings.TrimSpace(c.DispatcherGRPCListen) != "" {
|
|
for name, value := range map[string]string{"MTLS_CA_FILE": c.MTLSCAFile, "MTLS_CERT_FILE": c.MTLSCertFile, "MTLS_KEY_FILE": c.MTLSKeyFile} {
|
|
if strings.TrimSpace(value) == "" {
|
|
return fmt.Errorf("%s is required when DISPATCHER_GRPC_LISTEN is enabled", name)
|
|
}
|
|
}
|
|
for name, value := range map[string]string{"DISPATCHER_OSS_REGION": c.OSSRegion, "DISPATCHER_OSS_ENDPOINT": c.OSSEndpoint, "DISPATCHER_OSS_BUCKET": c.OSSBucket, "DISPATCHER_OSS_ACCESS_KEY_ID": c.OSSAccessKeyID, "DISPATCHER_OSS_ACCESS_KEY_SECRET": c.OSSAccessKeySecret} {
|
|
if strings.TrimSpace(value) == "" {
|
|
return fmt.Errorf("%s is required when Dispatcher gRPC is enabled", name)
|
|
}
|
|
}
|
|
if strings.TrimSpace(c.DispatcherGRPCAllowedAgentIDs) == "" {
|
|
return errors.New("DISPATCHER_ALLOWED_AGENT_IDS is required when Dispatcher gRPC is enabled")
|
|
}
|
|
fingerprints, err := ParseCertificateFingerprints(c.MTLSPeerCertificateFingerprints)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid MTLS_PEER_CERT_FINGERPRINTS: %w", err)
|
|
}
|
|
if len(fingerprints) == 0 {
|
|
return errors.New("MTLS_PEER_CERT_FINGERPRINTS is required when Dispatcher gRPC is enabled")
|
|
}
|
|
}
|
|
if role == "dispatcher" && strings.TrimSpace(c.AgentEndpointsFile) != "" {
|
|
for name, value := range map[string]string{"MTLS_CA_FILE": c.MTLSCAFile, "MTLS_CERT_FILE": c.MTLSCertFile, "MTLS_KEY_FILE": c.MTLSKeyFile} {
|
|
if strings.TrimSpace(value) == "" {
|
|
return fmt.Errorf("%s is required when DISPATCHER_AGENT_ENDPOINTS_FILE is enabled", name)
|
|
}
|
|
}
|
|
}
|
|
if c.Mode == "real" && strings.TrimSpace(c.RabbitURL) == "" && role == "dispatcher" {
|
|
return errors.New("real dispatcher mode requires RABBITMQ_URL")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func envOr(key, fallback string) string {
|
|
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envOrSecret(valueKey, fileKey string) string {
|
|
if value := strings.TrimSpace(os.Getenv(valueKey)); value != "" {
|
|
return value
|
|
}
|
|
path := strings.TrimSpace(os.Getenv(fileKey))
|
|
if path == "" {
|
|
return ""
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(data))
|
|
}
|
|
|
|
func envInt(key string, fallback int) int {
|
|
value := strings.TrimSpace(os.Getenv(key))
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
parsed, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
return parsed
|
|
}
|