Changing MacOS Location based on SSID - check current location before changing - bash

In this thread I received some assistance with getting this script to work correctly. The script essentially sets my network location according to the SSID I'm connected to. This is now working, however, it generates a lot of nuisance notifications.
Every time my laptop joins a wifi network, the script runs, sets the network location, and gives me a notification. Since power nap periodically joins the wifi to check for emails/updates and what have you, after a long weekend I'll get dozens of identical notifications.
How can I modify the script so that it only send a notification if the network location is changed to something different, not just when the script runs? Can I somehow check the existing network location and only change it/trigger a notification if the "new" location is different to the "existing" location?
Again, I'm extremely new to scripting on mac and GitHub in general; my previous experience is all on Windows and largely self taught.
Script:
#!/bin/bash
# automatically change configuration of Mac OS X based on location
# redirect all IO to a logfile
mkdir -p /usr/local/var/log
exec &>/usr/local/var/log/locationchanger.log
# get a little breather before we get data for things to settle down
sleep 2
# get SSID
SSID=$(/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -I | sed -n 's/^ *SSID: //p')
echo $(date) "New SSID found: $SSID"
# LOCATIONS
LOCATION=
Location_Automatic="Automatic"
Location_Office="Office"
Location_Site="Site"
# SSIDS
SSID_Office="My Office SSID"
SSID_Site="My Mobile SSID"
# SSID -> LOCATION mapping
case $SSID in
"$SSID_Office") LOCATION="$Location_Office";;
"$SSID_Site" ) LOCATION="$Location_Site";;
esac
REASON="SSID changed to $SSID"
# Location_Automatic
if [ -z "$LOCATION" ]; then
LOCATION="$Location_Automatic"
REASON="Automatic Fallback"
fi
# change network location
scselect "$LOCATION"
case $LOCATION in
"$Location_Automatic" )
osascript -e 'display notification "Network Location Changed to Automatic" with title "Network Location Changed"'
;;
"$Location_Office" )
osascript -e 'display notification "Network Location Changed to Office" with title "Network Location Changed"'
;;
"$Location_Site" )
osascript -e 'display notification "Network Location Changed to Site" with title "Network Location Changed"'
;;
esac
echo "--> Location Changer: $LOCATION - $REASON"
exit 0

This thread explains how to get the current network location.
I added the following code to get the current network location before making any changes:
CurrLoc=$(scselect | awk '{if ($1=="*") print $3}' | sed 's/[()]//g')
And then a simple if statement to exit the script early if the evaluated "new" network location matched the existing one:
if [ "$CurrLoc" = "$LOCATION" ]
then
exit 0
fi
# existing code to change network location and show notifications

Related

Trying to get BASH backup script to find an IP address based off known MAC address?

