Category Archives: Tutorials

Mikrotik Voltage Alarm/Beeper

I recently needed to make my routerboard generate an audible alarm based on voltage thresholds on the 12v battery wired directly to the RB2011.

This script was a modification of something i found when perusing the Mikrotik forums.
Change lowvoltage and highvoltage to suite your requirements.

#set lowvoltalarm to desired alarm voltage in tenths of a volt. 125 = 12.5v
:global lowvoltalarm 116
:global highvoltalarm 129
:global highvolt
:global lowvolt
:global starttime
:global hivolttime
:global lovolttime
:global vh
:local thisbox [/system identity get name]
:global voltage [/system health get voltage]
:local thistime [/system clock get time]
:local thisdate [/system clock get date]
:local thishour [:pick $thistime 0 2]
:local emessage ($thisbox . " voltage is: " . [:pick $voltage 0 2] . "." . [:pick $voltage 2 3])
:if ([:len $lowvolt] < 1) do={:set lowvolt 999; :set highvolt 0}
# set your email address in the next line
:if ($voltage <= $lowvoltalarm) do={
:for i from=1 to=20 step=1 do={
:beep as-value frequency=4000;
:delay 1
:put "Low voltage"
if ($voltage > $lowvoltalarm) do={
:put "Normal" 

Docker | Asterisk 16

While working on another project, (Callcenter suite) – i built a relatively lightweight Asterisk 16 image which was being used as the base image for a series of ARI tests.

I decided to release this to the open-source community, to try and motivate more people to start looking at using docker as a viable solution for voip builds. (Just please – don’t do NAT, bridge/route your networks!)

This image is also available on docker-hub, just do the below command to pull the image down to your local library.

docker pull keithrosezw/ubuntu18-asterisk16

Once you start this container, it automatically fires off asterisk with in console mode with 5 levels of verbosity specified. Note that for production you should NOT leave this in console mode. Override the startup script and start asterisk as a background process else the console thread will consume 100% of one CPU core. (This was visible when scaling the service using docker-compose)

I’m now using this image as the base image in a scalable call-center solution that I will be releasing to the public sometime in 2020.

Puppet & Chocolatey – Install GDrive Stream

Experimenting with Puppet & Chocolatey on Windows. Little snippet to install Google Drive Stream.

class windows::google_drive_stream {
Package {
  provider => 'chocolatey'
package { 'google-drive-file-stream':
  ensure => installed,

Postfix SMTP Sender Routing

Recently, I had need of setting up a postfix “smtp router” to distribute mail from various users among a cluster of outbound relays (on unique exit IP’s, with difference QoS policies applied)

Part of the functionality that was desired was the ability to route “priority” users and “bulk” users via different cluster members.

Step 1 – Upgrade postfix to > 3.1.x

Not going to tell you what to do here. google! is your friend.

Step 2 – Edit /etc/postfix/, add the following line to the bottom of the configurations.

$ vim /etc/postfix/
sender_dependent_default_transport_maps = hash:/etc/postfix/sender_transport_maps

Step 3 – Create the transport maps

Using your favorite editor (Vim/Nano/Vi) edit the postfix transport maps.

$ vim /etc/postfix/sender_transport_maps
# Priority Senders
# priority.smtprelay is configured in /etc/hosts to resolve to SMTP relay reserved for Priority users (duh) with higher bandwidth. smtp:[priority.smtprelay] smtp:[priority.smtprelay]

# Bulk Senders
# bulk.smtprelay resolves to a relay with less bandwidth smtp:[bulk.smtprelay] smtp:[bulk.smtprelay]

# All other mail delivers via the relay configured in relayhost= on OR via direct MX

Step 4 – Hash the configurations done above

$ postmap /etc/postfix/sender_transport_maps

Step 5 – Restart postfix

$ /etc/init.d/postfix restart

Step 6 – Analyze Logs

Tail the logs, and watch your mail get distributed between your relays.

$ tail -f /var/log/maillog

Perl Get Active Queue Size (NetXMS/PRTG Use case)

The following Perl script gets the size of the Postfix Active queue and outputs it.
(Used for queue monitoring on our NMS via SSH sensors, but might be useful in your environment)

#!/usr/bin/env perl
use strict;
use warnings;
use Symbol;
sub count {
my ($dir) = @_;
my $dh = gensym();
my $c = 0;
opendir($dh, $dir) or die "$0: opendir: $dir: $!\n";
while (my $f = readdir($dh)) {
if ($f =~ m{^[A-F0-9]{5,}$}) {
} elsif ($f =~ m{^[A-F0-9]$}) {
$c += count("$dir/$f");
closedir($dh) or die "closedir: $dir: $!\n";
return $c;
my $qdir = `postconf -h queue_directory`;
chdir($qdir) or die "$0: chdir: $qdir: $!\n";
printf count("active\n");

The output from this is extremely short – just the queue size, nothing else.

[root@home kscripts]# ./
[root@home kscripts]#

Proxmox – Remove local LVM

I’m  a big Proxmox fan, having deployed more than 20 systems both at customers and on my home lab this year without drama. However, the Proxmox LVM structure bugs the heck out of me. I prefer to utilize a single LVM Volume due to some weird glitches with LXC images and small root LVM partitions. (Not perfect for production, but works for my environment as everything is stored on NFS)

Run the following as root, on your proxmox server. (Change +200GB to what is appropriate for your system)


lvremove /dev/pve/data
lvresize -L +200GB /dev/pve/root
resize2fs /dev/mapper/pve-root


Fortinet/Cisco IPSec VPN – Asterisk Peer Unreachable

Recently, i had to troubleshoot an Asterisk to Asterisk trunk which was running across a site to site IPSec VPN. (Fortinet to Cisco)

After running tcpdump “port 5060 and proto UDP” on either end, I discovered traffic from the Cisco end was not reaching the PBX behind the Fortinet. Packet capture on the Fortinet showed traffic being matched, and classified as SIP.

We had done the usual commands to stop the Fortigate from acting as a SIP ALG, but nothing was working. After a bit of tinkering, i found that the following command fixed our problem. (Basically fooled the Fortigate into thinking SIP traffic, was not SIP)

config system settings
 set sip-udp-port 5067

[Snippet] FreePBX – Strip Incoming Country Code

This short & simple dial-plan removes the 3 digit country code from incoming calls and then pushes it to our FPBX IVR.

nano /etc/asterisk/extensions_custom.conf

After opening the above ^, modify and paste the below into the bottom of the file.
Modify your SIP trunk context to reflect the name below.

exten=>_X.,1,Verbose(Incoming call via SIP Trunk)
same=>n,GotoIf(["${CALLERID(num):0:3}" != "263"]?:noCCatstart)
same=>n,Verbose(a CallerID is ${CALLERID(num)})
same=>n(noCCatstart),Verbose(b CallerID is ${CALLERID(num)})
; This is the freepbx IVR we used to handle incoming calls. Change to whatever is in use on your environment. (extension/queue/etc)

FreePBX – Yealink Phonebook generator.

After deploying a few PBX’s, you get bored of manually generating the required XML for remote phonebooks on Yealink Handsets.
Here’s the first version of the phonebook.xml creation script. (Works on FPBX without any hitches) Observe the notes in the code please.

If you’d like a more updated version, please check my github repo here.

Once configured, point your yealink phones Remote phonebook to http://freepbx-ipaddress/phonebook.xml

This might need to be modified for other FPBX derivatives. (Elastix, Farsouth, etc)

Feel free to add me on Linkedin, or like my facebook page.

#       FreePBX Phonebook Creation Script
#      (c) Keith Rose All Rights Reserved.
# Assumptions
# 1. Base FPBX install. Database in use = asterisk
# 2. Localhost requires no authentication
# 3. Standard Yealink XML Phonebook
# 4. Apache webdir = /var/www/html
# 5. CallerID format = EXTENSION - CALLERID. E.g 2000 - Keith Rose
echo "FreePBX Yealink Phonebook creation script."
echo "  (c) Keith Rose. All Rights Reserved."
echo ""
echo ""
echo ""
echo ""
# Export Extension List to CSV
echo "1....Exporting DB"
mysql -B -e  "select name,extension from users;" asterisk | sed "s/'/\'/;s/\t/\",\"/g;s/^/\"/;s/$/\"/;s/\n//g" > /tmp/exp1.csv
echo "2....Tidying up content"
# Remove first line
tail -n +2 /tmp/exp1.csv > /tmp/exp2.csv
# Remove content prior to hyphen. E.g "2000 - Keith Rose" becomes "Keith Rose"
awk 'BEGIN{FS=OFS="- "} NF>1{$1="";sub(/^- */, "")}'1 /tmp/exp2.csv > /tmp/exp3.csv
# Remove all double quotes
sed 's/\"//g' /tmp/exp3.csv > /tmp/exp4.csv
echo "3....Generating XML"
echo '<?xml version="1.0"?>' > $file_out
echo '<YealinkIPPhoneDirectory>' >> $file_out
while IFS=$',' read -r -a arry
  echo '  <DirectoryEntry>' >> $file_out
  echo '    <Name>'${arry[0]}'</Name>' >> $file_out
  echo '    <Telephone>'${arry[1]}'</Telephone>' >> $file_out
  echo '  </DirectoryEntry>' >> $file_out
done < $file_in
echo '</YealinkIPPhoneDirectory>' >> $file_out
echo "4....Copying phonebook to /var/www/html/phonebook.xml"
cp /tmp/exp5.xml /var/www/html/phonebook.xml
echo "5....Fixing permissions"
chmod 644 /var/www/html/phonebook.xml
echo "6....Tidying up"
# tidying up
rm /tmp/exp* -f
echo ""
echo ""
echo "    Export complete!!"

Snippet – Nginx Wildcard Subdomain

This snippet works with Letsencrypt Wildcard certificates! Specify -d * to install the certificate when using certbot.

Insert into server block. Change to correct domain.

server_name ~^(?<subdomain>.+)\.domain\.com$; 

root /var/www/html/domain,com/$subdomain/htdocs;