Tutorial

New updates and improvements to Macfleet.

Disk Image Management on macOS

Create and manage disk images on your MacFleet devices for efficient data packaging, software distribution, and system deployment. This tutorial covers disk image creation from folders, advanced formatting options, encryption, compression, and enterprise-scale image management workflows.

Understanding Disk Images

Disk images are file containers that replicate the exact structure and content of storage volumes, providing powerful capabilities for enterprise deployment:

Benefits of Disk Images

  • Software Distribution - Package applications and configurations for deployment
  • Data Backup - Create portable snapshots of folder contents
  • System Deployment - Standardize configurations across multiple devices
  • Secure Transport - Encrypted images for sensitive data transfer
  • Version Control - Maintain different versions of software packages
  • Network Efficiency - Compressed images reduce transfer time

Disk Image Types

  • .dmg - Apple Disk Image (primary format)
  • .iso - ISO 9660 format for cross-platform compatibility
  • .sparseimage - Sparse images that grow as needed
  • .sparsebundle - Bundle format for network storage

Basic Disk Image Creation

Simple Folder to Disk Image

#!/bin/bash

# Create basic disk image from folder
SOURCE_FOLDER="$1"
IMAGE_NAME="$2"
OUTPUT_PATH="$3"

if [[ -z "$SOURCE_FOLDER" || -z "$IMAGE_NAME" || -z "$OUTPUT_PATH" ]]; then
    echo "❌ Usage: $0 <source_folder> <image_name> <output_path>"
    echo "Example: $0 /Users/Shared MyImage /Users/username/Documents"
    exit 1
fi

# Validate source folder exists
if [[ ! -d "$SOURCE_FOLDER" ]]; then
    echo "❌ Source folder does not exist: $SOURCE_FOLDER"
    exit 1
fi

# Create output directory if it doesn't exist
mkdir -p "$OUTPUT_PATH"

echo "💿 Creating disk image from folder..."
echo "Source: $SOURCE_FOLDER"
echo "Image name: $IMAGE_NAME"
echo "Output: $OUTPUT_PATH/$IMAGE_NAME.dmg"

# Create the disk image
if hdiutil create -fs HFS+ -srcfolder "$SOURCE_FOLDER" -volname "$IMAGE_NAME" "$OUTPUT_PATH/$IMAGE_NAME.dmg"; then
    echo "✅ Disk image created successfully"
    
    # Get image size
    IMAGE_SIZE=$(du -h "$OUTPUT_PATH/$IMAGE_NAME.dmg" | cut -f1)
    echo "Image size: $IMAGE_SIZE"
    
    # Get source folder size for comparison
    SOURCE_SIZE=$(du -sh "$SOURCE_FOLDER" | cut -f1)
    echo "Source folder size: $SOURCE_SIZE"
else
    echo "❌ Failed to create disk image"
    exit 1
fi

Advanced Disk Image Creation

#!/bin/bash

# Advanced disk image creation with options
create_advanced_disk_image() {
    local source_folder="$1"
    local image_name="$2"
    local output_path="$3"
    local filesystem="${4:-HFS+}"
    local compression="${5:-UDBZ}"
    local encryption="${6:-false}"
    
    if [[ ! -d "$source_folder" ]]; then
        echo "❌ Source folder not found: $source_folder"
        return 1
    fi
    
    mkdir -p "$output_path"
    
    echo "💿 Creating advanced disk image..."
    echo "Source: $source_folder"
    echo "Filesystem: $filesystem"
    echo "Compression: $compression"
    echo "Encryption: $encryption"
    
    # Build hdiutil command
    local cmd="hdiutil create"
    cmd="$cmd -fs $filesystem"
    cmd="$cmd -srcfolder '$source_folder'"
    cmd="$cmd -volname '$image_name'"
    
    # Add compression if specified
    if [[ "$compression" != "none" ]]; then
        cmd="$cmd -format $compression"
    fi
    
    # Add encryption if requested
    if [[ "$encryption" == "true" ]]; then
        cmd="$cmd -encryption AES-256"
        cmd="$cmd -stdinpass"
        echo "🔐 Encryption enabled - you will be prompted for a password"
    fi
    
    cmd="$cmd '$output_path/$image_name.dmg'"
    
    # Execute command
    if [[ "$encryption" == "true" ]]; then
        echo "Enter password for disk image encryption:"
        read -s password
        echo "$password" | eval "$cmd"
    else
        eval "$cmd"
    fi
    
    if [[ $? -eq 0 ]]; then
        echo "✅ Advanced disk image created successfully"
        
        # Display image information
        echo ""
        echo "=== Image Information ==="
        hdiutil imageinfo "$output_path/$image_name.dmg" | grep -E "Format|Compressed|Encrypted|Checksum"
        
        return 0
    else
        echo "❌ Failed to create disk image"
        return 1
    fi
}

# Usage examples
# create_advanced_disk_image "/Users/Shared" "SharedData" "/tmp" "HFS+" "UDBZ" "false"
# create_advanced_disk_image "/Applications/MyApp.app" "MyApp" "/tmp" "HFS+" "UDZO" "true"

Filesystem and Format Options

Supported Filesystems

#!/bin/bash

# Display available filesystem and format options
show_disk_image_options() {
    echo "=== Available Filesystems ==="
    echo "HFS+     - Mac OS Extended (default)"
    echo "HFS+J    - Mac OS Extended (Journaled)"
    echo "HFSX     - Mac OS Extended (Case-sensitive)"
    echo "JHFS+X   - Mac OS Extended (Case-sensitive, Journaled)"
    echo "MS-DOS   - FAT32 (compatible with Windows)"
    echo "UDF      - Universal Disk Format"
    echo "APFS     - Apple File System (macOS 10.13+)"
    echo ""
    
    echo "=== Image Format Options ==="
    echo "UDRO     - Read-only, smaller size"
    echo "UDCO     - ADC compressed (obsolete)"
    echo "UDZO     - zlib compressed (most common)"
    echo "UDBZ     - bzip2 compressed (better compression)"
    echo "ULFO     - lzfse compressed (macOS 10.11+)"
    echo "ULMO     - lzma compressed"
    echo "UFBI     - Entire device"
    echo "SPARSE   - Sparse image (grows as needed)"
    echo "SPARSEBUNDLE - Sparse bundle (network friendly)"
    echo ""
    
    echo "=== Encryption Options ==="
    echo "AES-128  - 128-bit AES encryption"
    echo "AES-256  - 256-bit AES encryption (recommended)"
}

# Create disk image with specific options
create_custom_disk_image() {
    local source="$1"
    local name="$2"
    local output="$3"
    local fs="$4"
    local format="$5"
    local encrypt="$6"
    
    echo "Creating custom disk image with:"
    echo "  Filesystem: $fs"
    echo "  Format: $format"
    echo "  Encryption: $encrypt"
    
    local cmd="hdiutil create -fs '$fs' -format '$format'"
    
    if [[ "$encrypt" != "none" ]]; then
        cmd="$cmd -encryption '$encrypt' -stdinpass"
    fi
    
    cmd="$cmd -srcfolder '$source' -volname '$name' '$output/$name.dmg'"
    
    if [[ "$encrypt" != "none" ]]; then
        echo "Enter encryption password:"
        read -s password
        echo "$password" | eval "$cmd"
    else
        eval "$cmd"
    fi
}

# Example usage
# show_disk_image_options
# create_custom_disk_image "/path/to/folder" "MyImage" "/output/path" "HFS+" "UDBZ" "AES-256"

Batch Disk Image Operations

Batch Image Creation

#!/bin/bash

# Batch create disk images from multiple folders
batch_create_disk_images() {
    local base_path="$1"
    local output_dir="$2"
    local filesystem="${3:-HFS+}"
    local format="${4:-UDBZ}"
    
    if [[ ! -d "$base_path" ]]; then
        echo "❌ Base path not found: $base_path"
        return 1
    fi
    
    mkdir -p "$output_dir"
    
    echo "🔄 Starting batch disk image creation..."
    echo "Base path: $base_path"
    echo "Output directory: $output_dir"
    echo "Filesystem: $filesystem"
    echo "Format: $format"
    echo ""
    
    local success_count=0
    local failed_count=0
    local total_size=0
    
    # Process each subdirectory
    find "$base_path" -type d -mindepth 1 -maxdepth 1 | while read -r folder; do
        local folder_name=$(basename "$folder")
        local image_path="$output_dir/${folder_name}.dmg"
        
        echo "Processing: $folder_name"
        
        # Skip if image already exists
        if [[ -f "$image_path" ]]; then
            echo "  ⏭️  Image already exists, skipping"
            continue
        fi
        
        # Create disk image
        if hdiutil create -fs "$filesystem" -format "$format" \
           -srcfolder "$folder" -volname "$folder_name" \
           "$image_path" >/dev/null 2>&1; then
            
            local size=$(du -h "$image_path" | cut -f1)
            echo "  ✅ Created successfully ($size)"
            ((success_count++))
            
            # Add to total size calculation
            local size_bytes=$(du -b "$image_path" | cut -f1)
            total_size=$((total_size + size_bytes))
        else
            echo "  ❌ Failed to create image"
            ((failed_count++))
        fi
    done
    
    echo ""
    echo "=== Batch Operation Summary ==="
    echo "Successful: $success_count"
    echo "Failed: $failed_count"
    echo "Total size: $(numfmt --to=iec $total_size)"
}

# Usage example
# batch_create_disk_images "/Users/Shared/Projects" "/tmp/project_images" "HFS+" "UDBZ"

Image Verification and Analysis

#!/bin/bash