I have a small BASH backup script that uses Rsync to grab a handful of computers on my LAN. It works very well for static devices using an Ethernet cable - trouble comes in for my even smaller number of Laptop users that have docks. Every once in a while they do not connect to the Dock & Ethernet cable/statically assigned address and end up on the WiFi with a DHCP assigned address. I already have a list of known statically assigned targets in a file that is parsed through to actually backed up. So I keep thinking I should fairly easily be able to create a second file with an nmap scan before each backup run with other code I found - something like:
sudo nmap -n -sP 192.168.2.0/24 | awk '/Nmap scan report for/{printf $5;}/MAC Address:/{print " => "$3;}' | sort
which gives me a list of 192.168.2.101 => B4:FB:E4:FE:C6:6F for all found devices in the LAN. I just removed the | sort and send it to a file > found.devices instead.
So Now I have a list of found devices IP and MAC address - and I'd like to compare the two files and create a new target list with any changed IP addresses found (for those Laptop users that forgot to connect to the Dock and are now using DHCP). But I still want to keep my original targets file clean for the times that they do remember and also continue to get those other devices that are wired all the time while ignoring everything else on the LAN.
found.devices
192.168.2.190 => D4:XB:E4:FE:C6:6F
192.168.2.102 => B4:QB:Y4:FE:C6:6F
192.168.2.200 => B4:FB:P4:ZE:C6:6F
192.168.2.104 => B4:FB:E4:BE:P6:6F
known.targets
192.168.2.101 D4:XB:E4:FE:C6:6F domain source destination
192.168.2.102 B4:QB:Y4:FE:C6:6F domain source destination
192.168.2.103 B4:FB:P4:ZE:C6:6F domain source destination
192.168.2.104 B4:FB:E4:BE:P6:6F domain source destination
Should get a list or a file for the current back run to use of:
192.168.2.190 domain source destination
192.168.2.102 domain source destination
192.168.2.200 domain source destination
192.168.2.104 domain source destination
Currently my bash script just reads the file of known.targets one line at a time:
cat /known.targets | while read ip hostname source destination
do
this mounts and backs up the data I want ...
I really like the current system, and have found it to be very reliable for my simple needs, just need to find some way to get those users that intermittently forget to dock. I expect its series of nested loops, but I cannot get my head to wrap around it - been away from actual coding for too long - Any suggestions would be greatly appreciated. I'd also really like to get rid of the => and just use comma or space separated data but every time I mess with that awk statement - I end up shifting the data and getting an oddly placed CR somewhere I cannot figure it either!
Try this pure Bash code:
declare -A found_mac2ip
while read -r ip _ mac; do
[[ -n $mac ]] && found_mac2ip[$mac]=$ip
done <'found.devices'
while read -r ip mac domain source destination; do
ip=${found_mac2ip[$mac]-$ip}
# ... DO BACKUP FOR $ip ...
done <'known.targets'
It first sets up a Bash associative array mapping found mac address to ip addresses.
It then loops through the known.targets file and for each mac address it uses the ip address from the known.targets file if the mac address is listed in it. Otherwise it uses the ip address read from the known.targets file.
It's also possible to extract the "found" MAC and IP address information by getting it directly from the nmap output instead of from a `found.devices' file. This alternative version of the code does that:
declare -A found_mac2ip
nmap_output=$(sudo nmap -n -sP 192.168.2.0/24)
while IFS=$' \t\n()' read -r f1 f2 f3 f4 f5 _; do
[[ "$f1 $f2 $f3 $f4" == 'Nmap scan report for' ]] && ip=$f5
[[ "$f1 $f2" == 'MAC Address:' ]] && found_mac2ip[$f3]=$ip
done <<<"$nmap_output"
while read -r ip mac domain source destination; do
ip=${found_mac2ip[$mac]-$ip}
# ... DO BACKUP FOR $ip ...
done <'known.targets'
UPDATE: per comment from OP, dropping assumption (and associated code) about keeping an IP address that shows up in found.devices but without a match in known.targets (ie, this cannot happen)
Assumptions:
start with a list of IP/MAC addresses from known.targets
if a MAC address also shows up in found.devices then the IP address from found.devices takes precendence
Adding a standalone entry to both files:
$ cat known.targets
192.168.2.101 D4:XB:E4:FE:C6:6F domain source destination
192.168.2.102 B4:QB:Y4:FE:C6:6F domain source destination
192.168.2.103 B4:FB:P4:ZE:C6:6F domain source destination
192.168.2.104 B4:FB:E4:BE:P6:6F domain source destination
111.111.111.111 AA:BB:CC:DD:EE:FF domain source destination
$ cat found.devices
192.168.2.190 => D4:XB:E4:FE:C6:6F
192.168.2.102 => B4:QB:Y4:FE:C6:6F
192.168.2.200 => B4:FB:P4:ZE:C6:6F
192.168.2.104 => B4:FB:E4:BE:P6:6F
222.222.222.222 => FF:EE:CC:BB:AA:11
One awk idea:
$ cat ip.awk
FNR==NR { ip[$2]=$1; dsd[$2]=$3 FS $4 FS $5; next }
$3 in ip { ip[$3]=$1 }
END { for (mac in ip) print ip[mac],dsd[mac] }
Running against our files:
$ awk -f ip.awk known.targets found.devices
192.168.2.200 domain source destination
192.168.2.190 domain source destination
192.168.2.104 domain source destination
111.111.111.111 domain source destination
192.168.2.102 domain source destination
Feeding this to a while loop:
while read ip hostname source destination
do
echo "${ip} : ${hostname} : ${source} : ${destination}"
done < <(awk -f ip.awk known.targets found.devices)
This generates:
192.168.2.200 : domain : source : destination
192.168.2.190 : domain : source : destination
192.168.2.104 : domain : source : destination
111.111.111.111 : domain : source : destination
192.168.2.102 : domain : source : destination

What could prevent frequently switching default source ip of a machine with several interfaces

