package client import ( "fmt" "sync" "github.com/rs/zerolog" "golang.org/x/crypto/ssh" ) // KamailioClient manages SSH connections to Kamailio servers for kamcmd type KamailioClient struct { servers []string sshUser string sshKey []byte logger zerolog.Logger } // NewKamailioClient creates a new Kamailio management client func NewKamailioClient(servers []string, sshUser string, sshKey []byte, logger zerolog.Logger) *KamailioClient { return &KamailioClient{ servers: servers, sshUser: sshUser, sshKey: sshKey, logger: logger.With().Str("client", "kamailio").Logger(), } } // ReloadDispatcher reloads the dispatcher module on all Kamailio servers func (c *KamailioClient) ReloadDispatcher() []ServerResult { return c.runOnAll("kamcmd dispatcher.reload") } // ReloadPermissions reloads address permissions on all Kamailio servers func (c *KamailioClient) ReloadPermissions() []ServerResult { return c.runOnAll("kamcmd permissions.addressReload") } // ReloadAll reloads dispatcher and permissions on all servers func (c *KamailioClient) ReloadAll() []ServerResult { results := make([]ServerResult, 0, len(c.servers)*2) r1 := c.ReloadDispatcher() r2 := c.ReloadPermissions() results = append(results, r1...) results = append(results, r2...) return results } // runOnAll executes a command on all Kamailio servers in parallel func (c *KamailioClient) runOnAll(command string) []ServerResult { var wg sync.WaitGroup results := make([]ServerResult, len(c.servers)) for i, srv := range c.servers { wg.Add(1) go func(idx int, host string) { defer wg.Done() results[idx] = c.execSSH(host, command) }(i, srv) } wg.Wait() return results } // execSSH connects via SSH and executes a command func (c *KamailioClient) execSSH(host, command string) ServerResult { result := ServerResult{Host: host} signer, err := ssh.ParsePrivateKey(c.sshKey) if err != nil { result.Error = fmt.Sprintf("failed to parse SSH key: %v", err) return result } config := &ssh.ClientConfig{ User: c.sshUser, Auth: []ssh.AuthMethod{ ssh.PublicKeys(signer), }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } client, err := ssh.Dial("tcp", host+":22", config) if err != nil { result.Error = fmt.Sprintf("SSH connection failed: %v", err) c.logger.Warn().Str("host", host).Err(err).Msg("Kamailio SSH connection failed") return result } defer client.Close() session, err := client.NewSession() if err != nil { result.Error = fmt.Sprintf("SSH session failed: %v", err) return result } defer session.Close() output, err := session.CombinedOutput(command) if err != nil { result.Error = fmt.Sprintf("command failed: %v, output: %s", err, string(output)) return result } result.Success = true result.Output = string(output) c.logger.Debug().Str("host", host).Str("command", command).Msg("Kamailio command executed") return result } // Health checks SSH connectivity to all Kamailio servers func (c *KamailioClient) Health() error { for _, srv := range c.servers { r := c.execSSH(srv, "kamcmd core.uptime") if !r.Success { return fmt.Errorf("kamailio %s: %s", srv, r.Error) } } return nil } // Servers returns the configured server list func (c *KamailioClient) Servers() []string { return c.servers }