Friday, November 29, 2019

AudioCodes determine IP

Here's a trick on an AudioCodes box that isn't talked about lot, the voice configuration menu.

There are a bunch of features.  

Plug a phone into the FXS port and dial ***12345 (that is the default login).  You might need to disconnect the ethernet on it in a situation where an ITSP might actually be working, it will probably try and dial that number.

You'll Hear "CONFIGURATION MENU" if it has worked

Dial " 1#  " to get the Current IP 


Send Asterisk voicemail notifications via Gmail


Here is how you can make asterisk use GMAIL to send voicemails

Tested on Issabel distro on Asterisk 16.6.1

1> Login to your Gmail Account (via browser)

2> Set the "Less secure app access" to "ON".  The fast way is to click on this URL after you have connected to GMAIL  http://www.google.com/settings/security/lesssecureapps

You will probably receive some google alerts that this is been set.

3> Install POSTFIX

yum -y install postfix mailx cyrus-sasl-plain
4> Edit the password file and put in your gmail credentials.  You can use VI or NANO (install instructions for nano below)
/etc/postfix/sasl_passwd
If you aren't familiar with Vi, you can install NANO using the following command
yum install nano
Put in the following information, alter it with your credentials for gmail.  This file is going to be encrypted after a successful test so dont worry about your credentials being stored in clear text, it is only for a few minutes.
smtp.gmail.com    GMailUserName@gmail.com:GMailPassword
5> Now we hash (encrypt) the password file so that it can't be stolen.
postmap hash:/etc/postfix/sasl_passwd
6> Edit the file /etc/postfix/main.cf

Paste in the information below and save
smtp_sasl_auth_enable = yes
relayhost = smtp.gmail.com:587
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = secure
smtp_tls_mandatory_protocols = TLSv1
smtp_tls_mandatory_ciphers = high
smtp_tls_secure_cert_match = nexthop
7> Reload the mail service
smtp.gmail.com    GMailUserName@gmail.com:GMailPassword
8> Send a test message from the CLI
[root@issabel asterisk]# mail SomeEmailAccount@domain.com
Subject: This is an email test
This is a test.
.
If everything worked correctly, you SHOULD get an email to the email account you used via your Gmail Account

If NO email showed up, you can look at the mail log /var/log/maillog
Sep 2 1:20:45 issabel postfix/pickup[50041]: AAD341428DC: uid=0 from=<root>
Sep 2 1:20:45 issabel postfix/cleanup[50295]: AAD341428DC: message-id=<20191129142045.AAD341428DC@issabel.local>
Sep 2 1:20:45 issabel postfix/qmgr[50042]: AAD341428DC: from=<root@issabel.local>, size=427, nrcpt=1 (queue active)
Sep 2 1:20:45 issabel postfix/smtp[50297]: connect to smtp.gmail.com[2607:f8b0:400d:c0e::6c]:587: Network is unreachable
Sep 2 1:20:47 issabel postfix/smtp[50297]: AAD341428DC: to=<SomeEmailAccount@domain.com>, relay=smtp.gmail.com[209.85.144.109]:587, delay=1.9, delays=0.05/0.04/1/0.74, dsn=2.0.0, status=sent (250 2.0.0 OK  1575037247 o70sm10311267qke.47 - gsmtp)

Sep 2 1:20:47 issabel postfix/qmgr[50042]: AAD341428DC: removed
9> Now delete the original file that contained your clear text password.  
rm /etc/postfix/sasl_passwd
10> Now you can go into the system and add the email addresses to your voicemail accounts.


Tuesday, October 29, 2019

Powershell - FTP download a file

This Powershell code will connect to an FTP site and download a file and store it in a location.

Method #1 LONG method with a bunch of error checking, mostly code i've found in other locations.
(method 2 below is far simpler).  I've included both for comparison and help with this