# Verify and analyze disk images
verify_disk_image() {
    local image_path="$1"
    
    if [[ ! -f "$image_path" ]]; then
        echo "❌ Image file not found: $image_path"
        return 1
    fi
    
    echo "🔍 Verifying disk image: $(basename "$image_path")"
    
    # Basic image information
    echo ""
    echo "=== Basic Information ==="
    echo "File size: $(du -h "$image_path" | cut -f1)"
    echo "File type: $(file "$image_path" | cut -d: -f2- | xargs)"
    
    # Detailed image information
    echo ""
    echo "=== Detailed Image Information ==="
    hdiutil imageinfo "$image_path" | grep -E "Format|Partition|Compressed|Encrypted|Checksum|Total"
    
    # Verify image integrity
    echo ""
    echo "=== Integrity Verification ==="
    if hdiutil verify "$image_path" >/dev/null 2>&1; then
        echo "✅ Image integrity verified successfully"
    else
        echo "❌ Image integrity verification failed"
        return 1
    fi
    
    # Test mount capability
    echo ""
    echo "=== Mount Test ==="
    local mount_point=$(mktemp -d)
    
    if hdiutil attach "$image_path" -mountpoint "$mount_point" -nobrowse -quiet; then
        echo "✅ Image mounts successfully"
        
        # Get volume information
        echo "Volume info: $(df -h "$mount_point" | tail -1 | awk '{print $2 " total, " $3 " used, " $4 " available"}')"
        
        # Count contents
        local file_count=$(find "$mount_point" -type f | wc -l | xargs)
        local dir_count=$(find "$mount_point" -type d | wc -l | xargs)
        echo "Contents: $file_count files, $dir_count directories"
        
        # Unmount
        hdiutil detach "$mount_point" -quiet
        rmdir "$mount_point"
    else
        echo "❌ Image failed to mount"
        rmdir "$mount_point"
        return 1
    fi
    
    echo ""
    echo "✅ Image verification completed successfully"
    return 0
}

# Batch verification
verify_multiple_images() {
    local image_dir="$1"
    
    if [[ ! -d "$image_dir" ]]; then
        echo "❌ Image directory not found: $image_dir"
        return 1
    fi
    
    echo "🔍 Verifying all disk images in: $image_dir"
    echo ""
    
    local verified=0
    local failed=0
    
    find "$image_dir" -name "*.dmg" -type f | while read -r image; do
        echo "Verifying: $(basename "$image")"
        if verify_disk_image "$image" >/dev/null 2>&1; then
            echo "  ✅ Verified"
            ((verified++))
        else
            echo "  ❌ Failed"
            ((failed++))
        fi
    done
    
    echo ""
    echo "Verification complete: $verified passed, $failed failed"
}

Enterprise Disk Image Management

#!/bin/bash

# MacFleet Disk Image Management Tool
# Comprehensive disk image creation and management for enterprise environments

# Configuration
LOG_FILE="/var/log/macfleet_diskimage.log"
BACKUP_DIR="/var/backups/macfleet/images"
REPORT_DIR="/var/reports/macfleet/images"
CONFIG_FILE="/etc/macfleet/image_policy.conf"
TEMP_DIR="/tmp/macfleet_images"

# Default settings
DEFAULT_FILESYSTEM="HFS+"
DEFAULT_FORMAT="UDBZ"
DEFAULT_ENCRYPTION="AES-256"
ENABLE_COMPRESSION=true
ENABLE_VERIFICATION=true
MAX_IMAGE_SIZE="10G"
CLEANUP_TEMP_FILES=true

# Logging function
log_action() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

# Setup directories
setup_directories() {
    for dir in "$BACKUP_DIR" "$REPORT_DIR" "$(dirname "$CONFIG_FILE")" "$TEMP_DIR"; do
        if [[ ! -d "$dir" ]]; then
            sudo mkdir -p "$dir"
            log_action "Created directory: $dir"
        fi
    done
}

# Check available disk space
check_disk_space() {
    local target_dir="$1"
    local required_space="$2"  # in bytes
    
    local available_space=$(df "$target_dir" | tail -1 | awk '{print $4}')
    available_space=$((available_space * 1024))  # Convert to bytes
    
    if [[ "$available_space" -lt "$required_space" ]]; then
        echo "❌ Insufficient disk space"
        echo "Required: $(numfmt --to=iec $required_space)"
        echo "Available: $(numfmt --to=iec $available_space)"
        return 1
    fi
    
    return 0
}

# Create enterprise disk image with full logging and validation
create_enterprise_disk_image() {
    local source_path="$1"
    local image_name="$2"
    local output_dir="$3"
    local filesystem="${4:-$DEFAULT_FILESYSTEM}"
    local format="${5:-$DEFAULT_FORMAT}"
    local encrypt="${6:-false}"
    local description="$7"
    
    log_action "Starting enterprise disk image creation: $image_name"
    
    # Validation
    if [[ ! -d "$source_path" ]]; then
        echo "❌ Source path not found: $source_path"
        log_action "ERROR: Source path not found: $source_path"
        return 1
    fi
    
    # Calculate source size
    local source_size_bytes=$(du -sb "$source_path" | cut -f1)
    local source_size_human=$(du -sh "$source_path" | cut -f1)
    
    echo "📁 Source analysis:"
    echo "  Path: $source_path"
    echo "  Size: $source_size_human ($source_size_bytes bytes)"
    
    # Check disk space (estimate 2x source size for safety)
    local estimated_space=$((source_size_bytes * 2))
    if ! check_disk_space "$output_dir" "$estimated_space"; then
        log_action "ERROR: Insufficient disk space for image creation"
        return 1
    fi
    
    # Create output directory
    mkdir -p "$output_dir"
    
    local output_path="$output_dir/$image_name.dmg"
    local start_time=$(date +%s)
    
    echo ""
    echo "💿 Creating disk image..."
    echo "  Name: $image_name"
    echo "  Filesystem: $filesystem"
    echo "  Format: $format"
    echo "  Output: $output_path"
    
    # Build command
    local cmd="hdiutil create -fs '$filesystem' -format '$format'"
    cmd="$cmd -srcfolder '$source_path' -volname '$image_name'"
    
    # Add encryption if requested
    if [[ "$encrypt" == "true" ]]; then
        cmd="$cmd -encryption '$DEFAULT_ENCRYPTION' -stdinpass"
        echo "  Encryption: Enabled ($DEFAULT_ENCRYPTION)"
    fi
    
    cmd="$cmd '$output_path'"
    
    # Execute image creation
    local creation_success=false
    
    if [[ "$encrypt" == "true" ]]; then
        echo ""
        echo "🔐 Enter password for disk image encryption:"
        read -s password
        if echo "$password" | eval "$cmd" >/dev/null 2>&1; then
            creation_success=true
        fi
    else
        if eval "$cmd" >/dev/null 2>&1; then
            creation_success=true
        fi
    fi
    
    local end_time=$(date +%s)
    local duration=$((end_time - start_time))
    
    if [[ "$creation_success" == "true" ]]; then
        local image_size_bytes=$(du -b "$output_path" | cut -f1)
        local image_size_human=$(du -h "$output_path" | cut -f1)
        local compression_ratio=$(echo "scale=2; $image_size_bytes * 100 / $source_size_bytes" | bc)
        
        echo ""
        echo "✅ Disk image created successfully"
        echo "  Creation time: ${duration}s"
        echo "  Image size: $image_size_human"
        echo "  Compression ratio: ${compression_ratio}%"
        
        # Verify image if enabled
        if [[ "$ENABLE_VERIFICATION" == "true" ]]; then
            echo ""
            echo "🔍 Verifying image integrity..."
            if hdiutil verify "$output_path" >/dev/null 2>&1; then
                echo "✅ Image verification passed"
                log_action "Image created and verified: $image_name ($image_size_human)"
            else
                echo "❌ Image verification failed"
                log_action "ERROR: Image verification failed: $image_name"
                return 1
            fi
        fi
        
        # Generate image metadata
        create_image_metadata "$output_path" "$source_path" "$description"
        
        return 0
    else
        echo "❌ Failed to create disk image"
        log_action "ERROR: Failed to create disk image: $image_name"
        return 1
    fi
}

# Create metadata file for the image
create_image_metadata() {
    local image_path="$1"
    local source_path="$2"
    local description="$3"
    
    local metadata_file="${image_path%.dmg}.json"
    
    cat > "$metadata_file" << EOF
{
    "image_name": "$(basename "$image_path")",
    "creation_date": "$(date -Iseconds)",
    "source_path": "$source_path",
    "image_path": "$image_path",
    "description": "$description",
    "file_size": $(du -b "$image_path" | cut -f1),
    "file_size_human": "$(du -h "$image_path" | cut -f1)",
    "checksum_md5": "$(md5 -q "$image_path")",
    "checksum_sha256": "$(shasum -a 256 "$image_path" | cut -d' ' -f1)",
    "format_info": $(hdiutil imageinfo "$image_path" -plist | plutil -convert json -o - -)
}
EOF
    
    echo "📄 Metadata saved: $metadata_file"
}

