Get user’s location based on their public IP

La Clementine
3 min readAug 19, 2023

I decided to merge an old script of mine with the one from William Smith. It pulls GeoIP data of a Mac (country, city, zip, isp…) and write them in /Users/Shared/ip_location.plist. It should be reasonably accurate at country level, but beyond that, it’s essentially a gamble. The script should not be relied upon as a source of truth, but rather as an initial step in investigating why the computer is appearing in an unexpected location.

When using the free tier of ip-api.com, there is a restriction of 45 HTTP requests per minute from an IP address. The main improvement of my script is its ability to save an API query if the public IP remains unchanged since the last check.

Make it run once per day in a policy and use the Jamf Extension Attribute at the bottom of the page to collect the data.

#!/bin/zsh

###
#
# Created by : La Clémentine · https://medium.com/@laclementine/
# Original code : William Smith · https://gist.github.com/talkingmoose/7d1bf4f884ca08f95fd3baf0014fc639
# Last Modified : 2023-08-19
# Version : 1.0
# Tested with : macOS Ventura 13.5.1
#
###

# The xpath tool changed in Big Sur
xpath() {
if [[ $( /usr/bin/sw_vers -buildVersion) > "20A" ]]; then
/usr/bin/xpath -e "$@"
else
/usr/bin/xpath "$@"
fi
}

# Get public IP
echo "Fetching IP from AWS"
PUBLIC_IP=$(curl --connect-timeout 5 -L -s checkip.amazonaws.com)

if [ "$PUBLIC_IP" = "" ]; then
echo "Fetching IP from v4.ipv6-test.com"
PUBLIC_IP=$(curl --connect-timeout 5 -L -s http://v4.ipv6-test.com/api/myip.php)
fi

if [ "$PUBLIC_IP" = "" ]; then
echo "Fetching IP from ipinfo.io"
PUBLIC_IP=$(curl --connect-timeout 5 -L -s https://ipinfo.io/ip)
fi

if [ "$PUBLIC_IP" = "" ]; then
echo "Fetching IP from ifconfig.me"
PUBLIC_IP=$(curl --connect-timeout 5 -L -s ifconfig.me)
fi

if [ "$PUBLIC_IP" = "" ]; then
echo "Fetching IP has failed. Abort, Abort!"
exit 1
else
echo "Public IP is $PUBLIC_IP"
fi

# Read old IP
if [ -e "/Users/Shared/ip_location.plist" ]; then
OLD_PUBLIC_IP=$(defaults read "/Users/Shared/ip_location.plist" "ip")
echo "Old public IP is $OLD_PUBLIC_IP"
else
echo "There is no old records"
fi

if [ "$PUBLIC_IP" = "$OLD_PUBLIC_IP" ]; then
echo "IPs are the same. Saving an API query. Exit."
exit 0
else
echo "IPs are different. Proceed."
fi

# Get GeoIP data
locationData=$( /usr/bin/curl http://ip-api.com/xml/$PUBLIC_IP \
--location \
--silent \
--max-time 10 )

locationPieces=( query country countryCode regionName city zip lat lon timezone isp org as asname )

for anItem in $locationPieces
do
export $anItem="$( xpath "/query/$anItem/text()" 2>/dev/null <<< "$locationData" )"
done

echo "Query is $query"
defaults write "/Users/Shared/ip_location.plist" ip -string "$query"

echo "Country is $country"
defaults write "/Users/Shared/ip_location.plist" country -string "$country"

echo "CountryCode is $countryCode"
defaults write "/Users/Shared/ip_location.plist" countryCode -string "$countryCode"

echo "Region Name is $regionName"
defaults write "/Users/Shared/ip_location.plist" regionName -string "$regionName"

echo "City is $city"
defaults write "/Users/Shared/ip_location.plist" city -string "$city"

echo "ZIP is $zip"
defaults write "/Users/Shared/ip_location.plist" zip -string "$zip"

echo "Latitude is $lat"
defaults write "/Users/Shared/ip_location.plist" latitude -string "$lat"

echo "Longitude is $lon"
defaults write "/Users/Shared/ip_location.plist" longitude -string "$lon"

echo "Timezone is $timezone"
defaults write "/Users/Shared/ip_location.plist" timezone -string "$timezone"

echo "ISP is $isp"
defaults write "/Users/Shared/ip_location.plist" isp -string "$isp"

echo "Organization is $org"
defaults write "/Users/Shared/ip_location.plist" org -string "$org"

echo "AS is $as"
defaults write "/Users/Shared/ip_location.plist" as -string "$as"

echo "AS name is $asname"
defaults write "/Users/Shared/ip_location.plist" asname -string "$asname"

# Optional : Hide file and make it readable
# chflags hidden "/Users/Shared/ip_location.plist"
# chmod 444 "/Users/Shared/ip_location.plist"

exit 0

In Jamf : ⚙️ > Computer management > Extension Attributes > New
Display name : Country (change line 5 if you want to collect something else)
Data type : String
Inventory display : User and Location
Input Type : Script

#!/bin/bash

# Extension Attribute for Jamf
# Possible values : ip · country · countryCode · regionName · city · zip · latitude · longitude · timezone · isp · org · as · asname
value="country"

if [ -e "/Users/Shared/ip_location.plist" ]; then
GEOIPDATA=$(defaults read "/Users/Shared/ip_location.plist" "$value")
echo "<result>$GEOIPDATA</result>"
else
echo "<result></result>"
fi

exit 0

--

--

La Clementine

Posts about Jamf, scripts and tutorials about managing Mac in organizations and workplaces.