Monday, December 14, 2015

Raspberry Pi Kiosk Carousel

We have a raspberry pi driving a TV in our office to show various web pages. I recently had to rebuild it, and it was kind of a pain trying to find all the information to get it all set back up again. Here's some steps that should work for setting up a web-carousel kiosk on Rasbian Jessie November 2015

1. Install luakit browser and tools

apt-get install xdotool luakit unclutter

2. Get luakit to start automatically when pi boots, disable screensaver/screen blanking

edit /home/pi/.config/lxsession/LXDE-pi/autostart to be like the following:

@lxpanel --profile LXDE-pi
@pcmanfm --desktop --profile LXDE-pi
#@xscreensaver -no-splash
@xset -dpms
@xset s off
@luakit

3. Create a script for running the kiosk

Create /home/pi/kiosk.sh with the following contents:

#!/bin/bash
sleep 25
export DISPLAY=':0'
WID=`xdotool search --onlyvisible luakit | head -1`
xdotool key --window $WID F11
xdotool type ":o https://www.website1.com/page1.html"
xdotool key Return
xdotool type ":t https://www.website2.com/page2.html"
xdotool key Return
xdotool type ":t https://www.website3.com/page3.html"
xdotool key Return

while true; do
    sleep 20
    xdotool key Ctrl+Page_Up
    xdotool key r
done

This script waits for 25 seconds (for luakit to start after boot) then starts opening tabs for the various sites you want to cycle through. Finally it sends a Ctrl+Page_Up (go to next tab) and F5 (refresh page) every 20 seconds, to cycle through the tabs indefinitely.

Make sure you mark it executable (chmod 744 /home/pi/kiosk.sh)

4. Edit /etc/rc.local so that kiosk.sh runs at startup

/etc/rc.local should look something like the following:

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

sudo -u pi /home/pi/start.sh&

exit 0

The key line is
sudo -u pi /home/pi/kiosk.sh&
which starts our kiosk as the pi user

Reboot and you're done!

Tuesday, September 29, 2015

Automatically Syncing Verified Google Classroom Teachers

At a recent google technical cooperative meetup I attended, one of the comments there was that it'd be nice to be able to add users as verified google classroom teachers automatically as an OU setting in google apps. While a setting like that would be handy, if you're syncing your active directory to google apps using google apps directory sync, there's actually a way to accomplish it. For the rest of this post, substitute gappsdomain.com for your actual google apps domain name

Google uses the special group classroom_teachers@gappsdomain.com to indicate which users are verified teachers who can use classroom. If you sync this group using google apps directory sync, you'll be able to automatically keep the group up to date with teachers in your organization.

There's a few different ways to sync the group, depending on your active directory setup, here's some examples

Scenario 1


"I have a security group in AD that contains all my individual teachers (not nested groups!)"

Probably the easiest situation. You might already be syncing this group to google apps as a different name like all-teachers@gappsdomain.com, if that's the case, no problem! Just tuck the email address classroom_teachers@gappsdomain.com into another LDAP attribute on that group and then add another sync rule to sync that group again, just using that attribute as the group email address.


Scope: Object - We just want to sync this one group
Rule: (objectClass=group) - We just want group objects
Base DN: The distinguished name of the group we're syncing, something like CN=all-teachers, OU=groups, DC=gappsdomain, DC=com
Group Email Address Attribute: The attribute on the group that contains classroom_teachers@gappsdomain.com
User Email Address Attribute: The attribute on your USERS that contains their google apps username
Member Reference Attribute: member

Scenario 2


"I have a security group in AD that contains other groups that contain all my teachers"

Here's where we get a bit creative, since syncing nested groups won't work with the classroom_teachers group. It has to contain a list of the individual accounts. If you've read this other blog post, then you know exactly where we're headed with this.

Similar to the previous scenario, if you're already syncing this group, you're going to need to tuck the string classroom_teachers@gappsdomain.com into an LDAP variable other than mail on the group. Although for simplicity's sake you might just want to create a new group that contains your all-teachers group. This has the added benefit of letting you be able to add non-teachers (admin positions, etc) to the group to allow classroom access. You'll also need to put an LDAP search string into another property of the group. To make the correct search string, get the DN of your group that contains all your other groups. Let's say that a group all-teachers contains groups for all-schoolA-teachers, all-schoolB-teachers, etc. Let's also say that the DN of group all-teachers is CN=all-teachers, OU=groups, DC=gappsdomain, DC=com

