Added --call-log-only flag to archive log script

Added FastAGIServer code

git-svn-id: svn://192.168.202.10@4015 3d104415-ff17-0410-8863-d5cf3c621b8a
This commit is contained in:
mattf
2026-08-27 02:37:32 +00:00
parent 454faac050
commit 8e6482f0f6
14 changed files with 21121 additions and 4 deletions
+82 -1
View File
@@ -78,6 +78,7 @@
# 251024-1518 - Added --vicidial-dial-log-only flag
# 260111-2138 - Added --agent-log-only flag
# 260516-2149 - Added internal logging
# 260826-2206 - Added --call-log-only flag
#
$CALC_TEST=0;
@@ -132,6 +133,8 @@ if (length($ARGV[0])>1)
print " [--vicidial-dial-log-days=XX] = REQUIRED FOR --vicidial-dial-log-only, number of days to archive vicidial_dial_log table only past\n";
print " [--extended-log-only] = OPTIONAL, only archive vicidial_log_extended table then exit\n";
print " [--extended-log-days=XX] = REQUIRED FOR --extended-log-only, number of days to archive vicidial_log_extended table only past\n";
print " [--call-log-only] = OPTIONAL, only archive call_log table then exit\n";
print " [--call-log-days=XX] = REQUIRED FOR --call-log-only, number of days to archive call_log table only past\n";
print " [--agent-log-only] = OPTIONAL, only archive vicidial_agent_log table then exit\n";
print " [--api-archive-only] = OPTIONAL, only purge vicidial_api_log_archive table then exit\n";
print " [--api-archive-days=XX] = REQUIRED FOR --api-archive-only, number of days to purge vicidial_api_log_archive table only past\n";
@@ -354,6 +357,25 @@ if (length($ARGV[0])>1)
{print "\n----- EXTENDED LOG ARCHIVE ACTIVE, DAYS: $extendeddays -----\n\n";}
}
if ($args =~ /--call-log-only/i)
{
$call_log_only++;
if ($Q < 1)
{print "\n----- CALL LOG ARCHIVE ONLY $call_log_only -----\n\n";}
}
if ($args =~ /--call-log-days=/i)
{
$call_log_only++;
@data_in = split(/--call-log-days=/,$args);
$extendeddays = $data_in[1];
$extendeddays =~ s/ .*$//gi;
$extendeddays =~ s/\D//gi;
if ($extendeddays > 999999)
{$extendeddays=1825;}
if ($Q < 1)
{print "\n----- CALL LOG ARCHIVE ACTIVE, DAYS: $extendeddays -----\n\n";}
}
if ($args =~ /--api-archive-only/i)
{
$api_archive_only++;
@@ -536,7 +558,7 @@ if ($url_log_only > 0)
if ($URLsec < 10) {$URLsec = "0$URLsec";}
$URLdel_time = "$URLyear-$URLmon-$URLmday $URLhour:$URLmin:$URLsec";
}
if ( ($extended_log_only > 0) || ($vicidial_log_only > 0) || ($vicidial_dial_log_only > 0) )
if ( ($extended_log_only > 0) || ($call_log_only > 0) || ($vicidial_log_only > 0) || ($vicidial_dial_log_only > 0) )
{
$EXTENDEDdel_epoch = ($secX - (86400 * $extendeddays)); # X days ago
($EXTENDEDsec,$EXTENDEDmin,$EXTENDEDhour,$EXTENDEDmday,$EXTENDEDmon,$EXTENDEDyear,$EXTENDEDwday,$EXTENDEDyday,$EXTENDEDisdst) = localtime($EXTENDEDdel_epoch);
@@ -2058,6 +2080,65 @@ if (!$T)
########## END --extended-log-only flag processing ##########
########## BEGIN --call-log-only flag processing ##########
if ($call_log_only > 0)
{
##### call_log
$stmtA = "SELECT count(*) from call_log;";
$sthA = $dbhA->prepare($stmtA) or die "preparing: ",$dbhA->errstr;
$sthA->execute or die "executing: $stmtA ", $dbhA->errstr;
$sthArows=$sthA->rows;
if ($sthArows > 0)
{
@aryA = $sthA->fetchrow_array;
$call_log_count = $aryA[0];
}
$sthA->finish();
$stmtA = "SELECT count(*) from call_log_archive;";
$sthA = $dbhA->prepare($stmtA) or die "preparing: ",$dbhA->errstr;
$sthA->execute or die "executing: $stmtA ", $dbhA->errstr;
$sthArows=$sthA->rows;
if ($sthArows > 0)
{
@aryA = $sthA->fetchrow_array;
$call_log_archive_count = $aryA[0];
}
$sthA->finish();
if (!$Q) {print "\nProcessing call_log table... ($call_log_count|$call_log_archive_count)\n";}
$stmtA = "INSERT IGNORE INTO call_log_archive SELECT * from call_log;";
$sthA = $dbhA->prepare($stmtA) or die "preparing: ",$dbhA->errstr;
$sthA->execute or die "executing: $stmtA ", $dbhA->errstr;
$sthArows = $sthA->rows;
if (!$Q) {print "$sthArows rows inserted into call_log_archive table \n";}
$rv = $sthA->err();
if (!$rv)
{
if ($wipe_all > 0)
{$stmtA = "DELETE FROM call_log;";}
else
{$stmtA = "DELETE FROM call_log WHERE start_time < '$EXTENDEDdel_time';";}
$sthA = $dbhA->prepare($stmtA) or die "preparing: ",$dbhA->errstr;
$sthA->execute or die "executing: $stmtA ", $dbhA->errstr;
$sthArows = $sthA->rows;
if (!$Q) {print "$sthArows rows deleted from call_log table \n";}
$stmtA = "optimize table call_log;";
$sthA = $dbhA->prepare($stmtA) or die "preparing: ",$dbhA->errstr;
$sthA->execute or die "executing: $stmtA ", $dbhA->errstr;
}
if (!$Q) {print "\nProcessing call_log table finished: ($sthArows rows deleted) \n";}
exit;
}
########## END --call-log-only flag processing ##########
########## BEGIN --vicidial-log-only flag processing ##########
if ($vicidial_log_only > 0)
{
+35 -2
View File
@@ -185,9 +185,10 @@
# 260515-2121 - Added truncating of vicidial_internal_log entries after 7 days, updating of some records
# 260527-0142 - Added dialplan filtering
# 260605-1002 - Added end-of-day log processing log entry for the vicidial_internal_log
# 260826-1814 - Added FastAGIServer code
#
$build = '260605-1002';
$build = '260826-1814';
$DB=0; # Debug flag
$teodDB=0; # flag to log Timeclock End of Day processes to log file
@@ -598,6 +599,7 @@ else
$AST_VDremote_agents=0;
$AST_VDadapt=0;
$FastAGI_log=0;
$FastAGIServer=0;
$email_inbound=0;
$AST_VDauto_dial_FILL=0;
$ip_relay=0;
@@ -619,6 +621,7 @@ else
$runningsip_logger=0;
$runningconf_updater=0;
$runningcrash_test=0;
$runningFastAGIServer=0;
$AST_conf_3way=0;
$AST_rec_monitor=0;
@@ -682,6 +685,11 @@ else
$conf_updater=1;
if ($DB) {print "Check to see if conference updater should run\n";}
}
if ($VARactive_keepalives =~ /F/)
{
$FastAGIServer=1;
if ($DB) {print "FastAGIServer set to keepalive\n";}
}
if ($cu3way > 0)
{
$AST_conf_3way=1;
@@ -800,6 +808,11 @@ else
$runningcrash_test++;
if ($DB) {print "AST_table_status RUNNING: |$psline[1]|\n";}
}
if ($psoutput[$i] =~ /$REGhome\/FastAGIServer\/FastAGIServer\.pl/)
{
$runningFastAGIServer++;
if ($DB) {print "FastAGIServer Running: |$psoutput[$i]|\n";}
}
$i++;
}
@@ -813,7 +826,14 @@ else
if($DB){print STDERR "$affected_rows|\n";}
}
# if $runningFastAGIServer running, update internal process log
if ( ($runningFastAGIServer > 0) && ($reset_test =~ /0$|5$/) )
{
$stmtA = "UPDATE vicidial_internal_log SET up_time=NOW(), action='running', stage=CONCAT((UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(db_time)),' seconds runtime') WHERE process='FastAGIServer.pl' and server_ip='$server_ip' order by db_time desc limit 1;";
if($DB){print STDERR "|$stmtA|";}
my $affected_rows = $dbhA->do($stmtA);
if($DB){print STDERR "$affected_rows|\n";}
}
##### Second, IF MORE THAN ONE LISTEN INSTANCE IS RUNNING, KILL THE SECOND ONE #####
@@ -889,6 +909,7 @@ else
( ($AST_VDremote_agents > 0) && ($runningAST_VDremote_agents < 1) ) ||
( ($AST_VDadapt > 0) && ($runningAST_VDadapt < 1) ) ||
( ($FastAGI_log > 0) && ($runningFastAGI_log < 1) ) ||
( ($FastAGIServer > 0) && ($runningFastAGIServer < 1) ) ||
( ($AST_VDauto_dial_FILL > 0) && ($runningAST_VDauto_dial_FILL < 1) ) ||
( ($ip_relay > 0) && ($runningip_relay < 1) ) ||
( ($AST_conf_3way > 0) && ($runningAST_conf_3way < 1) ) ||
@@ -998,6 +1019,11 @@ else
$runningcrash_test++;
if ($DB) {print "AST_table_status RUNNING: |$psline[1]|\n";}
}
if ($psoutput2[$i] =~ /$REGhome\/FastAGIServer\/FastAGIServer\.pl/)
{
$runningFastAGIServer++;
if ($DB) {print "FastAGIServer Running: |$psoutput2[$i]|\n";}
}
$i++;
}
@@ -1167,6 +1193,13 @@ else
}
}
}
if ( ($FastAGIServer > 0) && ($runningFastAGIServer < 1) )
{
# FastAGIServer does not run in a Screen session
if ($DB) {print "starting FastAGIServer...\n";}
`/usr/bin/screen -d -m -S FastAGIServer $PATHhome/FastAGIServer/FastAGIServer.pl --nofork --loglevel 4`;
# `/usr/bin/perl $PATHhome/FastAGIServer/FastAGIServer.pl &`;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
# Compiler settings
CC = gcc
CFLAGS = -O2 -Wall -Wextra -static
# Installation directory
AGI_BIN_DIR = /var/lib/asterisk/agi-bin
# Target binaries
TARGETS = agi-VDAD_ALL_outbound.agi agi-VDAD_ALL_inbound.agi VD_amd.agi
# Default rule (runs when you type 'make')
all: $(TARGETS)
# Compilation rules
agi-VDAD_ALL_outbound.agi: VDAD_ALL_outbound_redir.c
$(CC) $(CFLAGS) -o $@ $<
agi-VDAD_ALL_inbound.agi: VDAD_ALL_inbound_redir.c
$(CC) $(CFLAGS) -o $@ $<
VD_amd.agi: VD_amd_redir.c
$(CC) $(CFLAGS) -o $@ $<
# Installation rule (runs when you type 'sudo make install')
install: all
mkdir -p $(AGI_BIN_DIR)
cp $(TARGETS) $(AGI_BIN_DIR)/
chmod +x $(TARGETS:%=$(AGI_BIN_DIR)/%)
@echo "AGI scripts installed successfully to $(AGI_BIN_DIR)"
# Cleanup rule (runs when you type 'make clean')
clean:
rm -f $(TARGETS)
.PHONY: all install clean
@@ -0,0 +1,81 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DELIMITER "-----"
#define BUFFER_SIZE 4096
#define FAGI_CMD "EXEC AGI agi://127.0.0.1:4573/VDAD_ALL_inbound"
#define MOVED_MSG "ATTENTION! agi-VDAD_ALL_inbound.agi has replaced by a FastAGI at agi://127.0.0.1:4573/VDAD_ALL_inbound. You need to update your dialplan. Redirecting to the FastAGI.\n"
// Helper function to send commands to Asterisk and read the response
void send_agi_command(const char *cmd) {
char response[BUFFER_SIZE];
printf("%s\n", cmd);
fflush(stdout);
// Read response to keep communication synchronized
if (fgets(response, sizeof(response), stdin) != NULL) {
response[strcspn(response, "\r\n")] = 0;
}
}
int main(int argc, char *argv[]) {
char env_buffer[BUFFER_SIZE];
// Read the environment block from Asterisk
while (fgets(env_buffer, sizeof(env_buffer), stdin) != NULL) {
if (strcmp(env_buffer, "\n") == 0 || strcmp(env_buffer, "\r\n") == 0) {
break;
}
}
// Try to let an Admin know of the changes
fprintf(stderr, MOVED_MSG);
// 2. Parse the argument string if it exists
if (argc > 1 && argv != NULL && argv[1] != NULL) {
char var_buf[BUFFER_SIZE] = "";
char cmd_buf[BUFFER_SIZE];
char *src = argv[1]; // Safely assigned now that we've checked argv[1] != NULL
char *next_delim;
size_t delim_len = strlen(DELIMITER);
size_t var_len = 0;
int need_comma = 0;
// Find each occurrence of "-----"
while ((next_delim = strstr(src, DELIMITER)) != NULL) {
size_t segment_len = next_delim - src;
if (segment_len > 0) {
int written = snprintf(var_buf + var_len, sizeof(var_buf) - var_len,
"%s%.*s", need_comma ? "," : "", (int)segment_len, src);
if (written < 0 || (size_t)written >= sizeof(var_buf) - var_len) break;
var_len += written;
need_comma = 1;
}
src = next_delim + delim_len;
}
// Append the final remaining part of the string if it contains data
if (strlen(src) > 0 && var_len < sizeof(var_buf)) {
snprintf(var_buf + var_len, sizeof(var_buf) - var_len,
"%s%s", need_comma ? "," : "", src);
}
// ONLY send to Asterisk if we actually captured an argument string
if (strlen(var_buf) > 0) {
int cmd_written = snprintf(cmd_buf, sizeof(cmd_buf), "SET VARIABLE AGIARGS \"%s\"", var_buf);
if (cmd_written >= 0 && (size_t)cmd_written < sizeof(cmd_buf)) {
send_agi_command(cmd_buf);
}
}
}
// Hand off the call to the FastAGI server
send_agi_command(FAGI_CMD);
return 0;
}
@@ -0,0 +1,81 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DELIMITER "-----"
#define BUFFER_SIZE 4096
#define FAGI_CMD "EXEC AGI agi://127.0.0.1:4573/VDAD_ALL_outbound"
#define MOVED_MSG "ATTENTION! agi-VDAD_ALL_outbound.agi has replaced by a FastAGI at agi://127.0.0.1:4573/VDAD_ALL_outbound. You need to update your dialplan. Redirecting to the FastAGI.\n"
// Helper function to send commands to Asterisk and read the response
void send_agi_command(const char *cmd) {
char response[BUFFER_SIZE];
printf("%s\n", cmd);
fflush(stdout);
// Read response to keep communication synchronized
if (fgets(response, sizeof(response), stdin) != NULL) {
response[strcspn(response, "\r\n")] = 0;
}
}
int main(int argc, char *argv[]) {
char env_buffer[BUFFER_SIZE];
// Read the environment block from Asterisk
while (fgets(env_buffer, sizeof(env_buffer), stdin) != NULL) {
if (strcmp(env_buffer, "\n") == 0 || strcmp(env_buffer, "\r\n") == 0) {
break;
}
}
// Try to let an Admin know of the changes
fprintf(stderr, MOVED_MSG);
// 2. Parse the argument string if it exists
if (argc > 1 && argv != NULL && argv[1] != NULL) {
char var_buf[BUFFER_SIZE] = "";
char cmd_buf[BUFFER_SIZE];
char *src = argv[1]; // Safely assigned now that we've checked argv[1] != NULL
char *next_delim;
size_t delim_len = strlen(DELIMITER);
size_t var_len = 0;
int need_comma = 0;
// Find each occurrence of "-----"
while ((next_delim = strstr(src, DELIMITER)) != NULL) {
size_t segment_len = next_delim - src;
if (segment_len > 0) {
int written = snprintf(var_buf + var_len, sizeof(var_buf) - var_len,
"%s%.*s", need_comma ? "," : "", (int)segment_len, src);
if (written < 0 || (size_t)written >= sizeof(var_buf) - var_len) break;
var_len += written;
need_comma = 1;
}
src = next_delim + delim_len;
}
// Append the final remaining part of the string if it contains data
if (strlen(src) > 0 && var_len < sizeof(var_buf)) {
snprintf(var_buf + var_len, sizeof(var_buf) - var_len,
"%s%s", need_comma ? "," : "", src);
}
// ONLY send to Asterisk if we actually captured an argument string
if (strlen(var_buf) > 0) {
int cmd_written = snprintf(cmd_buf, sizeof(cmd_buf), "SET VARIABLE AGIARGS \"%s\"", var_buf);
if (cmd_written >= 0 && (size_t)cmd_written < sizeof(cmd_buf)) {
send_agi_command(cmd_buf);
}
}
}
// Hand off the call to the FastAGI server
send_agi_command(FAGI_CMD);
return 0;
}
Binary file not shown.
@@ -0,0 +1,81 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DELIMITER "-----"
#define BUFFER_SIZE 4096
#define FAGI_CMD "EXEC AGI agi://127.0.0.1:4573/VD_amd"
#define MOVED_MSG "ATTENTION! VD_amd.agi has replaced by a FastAGI at agi://127.0.0.1:4573/VD_amd. You need to update your dialplan. Redirecting to the FastAGI.\n"
// Helper function to send commands to Asterisk and read the response
void send_agi_command(const char *cmd) {
char response[BUFFER_SIZE];
printf("%s\n", cmd);
fflush(stdout);
// Read response to keep communication synchronized
if (fgets(response, sizeof(response), stdin) != NULL) {
response[strcspn(response, "\r\n")] = 0;
}
}
int main(int argc, char *argv[]) {
char env_buffer[BUFFER_SIZE];
// Read the environment block from Asterisk
while (fgets(env_buffer, sizeof(env_buffer), stdin) != NULL) {
if (strcmp(env_buffer, "\n") == 0 || strcmp(env_buffer, "\r\n") == 0) {
break;
}
}
// Try to let an Admin know of the changes
fprintf(stderr, MOVED_MSG);
// 2. Parse the argument string if it exists
if (argc > 1 && argv != NULL && argv[1] != NULL) {
char var_buf[BUFFER_SIZE] = "";
char cmd_buf[BUFFER_SIZE];
char *src = argv[1]; // Safely assigned now that we've checked argv[1] != NULL
char *next_delim;
size_t delim_len = strlen(DELIMITER);
size_t var_len = 0;
int need_comma = 0;
// Find each occurrence of "-----"
while ((next_delim = strstr(src, DELIMITER)) != NULL) {
size_t segment_len = next_delim - src;
if (segment_len > 0) {
int written = snprintf(var_buf + var_len, sizeof(var_buf) - var_len,
"%s%.*s", need_comma ? "," : "", (int)segment_len, src);
if (written < 0 || (size_t)written >= sizeof(var_buf) - var_len) break;
var_len += written;
need_comma = 1;
}
src = next_delim + delim_len;
}
// Append the final remaining part of the string if it contains data
if (strlen(src) > 0 && var_len < sizeof(var_buf)) {
snprintf(var_buf + var_len, sizeof(var_buf) - var_len,
"%s%s", need_comma ? "," : "", src);
}
// ONLY send to Asterisk if we actually captured an argument string
if (strlen(var_buf) > 0) {
int cmd_written = snprintf(cmd_buf, sizeof(cmd_buf), "SET VARIABLE AGIARGS \"%s\"", var_buf);
if (cmd_written >= 0 && (size_t)cmd_written < sizeof(cmd_buf)) {
send_agi_command(cmd_buf);
}
}
}
// Hand off the call to the FastAGI server
send_agi_command(FAGI_CMD);
return 0;
}
+340
View File
@@ -0,0 +1,340 @@
#!/usr/bin/env perl
#
# FastAGIServer.pl
#
# Runs Vicidial AGI Modules as a FastAGI server
#
# Copyright (C) 2026 Matt Florell, Mike Cargile <vicidial@gmail.com> LICENSE: AGPLv2
#
# ; VICIDIAL_auto_dialer transfer script Load Balanced:
# exten => 8380,1,AGI(agi://127.0.0.1:4577/call_log)
# exten => 8380,n,Set(AGIARGS=NORMAL,LB,${CONNECTEDLINE(name)})
# exten => 8380,n,AGI(agi://127.0.0.1:4573/VDAD_ALL_outbound)
# exten => 8380,n,Hangup()
#
# changes:
# 260826-1814 - Initial Build
#
use strict;
use warnings;
# tell perl where our modules are
use FindBin;
use lib "$FindBin::Bin";
# load standard modules
use base qw(Net::Server::PreFork);
use Asterisk::AGI;
use JSON::PP;
use DBI;
use Time::HiRes ('gettimeofday');
use POSIX qw(strftime);
use Cwd qw(abs_path);
use Getopt::Long qw(GetOptions);
BEGIN { $0 = abs_path($0); }
my $nofork = 0;
my $loglevel = 3;
my $logfile = '/var/log/astguiclient/FastAGI-server.log';
# Bind options to variables
GetOptions(
'nofork' => \$nofork,
'loglevel=i' => \$loglevel,
'help|h|?' => \&show_help,
) or die "Error in command line arguments\n";
my $agc_config_path = '/etc/astguiclient.conf';
# load /etc/astguiclient.conf
my %agc_config = parse_config( $agc_config_path );
# connect to vicidial DB
my $db_connect_str = "DBI:mysql:$agc_config{'VARDB_database'}:$agc_config{'VARDB_server'}:$agc_config{'VARDB_port'}";
my $dbh = DBI->connect(
$db_connect_str,
$agc_config{'VARDB_user'},
$agc_config{'VARDB_pass'}
) or die "Couldn't connect to database: " . DBI->errstr;
# get the FAST_AGI_SERVER_CONFIG settings container
my $stmt = "SELECT container_entry FROM vicidial_settings_containers WHERE container_id = 'FAST_AGI_SERVER_CONFIG';";
my $sth = $dbh->prepare($stmt) or die "preparing: ",$dbh->errstr;
$sth->execute or die "executing: $stmt ", $dbh->errstr;
if ( $sth->rows == 0 ) { die "FAST_AGI_SERVER_CONFIG Settings Container Not Found!!"; }
my @fagi_settiongs_ary = $sth->fetchrow_array;
my $fagi_config_json = $fagi_settiongs_ary[0];
$sth->finish();
# log to the internal_process_log
$stmt = "INSERT INTO vicidial_internal_log SET db_time=NOW(), up_time=NOW(), action='launched', stage='Run seconds: 0', process='FastAGIServer.pl', server_ip='$agc_config{'VARserver_ip'}';";
$sth = $dbh->prepare($stmt) or die "preparing: ",$dbh->errstr;
$sth->execute or die "executing: $stmt ", $dbh->errstr;
$sth->finish();
$dbh->disconnect();
my $fagi_config = decode_json($fagi_config_json);
if ( $loglevel > 4 ) {
$loglevel = 4;
}
if ( $loglevel < 0 ) {
$loglevel = 0;
}
# Set the server options
my $server_opts = $fagi_config->{server} || {};
$server_opts->{host} //= '0.0.0.0';
$server_opts->{port} //= 4573;
$server_opts->{min_servers} //= 5;
$server_opts->{max_servers} //= 20;
$server_opts->{max_requests} //= 1000;
$server_opts->{log_level} = $loglevel;
if ( $nofork == 1 ) {
print( "No Fork active.\n");
$server_opts->{background} = 0;
$server_opts->{log_file} = undef;
} else {
$server_opts->{background} = 1;
$server_opts->{log_file} //= $logfile;
}
my $server = __PACKAGE__->new(%$server_opts);
$server->{agc_config} = \%agc_config;
$server->{fagi_config} = $fagi_config;
my ($epoch, $usec) = gettimeofday();
$server->{conf_epoch} = $epoch;
$server->{conf_usec} = $usec;
$server->run();
sub show_help {
print << "EOF";
Usage: $0 [options]
Options:
--help Show this help message and exit.
--nofork Do not fork a background process and show all log data in the CLI.
--loglevel On a scale of 0 to 4 how verbose do you want the logs. (Defaults to 3)
Examples:
$0 --nofork --loglevel 4
$0 --help
$0
EOF
exit(0); # Exit cleanly since help was explicitly requested
}
# Overriding the default log writer hook cleanly
sub write_to_log_hook {
my ($self, $level, $msg) = @_;
# 1. Strip trailing newlines from the incoming message
$msg =~ s/\s+$//;
# 2. Build your custom timestamp format
my ($log_epoch,$log_usec) = gettimeofday();
my $log_msec = $log_usec / 1000;
my $timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime($log_epoch)) . sprintf(".%03d", $log_msec);
# 3. Create your custom log format string
my $formatted_line = sprintf("[%s] [PID: %d] [LVL: %s] %s\n", $timestamp, $$, $level, $msg);
# 4. Write it out to the configured file handle
if (my $fp = $self->{server}->{log_fp}) {
print $fp $formatted_line;
} else {
print STDERR $formatted_line;
}
}
sub idle_loop_hook {
my ($self, $readable_fh_ref) = @_;
$self->SUPER::idle_loop_hook($readable_fh_ref);
my ($idle_epoch,$idle_usec) = gettimeofday();
if ($idle_epoch - 30 > $self->{conf_epoch} ) {
my $old_fagi_config = $server->{fagi_config};
# connect to vicidial DB
my $db_connect_str = "DBI:mysql:$self->{agc_config}->{'VARDB_database'}:$self->{agc_config}->{'VARDB_server'}:$self->{agc_config}->{'VARDB_port'}";
my $dbh = DBI->connect(
$db_connect_str,
$self->{agc_config}->{'VARDB_user'},
$self->{agc_config}->{'VARDB_pass'}
) or die "Couldn't connect to database: " . DBI->errstr;
# get the FAST_AGI_SERVER_CONFIG settings container
my $stmt = "SELECT container_entry FROM vicidial_settings_containers WHERE container_id = 'FAST_AGI_SERVER_CONFIG';";
my $sth = $dbh->prepare($stmt) or die "preparing: ",$dbh->errstr;
$sth->execute or die "executing: $stmt ", $dbh->errstr;
if ( $sth->rows == 0 ) { die "FAST_AGI_SERVER_CONFIG Settings Container Not Found!!"; }
my @fagi_settiongs_ary = $sth->fetchrow_array;
my $fagi_config_json = $fagi_settiongs_ary[0];
$sth->finish();
$dbh->disconnect();
my $fagi_config = eval { decode_json($fagi_config_json); };
if ($@) {
$self->log(3, "FAST_AGI_SERVER_CONFIG settings container contains invalid JSON. Keeping old config");
} else {
my ($epoch, $usec) = gettimeofday();
$self->{conf_epoch} = $epoch;
$self->{conf_usec} = $usec;
# Settings are good. update the server
my $server_opts = $fagi_config->{server} || {};
if ($self->_config_has_changed($self->{fagi_config}->{server}, $server_opts)) {
$self->{fagi_config} = $fagi_config;
$self->log(3, "Server config has changed. Reloading." );
for my $key ( keys $server_opts->%* ) {
# skip keys that cannot be changed
next if $key =~ /^(port|host|proto|user|group|chroot|background)$/;
$self->log( 3, "\t" . $key . "=" . $server_opts->{$key} );
$self->{server}->{$key} = $server_opts->{$key};
}
$self->{server}->{count_servers} = 1;
}
}
}
}
sub _config_has_changed {
my ($self, $old_hash_ref, $new_hash_ref) = @_;
# Check if any keys in the new hash are different or completely missing from old
for my $key ( keys $new_hash_ref->%* ) {
if (!exists $old_hash_ref->{$key} || $old_hash_ref->{$key} ne $new_hash_ref->{$key}) {
return 1; # Change found!
}
}
# Check if any old keys were deleted in the new database update
for my $key ( keys $old_hash_ref->%* ) {
if (!exists $new_hash_ref->{$key}) {
return 1; # Change found!
}
}
return 0; # The two hash references are identical
}
# Net::Server Initialization Function Hook
sub post_configure_hook {
my $self = shift;
# Save the route mapping globally inside the runtime server object
$self->{_agi_routes} = $fagi_config->{routes} || {};
$self->log(3, "Loading and verifying routing configurations...");
# Dynamically load the configured call handler modules
foreach my $path (keys %{ $self->{_agi_routes} }) {
my $package = $self->{_agi_routes}->{$path};
my $file = $package;
$file =~ s/::/\//g;
$file .= ".pm";
$self->log(3, "Dynamically loading module: $package for route $path");
eval { require $file; };
if ($@) {
die "Fatal error loading module $package for route $path: $@\n";
}
}
}
# Request Handler
sub process_request {
my $self = shift;
my $AGI = Asterisk::AGI->new(
stdin => \*STDIN,
stdout => \*STDOUT
);
$AGI->{debug} = 0;
my %agc_conf = parse_config( $agc_config_path );
# Parse and extract ALL environment variables into a hash
my %input = $AGI->ReadParse();
my $request_uri = $input{'request'} || '';
my $path = '';
if ($request_uri =~ m{agi://[^/]+(/[^?#\s]*)}) {
$path = $1;
}
my $handler_class = $self->{_agi_routes}->{$path};
if ($handler_class) {
my $handler_conf = $fagi_config->{handler_settings}->{$handler_class} || {};
$self->log(3, "Routing $path to handler $handler_class");
eval {
my $worker = $handler_class->new($self, $AGI, $handler_conf, \%agc_conf, \%input );
$worker->run();
};
if ($@) {
$self->log(1, "Runtime crash in handler $handler_class: $@");
$AGI->verbose("AGI internal server execution error", 1);
$AGI->hangup();
}
}
else {
$self->log(2, "No configuration route found for path: $path");
$AGI->verbose("Unknown route specified: $path", 3);
$AGI->hangup();
}
}
# Config Parser
sub parse_config {
my ($filepath) = @_;
my %config;
open(my $fh, '<', $filepath) or die "Cannot open $filepath: $!";
while (my $line = <$fh>) {
chomp($line);
$line =~ s/#.*//; # Remove comments
$line =~ s/^\s+|\s+$//g; # Trim whitespace
next if $line eq ''; # Skip empty lines
# Match key => value or key = value
if ($line =~ /^([^=\s>]+)\s*(?:=>|=)\s*(.+)$/) {
my $key = $1;
my $val = $2;
$val =~ s/^['"]|['"]$//g; # Strip quotes
$config{$key} = $val;
}
}
close($fh);
return %config;
}
+5 -1
View File
@@ -2,7 +2,7 @@
# install.pl version 2.14
#
# Copyright (C) 2025 Matt Florell <vicidial@gmail.com> LICENSE: AGPLv2
# Copyright (C) 2026 Matt Florell <vicidial@gmail.com> LICENSE: AGPLv2
#
# CHANGES
@@ -54,6 +54,7 @@
# 240704-0830 - Added coldstorage DB entries
# 250103-1017 - Added ConfBridge conf files updates
# 250823-1411 - Added stereo recording directories
# 260826-1919 - Added 'F' keepalive option, for FastAGIServer
#
############################################
@@ -236,6 +237,7 @@ if (length($ARGV[0])>1)
print " 9 - Timeclock auto-logout\n";
print " C - ConfBridge process, (see the ConfBridge documentation for more info)\n";
print " E - Email processor, (If multi-server system, this must only be on one server)\n";
print " F - FastAGIServer, for faster routing of calls to agents\n";
print " S - SIP Logger (Patched Asterisk 13 or higher required)\n";
print " [--asterisk_version] = set the asterisk version you want to install for\n";
print " [--copy_sample_conf_files] = copies the sample conf files to /etc/asterisk/\n";
@@ -2070,6 +2072,7 @@ else
print " 9 - Timeclock auto logout\n";
print " C - ConfBridge process, (see the ConfBridge documentation for more info)\n";
print " E - Email processor, (If multi-server system, this must only be on one server)\n";
print " F - FastAGIServer, for faster routing of calls to agents\n";
print " S - SIP Logger (Patched Asterisk 13 or higher required)\n";
print "Enter active keepalives or press enter for default: [$VARactive_keepalives] ";
$PROMPTactive_keepalives = <STDIN>;
@@ -2711,6 +2714,7 @@ print conf "# 8 - ip_relay (used for blind agent monitoring)\n";
print conf "# 9 - Timeclock auto logout, (If multi-server system, this must only be on one server)\n";
print conf "# C - ConfBridge process, (see the ConfBridge documentation for more info)\n";
print conf "# E - Email processor, (If multi-server system, this must only be on one server)\n";
print conf "# F - FastAGIServer, for faster routing of calls to agents\n";
print conf "# S - SIP Logger (Patched Asterisk 13 or higher required)\n";
print conf "VARactive_keepalives => $VARactive_keepalives\n";
print conf "\n";