function Get-FTPfiles ($url,$credentials) 
        {
        write-host Connecting to FTP....
        $FTPrequest = [Net.WebRequest]::Create($url)
        $FTPrequest.Method = [System.Net.WebRequestMethods+FTP]::ListDirectory
        
        if ($credentials) 
            { 
            $FTPrequest.Credentials = $credentials 
            }
            
        $FTPresponse = $FTPrequest.GetResponse()
        
            if(!$FTPresponse)
            {   
            #if the response comes back as a 'null' it could mean that the FTP server might be down
            #the file wasn't found, something errored.  when this flag goes high (1) it cause the "UNTIL" statement to activate and break
            #out of this function loop
            $FTPState = 1
            break
            }
        
        $FTPfiles = New-Object IO.StreamReader $FTPresponse.GetResponseStream() 
                    
        while(-not $FTPfiles.EndOfStream) 
            {
            $FTPfiles.ReadLine()   
            }
    
        $FTPfiles.Close()
        $FTPresponse.Close()      
        }

$FTPserver = "ftp://123.123.123.123/" 
$FTPuser = 'FTPuserName' 
$FTPpassword = 'FTPpassword'
$FTPsourcefolder = '\'  #FTP subfolder if required
$FTPtargetfolder = "C:\downloads\" #local PC destination
$FTPfilename ="file to get"
$FTPState = 0

$credentials = new-object System.Net.NetworkCredential($FTPuser, $FTPpassword)
    
$folderPath= $FTPserver + "/" + $FTPsourcefolder + "/"
    
#the Do-Until loop below basically works in conjuction with the "get-FTPfiles" function.  In order to break out of the function,
#it runs 'until' the variable is not equal to 0, which causes the function to break out and continue with the program.
    do
        {
        $files = Get-FTPfiles -url $folderPath -credentials $credentials    
        $webclient = New-Object System.Net.WebClient 
        $webclient.Credentials = New-Object System.Net.NetworkCredential($FTPuser,$FTPpassword) 
   
        $FTPsourceFullPath=$folderPath + $FTPfilename  
        $ftpDestinationLocation = $FTPtargetfolder + $FTPfilename 
        $webclient.DownloadFile($FTPsourceFullPath, $FTPtargetfolder+$FTPfilename)
        $FTPState = 1
        }

    until ($FTPState -ne 0)
        {
        write-host FTP process flagged as completed
        }

$FileExists = Test-Path $ftpDestinationLocation

    If ($FileExists -eq $True) 
        {
        write-host File Downloaded Successfully
        
        }

METHOD 2
Script for DOWNLOADING from FTP

$FTPserverIP = "123.123.123.123"
$FTPuser = 'username'
$FTPpassword = 'password'
$FTPtargetfolder = "C:\download\"
$FTPfilename ="testfile.csv"

$client = New-Object System.Net.WebClient
$client.Credentials = New-Object System.Net.NetworkCredential("$FTPuser", "$FTPpassword")

$client.DownloadFile("ftp://$FTPserverIP/$FTPfilename", "$FTPtargetfolder\$FTPfilename")


Script for UPLOADING to FTP

$FTPserverIP = "123.123.123.123"
$FTPuser = 'username'
$FTPpassword = 'password'
$FTPtargetfolder = "C:\download\"
$FTPfilename ="testfile.csv"

$client = New-Object System.Net.WebClient
$client.Credentials = New-Object System.Net.NetworkCredential("$FTPuser", "$FTPpassword")
$client.UploadFile("ftp://$FTPserverIP/$FTPfilename", "$FTPtargetfolder\$FTPfilename")

Wednesday, October 9, 2019

Find the largest files in your linux file system


This will give you the top largest 20 files in a linux system collectively (directory of VAR in the example)

du -a /var/ | sort -n -r | head -n 20


du will estimate file space usage.
sort will sort out the output of du command.
head will only show top 20 largest file in /dir/

Tuesday, October 8, 2019

SQL parsing a carriage return delimited column

I've encountered this in the past and coworker helped me figure out a way to do it.  I'm positing this for my own memory but maybe it will help someone else too!

I was trying to pull a specific line from a database, but the column contained Carriage Delimited data.  As you can see "passwords" is not your normal database entry.  