# Generate comprehensive image report
generate_image_report() {
    local image_dir="${1:-$BACKUP_DIR}"
    local report_file="$REPORT_DIR/image_report_$(date +%Y%m%d_%H%M%S).txt"
    
    echo "📊 Generating disk image report..."
    
    {
        echo "MacFleet Disk Image Management Report"
        echo "Generated: $(date)"
        echo "Image Directory: $image_dir"
        echo "======================================"
        echo ""
        
        if [[ ! -d "$image_dir" ]]; then
            echo "❌ Image directory not found: $image_dir"
            return 1
        fi
        
        # Count and analyze images
        local total_images=$(find "$image_dir" -name "*.dmg" -type f | wc -l)
        local total_size_bytes=0
        local verified_count=0
        local failed_count=0
        
        echo "=== Summary ==="
        echo "Total disk images: $total_images"
        
        if [[ "$total_images" -eq 0 ]]; then
            echo "No disk images found in directory"
            return 0
        fi
        
        echo ""
        echo "=== Image Details ==="
        printf "%-30s %-10s %-15s %-10s\n" "Name" "Size" "Format" "Status"
        printf "%-30s %-10s %-15s %-10s\n" "----" "----" "------" "------"
        
        find "$image_dir" -name "*.dmg" -type f | sort | while read -r image_path; do
            local image_name=$(basename "$image_path")
            local image_size=$(du -h "$image_path" | cut -f1)
            local image_size_bytes=$(du -b "$image_path" | cut -f1)
            
            # Get format info
            local format_info=$(hdiutil imageinfo "$image_path" 2>/dev/null | grep "Format Description" | cut -d: -f2- | xargs)
            if [[ -z "$format_info" ]]; then
                format_info="Unknown"
            fi
            
            # Verify integrity
            local status="Unknown"
            if hdiutil verify "$image_path" >/dev/null 2>&1; then
                status="✅ Valid"
                ((verified_count++))
            else
                status="❌ Failed"
                ((failed_count++))
            fi
            
            printf "%-30s %-10s %-15s %-10s\n" "$image_name" "$image_size" "$format_info" "$status"
            
            total_size_bytes=$((total_size_bytes + image_size_bytes))
        done
        
        echo ""
        echo "=== Statistics ==="
        echo "Total size: $(numfmt --to=iec $total_size_bytes)"
        echo "Verified images: $verified_count"
        echo "Failed verification: $failed_count"
        echo "Average size: $(numfmt --to=iec $((total_size_bytes / total_images)))"
        
        # Storage recommendations
        echo ""
        echo "=== Recommendations ==="
        if [[ "$failed_count" -gt 0 ]]; then
            echo "⚠️  $failed_count images failed verification - investigate and recreate"
        fi
        
        if [[ "$total_size_bytes" -gt $((50 * 1024 * 1024 * 1024)) ]]; then  # 50GB
            echo "💡 Consider archiving older images to free up space"
        fi
        
        echo "💡 Regular verification recommended for critical images"
        
    } > "$report_file"
    
    echo "📊 Report saved to: $report_file"
    log_action "Image report generated: $report_file"
}

# Main execution function
main() {
    local action="${1:-help}"
    local source="${2:-}"
    local name="${3:-}"
    local output="${4:-$BACKUP_DIR}"
    
    log_action "=== MacFleet Disk Image Management Started ==="
    
    setup_directories
    
    case "$action" in
        "create")
            if [[ -n "$source" && -n "$name" ]]; then
                create_enterprise_disk_image "$source" "$name" "$output"
            else
                echo "❌ Usage: $0 create <source_folder> <image_name> [output_dir]"
                echo "Example: $0 create /Users/Shared MyImage /tmp"
            fi
            ;;
        "batch")
            if [[ -n "$source" ]]; then
                batch_create_disk_images "$source" "$output"
            else
                echo "❌ Usage: $0 batch <base_folder> [output_dir]"
                echo "Example: $0 batch /Users/Shared/Projects /tmp/images"
            fi
            ;;
        "verify")
            if [[ -n "$source" ]]; then
                if [[ -f "$source" ]]; then
                    verify_disk_image "$source"
                elif [[ -d "$source" ]]; then
                    verify_multiple_images "$source"
                else
                    echo "❌ Path not found: $source"
                fi
            else
                echo "❌ Usage: $0 verify <image_file_or_directory>"
            fi
            ;;
        "report")
            generate_image_report "$source"
            ;;
        "info")
            if [[ -n "$source" && -f "$source" ]]; then
                echo "=== Image Information ==="
                hdiutil imageinfo "$source"
            else
                echo "❌ Usage: $0 info <image_file>"
            fi
            ;;
        "options")
            show_disk_image_options
            ;;
        "help"|*)
            echo "MacFleet Disk Image Management Tool"
            echo "Usage: $0 [action] [options]"
            echo ""
            echo "Actions:"
            echo "  create <source> <name> [output]  - Create disk image from folder"
            echo "  batch <base_dir> [output]        - Create images from all subdirectories"
            echo "  verify <image_or_dir>            - Verify image integrity"
            echo "  report [image_dir]               - Generate comprehensive report"
            echo "  info <image_file>                - Show detailed image information"
            echo "  options                          - Show available filesystem and format options"
            echo "  help                             - Show this help message"
            echo ""
            echo "Examples:"
            echo "  $0 create /Users/Shared MyData   # Create MyData.dmg from /Users/Shared"
            echo "  $0 batch /Applications /tmp       # Create images for all apps"
            echo "  $0 verify /tmp/MyData.dmg         # Verify single image"
            echo "  $0 report /tmp                    # Generate report for all images in /tmp"
            echo ""
            echo "Configuration:"
            echo "  Default filesystem: $DEFAULT_FILESYSTEM"
            echo "  Default format: $DEFAULT_FORMAT"
            echo "  Default encryption: $DEFAULT_ENCRYPTION"
            echo "  Verification enabled: $ENABLE_VERIFICATION"
            ;;
    esac
    
    log_action "=== MacFleet Disk Image Management Completed ==="
}

# Execute main function
main "$@"

Image Format Comparison

FormatCompressionSizeCompatibilityUse Case
UDRONoneLargeUniversalTesting, temporary images
UDZOzlib (good)MediummacOS 10.2+General purpose, good balance
UDBZbzip2 (better)SmallmacOS 10.4+Archive storage, slower access
ULFOlzfse (fast)SmallmacOS 10.11+Modern systems, fast compression
SPARSENoneVariablemacOSGrowing images, development

Security and Encryption

Encrypted Image Creation

# Create encrypted disk image with enterprise policies
create_secure_image() {
    local source="$1"
    local name="$2"
    local output="$3"
    local encryption_level="${4:-AES-256}"
    
    echo "🔐 Creating encrypted disk image..."
    echo "Encryption: $encryption_level"
    
    # Generate secure password if not provided
    if [[ -z "$IMAGE_PASSWORD" ]]; then
        echo "Enter encryption password (or set IMAGE_PASSWORD environment variable):"
        read -s IMAGE_PASSWORD
    fi
    
    echo "$IMAGE_PASSWORD" | hdiutil create \
        -fs HFS+ \
        -format UDBZ \
        -encryption "$encryption_level" \
        -stdinpass \
        -srcfolder "$source" \
        -volname "$name" \
        "$output/$name.dmg"
    
    if [[ $? -eq 0 ]]; then
        echo "✅ Encrypted image created successfully"
        echo "🔒 Image is protected with $encryption_level encryption"
    else
        echo "❌ Failed to create encrypted image"
        return 1
    fi
}

Important Notes

  • Disk Space - Ensure sufficient space for image creation (2x source size recommended)
  • Performance - Compression trades size for creation/access speed
  • Security - Use AES-256 encryption for sensitive data
  • Compatibility - Choose appropriate filesystem for target systems
  • Verification - Always verify critical images after creation
  • Enterprise Deployment - Test image mounting on target systems before bulk deployment

Disable Automatic macOS Updates

In enterprise environments, controlling when and how macOS updates are installed is crucial for maintaining system stability and ensuring compatibility with business applications. This tutorial shows how to disable automatic macOS updates using shell scripts.

Understanding macOS Update Settings

macOS has two main automatic update behaviors:

  • Automatic Download: Downloads updates in the background
  • Automatic Check: Periodically checks Apple's servers for new updates

Both can be controlled via the com.apple.SoftwareUpdate preference domain.

Basic Scripts

Disable Automatic Download

#!/bin/bash
defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload -boolean false

Disable Automatic Check

#!/bin/bash
defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled -boolean false

Enterprise Deployment Script

For large-scale deployment across your MacFleet:

#!/bin/bash

# Disable Automatic macOS Updates - Enterprise Script
# Compatible with macOS 10.14+

# Function to log messages
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a /var/log/macos_update_config.log
}

# Function to get current user
get_current_user() {
    stat -f "%Su" /dev/console
}

# Main configuration function
configure_updates() {
    local current_user
    current_user=$(get_current_user)
    
    log_message "Starting macOS update configuration for user: $current_user"
    
    # Disable automatic download
    if defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload -boolean false; then
        log_message "✓ Automatic download disabled successfully"
    else
        log_message "✗ Failed to disable automatic download"
        return 1
    fi
    
    # Disable automatic check
    if defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled -boolean false; then
        log_message "✓ Automatic check disabled successfully"
    else
        log_message "✗ Failed to disable automatic check"
        return 1
    fi
    
    # Disable automatic installation (additional security)
    if defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -boolean false; then
        log_message "✓ Automatic installation disabled successfully"
    else
        log_message "✗ Failed to disable automatic installation"
    fi
    
    # Kill Software Update preferences to force reload
    killall -HUP cfprefsd 2>/dev/null || true
    
    log_message "macOS update configuration completed"
    return 0
}

# Verification function
verify_configuration() {
    local download_status check_status install_status
    
    download_status=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload 2>/dev/null || echo "not set")
    check_status=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled 2>/dev/null || echo "not set")
    install_status=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates 2>/dev/null || echo "not set")
    
    log_message "Configuration verification:"
    log_message "  Automatic Download: $download_status"
    log_message "  Automatic Check: $check_status"
    log_message "  Automatic Install: $install_status"
}

# Execute main function
if configure_updates; then
    verify_configuration
    log_message "Script executed successfully"
    exit 0
else
    log_message "Script execution failed"
    exit 1
fi

Verification Script

Check current update settings:

#!/bin/bash

