It's exists!
Only A, but normally it's enough.
https://github.com/xadhoom/mod_bcg729
Point: don't forget to modify Makefile with correct FreeSwitch paths.
Usually
FS_INCLUDES=/usr/local/freeswitch/include
FS_MODULES=/usr/local/freeswitch/mod
Little update: can't get it work on 1.6 in VmWare environment.
Update 2:
Due to forum a bit update for FusionPBX:
apt-get install build-essential automake gcc git libtool pkg-config libfreeswitch-dev
FS_INCLUDES=/usr/include/freeswitch
FS_MODULES=/usr/lib/freeswitch/mod
Saturday, November 21, 2015
Tuesday, October 27, 2015
Handling Austrian (and maybe German and Swiss) DID numbers with extensions at A2Billing
In Austria still using classic ISDN dialing rules. So, if your company have number 431234567, and your internal number is 101 you can dial 431234567101 from any phone and get to extension 101 without any crappy IVR's.
Causes several problems with dialing rules, but make life much more comfortable.
So, the problem is how to handle those DID's in A2Billing, cause this soft uses only exact matches.
Solution - use MySQL patterns in DID fields directly.
So, now DID in A2Billing will looks like
431234567%
yes with "%" sign at the end.
Also, for VoIP destination was made a quick modification, that allows to use variables in dialstring.
So, destination will be like
SIP/<YourSipUser>/%did%.
At %did% A2billing will automatically place original DID(extension)
Patch is below
diff -Naur AGI/a2billing.php agi-bin/a2billing.php
--- AGI/a2billing.php 2015-02-27 15:42:38.000000000 +0100
+++ agi-bin/a2billing.php 2015-10-27 13:17:49.000000000 +0100
@@ -643,7 +643,8 @@
" aleg_carrier_initblock_offp, aleg_carrier_increment_offp, aleg_retail_initblock_offp, aleg_retail_increment_offp ".
" FROM cc_did, cc_did_destination, cc_card ".
" WHERE id_cc_did=cc_did.id AND cc_card.status=1 AND cc_card.id=id_cc_card AND cc_did_destination.activated=1 ".
- " AND cc_did.activated=1 AND did='$mydnid' ".
+ //" AND cc_did.activated=1 AND did='$mydnid' ".
+ " AND cc_did.activated=1 AND '$mydnid' LIKE did ". // Added to Austrian DID handling
" AND cc_did.startingdate<= CURRENT_TIMESTAMP AND (cc_did.expirationdate > CURRENT_TIMESTAMP OR cc_did.expirationdate IS NULL ".
" AND cc_did_destination.validated=1";
if ($A2B->config["database"]['dbtype'] != "postgres") {
diff -Naur AGI/lib/Class.A2Billing.php agi-bin/lib/Class.A2Billing.php
--- AGI/lib/Class.A2Billing.php 2015-02-27 15:42:38.000000000 +0100
+++ agi-bin/lib/Class.A2Billing.php 2015-10-27 11:26:36.000000000 +0100
@@ -1324,6 +1324,8 @@
$dialparams = str_replace("%timeout%", min($time2call * 1000, $max_long), $dialparams);
$dialparams = str_replace("%timeoutsec%", min($time2call, $max_long), $dialparams);
$dialstr = $inst_listdestination[4] . $dialparams;
+ // Added support for %did% variable
+ $dialstr = str_replace("%did%", $this->orig_ext, $dialstr);
$this->debug(DEBUG, $agi, __FILE__, __LINE__, "[A2Billing] DID call friend: Dialing '$dialstr' Friend.\n");
Causes several problems with dialing rules, but make life much more comfortable.
So, the problem is how to handle those DID's in A2Billing, cause this soft uses only exact matches.
Solution - use MySQL patterns in DID fields directly.
So, now DID in A2Billing will looks like
431234567%
yes with "%" sign at the end.
Also, for VoIP destination was made a quick modification, that allows to use variables in dialstring.
So, destination will be like
SIP/<YourSipUser>/%did%.
At %did% A2billing will automatically place original DID(extension)
Patch is below
diff -Naur AGI/a2billing.php agi-bin/a2billing.php
--- AGI/a2billing.php 2015-02-27 15:42:38.000000000 +0100
+++ agi-bin/a2billing.php 2015-10-27 13:17:49.000000000 +0100
@@ -643,7 +643,8 @@
" aleg_carrier_initblock_offp, aleg_carrier_increment_offp, aleg_retail_initblock_offp, aleg_retail_increment_offp ".
" FROM cc_did, cc_did_destination, cc_card ".
" WHERE id_cc_did=cc_did.id AND cc_card.status=1 AND cc_card.id=id_cc_card AND cc_did_destination.activated=1 ".
- " AND cc_did.activated=1 AND did='$mydnid' ".
+ //" AND cc_did.activated=1 AND did='$mydnid' ".
+ " AND cc_did.activated=1 AND '$mydnid' LIKE did ". // Added to Austrian DID handling
" AND cc_did.startingdate<= CURRENT_TIMESTAMP AND (cc_did.expirationdate > CURRENT_TIMESTAMP OR cc_did.expirationdate IS NULL ".
" AND cc_did_destination.validated=1";
if ($A2B->config["database"]['dbtype'] != "postgres") {
diff -Naur AGI/lib/Class.A2Billing.php agi-bin/lib/Class.A2Billing.php
--- AGI/lib/Class.A2Billing.php 2015-02-27 15:42:38.000000000 +0100
+++ agi-bin/lib/Class.A2Billing.php 2015-10-27 11:26:36.000000000 +0100
@@ -1324,6 +1324,8 @@
$dialparams = str_replace("%timeout%", min($time2call * 1000, $max_long), $dialparams);
$dialparams = str_replace("%timeoutsec%", min($time2call, $max_long), $dialparams);
$dialstr = $inst_listdestination[4] . $dialparams;
+ // Added support for %did% variable
+ $dialstr = str_replace("%did%", $this->orig_ext, $dialstr);
$this->debug(DEBUG, $agi, __FILE__, __LINE__, "[A2Billing] DID call friend: Dialing '$dialstr' Friend.\n");
Saturday, July 4, 2015
A little update....
From now, all articles in this blog are to be written in English (or pretending to be :) ).
This is done, cause English is a modern Latin. You may not like it, but it is.
With this change I want to share my notes not only with Russian-speaking audience.
And to be honest, most info in my blog was taken from english-written resources.
This is done, cause English is a modern Latin. You may not like it, but it is.
With this change I want to share my notes not only with Russian-speaking audience.
And to be honest, most info in my blog was taken from english-written resources.
Wednesday, May 27, 2015
Veeam Backup и PowerShell. Бэкапы по расписанию.
Чудный продукт Veeam Backup and Replication, начиная с версии 8 (Patch 2) открыл небольшую лазейку для совершения бэкапов по расписанию, точнее, VeeamZIP.
Точнее - в бесплатной версии заработал cmdlet Start-VBRZip.
Итак, чем это грозит и как это можно использовать. А можно просто напилить скрипт, который будет делать полные бэкапы виртуальных машин. Ну и запихнуть его в планировщик.
Естественно, надо установить Veeam PowerShellSDK. Ну и в самом Veeam настроить все сервера.
Собственно, сам скрипт:
Точнее - в бесплатной версии заработал cmdlet Start-VBRZip.
Итак, чем это грозит и как это можно использовать. А можно просто напилить скрипт, который будет делать полные бэкапы виртуальных машин. Ну и запихнуть его в планировщик.
Естественно, надо установить Veeam PowerShellSDK. Ну и в самом Veeam настроить все сервера.
Собственно, сам скрипт:
param (
$limit = (Get-Date).AddDays(-$VMLimitStore)
}
}
catch {
}
}
[string]$csvFile = 'C:\ToBackup.txt',
[int]$storeDays = 7
)
$doVMBackup = {
param ($VMHost, $VMName, $VMStorePath, $VMLimitStore)
try {
Add-PSSnapin VeeamPSSnapin
$viServerObject = Get-VBRServer -Name $VMHost
$viObject = Find-VBRViEntity -Server $viServerObject -Name $VMName
Start-VBRZip -Folder $VMStorePath -Entity $viObject
if ($VMLimitStore -ne -1) {
# Delete files older than the $limit.
Get-ChildItem -Path $VMStorePath -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force
# Delete any empty directories left behind after deleting the old files.
Get-ChildItem -Path $VMStorePath -Recurse -Force | Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } | Remove-Item -Force -Recurse}
}
catch {
write-host "An error occured...."
}}
$VMTotal = Import-Csv $csvFile
foreach ($VM in $VMTotal) {
Start-Job -ScriptBlock $doVMBackup -ArgumentList $VM.Host,$VM.Name,$VM.Path,$storeDays
}
Скрипт, как параметр принимает 2 параметра - CSV файл c машинами, которые надо бэкапить и срок хранения бэкапов. Естественно, этот механизм можно переделать под себя.
Благодаря использованию Job задачи идут параллельно :)
Формат CSV файла:
Host, Name, Path
"10.10.10.10","MyVM","C:\BKPTest"
"<Host name in Veeam>","<VM name>","<Path to Backup>"
Спасибо ресурсам
http://memos.nomblot.org/veeam
http://go.veeam.com/learn-powershell-basics-free-tutorial-course/
Хабру и StackOverflow :)
Wednesday, May 6, 2015
Сервис определения страны по IP
Итак, возникла необходимость вычислять, так сказать по IP. А точнее - определять страну для автоматической подстановки телефонного кода. Как оказалось, за эту услугу хотят брать денюжки, например тут. Но хочется то халявного. Ну и по общим правилам опенсорса, если что-то надо - напиши сам. Тем более, база, пусть и не такая точная есть у того же MaxMind
Есть также и такая штука, но я что-то не очень уверен в ее скорости. Но можно попробовать.
В любом случае, родился такой сервис.
Абсолютно бесплатно, поддерживает https.
Используется связка Nginx + uwsgi + flask + postgresql. Почему так? Потому что хотелось попробовать, что есть фласк и постгре. Написано крайне коряво, потому что только учусь. Сейчас получается каждый запрос - это отдельное подключение к базе. Т.к. это все-таки сервер есть мнение переписать это под вариант одно подключение - много запросов и подключение держать постоянно. Но как это сделать я пока не знаю, посмотрим на нагрузку.
Ну и по традиции - код. Да, я знаю, он ужасен, в связи с чем попрошу в комментах наставить на путь истинный.
--
upd:
from flask import Flask,request,jsonify
import socket
import psycopg2
def ip_address_is_valid(address):
try: socket.inet_aton(address)
except socket.error: return False
else: return address.count('.') == 3
application = Flask(__name__)
con = psycopg2.connect(database='geoip', user='geoip', host='localhost', password = 'geoipuser')
@application.route('/')
def hello_world():
return "<b>Geoserver welcomes you. Usage = https://geoip.webcall.today/getcountry?ip=X.X.X.X</b><br> This product includes GeoLite2 data created by MaxMind, available from<br> <a href=\"http://www.maxmind.com\">http://www.maxmind.com</a>."
@application.route('/getcountry')
def getcountry():
global con
ip = request.args.get('ip', '')
if ip == 'auto':
ip = '%s' % request.remote_addr
if ip_address_is_valid(ip):
try:
cur = con.cursor()
cur.execute("SELECT code,location,fullname FROM countrydata WHERE geodata = (SELECT geodata FROM ipdata WHERE subnet >>= '%s'::inet LIMIT 1) LIMIT 1" % ip)
result = cur.fetchone()
if result:
result = jsonify(code = result[0],
location = result[1],
name = result[2]), 200
else:
result = jsonify(code = 'none',
location = 'None',
name = 'None'), 404
except psycopg2.DatabaseError, e:
if con:
con.rollback()
con = psycopg2.connect(database='geoip', user='geoip', host='localhost', password = 'geoipuser')
result = jsonify(code = 'Error %s' % e,
location = 'None',
name = 'None'), 500
finally:
pass
else:
result = jsonify(code = 'none',
location = 'None',
name = 'None'), 404
return result
if __name__ == '__main__':
application.run(host='127.0.0.1')
Ну и ip_address_is_valid можно переписать с использованием регулярок. Что-то типа
--import socket
++import re
def ip_address_is_valid(address):
regex_ip = "^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))$"
if re.match(regex_ip, address):
return True
return False
Есть также и такая штука, но я что-то не очень уверен в ее скорости. Но можно попробовать.
В любом случае, родился такой сервис.
Абсолютно бесплатно, поддерживает https.
Используется связка Nginx + uwsgi + flask + postgresql. Почему так? Потому что хотелось попробовать, что есть фласк и постгре. Написано крайне коряво, потому что только учусь. Сейчас получается каждый запрос - это отдельное подключение к базе. Т.к. это все-таки сервер есть мнение переписать это под вариант одно подключение - много запросов и подключение держать постоянно. Но как это сделать я пока не знаю, посмотрим на нагрузку.
Ну и по традиции - код. Да, я знаю, он ужасен, в связи с чем попрошу в комментах наставить на путь истинный.
--
upd:
- Постарался сделать так, чтобы соединение к базе было одно, а не дергать каждый раз.
- Добавил метод автоопределения места запроса https://geoip.webcall.today/getcountry?ip=auto
from flask import Flask,request,jsonify
import socket
import psycopg2
def ip_address_is_valid(address):
try: socket.inet_aton(address)
except socket.error: return False
else: return address.count('.') == 3
application = Flask(__name__)
con = psycopg2.connect(database='geoip', user='geoip', host='localhost', password = 'geoipuser')
@application.route('/')
def hello_world():
return "<b>Geoserver welcomes you. Usage = https://geoip.webcall.today/getcountry?ip=X.X.X.X</b><br> This product includes GeoLite2 data created by MaxMind, available from<br> <a href=\"http://www.maxmind.com\">http://www.maxmind.com</a>."
@application.route('/getcountry')
def getcountry():
global con
ip = request.args.get('ip', '')
if ip == 'auto':
ip = '%s' % request.remote_addr
if ip_address_is_valid(ip):
try:
cur = con.cursor()
cur.execute("SELECT code,location,fullname FROM countrydata WHERE geodata = (SELECT geodata FROM ipdata WHERE subnet >>= '%s'::inet LIMIT 1) LIMIT 1" % ip)
result = cur.fetchone()
if result:
result = jsonify(code = result[0],
location = result[1],
name = result[2]), 200
else:
result = jsonify(code = 'none',
location = 'None',
name = 'None'), 404
except psycopg2.DatabaseError, e:
if con:
con.rollback()
con = psycopg2.connect(database='geoip', user='geoip', host='localhost', password = 'geoipuser')
result = jsonify(code = 'Error %s' % e,
location = 'None',
name = 'None'), 500
finally:
pass
else:
result = jsonify(code = 'none',
location = 'None',
name = 'None'), 404
return result
if __name__ == '__main__':
application.run(host='127.0.0.1')
Ну и ip_address_is_valid можно переписать с использованием регулярок. Что-то типа
--import socket
++import re
def ip_address_is_valid(address):
regex_ip = "^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))$"
if re.match(regex_ip, address):
return True
return False
Subscribe to:
Posts (Atom)