In my situation, the first 4 digits are system generated, and the last 4 digits are manually generated.  So, if a password change was required, the only 4 digits i know are the first 4.

So I wanted to pull the exact line out of the system to obtain the password.

The problem is, doing a "select from passwords" would return the entire column into a single variable, treating every password as just one long huge single password.


> select * from pinsets where pinsets_id = 2
+------------+--------------------------+-------------+----------+----------+ | pinsets_id | passwords | description | addtocdr | deptname | +------------+--------------------------+-------------+----------+----------+ | 2 | 10001500 | testpins | 0 | | 10012400 10023400 10034400 10045400 +------------+--------------------------+-------------+----------+----------+ 1 row in set (0.00 sec)

I needed a way to search for the first 4 digits of the the password, and then have the system parse out the last 4 digits and give me just the line.

I want to search for "1000" and have the system return "10001500"

This was what I used

select MID((select passwords from pinsets where description = 'testpins'), INSTR ((select passwords from pinsets), "1000"),8);

The MID command pulls everything out of passwords into a single file, then I do an INSTR (instring) to find the first instance of 1000, and then pull all 8 characters (the first 4 characters '1000' followed by the last 4 characters '1500')

Running this command gave me the result I needed.

+---------------------------------------------------------------------------------------------------------------------------------+ | MID((select passwords from pinsets where description = 'testpins'), INSTR ((select passwords from pinsets), "1000"),8) | +---------------------------------------------------------------------------------------------------------------------------------+ | 10001500 | +---------------------------------------------------------------------------------------------------------------------------------+

This is not perfect, but it worked for my situation.  I had a degree of difficulty finding information on line for this situation, so I wanted to post what I found in case it helped someone else.

Monday, October 7, 2019

Asterisk - limit extension total usage duration

Here's how you an limit the extension duration based on values in a PINSET in the GUI.
This will use a PINSET with the description of "extenmaxduration" (system is hardcoded to this description for this example)
LIMITATION.
This code works with FOUR DIGIT extensions ONLY
Create a PINSET with the description "extensionduration"
In the pins put in pins using the SYNTAX:
Extension*Minutes
Example:
1005*00500
1006*01500
1103*99999
EXPLAINED:
PINSET values are stored in the database using "carriage return" delimitation within the actual column, so we need to parse the data returned by the SQL statement. This means the data MUST be a consistent length for my select statement..
This is why we have leading zero's ( 00 ) in the duration to maintain the length for the SQL script i have. I'm sure there are far smarter people than me who can work this out, but this is what I was able to make work.
In my example,
extension 1005 is allowed 500 minutes "1005*00500"
extension 1006 is allowed 1500 minutes "1006*01500"
extension 1103 is allowed 99999 minutes "1103*99999"
Extensions that are NOT in the list, the code will simply bypass them and allow those calls to continue.
So here is the entire code. Again the "outrt-X" heading will need to be altered to suit your environment.
[outrt-2] ; limit by extensions
include => outrt-2-custom
exten => _.,1,Macro(user-callerid,LIMIT,EXTERNAL,)
exten => _.,n,Set(MOHCLASS=${IF($["${MOHCLASS}"=""]?default:${MOHCLASS})})
exten => _.,n,Set(_NODEST=)
exten => _.,n,Gosub(sub-record-check,s,1(out,${EXTEN},))

;open connection to ASTERISK table to get the pinset data
exten => _.,n,MYSQL(Connect connid localhost root PBXPASS asterisk)
exten => _.,n,MYSQL(Query resultid ${connid} select MID((select passwords from pinsets where description = 'extensionduration'), INSTR ((select passwords from pinsets), "${ampuser}"),10) from pinsets)
exten => _.,n,MYSQL(Fetch fetchid ${resultid} extenmaxduration)
exten => _.,n,MYSQL(Clear ${resultid})
exten => _.,n,MYSQL(Disconnect ${connid})

;If we dont find a matching extension, we'll skip the rest of the checks and just let it dial out.
exten => _.,n,GotoIf($["${extenmaxduration}" = ""]?continue)