echo "=== macOS Update Settings Status ==="
echo "Automatic Download: $(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload 2>/dev/null || echo 'Not configured')"
echo "Automatic Check: $(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled 2>/dev/null || echo 'Not configured')"
echo "Automatic Install: $(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates 2>/dev/null || echo 'Not configured')"

Re-enabling Updates

To restore automatic updates:

#!/bin/bash

# Re-enable automatic download
defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload -boolean true

# Re-enable automatic check
defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled -boolean true

# Re-enable automatic installation
defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -boolean true

echo "Automatic updates re-enabled"

Troubleshooting

IssueSolution
Settings revert after restartEnsure scripts run with admin privileges
System Preferences shows old valuesRestart System Preferences or run killall -HUP cfprefsd
Changes don't applyCheck /var/log/macos_update_config.log for errors
Updates still downloadVerify the preference file permissions

Important Notes

  • Users with admin privileges can manually change these settings
  • The System Preferences/Settings app may need to be restarted to reflect changes
  • Consider using Configuration Profiles for stricter enforcement
  • Test on a small group before enterprise-wide deployment

Configuration Profile Alternative

For stricter control, consider deploying a Configuration Profile instead:

<key>AutomaticCheckEnabled</key>
<false/>
<key>AutomaticDownload</key>
<false/>
<key>AutomaticallyInstallMacOSUpdates</key>
<false/>

Directory Listing and File Management on macOS

Master file and directory listing operations on your MacFleet devices using powerful command-line tools. This tutorial covers basic file listing, hidden file discovery, detailed information display, and advanced sorting techniques for effective file system management.

Understanding macOS Directory Operations

macOS provides robust command-line tools for directory management:

  • ls - List directory contents with various options
  • find - Search for files and directories with advanced criteria
  • tree - Display directory structure in tree format
  • stat - Display detailed file/directory information

Basic File Listing Operations

List All Files in Directory

#!/bin/bash

# Basic directory listing
FOLDER_PATH="/Users/QA/Desktop/Wallpapers"

echo "Files in $FOLDER_PATH:"
ls "$FOLDER_PATH"

echo "Directory listing completed successfully"

Recursive Directory Listing

#!/bin/bash

# List all files recursively (including subdirectories)
FOLDER_PATH="/Users/QA/Desktop/Wallpapers"

echo "Recursive listing of $FOLDER_PATH:"
ls -R "$FOLDER_PATH"

echo "Recursive directory listing completed"

Hidden Files and Directories

View Hidden Content

#!/bin/bash

# Display all files including hidden ones
FOLDER_PATH="/Users/QA/Desktop/Wallpapers"

echo "All files (including hidden) in $FOLDER_PATH:"
ls -a "$FOLDER_PATH"

echo "Hidden files displayed successfully"

Hidden Files Analysis

#!/bin/bash

# Comprehensive hidden file analysis
analyze_hidden_files() {
    local folder_path="$1"
    
    echo "=== Hidden Files Analysis for: $folder_path ==="
    
    # Count hidden files
    local hidden_count
    hidden_count=$(ls -la "$folder_path" | grep "^\." | wc -l)
    echo "Hidden files found: $((hidden_count - 2))" # Subtract . and ..
    
    # List hidden files with details
    echo -e "\nHidden files:"
    ls -la "$folder_path" | grep "^\." | grep -v "^total"
    
    # Check for common hidden file types
    echo -e "\nCommon hidden file types:"
    ls -a "$folder_path" | grep "^\." | grep -E "\.(DS_Store|Spotlight-V100|Trashes|fseventsd)" || echo "No common system hidden files found"
}

# Usage
analyze_hidden_files "/Users/QA/Desktop/Wallpapers"

Detailed File Information

Long Format Listing

#!/bin/bash

# Display detailed file information
FOLDER_PATH="/Users/QA/Desktop/Wallpapers"

echo "Detailed file information for $FOLDER_PATH:"
ls -l "$FOLDER_PATH"

echo "Detailed listing completed"

Human-Readable File Sizes

#!/bin/bash

# Display file sizes in human-readable format
FOLDER_PATH="/Users/QA/Desktop/Wallpapers"

echo "Files with human-readable sizes in $FOLDER_PATH:"
ls -lh "$FOLDER_PATH"

echo "Human-readable listing completed"

Comprehensive File Details

#!/bin/bash

# Advanced file information display
show_file_details() {
    local folder_path="$1"
    
    echo "=== Comprehensive File Details: $folder_path ==="
    
    # Basic listing with permissions, sizes, dates
    echo "Detailed file listing:"
    ls -lah "$folder_path"
    
    echo -e "\nDirectory summary:"
    echo "Total items: $(ls -1 "$folder_path" | wc -l)"
    echo "Total size: $(du -sh "$folder_path" | cut -f1)"
    echo "Disk usage: $(du -sk "$folder_path" | cut -f1) KB"
    
    # File type breakdown
    echo -e "\nFile type analysis:"
    find "$folder_path" -maxdepth 1 -type f -exec file {} \; | cut -d: -f2 | sort | uniq -c | sort -nr
}

# Usage
show_file_details "/Users/QA/Desktop/Wallpapers"

File Sorting Options

Sort by Modification Time

#!/bin/bash

# Sort files by last modification time (newest first)
FOLDER_PATH="/Users/QA/Desktop/Wallpapers"

echo "Files sorted by modification time (newest first):"
ls -t "$FOLDER_PATH"

echo "Time-based sorting completed"

Sort by File Size

#!/bin/bash

# Sort files by size (largest first)
FOLDER_PATH="/Users/QA/Desktop/Wallpapers"

echo "Files sorted by size (largest first):"
ls -lhS "$FOLDER_PATH"

echo "Size-based sorting completed"

Advanced Sorting Options

#!/bin/bash

# Multiple sorting criteria
advanced_sorting() {
    local folder_path="$1"
    
    echo "=== Advanced File Sorting: $folder_path ==="
    
    echo "1. By modification time (newest first):"
    ls -lt "$folder_path" | head -10
    
    echo -e "\n2. By modification time (oldest first):"
    ls -ltr "$folder_path" | head -10
    
    echo -e "\n3. By size (largest first):"
    ls -lhS "$folder_path" | head -10
    
    echo -e "\n4. By size (smallest first):"
    ls -lhSr "$folder_path" | head -10
    
    echo -e "\n5. By name (alphabetical):"
    ls -l "$folder_path" | sort -k9
    
    echo -e "\n6. By extension:"
    ls -1 "$folder_path" | grep '\.' | sort -t. -k2
}

# Usage
advanced_sorting "/Users/QA/Desktop/Wallpapers"

Enterprise Directory Management System

#!/bin/bash

# MacFleet Directory Management Tool
# Comprehensive file and directory analysis for fleet devices

# Configuration
SCRIPT_VERSION="1.0.0"
LOG_FILE="/var/log/macfleet_directory.log"
REPORT_DIR="/etc/macfleet/reports/directory"
CONFIG_DIR="/etc/macfleet/directory"

# Create directories if they don't exist
mkdir -p "$REPORT_DIR" "$CONFIG_DIR"

# Directory categories for analysis
declare -A DIRECTORY_CATEGORIES=(
    ["system"]="/System,/Library,/usr,/bin,/sbin"
    ["user"]="/Users,/home"
    ["applications"]="/Applications,/Applications/Utilities"
    ["documents"]="/Documents,/Desktop,/Downloads"
    ["media"]="/Movies,/Music,/Pictures"
    ["temporary"]="/tmp,/var/tmp,/var/cache"
    ["logs"]="/var/log,/Library/Logs"
    ["configuration"]="/etc,/private/etc,/Library/Preferences"
    ["development"]="/usr/local,/opt,/Developer"
    ["network"]="/Network,/Volumes"
)

# Directory policies for different scanning levels
declare -A DIRECTORY_POLICIES=(
    ["quick_scan"]="basic_listing,file_count,size_summary"
    ["standard_scan"]="detailed_listing,hidden_files,type_analysis,permission_check"
    ["comprehensive_scan"]="full_recursive,security_scan,duplicate_detection,metadata_extraction"
    ["security_audit"]="permission_audit,ownership_check,sensitive_file_scan,access_log"
    ["performance_scan"]="large_file_detection,fragmentation_check,cache_analysis"
    ["compliance_scan"]="policy_validation,retention_check,classification_audit"
)

# Logging function
log_action() {
    local message="$1"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] $message" | tee -a "$LOG_FILE"
}