This search string will look like the following:

 (&(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=CN=all-teachers,OU=Groups,DC=gappsdomain,DC=com))  

Let's say that we put that long string into the "extensionAttribute2" LDAP attribute for that group, and the classroom_teachers@gappsdomain.com value into the "extensionAttribute1" LDAP attribute. Our GADS settings should look like the following:


Scope: Object - We just want to sync this one group
Rule: (objectClass=group) - We just want group objects
Base DN: The distinguished name of the group we're syncing, something like CN=all-teachers, OU=groups, DC=gappsdomain, DC=com
Group Email Address Attribute: extensionAttribute1 - The attribute on the group that contains classroom_teachers@gappsdomain.com
User Email Address Attribute: The attribute on your USERS that contains their google apps username
Dynamic (Query-based) group?: Checked ON, this tells GADS to use the following attribute to search for the members
Member Reference Attribute: extensionAttribute2 (or whichever LDAP attribute you stored the search string)

Thursday, September 25, 2014

Syncing Nested Groups to Google Apps using GADS

We use GADS (Google Apps Directory Sync) to sync our Active Directory structure with our Google apps setup. We wanted to start syncing groups, however the users we wanted to be part of the group were nested within other groups. One way we could resolve this would be to sync all the intermediary groups, but in our case that would create a lot of groups that we really wouldn't need. The solution we came up with has two parts:

1) In Active Directory create a special LDAP filter and assign it to one of the group's extensionAttribute values

2) In GADS sync the group as a Dynamic group, using the extensionAttribute to perform the query to find the users

Lets say we have a security group group called cool-users. cool-users doesn't have any users, but the security groups rad-dudes and awesome-guys are members of it and THOSE groups have users. We want to sync this to Google Apps as cool-users@contoso.com but we don't want to create groups for rad-dudes and awesome-guys and just want all the users to be a part of cool-users@contoso.com.

For step one, the query we needed to craft was as follows:

 (&(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=CN=cool-users,OU=Groups,DC=contoso,DC=com))  

For those not super familiar with LDAP search syntax this is a query that looks for an object that is a user and is a member of the group identified by the distinguished name CN=cool-users,OU=Groups,DC=contoso,DC=com The weird looking bit (memberOf:1.2.840.113556.1.4.1941) is the magical part that does it's own search to find the all members of the group (including nested ones). For further reading check out http://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx and look for LDAP_MATCHING_RULE_IN_CHAIN.

With our rule made we now need to assign it to the group, which we COULD do a number of different ways (such as editing the object directly with object editor), but we have a few objects we want to sync like this, so we opted to do it programmatically via powershell using the following script:

 Get-ADGroup -LDAPFilter "(&(objectClass=group)(cn=*-users))" -SearchBase "OU=Groups,DC=contoso,DC=com" |  
 ForEach-Object {  
   $EXT1 = "(&(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=CN=" + $_.name + ",OU=Groups,DC=contoso,DC=com))"  
   Set-ADGroup $_ -Replace @{extensionAttribute1=$EXT1}  
 }  
What this does is grab any groups that end in -users in the OU=Groups,DC=contoso,DC=com OU and sets extensionAttribute1 to the proper search string. It might not be a bad idea to run this as a scheduled powershell task every so often so that new groups and any renamed groups get the correct search string.

Now that our groups are set properly, we can configure GADS to use them. Open GADS, head for the groups tab and add a search rule that looks like the following:

We're going to find any groups that match *-users in our Groups OU and sync them to Google Apps, but we're going to make them Dynamic and use extensionAttribute1 as the object property that contains the search that has all of our users. Run a test of your sync and voila! You should have the groups syncing all the users that belong to it!

One word of caution would be that using LDAP_MATCHING_RULE_IN_CHAIN to search in this manner can be computationally expensive, so it's possible that your sync could take noticeably longer to run. It's best to make use of filters and only use this method on necessary groups. I hope you find this useful!

Saturday, November 30, 2013

Monitoring ESXi CPU/Memory via SNMP

I didn't find a particularly good resource for configuring and monitoring ESXi via SNMP, so here's what I did to get it to work. These instructions assume you want to use SNMPv3. You'll also need a host with vmcli installed on it.

1. create a local user on the esxi server by connecting directly to the esxi host using vsphere and going to the local users and groups tab. Right click somewhere in the user list and hit add
login: cacti (my poller)
password: somepassword