;parse the return using an * as the delimiter between extension and minute values
exten => _.,n,Set(extenmaxduration=${CUT(extenmaxduration,*,2)})

;change mins to seconds
exten => _.,n,Set(extenmaxduration=${MATH(${extenmaxduration}*60,int)})

;open a new connection CDR table
exten => _.,n,MYSQL(Connect connid localhost root PBXPASS asteriskcdrdb)
exten => _.,n,MYSQL(Query resultid ${connid} SELECT if(sum(duration) < ${extenmaxduration}, 1, 0) FROM cdr WHERE (calldate between DATE_FORMAT(NOW() ,'%Y-%m-01') AND NOW() ) and src like '${AMPUSER}')
exten => _.,n,MYSQL(Fetch fetchid ${resultid} extenmaxduration)
exten => _.,n,MYSQL(Clear ${resultid})
exten => _.,n,MYSQL(Disconnect ${connid})

;if we dont find any value in pinset, we'll default allow this call to go through.
exten => _.,n(continue),execif($["${extenmaxduration}" = ""]?set(extenmaxduration=1))

;if montly duration is less than pinset duration, go out trunk, else say "time" and hangup
exten => _.,n,execif($["${extenmaxduration}" = "1"]?macro(dialout-trunk,1,${EXTEN},,off))
exten => _.,n,execif($["${extenmaxduration}" = "0"]?playback(time))
exten => _.,n,Macro(outisbusy,)
Save your changes and reload your configs.
Give it a try!

Remember that your values for your extensions need a leading zereo, and those values are stored in MINUTES. The script will do the calculation to change it to SECONDS which the CDR table uses.
In this example, if the extension has not exceed its maximum, the call will proceed out the trunk.
If it has exceeded the max, you will hear the voice "TIME" and then a "all circuits busy"

Thursday, October 3, 2019

Asterisk Balancing outbound calls across multiple random trunks

This code will send calls out trunks via a random generator

HOW IT WORKS
It adds a random generator and then will send calls across multiple trunks
(GUI) PBX -> Outbound Routes
Open the route you want to apply this random trunk
1: Under "Trunk Sequence" you want the first route to be the one if the time limit is NOT exceeded
2: The next route is the one ff the time IS exceeded. (you can have as many more as you want)
Make sure those changes are saved
Now open up /etc/asterisk/extensions_additional.conf
Find your route in the dialplan. it will look SOMETHING like the one below. Yours maybe different heading and number of trunks, so just use mine as an example only, modify your file.
This is mine, and i have TWO Destinations

[outrt-2] ; randomtrunk
include => outrt-2-custom
exten => _.,1,Macro(user-callerid,LIMIT,EXTERNAL,)
exten => _.,n,Set(MOHCLASS=${IF($["${MOHCLASS}"=""]?default:${MOHCLASS})})
exten => _.,n,Set(_NODEST=)
exten => _.,n,Gosub(sub-record-check,s,1(out,${EXTEN},))
exten => _.,n,Macro(dialout-trunk,1,${EXTEN},,off)
exten => _.,n,Macro(dialout-trunk,2,${EXTEN},,off)
exten => _.,n,Macro(outisbusy,)
;--== end of [outrt-2] ==--;