# Directory analysis engine
analyze_directory() {
    local target_path="$1"
    local scan_level="${2:-standard_scan}"
    local category="${3:-general}"
    
    log_action "Starting directory analysis: $target_path (Level: $scan_level, Category: $category)"
    
    if [[ ! -d "$target_path" ]]; then
        log_action "ERROR: Directory not found: $target_path"
        return 1
    fi
    
    local report_file="$REPORT_DIR/directory_analysis_$(echo "$target_path" | tr '/' '_')_$(date +%Y%m%d_%H%M%S).json"
    
    # Initialize report
    cat > "$report_file" << EOF
{
    "analysis_info": {
        "target_path": "$target_path",
        "scan_level": "$scan_level",
        "category": "$category",
        "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
        "hostname": "$(hostname)",
        "script_version": "$SCRIPT_VERSION"
    },
    "directory_metrics": {},
    "file_analysis": {},
    "security_findings": {},
    "recommendations": []
}
EOF
    
    # Basic directory metrics
    local total_items=$(find "$target_path" -maxdepth 1 2>/dev/null | wc -l)
    local total_files=$(find "$target_path" -maxdepth 1 -type f 2>/dev/null | wc -l)
    local total_dirs=$(find "$target_path" -maxdepth 1 -type d 2>/dev/null | wc -l)
    local total_size=$(du -sk "$target_path" 2>/dev/null | cut -f1)
    
    # Update report with metrics
    jq --argjson total_items "$((total_items - 1))" \
       --argjson total_files "$total_files" \
       --argjson total_dirs "$((total_dirs - 1))" \
       --argjson total_size_kb "$total_size" \
       '.directory_metrics = {
           "total_items": $total_items,
           "total_files": $total_files,
           "total_directories": $total_dirs,
           "total_size_kb": $total_size_kb,
           "average_file_size": (if $total_files > 0 then ($total_size_kb / $total_files) else 0 end)
       }' "$report_file" > "${report_file}.tmp" && mv "${report_file}.tmp" "$report_file"
    
    # Perform analysis based on scan level
    case "$scan_level" in
        "quick_scan")
            perform_quick_scan "$target_path" "$report_file"
            ;;
        "standard_scan")
            perform_standard_scan "$target_path" "$report_file"
            ;;
        "comprehensive_scan")
            perform_comprehensive_scan "$target_path" "$report_file"
            ;;
        "security_audit")
            perform_security_audit "$target_path" "$report_file"
            ;;
        "performance_scan")
            perform_performance_scan "$target_path" "$report_file"
            ;;
        "compliance_scan")
            perform_compliance_scan "$target_path" "$report_file"
            ;;
    esac
    
    log_action "Directory analysis completed: $report_file"
    echo "$report_file"
}

# Quick scan implementation
perform_quick_scan() {
    local target_path="$1"
    local report_file="$2"
    
    echo "Performing quick scan of $target_path..."
    
    # Basic file listing
    local file_listing=$(ls -la "$target_path" 2>/dev/null | tail -n +2)
    
    # File type distribution
    local file_types=$(find "$target_path" -maxdepth 1 -type f -exec file {} \; 2>/dev/null | cut -d: -f2 | sort | uniq -c | sort -nr)
    
    # Update report
    jq --arg file_listing "$file_listing" \
       --arg file_types "$file_types" \
       '.file_analysis.quick_scan = {
           "file_listing": $file_listing,
           "file_type_distribution": $file_types
       }' "$report_file" > "${report_file}.tmp" && mv "${report_file}.tmp" "$report_file"
}

# Standard scan implementation
perform_standard_scan() {
    local target_path="$1"
    local report_file="$2"
    
    echo "Performing standard scan of $target_path..."
    
    # Detailed file analysis
    local detailed_listing=$(ls -lah "$target_path" 2>/dev/null)
    local hidden_files=$(ls -la "$target_path" 2>/dev/null | grep "^\." | wc -l)
    local executable_files=$(find "$target_path" -maxdepth 1 -type f -executable 2>/dev/null | wc -l)
    
    # File size analysis
    local large_files=$(find "$target_path" -maxdepth 1 -type f -size +10M 2>/dev/null)
    local small_files=$(find "$target_path" -maxdepth 1 -type f -size -1k 2>/dev/null | wc -l)
    
    # File age analysis
    local recent_files=$(find "$target_path" -maxdepth 1 -type f -mtime -7 2>/dev/null | wc -l)
    local old_files=$(find "$target_path" -maxdepth 1 -type f -mtime +365 2>/dev/null | wc -l)
    
    # Update report
    jq --arg detailed_listing "$detailed_listing" \
       --argjson hidden_files "$((hidden_files - 2))" \
       --argjson executable_files "$executable_files" \
       --arg large_files "$large_files" \
       --argjson small_files "$small_files" \
       --argjson recent_files "$recent_files" \
       --argjson old_files "$old_files" \
       '.file_analysis.standard_scan = {
           "detailed_listing": $detailed_listing,
           "hidden_files_count": $hidden_files,
           "executable_files_count": $executable_files,
           "large_files": $large_files,
           "small_files_count": $small_files,
           "recent_files_count": $recent_files,
           "old_files_count": $old_files
       }' "$report_file" > "${report_file}.tmp" && mv "${report_file}.tmp" "$report_file"
}

# Comprehensive scan implementation
perform_comprehensive_scan() {
    local target_path="$1"
    local report_file="$2"
    
    echo "Performing comprehensive scan of $target_path..."
    
    # Recursive analysis
    local total_recursive_items=$(find "$target_path" 2>/dev/null | wc -l)
    local max_depth=$(find "$target_path" -type d 2>/dev/null | awk -F/ '{print NF}' | sort -n | tail -1)
    
    # File extension analysis
    local extensions=$(find "$target_path" -type f 2>/dev/null | grep '\.' | rev | cut -d. -f1 | rev | sort | uniq -c | sort -nr)
    
    # Duplicate file detection
    local duplicates=$(find "$target_path" -type f 2>/dev/null -exec md5 {} \; | sort | uniq -d -w 32)
    
    # Symbolic link analysis
    local symlinks=$(find "$target_path" -type l 2>/dev/null)
    
    # Update report
    jq --argjson total_recursive "$((total_recursive_items - 1))" \
       --argjson max_depth "$((max_depth - $(echo "$target_path" | tr -cd '/' | wc -c) - 1))" \
       --arg extensions "$extensions" \
       --arg duplicates "$duplicates" \
       --arg symlinks "$symlinks" \
       '.file_analysis.comprehensive_scan = {
           "total_recursive_items": $total_recursive,
           "maximum_depth": $max_depth,
           "file_extensions": $extensions,
           "duplicate_files": $duplicates,
           "symbolic_links": $symlinks
       }' "$report_file" > "${report_file}.tmp" && mv "${report_file}.tmp" "$report_file"
}

# Security audit implementation
perform_security_audit() {
    local target_path="$1"
    local report_file="$2"
    
    echo "Performing security audit of $target_path..."
    
    # Permission analysis
    local world_writable=$(find "$target_path" -type f -perm -002 2>/dev/null)
    local world_readable=$(find "$target_path" -type f -perm -004 2>/dev/null | wc -l)
    local setuid_files=$(find "$target_path" -type f -perm -4000 2>/dev/null)
    local setgid_files=$(find "$target_path" -type f -perm -2000 2>/dev/null)
    
    # Ownership analysis
    local root_owned=$(find "$target_path" -user root 2>/dev/null | wc -l)
    local no_owner=$(find "$target_path" -nouser 2>/dev/null)
    local no_group=$(find "$target_path" -nogroup 2>/dev/null)
    
    # Sensitive file patterns
    local sensitive_patterns="password|secret|key|token|credential|private"
    local sensitive_files=$(find "$target_path" -type f 2>/dev/null | xargs grep -l -i "$sensitive_patterns" 2>/dev/null)
    
    # Update report
    jq --arg world_writable "$world_writable" \
       --argjson world_readable "$world_readable" \
       --arg setuid_files "$setuid_files" \
       --arg setgid_files "$setgid_files" \
       --argjson root_owned "$root_owned" \
       --arg no_owner "$no_owner" \
       --arg no_group "$no_group" \
       --arg sensitive_files "$sensitive_files" \
       '.security_findings = {
           "world_writable_files": $world_writable,
           "world_readable_count": $world_readable,
           "setuid_files": $setuid_files,
           "setgid_files": $setgid_files,
           "root_owned_count": $root_owned,
           "orphaned_files": $no_owner,
           "orphaned_groups": $no_group,
           "sensitive_files": $sensitive_files
       }' "$report_file" > "${report_file}.tmp" && mv "${report_file}.tmp" "$report_file"
}

# Performance scan implementation
perform_performance_scan() {
    local target_path="$1"
    local report_file="$2"
    
    echo "Performing performance scan of $target_path..."
    
    # Large file analysis
    local files_over_100mb=$(find "$target_path" -type f -size +100M 2>/dev/null)
    local files_over_1gb=$(find "$target_path" -type f -size +1G 2>/dev/null | wc -l)
    
    # Cache and temporary file analysis
    local cache_files=$(find "$target_path" -name "*cache*" -o -name "*.tmp" -o -name "*.temp" 2>/dev/null)
    local log_files=$(find "$target_path" -name "*.log" 2>/dev/null)
    
    # File access patterns
    local recently_accessed=$(find "$target_path" -type f -atime -1 2>/dev/null | wc -l)
    local never_accessed=$(find "$target_path" -type f -atime +365 2>/dev/null | wc -l)
    
    # Update report
    jq --arg files_over_100mb "$files_over_100mb" \
       --argjson files_over_1gb "$files_over_1gb" \
       --arg cache_files "$cache_files" \
       --arg log_files "$log_files" \
       --argjson recently_accessed "$recently_accessed" \
       --argjson never_accessed "$never_accessed" \
       '.file_analysis.performance_scan = {
           "large_files_100mb": $files_over_100mb,
           "large_files_1gb_count": $files_over_1gb,
           "cache_temp_files": $cache_files,
           "log_files": $log_files,
           "recently_accessed_count": $recently_accessed,
           "stale_files_count": $never_accessed
       }' "$report_file" > "${report_file}.tmp" && mv "${report_file}.tmp" "$report_file"
}

