Device Location Tracking on macOS
Track and monitor the geographical location of your MacFleet devices using IP address geolocation services. This tutorial covers location discovery, security monitoring, geofencing policies, and compliance reporting for enterprise device management.
Understanding IP-Based Location Tracking
IP geolocation provides valuable information about device location and network environment, essential for enterprise security and compliance. This method tracks devices based on their internet connection rather than GPS, making it suitable for fleet management and security monitoring.
Key Information Retrieved
- IP Address - Public IP address of the device
- Geographic Location - City, region, and country
- Coordinates - Latitude and longitude for mapping
- Network Provider - ISP or organization details
- Timezone - Local timezone information
- Security Context - Network environment assessment
Basic Location Discovery
Simple IP Geolocation
#!/bin/bash
# Basic location lookup using IP address
curl -s https://ipinfo.io
echo "Location information retrieved successfully"
Enhanced Location Query
#!/bin/bash
# Enhanced location discovery with formatted output
get_device_location() {
echo "📍 Retrieving device location information..."
# Query location data
local location_data
location_data=$(curl -s https://ipinfo.io)
if [[ $? -eq 0 && -n "$location_data" ]]; then
echo "✅ Location data retrieved successfully:"
echo "$location_data" | jq '.' 2>/dev/null || echo "$location_data"
else
echo "❌ Failed to retrieve location information"
return 1
fi
}
# Execute location discovery
get_device_location
Structured Location Information
#!/bin/bash
# Parse and display structured location information
parse_location_data() {
echo "🌍 Device Location Analysis"
echo "=========================="
# Get location data
local location_json
location_json=$(curl -s https://ipinfo.io)
if [[ $? -ne 0 ]]; then
echo "❌ Failed to retrieve location data"
return 1
fi
# Parse individual fields
local ip city region country loc org timezone
ip=$(echo "$location_json" | jq -r '.ip // "Unknown"')
city=$(echo "$location_json" | jq -r '.city // "Unknown"')
region=$(echo "$location_json" | jq -r '.region // "Unknown"')
country=$(echo "$location_json" | jq -r '.country // "Unknown"')
loc=$(echo "$location_json" | jq -r '.loc // "Unknown"')
org=$(echo "$location_json" | jq -r '.org // "Unknown"')
timezone=$(echo "$location_json" | jq -r '.timezone // "Unknown"')
# Display formatted information
echo "🔗 IP Address: $ip"
echo "🏙️ City: $city"
echo "🗺️ Region: $region"
echo "🌎 Country: $country"
echo "📍 Coordinates: $loc"
echo "🏢 Organization: $org"
echo "🕐 Timezone: $timezone"
# Additional security information
echo ""
echo "🔒 Security Assessment:"
if [[ "$org" == *"VPN"* ]] || [[ "$org" == *"Proxy"* ]]; then
echo " ⚠️ Potential VPN/Proxy detected"
else
echo " ✅ Direct internet connection"
fi
}
# Execute location parsing
parse_location_data
Advanced Location Tracking
Multi-Source Location Verification
#!/bin/bash
# Verify location using multiple geolocation services
verify_location_multi_source() {
echo "🔍 Multi-source location verification..."
# Service 1: ipinfo.io
echo "Checking ipinfo.io..."
local ipinfo_data
ipinfo_data=$(curl -s https://ipinfo.io)
# Service 2: ipapi.co (alternative)
echo "Checking ipapi.co..."
local ipapi_data
ipapi_data=$(curl -s https://ipapi.co/json/)
# Service 3: ip-api.com (backup)
echo "Checking ip-api.com..."
local ipapi_alt_data
ipapi_alt_data=$(curl -s http://ip-api.com/json/)
# Compare results
echo ""
echo "📊 Location Verification Results:"
echo "================================="
if [[ -n "$ipinfo_data" ]]; then
echo "ipinfo.io Results:"
echo "$ipinfo_data" | jq '.city, .region, .country' 2>/dev/null || echo "$ipinfo_data"
echo ""
fi
if [[ -n "$ipapi_data" ]]; then
echo "ipapi.co Results:"
echo "$ipapi_data" | jq '.city, .region, .country_name' 2>/dev/null || echo "$ipapi_data"
echo ""
fi
if [[ -n "$ipapi_alt_data" ]]; then
echo "ip-api.com Results:"
echo "$ipapi_alt_data" | jq '.city, .regionName, .country' 2>/dev/null || echo "$ipapi_alt_data"
fi
}
# Execute multi-source verification
verify_location_multi_source
Location Change Detection
#!/bin/bash
# Monitor and detect location changes
monitor_location_changes() {
local previous_location_file="/tmp/last_known_location.json"
local current_location
echo "📱 Monitoring device location changes..."
# Get current location
current_location=$(curl -s https://ipinfo.io)
if [[ $? -ne 0 ]]; then
echo "❌ Failed to retrieve current location"
return 1
fi
# Check if previous location exists
if [[ -f "$previous_location_file" ]]; then
local previous_location
previous_location=$(cat "$previous_location_file")
# Compare locations
local current_city previous_city
current_city=$(echo "$current_location" | jq -r '.city // "Unknown"')
previous_city=$(echo "$previous_location" | jq -r '.city // "Unknown"')
if [[ "$current_city" != "$previous_city" ]]; then
echo "🚨 Location change detected!"
echo "Previous: $previous_city"
echo "Current: $current_city"
# Log the change
echo "$(date): Location changed from $previous_city to $current_city" >> /var/log/location_changes.log
else
echo "✅ No location change detected"
fi
else
echo "📝 First time location check - saving baseline"
fi
# Save current location as previous
echo "$current_location" > "$previous_location_file"
}
# Execute location monitoring
monitor_location_changes
Enterprise Location Management Script
#!/bin/bash
# MacFleet Device Location Management System
# Comprehensive location tracking and security monitoring for enterprise devices
# Configuration
LOG_FILE="/var/log/macfleet_location.log"
CONFIG_FILE="/etc/macfleet/location_policy.conf"
LOCATION_DB="/var/lib/macfleet/location_history.db"
ALERT_THRESHOLD_MILES=100
# Logging function
log_action() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# Setup directories and files
setup_environment() {
sudo mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null
sudo mkdir -p "$(dirname "$CONFIG_FILE")" 2>/dev/null
sudo mkdir -p "$(dirname "$LOCATION_DB")" 2>/dev/null
# Create location database if it doesn't exist
if [[ ! -f "$LOCATION_DB" ]]; then
echo "timestamp,ip,city,region,country,lat,lon,org,timezone" > "$LOCATION_DB"
fi
}
# Load configuration
load_config() {
if [[ -f "$CONFIG_FILE" ]]; then
source "$CONFIG_FILE"
log_action "Configuration loaded from $CONFIG_FILE"
else
# Default configuration
LOCATION_TRACKING_ENABLED=true
GEOFENCE_ENABLED=false
ALLOWED_COUNTRIES=("US" "CA" "GB")
BLOCKED_NETWORKS=("VPN" "TOR" "PROXY")
MONITORING_INTERVAL=3600 # 1 hour
REPORT_EMAIL="admin@company.com"
log_action "Using default location configuration"
fi
}
# Get comprehensive location information
get_location_info() {
local provider="${1:-ipinfo.io}"
case "$provider" in
"ipinfo.io")
curl -s https://ipinfo.io
;;
"ipapi.co")
curl -s https://ipapi.co/json/
;;
"ip-api.com")
curl -s http://ip-api.com/json/
;;
*)
curl -s https://ipinfo.io
;;
esac
}
# Validate location data
validate_location() {
local location_data="$1"
# Check if data is valid JSON
if ! echo "$location_data" | jq empty 2>/dev/null; then
log_action "Invalid location data format"
return 1
fi
# Check for required fields
local ip city country
ip=$(echo "$location_data" | jq -r '.ip // empty')
city=$(echo "$location_data" | jq -r '.city // empty')
country=$(echo "$location_data" | jq -r '.country // empty')
if [[ -z "$ip" ]] || [[ -z "$city" ]] || [[ -z "$country" ]]; then
log_action "Missing required location fields"
return 1
fi
return 0
}
# Store location in database
store_location() {
local location_data="$1"
if ! validate_location "$location_data"; then
return 1
fi
# Parse location data
local timestamp ip city region country lat lon org timezone
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
ip=$(echo "$location_data" | jq -r '.ip')
city=$(echo "$location_data" | jq -r '.city')
region=$(echo "$location_data" | jq -r '.region // ""')
country=$(echo "$location_data" | jq -r '.country')
lat=$(echo "$location_data" | jq -r '.loc | split(",")[0] // ""')
lon=$(echo "$location_data" | jq -r '.loc | split(",")[1] // ""')
org=$(echo "$location_data" | jq -r '.org // ""')
timezone=$(echo "$location_data" | jq -r '.timezone // ""')
# Append to database
echo "$timestamp,$ip,$city,$region,$country,$lat,$lon,$org,$timezone" >> "$LOCATION_DB"
log_action "Location stored: $city, $region, $country"
}
# Check geofence compliance
check_geofence() {
local location_data="$1"
local country
country=$(echo "$location_data" | jq -r '.country')
if [[ "$GEOFENCE_ENABLED" == "true" ]]; then
# Check if country is in allowed list
local allowed=false
for allowed_country in "${ALLOWED_COUNTRIES[@]}"; do
if [[ "$country" == "$allowed_country" ]]; then
allowed=true
break
fi
done
if [[ "$allowed" == "false" ]]; then
log_action "ALERT: Device outside allowed geofence - Country: $country"
send_security_alert "Geofence violation" "Device detected in unauthorized country: $country"
return 1
fi
fi
return 0
}
# Check for suspicious networks
check_network_security() {
local location_data="$1"
local org
org=$(echo "$location_data" | jq -r '.org')
# Check against blocked networks
for blocked_network in "${BLOCKED_NETWORKS[@]}"; do
if [[ "$org" == *"$blocked_network"* ]]; then
log_action "SECURITY ALERT: Suspicious network detected - $org"
send_security_alert "Suspicious Network" "Device connected via potentially risky network: $org"
return 1
fi
done
return 0
}
# Send security alerts
send_security_alert() {
local alert_type="$1"
local message="$2"
local hostname
hostname=$(hostname)
# Log the alert
log_action "SECURITY ALERT [$alert_type]: $message"
# Send email alert (if configured)
if command -v mail >/dev/null 2>&1 && [[ -n "$REPORT_EMAIL" ]]; then
echo "Security Alert from MacFleet Device: $hostname
Alert Type: $alert_type
Message: $message
Timestamp: $(date)
Device: $hostname
This is an automated security alert from MacFleet Location Monitoring." | \
mail -s "MacFleet Security Alert: $alert_type" "$REPORT_EMAIL"
fi
}
# Calculate distance between coordinates
calculate_distance() {
local lat1="$1" lon1="$2" lat2="$3" lon2="$4"
# Use python for more accurate calculation if available
if command -v python3 >/dev/null 2>&1; then
python3 -c "
import math
def haversine(lat1, lon1, lat2, lon2):
R = 3959 # Earth's radius in miles
dlat = math.radians(float(lat2) - float(lat1))
dlon = math.radians(float(lon2) - float(lon1))
a = math.sin(dlat/2)**2 + math.cos(math.radians(float(lat1))) * math.cos(math.radians(float(lat2))) * math.sin(dlon/2)**2
c = 2 * math.asin(math.sqrt(a))
return R * c
print(f'{haversine($lat1, $lon1, $lat2, $lon2):.2f}')
"
else
echo "0"
fi
}
# Monitor location changes
monitor_location() {
log_action "Starting location monitoring..."
while true; do
# Get current location
local current_location
current_location=$(get_location_info)
if validate_location "$current_location"; then
# Store location
store_location "$current_location"
# Security checks
check_geofence "$current_location"
check_network_security "$current_location"
# Check for significant movement
check_location_change "$current_location"
else
log_action "Failed to retrieve valid location data"
fi
sleep "$MONITORING_INTERVAL"
done
}
# Check for significant location changes
check_location_change() {
local current_location="$1"
local last_location_file="/tmp/last_location.json"
if [[ -f "$last_location_file" ]]; then
local previous_location
previous_location=$(cat "$last_location_file")
# Get coordinates
local curr_lat curr_lon prev_lat prev_lon
curr_lat=$(echo "$current_location" | jq -r '.loc | split(",")[0] // ""')
curr_lon=$(echo "$current_location" | jq -r '.loc | split(",")[1] // ""')
prev_lat=$(echo "$previous_location" | jq -r '.loc | split(",")[0] // ""')
prev_lon=$(echo "$previous_location" | jq -r '.loc | split(",")[1] // ""')
if [[ -n "$curr_lat" && -n "$curr_lon" && -n "$prev_lat" && -n "$prev_lon" ]]; then
local distance
distance=$(calculate_distance "$prev_lat" "$prev_lon" "$curr_lat" "$curr_lon")
if (( $(echo "$distance > $ALERT_THRESHOLD_MILES" | bc -l) )); then
log_action "ALERT: Significant location change detected - Distance: ${distance} miles"
send_security_alert "Location Change" "Device moved ${distance} miles from previous location"
fi
fi
fi
# Save current location
echo "$current_location" > "$last_location_file"
}
# Generate location report
generate_location_report() {
local report_file="/tmp/macfleet_location_report_$(date +%Y%m%d_%H%M%S).txt"
local hostname
hostname=$(hostname)
{
echo "MacFleet Device Location Report"
echo "==============================="
echo "Generated: $(date)"
echo "Device: $hostname"
echo ""
echo "Current Location:"
local current_location
current_location=$(get_location_info)
if validate_location "$current_location"; then
echo "$current_location" | jq '.'
else
echo "Unable to retrieve current location"
fi
echo ""
echo "Location History (Last 10 entries):"
if [[ -f "$LOCATION_DB" ]]; then
tail -10 "$LOCATION_DB" | column -t -s ','
else
echo "No location history available"
fi
echo ""
echo "Security Events (Last 10):"
if [[ -f "$LOG_FILE" ]]; then
grep -i "alert\|security" "$LOG_FILE" | tail -10
else
echo "No security events logged"
fi
} > "$report_file"
echo "📄 Location report generated: $report_file"
log_action "Location report generated: $report_file"
}
# Main function
main() {
case "${1:-status}" in
"track")
setup_environment
load_config
log_action "=== Location Tracking: Single Check ==="
current_location=$(get_location_info)
if validate_location "$current_location"; then
store_location "$current_location"
check_geofence "$current_location"
check_network_security "$current_location"
echo "$current_location" | jq '.'
fi
;;
"monitor")
setup_environment
load_config
log_action "=== Location Monitoring: Continuous ==="
monitor_location
;;
"report")
setup_environment
load_config
generate_location_report
;;
"history")
if [[ -f "$LOCATION_DB" ]]; then
echo "📍 Location History:"
column -t -s ',' "$LOCATION_DB"
else
echo "No location history available"
fi
;;
"status"|*)
setup_environment
load_config
echo "📊 MacFleet Location Status:"
echo "============================"
current_location=$(get_location_info)
if validate_location "$current_location"; then
echo "$current_location" | jq '.'
else
echo "Unable to retrieve location information"
fi
;;
esac
}
# Execute main function with parameters
main "$@"
Location Tracking Commands
Quick Reference
Task | Command |
---|---|
Get current location | curl -s https://ipinfo.io |
Formatted location info | curl -s https://ipinfo.io | jq '.' |
Save location to file | curl -s https://ipinfo.io > location.json |
Get specific field | curl -s https://ipinfo.io | jq -r '.city' |
Location Data Fields
# Extract specific information
IP=$(curl -s https://ipinfo.io | jq -r '.ip')
CITY=$(curl -s https://ipinfo.io | jq -r '.city')
REGION=$(curl -s https://ipinfo.io | jq -r '.region')
COUNTRY=$(curl -s https://ipinfo.io | jq -r '.country')
COORDINATES=$(curl -s https://ipinfo.io | jq -r '.loc')
ORGANIZATION=$(curl -s https://ipinfo.io | jq -r '.org')
TIMEZONE=$(curl -s https://ipinfo.io | jq -r '.timezone')
Security and Compliance
Privacy Considerations
- Data Collection - Only collect necessary location data
- Storage Security - Encrypt location databases
- Access Control - Limit access to location information
- Retention Policy - Define data retention periods
- User Consent - Ensure proper authorization
Geofencing Implementation
#!/bin/bash
# Simple geofencing check
check_location_compliance() {
local location_data
location_data=$(curl -s https://ipinfo.io)
local country
country=$(echo "$location_data" | jq -r '.country')
case "$country" in
"US"|"CA"|"GB")
echo "✅ Location approved: $country"
;;
*)
echo "⚠️ Location outside approved regions: $country"
# Log security event
logger "MacFleet: Device in unauthorized location: $country"
;;
esac
}
Troubleshooting
Common Issues
-
Network connectivity problems
- Check internet connection
- Verify firewall settings
- Test alternative geolocation services
-
Inaccurate location data
- VPN/proxy connections affect accuracy
- ISP location may not match physical location
- Use multiple services for verification
-
API rate limits
- Implement proper delays between requests
- Use authenticated API keys when available
- Cache location data appropriately
Verification Commands
# Test network connectivity
ping -c 3 ipinfo.io
# Check if jq is installed
which jq || echo "jq not installed"
# Verify location service availability
curl -I https://ipinfo.io
Important Notes
- Accuracy limitations: IP geolocation provides approximate location, not precise coordinates
- Network dependencies: Requires internet connectivity to function
- Privacy compliance: Ensure compliance with local privacy laws and regulations
- VPN detection: Results may be affected by VPN or proxy services
- Rate limiting: Implement appropriate delays to avoid service rate limits
- Data storage: Securely store and manage collected location data
Enterprise Deployment
For enterprise deployment, consider:
- Policy definition - Establish clear location tracking policies
- Legal compliance - Ensure compliance with privacy regulations
- Security monitoring - Implement real-time location alerts
- Data governance - Define data retention and access policies
- User communication - Inform users about location tracking practices