// Package agent реализует агент сбора метрик для pulse-lets-go. // Агент работает на каждой PBX-ноде (FreeSWITCH или Asterisk), // собирает метрики и публикует их в NATS каждые 5 секунд. // // Поддерживаемые типы PBX: // - "freeswitch" — сбор через ESL (Event Socket Library) // - "asterisk" — сбор через AMI (Asterisk Manager Interface) // // Пример agent.json: // // { // "node_id": "uc06", // "type": "asterisk", // "nats_url": "nats://10.101.60.81:4222", // "sip_gateway": "sip:10.101.60.115:5060", // "interval_sec": 5, // "max_calls": 150, // "ami": { "host": "127.0.0.1", "port": 6154, "username": "ctt", "password": "cttpass" } // } package agent import ( "encoding/json" "fmt" "os" ) // Config — конфигурация агента (agent.json). type Config struct { NodeID string `json:"node_id"` // уникальный ID ноды (uc06, ses-sip) Type string `json:"type"` // "freeswitch" или "asterisk" NatsURL string `json:"nats_url"` // NATS URL NatsUser string `json:"nats_user,omitempty"` NatsPassword string `json:"nats_password,omitempty"` IntervalSec int `json:"interval_sec"` // интервал сбора (default: 5) MaxCalls int `json:"max_calls"` // ёмкость ноды (default: 250) SIPGateway string `json:"sip_gateway"` // SIP-адрес для auto-транка SIPGatewayAuto bool `json:"sip_gateway_auto"` // авто-определить из PBX FailureWindow int `json:"failure_window"` // окно для call_failure_rate (default: 1000) ESL ESLCfg `json:"esl,omitempty"` AMI AMICfg `json:"ami,omitempty"` } // ESLCfg — настройки подключения к FreeSWITCH ESL. type ESLCfg struct { Host string `json:"host"` Port int `json:"port"` Password string `json:"password"` } // AMICfg — настройки подключения к Asterisk AMI. type AMICfg struct { Host string `json:"host"` Port int `json:"port"` Username string `json:"username"` Password string `json:"password"` } // LoadConfig загружает конфигурацию агента из JSON-файла. func LoadConfig(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("чтение %s: %w", path, err) } var cfg Config if err := json.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("парсинг %s: %w", path, err) } // Defaults if cfg.IntervalSec <= 0 { cfg.IntervalSec = 5 } if cfg.MaxCalls <= 0 { cfg.MaxCalls = 250 } if cfg.FailureWindow <= 0 { cfg.FailureWindow = 1000 } if cfg.ESL.Port == 0 { cfg.ESL.Port = 8021 } if cfg.AMI.Port == 0 { cfg.AMI.Port = 5038 } return &cfg, nil } // Validate проверяет корректность конфигурации. func (c *Config) Validate() error { if c.NodeID == "" { return fmt.Errorf("node_id обязателен") } if c.NatsURL == "" { return fmt.Errorf("nats_url обязателен") } switch c.Type { case "freeswitch": if c.ESL.Host == "" { return fmt.Errorf("esl.host обязателен для type=freeswitch") } case "asterisk": if c.AMI.Host == "" { return fmt.Errorf("ami.host обязателен для type=asterisk") } default: return fmt.Errorf("type должен быть freeswitch или asterisk, получен: %s", c.Type) } return nil } // GetSIPGateway возвращает SIP-адрес для авто-транка. // Если SIPGatewayAuto = true — вернётся "" (collector определит сам). func (c *Config) GetSIPGateway(autoDetected string) string { if c.SIPGateway != "" { return c.SIPGateway } if c.SIPGatewayAuto { return autoDetected } return "" }