# Compliance scan implementation
perform_compliance_scan() {
    local target_path="$1"
    local report_file="$2"
    
    echo "Performing compliance scan of $target_path..."
    
    # Data classification patterns
    local pii_patterns="ssn|social.security|credit.card|phone|email|address"
    local financial_patterns="account.number|routing|swift|iban|tax.id"
    local health_patterns="medical|patient|diagnosis|prescription|hipaa"
    
    local pii_files=$(find "$target_path" -type f 2>/dev/null | xargs grep -l -i "$pii_patterns" 2>/dev/null)
    local financial_files=$(find "$target_path" -type f 2>/dev/null | xargs grep -l -i "$financial_patterns" 2>/dev/null)
    local health_files=$(find "$target_path" -type f 2>/dev/null | xargs grep -l -i "$health_patterns" 2>/dev/null)
    
    # Retention analysis
    local files_over_7_years=$(find "$target_path" -type f -mtime +2555 2>/dev/null | wc -l)
    local files_over_3_years=$(find "$target_path" -type f -mtime +1095 2>/dev/null | wc -l)
    
    # Update report
    jq --arg pii_files "$pii_files" \
       --arg financial_files "$financial_files" \
       --arg health_files "$health_files" \
       --argjson files_over_7_years "$files_over_7_years" \
       --argjson files_over_3_years "$files_over_3_years" \
       '.file_analysis.compliance_scan = {
           "pii_files": $pii_files,
           "financial_files": $financial_files,
           "health_files": $health_files,
           "retention_7_years": $files_over_7_years,
           "retention_3_years": $files_over_3_years
       }' "$report_file" > "${report_file}.tmp" && mv "${report_file}.tmp" "$report_file"
}

# Fleet directory management
manage_fleet_directories() {
    local action="$1"
    local target_pattern="$2"
    local scan_level="${3:-standard_scan}"
    
    log_action "Fleet directory management: $action on $target_pattern"
    
    case "$action" in
        "scan_all")
            for category in "${!DIRECTORY_CATEGORIES[@]}"; do
                IFS=',' read -ra PATHS <<< "${DIRECTORY_CATEGORIES[$category]}"
                for path in "${PATHS[@]}"; do
                    if [[ -d "$path" ]]; then
                        analyze_directory "$path" "$scan_level" "$category"
                    fi
                done
            done
            ;;
        "scan_category")
            if [[ -n "${DIRECTORY_CATEGORIES[$target_pattern]}" ]]; then
                IFS=',' read -ra PATHS <<< "${DIRECTORY_CATEGORIES[$target_pattern]}"
                for path in "${PATHS[@]}"; do
                    if [[ -d "$path" ]]; then
                        analyze_directory "$path" "$scan_level" "$target_pattern"
                    fi
                done
            fi
            ;;
        "scan_path")
            if [[ -d "$target_pattern" ]]; then
                analyze_directory "$target_pattern" "$scan_level" "custom"
            fi
            ;;
        "generate_report")
            generate_fleet_report
            ;;
    esac
}

# Generate comprehensive fleet report
generate_fleet_report() {
    local fleet_report="$REPORT_DIR/fleet_directory_report_$(date +%Y%m%d_%H%M%S).json"
    
    echo "Generating fleet directory report..."
    
    # Combine all individual reports
    local reports=($(find "$REPORT_DIR" -name "directory_analysis_*.json" -mtime -1))
    
    cat > "$fleet_report" << EOF
{
    "fleet_report": {
        "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
        "hostname": "$(hostname)",
        "total_reports": ${#reports[@]},
        "reports": []
    }
}
EOF
    
    for report in "${reports[@]}"; do
        if [[ -f "$report" ]]; then
            jq --slurpfile new_report "$report" '.fleet_report.reports += $new_report' "$fleet_report" > "${fleet_report}.tmp" && mv "${fleet_report}.tmp" "$fleet_report"
        fi
    done
    
    log_action "Fleet report generated: $fleet_report"
    echo "$fleet_report"
}

# Main execution function
main() {
    local action="${1:-analyze}"
    local target="${2:-/Users}"
    local scan_level="${3:-standard_scan}"
    
    log_action "=== MacFleet Directory Management Started ==="
    log_action "Action: $action, Target: $target, Scan Level: $scan_level"
    
    case "$action" in
        "analyze")
            analyze_directory "$target" "$scan_level"
            ;;
        "fleet-scan")
            manage_fleet_directories "scan_all" "" "$scan_level"
            ;;
        "category-scan")
            manage_fleet_directories "scan_category" "$target" "$scan_level"
            ;;
        "fleet-report")
            manage_fleet_directories "generate_report"
            ;;
        "help")
            echo "Usage: $0 [action] [target] [scan_level]"
            echo "Actions: analyze, fleet-scan, category-scan, fleet-report, help"
            echo "Scan Levels: quick_scan, standard_scan, comprehensive_scan, security_audit, performance_scan, compliance_scan"
            echo "Categories: ${!DIRECTORY_CATEGORIES[*]}"
            ;;
        *)
            log_action "ERROR: Unknown action: $action"
            exit 1
            ;;
    esac
    
    log_action "=== Directory management completed ==="
}

# Execute main function
main "$@"

Directory Listing Best Practices

Handle Spaces in File Names

# Safe handling of file names with spaces
while IFS= read -r -d '' file; do
    echo "Processing: $file"
    # Your processing logic here
done < <(find "/path/with spaces" -type f -print0)

Filter by File Types