The goal was to frequently change default outgoing source ip on a machine with multiple interfaces and live ips.
I used ip route replace default as per its documentation and let a script run in loop for some interval. It changes source ip fine for a while but then all internet access to the machine is lost. It has to be remotely rebooted from a web interface to have any thing working
Is there any thing that could possibly prevent this from working stably. I have tried this on more than one servers?
Following is a minimum example
# extract all currently active source ips except loopback
IPs="$(ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 |
awk '{ print $1}')"
read -a ip_arr <<<$IPs
# extract all currently active mac / ethernet addresses
Int="$(ifconfig | grep 'eth'| grep -v 'lo' | awk '{print $1}')"
read -a eth_arr <<<$Int
ip_len=${#ip_arr[#]}
eth_len=${#eth_arr[#]}
i=0;
e=0;
while(true); do
#ip route replace 0.0.0.0 dev eth0:1 src 192.168.1.18
route_cmd="ip route replace 0.0.0.0 dev ${eth_arr[e]} src ${ip_arr[i]}"
echo $route_cmd
eval $route_cmd
sleep 300
(i++)
(e++)
if [ $i -eq $ip_len ]; then
i=0;
e=0;
echo "all ips exhausted - starting from first again"
# break;
fi
done
I wanted to comment, but as I'm not having enough points, it won't let me.
Consider:
Does varying the delay time before its run again change the number of iterations before it fails?
Exporting the ifconfig & routes every time you change it, to see if something is meaningfully different over time. Maybe some basic tests to it (ping, nslookup, etc) Basically, find what is exactly going wrong with it. Also exporting the commands you send to a logfile (text file per change?) to see changes in them to see if some is different after x iterations.
What connectivity is lost? Incoming? Outgoing? Specific applications?
You say you use/do this on other servers without problems?
Are the IP's: Static (/etc/network/interfaces), bootp/DHCP, semi-static (bootp/DHCP server serving, based on MAC address), and if served by bootp/DHCP, what is the lease duration?
On the last remark:
bootp/dhcp will give IP's for x duration. say its 60 minutes. After half that time it will "check" with the bootp/dhcp server if it can keep the IP, and extend the lease to 60 minutes again, this can mean a small reconfig on the ifconfig (maybe even at the same time of your script?).
hth

OS X Yosemite - Adding a Printer - UI Vs lpadmin

My problem is that when I add a printer using the Printers and Scanners UI printing works, when I add the same printer using lpadmin it doesn't.
To Add it through the UI I did the following:
From Printers and Scanners I selected the IP tab.
Address: 10.20.30.40, Protocol HP Jetdirect - Socket, Queue left blank, Name: TEST_01, Location "Top Floor", Use -> Select software -> HP LaserJet P3010 Series
After doing this, the Printer works as expected.
This is a (segment from a) script containing my lpadmin command that doesn't work
SUBNET=socket://10.20.30.
TEST_01=40
PPD_DIR=/Library/Printers/PPDs/Contents/Resources
TEST_01_PPD="hp LaserJet P3010 Series.gz"
lpadmin -E -p TEST_01 -v $SUBNET$TEST_01 -P "$PPD_DIR/$TEST_01_PPD" -D "TEST_01" -L "Top Floor"
The printer appears correctly in the UI but shows as paused.
I did find a message in system.log that may or may not be relevant - I was using Notes to test the printer:
Notes[502]: Failed to connect (_delegate) outlet from (com_hp_psdriver_19_11_0_PDEView) to (com_hp_psdriver_19_11_0_PDEAccountingController): missing setter or instance variable
Notes[2198]: Printing failed because PMSessionEndDocumentNoDialog() returned -30871.
The reason I want to use a script is that there are 20 printers to add on each of 30 new Macs. The actual script uses a series of arrays with lpadmin in a for loop. Everything I have read says it should work. What am I missing?
I think -E specified before the printer name enables encryption, whereas specified after it Enables the printer - effectively "unpausing" it. Madness- I know!
Mad Apple Documentation - see second sentence
I think you want:
lpadmin -p TEST_01 -v $SUBNET$TEST_01 -P "$PPD_DIR/$TEST_01_PPD" -D "TEST_01" -L "Top Floor" -E
I don't have a direct answer, but I can suggest an alternate approach: set up all 20 printers by hand on one computer, then copy the /etc/cups directory from that one to the other 29.

Bash case not properly evaluating value

The Problem
I have a script that has a case statement which I'm expecting to execute based on the value of a variable. The case statement appears to either ignore the value or not properly evaluate it instead dropping to the default.
The Scenario
I pull a specific character out of our server hostnames which indicates where in our environment the server resides. We have six different locations:
Management(m): servers that are part of the infrastructure such as monitoring, email, ticketing, etc
Development(d): servers that are for developing code and application functionality
Test(t): servers that are used for initial testing of the code and application functionality
Implementation(i): servers that the code is pushed to for pre-production evaluation
Production(p): self-explanatory
Services(s): servers that the customer needs to integrate that provide functionality across their project. These are separate from the Management servers in that these are customer servers while Management servers are owned and operated by us.
After pulling the character from the hostname I pass it to a case block. I expect the case block to evaluate the character and add a couple lines of text to our rsyslog.conf file. What is happening instead is that the case block returns the default which does nothing but tell the person building the server to manually configure the entry due to an unrecognized character.
I've tested this manually against a server I recently built and verified that the character I am pulling from the hostname (an 's') is expected and accounted for in the case block.
The Code
# Determine which environment our server resides in
host=$(hostname -s)
env=${host:(-8):1}
OLDFILE=/etc/rsyslog.conf
NEWFILE=/etc/rsyslog.conf.new
# This is the configuration we need on every server regardless of environment
read -d '' common <<- EOF
...
TEXT WHICH IS ADDED TO ALL CONFIG FILES REGARDLESS OF FURTHER CODE EXECUTION
SNIPPED
....
EOF
# If a server is in the Management, Dev or Test environments send logs to lg01
read -d '' lg01conf <<- EOF
# Relay messages to lg01
*.notice ##xxx.xxx.xxx.100
#### END FORWARDING RULE ####
EOF
# If a server is in the Imp, Prod or is a non-affiliated Services zone server send logs to lg02
read -d '' lg02conf <<- EOF
# Relay messages to lg02
*.notice ##xxx.xxx.xxx.101
#### END FORWARDING RULE ####
EOF
# The general rsyslog configuration remains the same; pull it out and write it to a new file
head -n 63 $OLDFILE > $NEWFILE
# Add the common language to our config file
echo "$common" >> $NEWFILE
# Depending on which environment ($env) our server is in, add the appropriate
# remote log server to the configuration with the $common settings.
case $env in
m) echo "$lg01conf" >> $NEWFILE;;
d) echo "$lg01conf" >> $NEWFILE;;
t) echo "$lg01conf" >> $NEWFILE;;
i) echo "$lg02conf" >> $NEWFILE;;
p) echo "$lg02conf" >> $NEWFILE;;
s) echo "$lg02conf" >> $NEWFILE;;
*) echo "Unknown environment; Manually configure"
esac
# Keep a dated backup of the original rsyslog.conf file
cp $OLDFILE $OLDFILE.$(date +%Y%m%d)
# Replace the original rsyslog.conf file with the new version
mv $NEWFILE $OLDFILE
An Aside
I've already determined that I can combine the different groups of code from the case block onto single lines (a total of two) using the | operator. I've listed it in the manner above since this is how it is coded while I'm having issues with it.
I can't see what's wrong with your code. Maybe add another ;; to the default clause. To find the problem add a set -vx as a first line. Will show you lots of debug information.

Loop for checking string change in system function output (monitoring a DNS update)

I am switching DNS servers and I'd like to write a short ruby script that runs every 10s and triggers a local Mac OS X system notification as soon as my website resolves to a different IP.
Using terminal-notifier sending a system notification is as easy as this
terminal-notifier -message "DNS Changed"
I'd like to trigger it as soon as the output of
ping -i 10 mywebsite.com
... changes or simply does not contain a defined IP string anymore.
> 64 bytes from 12.34.56.789: icmp_seq=33 ttl=41 time=241.564 ms
in this case "12.34.56.789".
How do I monitor the change of the output string of the ping -i 10 mywebsite.com and call the notification function once a change has been detected?
I thought this might be a nice practice while waiting for the DNS to be updated.
Try this:
IP = "12.34.56.789"
p = IO.popen("ping -i 10 mywebsite.com")
p.each_line do |l|
if(! l =~ /from #{IP}/) #The IP has changed
system("terminal-notifier -message \"DNS Changed\"")
end
end

Resources