Copy and paste the entire section
open up /etc/asterisk/extensions_override_issabelpbx.conf  (if you are using a different deployment "extensions_custom.conf" should work as well
Go to the bottom of the file, and PASTE it

Now under the line with "GOSUB" put this value in

exten => _.,n,Set(randomtrunk=${RAND(1,2)});

The 1 and 2 signify the low and high number.  So if you have 2 trunks, it would be 1 & 2.  If you had 4, it would be 1 & 4.

These are the trunks are called. 
In my example two trunks are called, and if they do not work, "outisbusy" is run which is "all circuits are busy" message. You'll notice in my example it has "..trunk, 1, $ ..." and followed by "... trunk, 2, $ ...".
exten => _.,n,Macro(dialout-trunk,1,${EXTEN},,off)
exten => _.,n,Macro(dialout-trunk,2,${EXTEN},,off)
exten => _.,n,Macro(outisbusy,)
;--== end of [outrt-2] ==--;

It IS possible that, although mine are in numeric order, yours may not be. So just make note of that when you see the modification example
Change the first line to look like this instead. Just note if your 1 or 2 values ​​are in a different order and adjust accordingly.

execif($["${randomtrunk}" = "X"]?......

exten => _.,n,execif($["${randomtrunk}" = "1"]?macro(dialout-trunk,1,${EXTEN},,off))
exten => _.,n,execif($["${randomtrunk}" = "2"]?macro(dialout-trunk,2,${EXTEN},,off))


Just change the "X" value to be the match to one of your randomly generated values.

Heres what it will look like when you are done.

[outrt-2] ; randomtrunk
include => outrt-2-custom
exten => _.,1,Macro(user-callerid,LIMIT,EXTERNAL,)
exten => _.,n,Set(MOHCLASS=${IF($["${MOHCLASS}"=""]?default:${MOHCLASS})})
exten => _.,n,Set(_NODEST=)
exten => _.,n,Gosub(sub-record-check,s,1(out,${EXTEN},))

exten => _.,n,Set(randomtrunk=${RAND(1,2)});
exten => _.,n,execif($["${randomtrunk}" = "1"]?macro(dialout-trunk,1,${EXTEN},,off))
exten => _.,n,execif($["${randomtrunk}" = "2"]?macro(dialout-trunk,2,${EXTEN},,off))

exten => _.,n,Macro(outisbusy,)
;--== end of [outrt-2] ==--;

From the command line type
asterisk -rx 'core reload'
Test and try!
Please note that once this code is in place, you can't change the trunk order in the GUI for this outbound route. This code will over-ride the GUI for that. So if you DO need to make changes, you can make the change the GUI, but you will need to re-perform these custom modifications for it to work.
Anyone who is trying this, and you should only do this in a test server if you aren't familiar, but if you DO have problems and its messing up your PBX, in the "extensions_override_issabelpbx.conf" file. You can just erase these changes and save / reload the file and your pBX will revert to its original config for this rule.

Asterisk - limit total time usage of a trunk

Here's how you can limit the amount of time used on a particular trunk by looking up the time in SQL.
I tested this in the ISSABEL deployment of asterisk, but should work in any.

Once the time is exceeded on a trunk, the dialplan will skip to subsequent trunks.
(scroll to the bottom for advanced users for the completed code, the next bit I'm showing how to make the code change for users unfamiliar with dialplan changes)

HOW IT WORKS
The way this works is upon making a call that would use a particular trunk, the system does an SQL query on total outbound seconds used for the last 30 days.  It selects it by looking at the trunk name and adding up the values.
Then depending if you've reached your maximum or not, it will send the call to trunks accordingly.
(GUI) PBX -> Outbound Routes
Open the route you want to apply this "time limit" rule and verify at least these TWO conditions
1: Under "Trunk Sequence" you want the first route to be the one if the time limit is NOT exceeded
2: The next route is the one ff the time IS exceeded. (you can have as many more as you want)
Make sure those changes are saved
Now open up /etc/asterisk/extensions_additional.conf
Find your route in the dialplan. it will look SOMETHING like the one below. Yours maybe different heading and number of trunks, so just use mine as an example only, modify your file.
This is mine, and i have TWO Destinations
[outrt-2] ; 1000minlimitroute
include => outrt-2-custom
exten => _.,1,Macro(user-callerid,LIMIT,EXTERNAL,)
exten => _.,n,Set(MOHCLASS=${IF($["${MOHCLASS}"=""]?default:${MOHCLASS})})
exten => _.,n,Set(_NODEST=)
exten => _.,n,Gosub(sub-record-check,s,1(out,${EXTEN},))
exten => _.,n,Macro(dialout-trunk,1,${EXTEN},,off)
exten => _.,n,Macro(dialout-trunk,2,${EXTEN},,off)
exten => _.,n,Macro(outisbusy,)
;--== end of [outrt-2] ==--;
Copy and paste the entire section
open up /etc/asterisk/extensions_override_issabelpbx.conf  (if you are using a different deployment "extensions_custom.conf" should work as well
Go to the bottom of the file, and PASTE it
Now we'll modify this file.
First we'll add the database lookup, insert this code. Change USERNAME and PASSWORD to be your database:
Change TRUNKNAME to be the trunk name you want to check against.
Change "30" to be the number of days you want to look back on
Change 60000 to be the number of seconds you want to limit to
exten => _.,n,MYSQL(Connect connid localhost USERNAME PASSWORD asteriskcdrdb)
exten => _.,n,MYSQL(Query resultid ${connid} SELECT if(sum(duration) < 60000, 1, 0) FROM cdr WHERE DATE(calldate) >= DATE(NOW()) - INTERVAL 30 DAY and dstchannel like '%TRUNKNAME%')
exten => _.,n,MYSQL(Fetch fetchid ${resultid} dynroute)
exten => _.,n,MYSQL(Clear ${resultid})
exten => _.,n,MYSQL(Disconnect ${connid})
Put it under the GOSUB statement in the base dialplan code:
It will look like this:
[outrt-2] ; 1000minlimitroute
include => outrt-2-custom
exten => _.,1,Macro(user-callerid,LIMIT,EXTERNAL,)
exten => _.,n,Set(MOHCLASS=${IF($["${MOHCLASS}"=""]?default:${MOHCLASS})})
exten => _.,n,Set(_NODEST=)
exten => _.,n,Gosub(sub-record-check,s,1(out,${EXTEN},))

exten => _.,n,MYSQL(Connect connid localhost user password asteriskcdrdb)
exten => _.,n,MYSQL(Query resultid ${connid} SELECT if(sum(duration) < 60000, 1, 0) FROM cdr WHERE DATE(calldate) >= DATE(NOW()) - INTERVAL 30 DAY and dstchannel like '%trunkname%')
exten => _.,n,MYSQL(Fetch fetchid ${resultid} dynroute)
exten => _.,n,MYSQL(Clear ${resultid})
exten => _.,n,MYSQL(Disconnect ${connid})
Now, we'll modify the trunk to be dialed. Your code will look like something like this.
These are the trunks are called. In my example two trunks are called, and if they do not work, "outisbusy" is run which is "all circuits are busy" message. You'll notice in my example it has "..trunk, 1, $ ..." and followed by "... trunk, 2, $ ...".
exten => _.,n,Macro(dialout-trunk,1,${EXTEN},,off)
exten => _.,n,Macro(dialout-trunk,2,${EXTEN},,off)
exten => _.,n,Macro(outisbusy,)
;--== end of [outrt-2] ==--;
It IS possible that, although mine are in numeric order, yours may not be. So just make note of that when you see the modification example
Change the first line to look like this instead. Just note if your 1 or 2 values ​​are in a different order and adjust accordingly.
exten => _.,n,execif($["${dynroute}" = "0"]?macro(dialout-trunk,1,${EXTEN},,off))
This will tell the dialplan that IF the total time in SQL is NOT 1000 minutes (60000 seconds) then use the first trunk.
IF the dialplan is told by SQL IS more than 1000 minutes, the dialplan will execute the remaining trunk (s) in the order you have them.
Now your code will look something like this when completed:
username / password are you MYsql credentials
TRUNKNAME = trunk name in GUI (can be partial).
Change "30" to be the number of days you want to look back on
Change 60000 to be the number of seconds you want to limit to
[outrt-2] ; 1000minlimitroute
include => outrt-2-custom
exten => _.,1,Macro(user-callerid,LIMIT,EXTERNAL,)
exten => _.,n,Set(MOHCLASS=${IF($["${MOHCLASS}"=""]?default:${MOHCLASS})})
exten => _.,n,Set(_NODEST=)
exten => _.,n,Gosub(sub-record-check,s,1(out,${EXTEN},))

exten => _.,n,MYSQL(Connect connid localhost username password asteriskcdrdb)
exten => _.,n,MYSQL(Query resultid ${connid} SELECT if(sum(duration) < 60000, 1, 0) FROM cdr WHERE DATE(calldate) >= DATE(NOW()) - INTERVAL 30 DAY and dstchannel like '%trunkname%')
exten => _.,n,MYSQL(Fetch fetchid ${resultid} dynroute)
exten => _.,n,MYSQL(Clear ${resultid})
exten => _.,n,MYSQL(Disconnect ${connid})

exten => _.,n,execif($["${dynroute}" = "0"]?macro(dialout-trunk,1,${EXTEN},,off))

exten => _.,n,Macro(dialout-trunk,2,${EXTEN},,off)   
exten => _.,n,Macro(outisbusy,)
;--== end of [outrt-2] ==--;
From the command line type
asterisk -rx 'core reload'
Test and try!
Please note that once this code is in place, you can't change the trunk order in the GUI for this outbound route. This code will over-ride the GUI for that. So if you DO need to make changes, you can make the change the GUI, but you will need to re-perform these custom modifications for it to work.
Anyone who is trying this, and you should only do this in a test server if you aren't familiar, but if you DO have problems and its messing up your PBX, in the "extensions_override_issabelpbx.conf" file. You can just erase these changes and save / reload the file and your pBX will revert to its original config for this rule.

Thursday, September 5, 2019

Modify Asterisk Queue members via SQL

It is possible to add static agents to a queue via a SQL statement.
Here you see the static members of a queue 
https://imgur.com/a/vzEgDLv
In the table "queues_details" in the database "asterisk" this select will show you all your configured queues.
MariaDB [asterisk]> select * from queues_details;
+------+-----------------------------+---------------------------+-------+
| id   | keyword                     | data                      | flags |
+------+-----------------------------+---------------------------+-------+
| 2001 | member                      | Local/1005@from-queue/n,0 |     3 |
| 2001 | member                      | Local/1006@from-queue/n,0 |     4 |
| 2001 | member                      | Local/1004@from-queue/n,0 |     2 |
| 2001 | member                      | Local/1002@from-queue/n,0 |     1 |
| 2001 | member                      | Local/1001@from-queue/n,0 |     0 |
| 2001 | answered_elsewhere          | 0                         |     0 |
| 2001 | penaltymemberslimit         | 0                         |     0 |
| 2001 | timeoutpriority             | app                       |     0 |
My example shows Queue (ID) 2001 with static members 1001-1006 (1003 is missing)
You can add a member (which will show up in the GUI if you edit it too) using this command. I'm adding extension "1003" into queue 2001.
MariaDB [asterisk]> insert into `queues_details` (`id`,`keyword`,`data`,`flags`) values (2001,'member','Local/1003@from-queue/n,0',100);
Query OK, 1 row affected (0.00 sec)

MariaDB [asterisk]> select * from queues_details;
+------+-----------------------------+---------------------------+-------+
| id   | keyword                     | data                      | flags |
+------+-----------------------------+---------------------------+-------+
| 2001 | member                      | Local/1005@from-queue/n,0 |     3 |
| 2001 | member                      | Local/1006@from-queue/n,0 |     4 |
| 2001 | member                      | Local/1004@from-queue/n,0 |     2 |
| 2001 | member                      | Local/1002@from-queue/n,0 |     1 |
| 2001 | member                      | Local/1001@from-queue/n,0 |     0 |
| 2001 | answered_elsewhere          | 0                         |     0 |
| 2001 | maxlen                      | 0                         |     0 |
| 2001 | member                      | Local/1003@from-queue/n,0 |   100 |
+------+-----------------------------+---------------------------+-------+
42 rows in set (0.00 sec)
What you have to do is set the "FLAG" value to a high value (higher than any current) when you enter it, and you have increment it. My example uses 100.
If you look in the GUI, you'll see it shows up there as well now.