# List only image files
ls -la /Users/QA/Desktop/Wallpapers/*.{jpg,jpeg,png,gif,bmp} 2>/dev/null

# List only text files
find /path/to/directory -name "*.txt" -o -name "*.md" -o -name "*.doc"

Performance Considerations

# For large directories, use find with limited depth
find /large/directory -maxdepth 2 -type f | head -100

# Use xargs for processing many files
find /directory -name "*.log" | xargs wc -l

Common Directory Operations

Directory Size Analysis

#!/bin/bash

# Analyze directory sizes
analyze_directory_sizes() {
    local base_path="$1"
    
    echo "Directory size analysis for: $base_path"
    echo "=================================="
    
    # Top 10 largest subdirectories
    du -h "$base_path"/* 2>/dev/null | sort -hr | head -10
    
    echo -e "\nTotal directory size:"
    du -sh "$base_path"
}

analyze_directory_sizes "/Users"

File Count by Type

#!/bin/bash

# Count files by extension
count_files_by_type() {
    local directory="$1"
    
    echo "File type distribution in: $directory"
    find "$directory" -type f | grep '\.' | rev | cut -d. -f1 | rev | sort | uniq -c | sort -nr
}

count_files_by_type "/Users/QA/Desktop/Wallpapers"

Important Notes

  • Use quotes around paths containing spaces: ls "/path with spaces"
  • Escape special characters with backslash when not using quotes
  • Check permissions before accessing system directories
  • Test scripts on sample directories before production use
  • Monitor performance when analyzing large directory structures
  • Consider security when listing sensitive directories in logs

Device Naming and Identity Management on macOS

Implement enterprise-grade device naming and identity management across your MacFleet deployment with standardized naming conventions, automated provisioning, and comprehensive identity validation. This tutorial provides solutions for maintaining consistent device identification and network presence.

Understanding macOS Device Identity

macOS devices have three distinct naming components that affect identification and network behavior:

  • Computer Name - User-friendly name displayed in Sharing preferences and Finder
  • Local Host Name - Network identifier with ".local" suffix for Bonjour/mDNS
  • Hostname - Primary network hostname used in terminal and network services
  • NetBIOS Name - Windows network compatibility identifier

Basic Device Naming Operations

Change Computer Name

#!/bin/bash

# Set computer name
scutil --set ComputerName "$ComputerName"

echo "Computer name set to: $ComputerName"

Change Local Host Name

#!/bin/bash

# Set local hostname for Bonjour/mDNS discovery
scutil --set LocalHostName "$LocalHostName"

echo "Local hostname set to: $LocalHostName"

Change Hostname

#!/bin/bash

# Set primary hostname for network services
# Example: Your-iMac.domain.com
scutil --set HostName "$HostName"

echo "Hostname set to: $HostName"

Flush DNS Cache

#!/bin/bash

# Flush DNS cache after name changes
sudo dscacheutil --flushcache

echo "DNS cache flushed successfully"

Enterprise Naming Standards

Standardized Naming Convention Script

#!/bin/bash

# MacFleet Enterprise Device Naming Tool
# Implements standardized naming conventions across fleet

# Configuration
CONFIG_FILE="/etc/macfleet/naming_policy.conf"
LOG_FILE="/var/log/macfleet_naming.log"
REPORT_DIR="/var/log/macfleet_reports"

# Create directories
mkdir -p "$(dirname "$CONFIG_FILE")" "$(dirname "$LOG_FILE")" "$REPORT_DIR"

# Default naming policy
cat > "$CONFIG_FILE" 2>/dev/null << 'EOF' || true
# MacFleet Device Naming Policy
# Version: 2.0

# Naming Convention Format
# Pattern: {PREFIX}-{LOCATION}-{TYPE}-{SEQUENCE}
# Example: MF-NYC-MBP-001

# Organization Settings
ORG_PREFIX="MF"
DOMAIN_SUFFIX="macfleet.local"
ENABLE_AUTO_NAMING=true
VALIDATE_NAMES=true

# Location Codes
LOCATION_CODES="NYC,SF,LA,CHI,BOS,SEA,ATL,MIA"
DEFAULT_LOCATION="NYC"

# Device Type Mapping
# Format: MODEL_IDENTIFIER:SHORT_CODE
DEVICE_TYPES=(
    "MacBookPro:MBP"
    "MacBookAir:MBA"
    "iMac:iMac"
    "iMacPro:iMP"
    "MacPro:MP"
    "Macmini:MM"
    "MacStudio:MS"
)

# Network Settings
ENABLE_NETBIOS=true
SYNC_ALL_NAMES=true
FLUSH_DNS_AFTER_CHANGE=true
EOF

# Source configuration
source "$CONFIG_FILE" 2>/dev/null || true

# Logging function
log_action() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

# Get device model information
get_device_model() {
    local model_identifier
    model_identifier=$(system_profiler SPHardwareDataType | grep "Model Identifier" | awk -F: '{print $2}' | xargs)
    
    # Map model to device type
    for mapping in "${DEVICE_TYPES[@]}"; do
        local model_pattern="${mapping%:*}"
        local type_code="${mapping#*:}"
        
        if [[ "$model_identifier" == *"$model_pattern"* ]]; then
            echo "$type_code"
            return 0
        fi
    done
    
    # Default fallback
    echo "MAC"
}

# Get location from user input or configuration
get_location_code() {
    local location="$1"
    
    if [[ -z "$location" ]]; then
        location="$DEFAULT_LOCATION"
    fi
    
    # Validate location code
    if [[ ",$LOCATION_CODES," == *",$location,"* ]]; then
        echo "$location"
    else
        echo "$DEFAULT_LOCATION"
        log_action "WARNING: Invalid location '$location', using default '$DEFAULT_LOCATION'"
    fi
}

# Generate sequence number
generate_sequence() {
    local prefix="$1"
    local sequence_file="/var/lib/macfleet/sequences/${prefix}.seq"
    
    mkdir -p "$(dirname "$sequence_file")"
    
    if [[ -f "$sequence_file" ]]; then
        local current_seq
        current_seq=$(cat "$sequence_file")
        ((current_seq++))
    else
        current_seq=1
    fi
    
    printf "%03d" "$current_seq" > "$sequence_file"
    printf "%03d" "$current_seq"
}

# Generate standardized device name
generate_device_name() {
    local location="$1"
    local custom_suffix="$2"
    
    local device_type
    device_type=$(get_device_model)
    
    local location_code
    location_code=$(get_location_code "$location")
    
    local base_prefix="${ORG_PREFIX}-${location_code}-${device_type}"
    
    if [[ -n "$custom_suffix" ]]; then
        echo "${base_prefix}-${custom_suffix}"
    else
        local sequence
        sequence=$(generate_sequence "$base_prefix")
        echo "${base_prefix}-${sequence}"
    fi
}

# Validate naming compliance
validate_device_name() {
    local name="$1"
    local errors=()
    
    # Length validation
    if [[ ${#name} -gt 15 ]]; then
        errors+=("Name too long (max 15 characters for NetBIOS compatibility)")
    fi
    
    # Character validation
    if [[ "$name" =~ [^A-Za-z0-9\-] ]]; then
        errors+=("Invalid characters (only alphanumeric and hyphens allowed)")
    fi
    
    # Format validation
    if [[ ! "$name" =~ ^[A-Z]{2,4}-[A-Z]{2,4}-[A-Z]{2,4}-[0-9]{3}$ ]] && [[ "$VALIDATE_NAMES" == "true" ]]; then
        errors+=("Name doesn't match organization standard format")
    fi
    
    if [[ ${#errors[@]} -eq 0 ]]; then
        return 0
    else
        printf '%s\n' "${errors[@]}"
        return 1
    fi
}

# Apply device names with validation
apply_device_names() {
    local computer_name="$1"
    local hostname="$2"
    local local_hostname="$3"
    
    echo "=== Applying Device Names ==="
    
    # Validate all names
    local validation_errors=0
    
    if ! validate_device_name "$computer_name"; then
        echo "❌ Computer name validation failed"
        ((validation_errors++))
    fi
    
    if [[ -n "$hostname" ]] && ! validate_device_name "${hostname%%.*}"; then
        echo "❌ Hostname validation failed"
        ((validation_errors++))
    fi
    
    if [[ -n "$local_hostname" ]] && ! validate_device_name "$local_hostname"; then
        echo "❌ Local hostname validation failed"
        ((validation_errors++))
    fi
    
    if [[ $validation_errors -gt 0 ]] && [[ "$VALIDATE_NAMES" == "true" ]]; then
        echo "⚠️  Name validation failed, aborting changes"
        return 1
    fi
    
    # Apply computer name
    if [[ -n "$computer_name" ]]; then
        scutil --set ComputerName "$computer_name"
        echo "✅ Computer name: $computer_name"
        log_action "Computer name changed to: $computer_name"
    fi
    
    # Apply hostname
    if [[ -n "$hostname" ]]; then
        scutil --set HostName "$hostname"
        echo "✅ Hostname: $hostname"
        log_action "Hostname changed to: $hostname"
    fi
    
    # Apply local hostname
    if [[ -n "$local_hostname" ]]; then
        scutil --set LocalHostName "$local_hostname"
        echo "✅ Local hostname: $local_hostname"
        log_action "Local hostname changed to: $local_hostname"
    fi
    
    # Apply NetBIOS name if enabled
    if [[ "$ENABLE_NETBIOS" == "true" ]] && [[ -n "$computer_name" ]]; then
        defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server NetBIOSName "$computer_name"
        echo "✅ NetBIOS name: $computer_name"
        log_action "NetBIOS name changed to: $computer_name"
    fi
    
    # Flush DNS cache
    if [[ "$FLUSH_DNS_AFTER_CHANGE" == "true" ]]; then
        sudo dscacheutil --flushcache
        echo "✅ DNS cache flushed"
        log_action "DNS cache flushed after name changes"
    fi
    
    return 0
}

# Auto-generate and apply names
auto_provision_device() {
    local location="$1"
    local custom_suffix="$2"
    
    echo "=== Auto-Provisioning Device Names ==="
    
    if [[ "$ENABLE_AUTO_NAMING" != "true" ]]; then
        echo "❌ Auto-naming disabled in policy"
        return 1
    fi
    
    # Generate base name
    local base_name
    base_name=$(generate_device_name "$location" "$custom_suffix")
    
    # Create all naming variants
    local computer_name="$base_name"
    local local_hostname="$base_name"
    local hostname="${base_name}.${DOMAIN_SUFFIX}"
    
    echo "Generated names:"
    echo "  Computer Name: $computer_name"
    echo "  Local Hostname: $local_hostname"
    echo "  Hostname: $hostname"
    echo ""
    
    # Apply names
    apply_device_names "$computer_name" "$hostname" "$local_hostname"
}

# Get current device identity
get_current_identity() {
    echo "=== Current Device Identity ==="
    
    local computer_name
    computer_name=$(scutil --get ComputerName 2>/dev/null || echo "Not Set")
    
    local hostname
    hostname=$(scutil --get HostName 2>/dev/null || echo "Not Set")
    
    local local_hostname
    local_hostname=$(scutil --get LocalHostName 2>/dev/null || echo "Not Set")
    
    local netbios_name
    netbios_name=$(defaults read /Library/Preferences/SystemConfiguration/com.apple.smb.server NetBIOSName 2>/dev/null || echo "Not Set")
    
    echo "Computer Name: $computer_name"
    echo "Hostname: $hostname"
    echo "Local Hostname: $local_hostname"
    echo "NetBIOS Name: $netbios_name"
    echo "Serial Number: $(system_profiler SPHardwareDataType | grep "Serial Number" | awk -F: '{print $2}' | xargs)"
    echo "Model: $(system_profiler SPHardwareDataType | grep "Model Identifier" | awk -F: '{print $2}' | xargs)"
}

# Generate compliance report
generate_naming_report() {
    local report_file="$REPORT_DIR/device_naming_$(date +%Y%m%d_%H%M%S).json"
    
    echo "=== Generating Naming Compliance Report ==="
    
    local computer_name hostname local_hostname netbios_name
    computer_name=$(scutil --get ComputerName 2>/dev/null || echo "")
    hostname=$(scutil --get HostName 2>/dev/null || echo "")
    local_hostname=$(scutil --get LocalHostName 2>/dev/null || echo "")
    netbios_name=$(defaults read /Library/Preferences/SystemConfiguration/com.apple.smb.server NetBIOSName 2>/dev/null || echo "")
    
    # Check compliance
    local computer_compliant="false"
    local hostname_compliant="false"
    local naming_compliant="false"
    
    if validate_device_name "$computer_name" >/dev/null 2>&1; then
        computer_compliant="true"
    fi
    
    if [[ -n "$hostname" ]] && validate_device_name "${hostname%%.*}" >/dev/null 2>&1; then
        hostname_compliant="true"
    fi
    
    if [[ "$computer_compliant" == "true" ]] && [[ "$hostname_compliant" == "true" ]]; then
        naming_compliant="true"
    fi
    
    # Create JSON report
    cat > "$report_file" << EOF
{
  "report_type": "device_naming_compliance",
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "device_info": {
    "hostname": "$(hostname)",
    "serial_number": "$(system_profiler SPHardwareDataType | grep "Serial Number" | awk -F: '{print $2}' | xargs)",
    "model_identifier": "$(system_profiler SPHardwareDataType | grep "Model Identifier" | awk -F: '{print $2}' | xargs)",
    "macos_version": "$(sw_vers -productVersion)"
  },
  "current_names": {
    "computer_name": "$computer_name",
    "hostname": "$hostname",
    "local_hostname": "$local_hostname",
    "netbios_name": "$netbios_name"
  },
  "compliance": {
    "computer_name_compliant": $computer_compliant,
    "hostname_compliant": $hostname_compliant,
    "overall_compliant": $naming_compliant,
    "policy_version": "2.0"
  },
  "policy_settings": {
    "org_prefix": "$ORG_PREFIX",
    "domain_suffix": "$DOMAIN_SUFFIX",
    "auto_naming_enabled": $ENABLE_AUTO_NAMING,
    "validation_enabled": $VALIDATE_NAMES
  }
}
EOF
    
    echo "Naming report saved to: $report_file"
    log_action "Device naming report generated: $report_file"
}

# Main function with argument handling
main() {
    log_action "=== MacFleet Device Naming Tool Started ==="
    
    case "${1:-status}" in
        "auto-provision")
            auto_provision_device "$2" "$3"
            ;;
        "set-names")
            apply_device_names "$2" "$3" "$4"
            ;;
        "validate")
            get_current_identity
            echo ""
            if validate_device_name "$2" 2>/dev/null; then
                echo "✅ Name '$2' is compliant"
            else
                echo "❌ Name '$2' validation failed:"
                validate_device_name "$2"
            fi
            ;;
        "report")
            generate_naming_report
            ;;
        "status"|*)
            get_current_identity
            ;;
    esac
    
    log_action "=== Device naming operation completed ==="
}

# Execute main function
main "$@"

Advanced Identity Management

Network Identity Synchronization

#!/bin/bash

# Synchronize all naming components for consistency
sync_network_identity() {
    echo "=== Network Identity Synchronization ==="
    
    # Get current computer name as source
    local source_name
    source_name=$(scutil --get ComputerName 2>/dev/null)
    
    if [[ -z "$source_name" ]]; then
        echo "❌ No computer name set, cannot synchronize"
        return 1
    fi
    
    # Generate consistent names
    local clean_name
    clean_name=$(echo "$source_name" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
    
    local local_hostname="$clean_name"
    local hostname="${clean_name}.local"
    
    echo "Synchronizing identity from: $source_name"
    echo "Local Hostname: $local_hostname"
    echo "Hostname: $hostname"
    
    # Apply synchronized names
    scutil --set LocalHostName "$local_hostname"
    scutil --set HostName "$hostname"
    
    # Update NetBIOS
    defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server NetBIOSName "$source_name"
    
    # Flush DNS cache
    sudo dscacheutil --flushcache
    
    echo "✅ Network identity synchronized"
}

sync_network_identity

Bulk Device Provisioning

#!/bin/bash

# Bulk device naming for fleet deployment
bulk_provision_devices() {
    local csv_file="$1"
    local report_file="/tmp/bulk_naming_$(date +%Y%m%d_%H%M%S).csv"
    
    echo "=== Bulk Device Provisioning ==="
    echo "Serial,Computer_Name,Hostname,Status,Timestamp" > "$report_file"
    
    # Read CSV file with device assignments
    # Format: Serial,Location,CustomSuffix
    while IFS=',' read -r serial location suffix; do
        # Skip header
        [[ "$serial" == "Serial" ]] && continue
        
        local current_serial
        current_serial=$(system_profiler SPHardwareDataType | grep "Serial Number" | awk -F: '{print $2}' | xargs)
        
        if [[ "$current_serial" == "$serial" ]]; then
            echo "Processing device: $serial"
            
            # Generate names for this device
            local device_name
            device_name=$(generate_device_name "$location" "$suffix")
            
            local computer_name="$device_name"
            local hostname="${device_name}.macfleet.local"
            
            # Apply names
            if apply_device_names "$computer_name" "$hostname" "$device_name"; then
                echo "$serial,$computer_name,$hostname,SUCCESS,$(date)" >> "$report_file"
                echo "✅ Successfully provisioned: $computer_name"
            else
                echo "$serial,$computer_name,$hostname,FAILED,$(date)" >> "$report_file"
                echo "❌ Failed to provision: $computer_name"
            fi
            
            break
        fi
    done < "$csv_file"
    
    echo "Bulk provisioning report: $report_file"
}

# Example usage:
# bulk_provision_devices "/path/to/device_assignments.csv"

Identity Validation and Compliance

Comprehensive Validation Script

#!/bin/bash

# Complete device identity validation
validate_device_identity() {
    echo "=== Device Identity Validation ==="
    
    local issues=0
    local warnings=0
    
    # Get all current names
    local computer_name hostname local_hostname
    computer_name=$(scutil --get ComputerName 2>/dev/null)
    hostname=$(scutil --get HostName 2>/dev/null)
    local_hostname=$(scutil --get LocalHostName 2>/dev/null)
    
    # Computer name validation
    if [[ -z "$computer_name" ]]; then
        echo "❌ Computer name not set"
        ((issues++))
    elif ! validate_device_name "$computer_name" >/dev/null 2>&1; then
        echo "⚠️  Computer name format non-compliant: $computer_name"
        ((warnings++))
    else
        echo "✅ Computer name compliant: $computer_name"
    fi
    
    # Hostname validation
    if [[ -z "$hostname" ]]; then
        echo "⚠️  Hostname not set"
        ((warnings++))
    elif ! validate_device_name "${hostname%%.*}" >/dev/null 2>&1; then
        echo "⚠️  Hostname format non-compliant: $hostname"
        ((warnings++))
    else
        echo "✅ Hostname compliant: $hostname"
    fi
    
    # Local hostname validation
    if [[ -z "$local_hostname" ]]; then
        echo "⚠️  Local hostname not set"
        ((warnings++))
    elif ! validate_device_name "$local_hostname" >/dev/null 2>&1; then
        echo "⚠️  Local hostname format non-compliant: $local_hostname"
        ((warnings++))
    else
        echo "✅ Local hostname compliant: $local_hostname"
    fi
    
    # Consistency check
    if [[ -n "$computer_name" && -n "$local_hostname" ]]; then
        local expected_local
        expected_local=$(echo "$computer_name" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
        
        if [[ "$local_hostname" != "$expected_local" ]]; then
            echo "⚠️  Names not synchronized (Computer: $computer_name, Local: $local_hostname)"
            ((warnings++))
        fi
    fi
    
    # Network connectivity validation
    echo ""
    echo "=== Network Identity Testing ==="
    
    # Test Bonjour resolution
    if ping -c 1 "${local_hostname}.local" >/dev/null 2>&1; then
        echo "✅ Bonjour resolution working"
    else
        echo "⚠️  Bonjour resolution failed"
        ((warnings++))
    fi
    
    # Test hostname resolution
    if [[ -n "$hostname" ]] && ping -c 1 "$hostname" >/dev/null 2>&1; then
        echo "✅ Hostname resolution working"
    elif [[ -n "$hostname" ]]; then
        echo "⚠️  Hostname resolution failed"
        ((warnings++))
    fi
    
    # Summary
    echo ""
    echo "=== Validation Summary ==="
    echo "Issues: $issues"
    echo "Warnings: $warnings"
    
    if [[ $issues -eq 0 && $warnings -eq 0 ]]; then
        echo "✅ Device identity fully compliant"
        return 0
    elif [[ $issues -eq 0 ]]; then
        echo "⚠️  Device identity has warnings but is functional"
        return 1
    else
        echo "❌ Device identity has critical issues"
        return 2
    fi
}

validate_device_identity

Network Service Integration

DNS and Directory Services

#!/bin/bash

# Update DNS and directory service entries
update_network_services() {
    echo "=== Network Services Update ==="
    
    # Get current names
    local computer_name hostname
    computer_name=$(scutil --get ComputerName 2>/dev/null)
    hostname=$(scutil --get HostName 2>/dev/null)
    
    # Update mDNSResponder
    echo "Restarting mDNSResponder..."
    sudo launchctl kickstart -k system/com.apple.mDNSResponder
    
    # Update NetBIOS registration
    if [[ -n "$computer_name" ]]; then
        echo "Updating NetBIOS registration..."
        defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server NetBIOSName "$computer_name"
        sudo launchctl kickstart -k system/com.apple.smbd
    fi
    
    # Flush all caches
    echo "Flushing system caches..."
    sudo dscacheutil --flushcache
    sudo killall -HUP mDNSResponder
    
    # Update Open Directory if bound
    if dsconfigldap -v >/dev/null 2>&1; then
        echo "Updating Open Directory registration..."
        sudo dsconfigldap -f
    fi
    
    echo "✅ Network services updated"
}

update_network_services

Important Configuration Notes

System Configuration Utility (scutil)

The scutil command manages macOS system configuration:

  • ComputerName - User-visible device name
  • HostName - Primary network hostname
  • LocalHostName - Bonjour/mDNS identifier
  • NetBIOSName - Windows network compatibility

Best Practices for Enterprise

  1. Standardized Naming Convention

    • Use consistent format: ORG-LOCATION-TYPE-SEQUENCE
    • Keep names under 15 characters for NetBIOS compatibility
    • Avoid special characters and spaces
  2. Network Considerations

    • Ensure unique names across network segments
    • Test DNS resolution after changes
    • Coordinate with network administrators
  3. Fleet Management

    • Implement automated provisioning
    • Maintain naming databases
    • Regular compliance auditing
    • Backup configuration before changes
  4. Security Implications

    • Device names may reveal organizational structure
    • Consider privacy requirements
    • Implement access controls for naming changes

Troubleshooting Common Issues

  • Name changes not taking effect - Restart networking services or reboot
  • Bonjour conflicts - Ensure unique LocalHostName values
  • DNS resolution issues - Flush cache and restart mDNSResponder
  • Windows network problems - Verify NetBIOS name configuration

Remember to test naming changes thoroughly and coordinate with network administrators before implementing across your MacFleet deployment.

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

TaskCommand
Get current locationcurl -s https://ipinfo.io
Formatted location infocurl -s https://ipinfo.io | jq '.'
Save location to filecurl -s https://ipinfo.io > location.json
Get specific fieldcurl -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

  1. Data Collection - Only collect necessary location data
  2. Storage Security - Encrypt location databases
  3. Access Control - Limit access to location information
  4. Retention Policy - Define data retention periods
  5. 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

  1. Network connectivity problems

    • Check internet connection
    • Verify firewall settings
    • Test alternative geolocation services
  2. Inaccurate location data

    • VPN/proxy connections affect accuracy
    • ISP location may not match physical location
    • Use multiple services for verification
  3. 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:

  1. Policy definition - Establish clear location tracking policies
  2. Legal compliance - Ensure compliance with privacy regulations
  3. Security monitoring - Implement real-time location alerts
  4. Data governance - Define data retention and access policies
  5. User communication - Inform users about location tracking practices