2. From the vCLI host, issue the following commands
esxcli -s esxiserverhostnameorip -u root -p rootpassword system snmp set --authentication [none,SHA1,MD5]
optional - to configure privacy do
esxcli -s esxiserverhostnameorip -u root -p rootpassword system snmp set --privacy [none,AES128]

3. Configure users by issuing the following commands
esxcli -s esxiserverhostnameorip -u root -p rootpassword system snmp hash --raw-secret --auth-hash yourdesiredsnmpv3authpass
optionally add --priv-hash yourprivsecret if using privacy
Hashes will be generated and displayed on screen, then do:
esxcli -s esxiserverhostnameorip -u root -p rootpassword system snmp set --users userid/authhash/privhash/security
where userid is the user in step 1, authhash is the authentication hash, privhash is the privacy hash (or use a single dash "-" if not using privacy) and security is either none, auth, or priv

4. Test the user by executing the following:
esxcli -s esxiserverhostnameorip -u root -p rootpassword system snmp test --user username --raw-secret --auth-hash desiredsnmpv3authpass (also include --priv-hash if you're using privacy)

5. enable SNMP by executing:
esxcli -s esxiserverhostnameorip -u root -p rootpassword system snmp set --enable yes

Now you can monitor your ESXi host via SNMP, some useful OIDs

1.3.6.1.2.1.25.2 - hrStorage
Not only contains information about datastores, but one of the entries is "Real Memory" which contains information about memory usage.

1.3.6.1.2.1.25.3.3 - hrProcessorTable
Contains CPU usage values for each core on the system

.1.3.6.1.2.1.2 - Interfaces MIB
Information on various network interfaces defined on the host

VMware documentation on configuring SNMP: https://pubs.vmware.com/vsphere-51/index.jsp?topic=%2Fcom.vmware.vsphere.monitoring.doc%2FGUID-2E4B0F2A-11D8-4649-AC6C-99F89CE93026.html

Wednesday, October 23, 2013

Modifying Bell Paging Schedules in Asterisk

The site that caused me to set up bell paging on our trixbox system also threw me a curveball by having an "alternate" schedule that they needed implemented every Thursday, although occasionally they need to set the "Thursday" schedule on other days of the week. I got tired of manually updating the crontab for these times, so I configured a feature code so that they could override the normally programmed time themselves.

First create a custom context to use as a destination that will run the script that modifies our crontab
edit /etc/asterisk/extensions_custom.conf ad add something like
[ext-belltoggle-custom]
exten => s,1,AGI(toggle-bells.sh)

Next create a custom destination to trigger that context
Custom destination : ext-belltoggle-custom,s,1
Description: Toggle Bells

Then create a Misc Application to trigger our custom destination:
Description: Toggle Bells
Feature Code: *281 (or whatever you like)
Feature Status: Enabled
Destination: Custom Destinations -> Toggle Bells

Create toggle-bells.sh in /var/lib/asterisk/agi-bin with the following

#!/bin/bash

# Consume all variables sent by Asterisk
while read VAR && [ -n ${VAR} ] ; do : ; done

# Answer the call.
echo "ANSWER"
read RESPONSE

# Say the letters of "Hello World"
#echo 'SAY ALPHA "Hello World" ""'
#read RESPONSE

if crontab -l | grep -q "ALTERNATE"; then
  echo "51 * * * * /var/lib/asterisk/bin/freepbx-cron-scheduler.php
1 00 * * 1,2,3,5 /usr/bin/crontab /var/lib/asterisk/scripts/normalbells
1 00 * * 4 /usr/bin/crontab /var/lib/asterisk/scripts/alternatebells
1 00 * * 0,6 /usr/bin/crontab /var/lib/asterisk/scripts/weekendbells
##NORMAL BELLS##
##Put your normal schedule here with lines like the following##
30 08 * * * /usr/sbin/asterisk -rx 'originate Local/8102@ext-paging extension 8103@ext-paging'
" | crontab -

  echo 'EXEC PLAYBACK "custom/NormalBellActivated" ""'
  read RESPONSE

elif crontab -l | grep -q "NORMAL"; then
  echo "51 * * * * /var/lib/asterisk/bin/freepbx-cron-scheduler.php
1 00 * * 1,2,3,5 /usr/bin/crontab /var/lib/asterisk/scripts/normalbells
1 00 * * 4 /usr/bin/crontab /var/lib/asterisk/scripts/alternatebells
1 00 * * 0,6 /usr/bin/crontab /var/lib/asterisk/scripts/weekendbells
##ALTERNATE BELLS##
##Put your alternate schedule here with lines like the following##
35 08 * * * /usr/sbin/asterisk -rx 'originate Local/8102@ext-paging extension 8103@ext-paging'
" | crontab -

  echo 'EXEC PLAYBACK "custom/AlternateBellActivated" ""'
  read RESPONSE

fi

exit 0

Create 3 crontab files in /var/lib/asterisk/scripts/ called normalbells alternatebells and weekendbells that will be sourced to set the schedule to the normal time each day.

They should look identical to what the asterisk user's crontab looks like when it's in one of the modes.

Finally, create two system recordings named NormalBellActivated and AlternateBellActivated that will be used to inform the user which mode they're in.

Friday, October 18, 2013

FreePBX BLF Time Conditions

My previous post was about increasing the number of Call Routing toggles in FreePBX because I thought that was what I would need to do in order to get BLF (Busy Lamp Field) working with the presence feature on our Polycom SoundPoint phones. The reason I wanted to do that was that so users could have an indication if their phone was in "night mode" or not. As it turns out there is a MUCH better way to handle this.

Step 1
In FreePBX, go to Settings -> Advanced Settings and change "Enable Custom Device States" to True

Step 2
In Applications -> Time Conditions, create your Time Condition. You'll notice that by enabling the previous option, you now have a "Generate BLF Hint" checkbox. Be sure to set that to ON, make sure you also set the "Enable Override Code" box to ON. Once you save the Time Condition, make a note of the override code assigned to it (likely something like *270)

Step 3
On your phone, enable the presence feature. For my SoundPoint IP 601, this meant setting         feature.1.enabled="{$feature_1_enabled|1}" in my server.cfg
I had tried to set feature.1.enabled="1" in sip.cfg which I had seen referenced multiple times, however it never seemed to enable the presence feature.

Step 4
Add the extension *270 to the list of watched extensions on your phone. You can provision it via config file, or set up the extension in the local directory on the phone, and while doing so, scroll down to the "Buddy Watch" option and toggle it to "Enabled"

Thursday, October 17, 2013

Increasing the number of FreePBX Call Flow Control Options

We're starting to retool our VOIP infrastructure, and consolidate down from 30+ Trixbox servers to a single FreePBX server. One issue with the amalgamation however is that by default, FreePBX only allows you 10 Call Flow Control indexes. Since we have some 30 odd sites, this wouldn't work for us. However, this fantastic blog post: http://sysadminman.net/blog/2013/daynight-aka-call-flow-control-more-than-10-4884 shows you how to increase that to the number of your choosing. I'll repost the gist here.

1. copy /var/www/html/admin/modules/daynight/functions.inc.php to /var/www/html/admin/modules/daynight/functions.inc.php.dist (good to have a backup)
2. edit /var/www/html/admin/modules/daynight/functions.inc.php and find function daynight_get_avail() { (should start around line 258)
3. under global $db; add another line that reads $NUMDAYNIGHT = 99;
4. comment out the section that starts for ($i=0;$i<=9;$i++) { and ends return $list; (since this is PHP, just add // to the start of those lines
5. under the commented out lines, add the following:

         for ($i=0;$i<=($NUMDAYNIGHT-1);$i++) {
                 $n=ceil(log10($NUMDAYNIGHT));
                 $format="%0".$n."d";
                 $j=sprintf($format,$i);
                 if (!in_array($j,$results)) {
                         $list[]=$j;
                 }
         }
         return $list;

The whole block of code from line 258 function daynight_get_avail() { to line 284 } should look like:

function daynight_get_avail() {
        global $db;
        $NUMDAYNIGHT = 99;

        $sql = "SELECT ext FROM daynight ORDER BY ext";
        $results = $db->getCol($sql);
        if(DB::IsError($results)) {
                $results = array();
        }

//      for ($i=0;$i<=9;$i++) {
//              if (!in_array($i,$results)) {
//                      $list[]=$i;
//              }
//      }
//      return $list;

        for ($i=0;$i<=($NUMDAYNIGHT-1);$i++) {
                $n=ceil(log10($NUMDAYNIGHT));
                $format="%0".$n."d";
                $j=sprintf($format,$i);
                if (!in_array($j,$results)) {
                        $list[]=$j;
                }
        }
        return $list;
}

Now if you go to Applications -> Call Flow Control you will have 99 options for Flow Toggles!