Tutorial

Nuevas actualizaciones y mejoras para Macfleet.

Aviso importante

Los ejemplos de código y scripts proporcionados en estos tutoriales son solo para propósitos educativos. Macfleet no es responsable de ningún problema, daño o vulnerabilidad de seguridad que pueda surgir del uso, modificación o implementación de estos ejemplos. Siempre revisa y prueba el código en un entorno seguro antes de usarlo en sistemas de producción.

App Store Inventory Management on macOS

Efficiently manage and track App Store applications across your MacFleet devices with comprehensive inventory tools. This tutorial covers app discovery, license compliance, usage analytics, and enterprise-grade reporting for Mac App Store applications.

Understanding App Store App Management

App Store applications on macOS contain unique identifiers and receipts that enable enterprise tracking:

Core Components

  • MAS Receipts - Digital receipts stored in app bundles for verification
  • Bundle Identifiers - Unique app identification across the App Store ecosystem
  • Version Tracking - App version management and update compliance
  • License Management - Enterprise license allocation and compliance monitoring
  • Usage Analytics - Application usage patterns and adoption metrics

Enterprise Benefits

  • Software Asset Management - Complete inventory of App Store applications
  • License Compliance - Track enterprise app licenses and allocations
  • Security Monitoring - Identify unauthorized or non-compliant applications
  • Cost Optimization - Optimize App Store spending and license utilization
  • Policy Enforcement - Ensure compliance with enterprise app policies

Basic App Store App Discovery

Simple App Store App List

#!/bin/bash

# Basic App Store app discovery
echo "📱 Discovering App Store Applications"
echo "===================================="
echo ""

app_store_apps=$(find /Applications -path '*Contents/_MASReceipt/receipt' -maxdepth 4 -print | \
    sed 's#.app/Contents/_MASReceipt/receipt#.app#g; s#/Applications/##')

if [[ -n "$app_store_apps" ]]; then
    echo "App Store Applications Found:"
    echo "$app_store_apps" | sort
    
    local app_count=$(echo "$app_store_apps" | wc -l | xargs)
    echo ""
    echo "Total App Store apps: $app_count"
else
    echo "No App Store applications found in /Applications"
fi

Enhanced App Discovery with Details

#!/bin/bash

# Comprehensive App Store app discovery with metadata
discover_app_store_apps() {
    echo "🔍 Comprehensive App Store App Discovery"
    echo "======================================="
    echo ""
    
    local apps_found=0
    local total_size=0
    
    # Find all App Store apps with receipts
    while IFS= read -r -d '' app_path; do
        if [[ -n "$app_path" ]]; then
            local app_name=$(basename "$app_path" .app)
            local app_bundle_id=""
            local app_version=""
            local app_size=""
            local install_date=""
            
            # Get bundle identifier
            if [[ -f "$app_path/Contents/Info.plist" ]]; then
                app_bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2>/dev/null || echo "Unknown")
                app_version=$(defaults read "$app_path/Contents/Info.plist" CFBundleShortVersionString 2>/dev/null || echo "Unknown")
            fi
            
            # Get app size
            if [[ -d "$app_path" ]]; then
                app_size=$(du -sh "$app_path" 2>/dev/null | cut -f1 || echo "Unknown")
                # Convert size to bytes for totaling
                local size_bytes=$(du -s "$app_path" 2>/dev/null | cut -f1 || echo "0")
                total_size=$((total_size + size_bytes))
            fi
            
            # Get installation/modification date
            if [[ -f "$app_path/Contents/_MASReceipt/receipt" ]]; then
                install_date=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$app_path/Contents/_MASReceipt/receipt" 2>/dev/null || echo "Unknown")
            fi
            
            echo "App: $app_name"
            echo "  Bundle ID: $app_bundle_id"
            echo "  Version: $app_version"
            echo "  Size: $app_size"
            echo "  Install Date: $install_date"
            echo "  Path: $app_path"
            echo ""
            
            ((apps_found++))
        fi
    done < <(find /Applications -name "*.app" -path '*Contents/_MASReceipt/receipt' -exec dirname {} \; 2>/dev/null | \
             sed 's|/Contents/_MASReceipt||' | sort -u | tr '\n' '\0')
    
    # Summary statistics
    echo "=== Discovery Summary ==="
    echo "Total App Store apps found: $apps_found"
    echo "Total disk space used: $(echo "scale=2; $total_size / 1024 / 1024" | bc 2>/dev/null || echo "Unknown") MB"
    echo "Average app size: $(echo "scale=2; $total_size / $apps_found / 1024 / 1024" | bc 2>/dev/null || echo "Unknown") MB"
    echo "Scan completed: $(date)"
    
    return $apps_found
}

# Execute discovery
discover_app_store_apps

Enterprise App Inventory Management

Comprehensive App Inventory Script

#!/bin/bash

# Enterprise App Store inventory management
generate_app_store_inventory() {
    local output_format="${1:-json}"
    local include_metadata="${2:-true}"
    local export_path="${3:-/tmp}"
    
    echo "📊 Generating Enterprise App Store Inventory"
    echo "==========================================="
    echo "Output format: $output_format"
    echo "Include metadata: $include_metadata"
    echo ""
    
    local timestamp=$(date +"%Y%m%d_%H%M%S")
    local hostname=$(hostname)
    local report_file="$export_path/app_store_inventory_${hostname}_${timestamp}"
    
    # System information
    local device_info=$(system_profiler SPHardwareDataType | grep -E "(Model Name|Serial Number)" | \
                        awk -F': ' '{print $2}' | xargs | tr ' ' '_')
    local macos_version=$(sw_vers -productVersion)
    local current_user=$(stat -f%Su /dev/console 2>/dev/null || echo "$USER")
    
    # Initialize inventory data structure
    local inventory_data=""
    local app_count=0
    local total_size_bytes=0
    
    echo "Scanning applications..."
    
    # Discover all App Store applications
    while IFS= read -r -d '' app_path; do
        if [[ -d "$app_path" && -f "$app_path/Contents/_MASReceipt/receipt" ]]; then
            local app_name=$(basename "$app_path" .app)
            local bundle_id=""
            local version=""
            local build_version=""
            local size_bytes=0
            local size_human=""
            local install_date=""
            local last_modified=""
            local executable_path=""
            local app_category=""
            local developer_name=""
            local app_store_url=""
            
            # Extract app metadata
            if [[ -f "$app_path/Contents/Info.plist" ]]; then
                bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2>/dev/null || echo "unknown")
                version=$(defaults read "$app_path/Contents/Info.plist" CFBundleShortVersionString 2>/dev/null || echo "unknown")
                build_version=$(defaults read "$app_path/Contents/Info.plist" CFBundleVersion 2>/dev/null || echo "unknown")
                executable_path=$(defaults read "$app_path/Contents/Info.plist" CFBundleExecutable 2>/dev/null || echo "unknown")
                app_category=$(defaults read "$app_path/Contents/Info.plist" LSApplicationCategoryType 2>/dev/null || echo "unknown")
            fi
            
            # Get app size
            size_bytes=$(du -sk "$app_path" 2>/dev/null | cut -f1 || echo "0")
            size_bytes=$((size_bytes * 1024))  # Convert KB to bytes
            size_human=$(du -sh "$app_path" 2>/dev/null | cut -f1 || echo "unknown")
            total_size_bytes=$((total_size_bytes + size_bytes))
            
            # Get timestamps
            install_date=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$app_path/Contents/_MASReceipt/receipt" 2>/dev/null || echo "unknown")
            last_modified=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$app_path" 2>/dev/null || echo "unknown")
            
            # Try to extract additional metadata if requested
            if [[ "$include_metadata" == "true" ]]; then
                # Look for team identifier
                local team_id=""
                if command -v codesign >/dev/null 2>&1; then
                    team_id=$(codesign -dv "$app_path" 2>&1 | grep "TeamIdentifier" | awk -F'=' '{print $2}' || echo "unknown")
                fi
                
                # Check if app is running
                local is_running="false"
                if pgrep -f "$app_name" >/dev/null 2>&1; then
                    is_running="true"
                fi
                
                # Get app permissions (simplified check)
                local has_camera_permission="unknown"
                local has_microphone_permission="unknown"
                local has_location_permission="unknown"
                
                if [[ -f "$app_path/Contents/Info.plist" ]]; then
                    if defaults read "$app_path/Contents/Info.plist" NSCameraUsageDescription >/dev/null 2>&1; then
                        has_camera_permission="requested"
                    else
                        has_camera_permission="not_requested"
                    fi
                    
                    if defaults read "$app_path/Contents/Info.plist" NSMicrophoneUsageDescription >/dev/null 2>&1; then
                        has_microphone_permission="requested"
                    else
                        has_microphone_permission="not_requested"
                    fi
                    
                    if defaults read "$app_path/Contents/Info.plist" NSLocationUsageDescription >/dev/null 2>&1; then
                        has_location_permission="requested"
                    else
                        has_location_permission="not_requested"
                    fi
                fi
            fi
            
            # Format output based on requested format
            case "$output_format" in
                "json")
                    local app_json="{
                        \"app_name\": \"$app_name\",
                        \"bundle_id\": \"$bundle_id\",
                        \"version\": \"$version\",
                        \"build_version\": \"$build_version\",
                        \"size_bytes\": $size_bytes,
                        \"size_human\": \"$size_human\",
                        \"install_date\": \"$install_date\",
                        \"last_modified\": \"$last_modified\",
                        \"app_path\": \"$app_path\",
                        \"executable_path\": \"$executable_path\",
                        \"category\": \"$app_category\""
                    
                    if [[ "$include_metadata" == "true" ]]; then
                        app_json="$app_json,
                        \"team_id\": \"$team_id\",
                        \"is_running\": $is_running,
                        \"camera_permission\": \"$has_camera_permission\",
                        \"microphone_permission\": \"$has_microphone_permission\",
                        \"location_permission\": \"$has_location_permission\""
                    fi
                    
                    app_json="$app_json}"
                    
                    if [[ $app_count -gt 0 ]]; then
                        inventory_data="$inventory_data,$app_json"
                    else
                        inventory_data="$app_json"
                    fi
                    ;;
                    
                "csv")
                    if [[ $app_count -eq 0 ]]; then
                        # CSV header
                        if [[ "$include_metadata" == "true" ]]; then
                            inventory_data="App Name,Bundle ID,Version,Build Version,Size (Bytes),Size (Human),Install Date,Last Modified,App Path,Category,Team ID,Is Running,Camera Permission,Microphone Permission,Location Permission"
                        else
                            inventory_data="App Name,Bundle ID,Version,Build Version,Size (Bytes),Size (Human),Install Date,Last Modified,App Path,Category"
                        fi
                    fi
                    
                    local csv_line="\"$app_name\",\"$bundle_id\",\"$version\",\"$build_version\",$size_bytes,\"$size_human\",\"$install_date\",\"$last_modified\",\"$app_path\",\"$app_category\""
                    
                    if [[ "$include_metadata" == "true" ]]; then
                        csv_line="$csv_line,\"$team_id\",$is_running,\"$has_camera_permission\",\"$has_microphone_permission\",\"$has_location_permission\""
                    fi
                    
                    inventory_data="$inventory_data\n$csv_line"
                    ;;
                    
                "text"|*)
                    inventory_data="$inventory_data
App: $app_name
  Bundle ID: $bundle_id
  Version: $version ($build_version)
  Size: $size_human ($size_bytes bytes)
  Install Date: $install_date
  Last Modified: $last_modified
  Path: $app_path
  Category: $app_category"
                    
                    if [[ "$include_metadata" == "true" ]]; then
                        inventory_data="$inventory_data
  Team ID: $team_id
  Currently Running: $is_running
  Camera Permission: $has_camera_permission
  Microphone Permission: $has_microphone_permission
  Location Permission: $has_location_permission"
                    fi
                    
                    inventory_data="$inventory_data
"
                    ;;
            esac
            
            ((app_count++))
        fi
    done < <(find /Applications -name "*.app" -type d -print0 2>/dev/null)
    
    # Generate final report
    local total_size_human=$(echo "scale=2; $total_size_bytes / 1024 / 1024 / 1024" | bc 2>/dev/null || echo "unknown")
    
    case "$output_format" in
        "json")
            local final_report="{
                \"report_metadata\": {
                    \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\",
                    \"hostname\": \"$hostname\",
                    \"device_info\": \"$device_info\",
                    \"macos_version\": \"$macos_version\",
                    \"current_user\": \"$current_user\",
                    \"total_apps\": $app_count,
                    \"total_size_bytes\": $total_size_bytes,
                    \"total_size_gb\": \"$total_size_human GB\"
                },
                \"applications\": [$inventory_data]
            }"
            
            echo "$final_report" > "${report_file}.json"
            echo "✅ JSON report saved to: ${report_file}.json"
            ;;
            
        "csv")
            echo -e "$inventory_data" > "${report_file}.csv"
            echo "✅ CSV report saved to: ${report_file}.csv"
            ;;
            
        "text"|*)
            {
                echo "MacFleet App Store Inventory Report"
                echo "=================================="
                echo "Generated: $(date)"
                echo "Hostname: $hostname"
                echo "Device: $device_info"
                echo "macOS Version: $macos_version"
                echo "Current User: $current_user"
                echo "Total Apps: $app_count"
                echo "Total Size: $total_size_human GB"
                echo ""
                echo "Applications:"
                echo "============"
                echo "$inventory_data"
            } > "${report_file}.txt"
            echo "✅ Text report saved to: ${report_file}.txt"
            ;;
    esac
    
    # Summary output
    echo ""
    echo "=== Inventory Summary ==="
    echo "Apps discovered: $app_count"
    echo "Total size: $total_size_human GB"
    echo "Report format: $output_format"
    echo "Report location: $report_file"
    echo ""
    
    return $app_count
}

# Usage examples
echo "📊 Enterprise App Store Inventory Management"
echo ""
echo "1. Generate JSON inventory with metadata"
generate_app_store_inventory "json" "true" "/tmp"
echo ""

echo "2. Generate CSV inventory (basic)"
generate_app_store_inventory "csv" "false" "/tmp"
echo ""

echo "3. Generate text inventory with metadata"
generate_app_store_inventory "text" "true" "/tmp"

App Compliance and Security Analysis

#!/bin/bash

# App Store app compliance and security analysis
analyze_app_compliance() {
    echo "🔒 App Store Application Compliance Analysis"
    echo "==========================================="
    echo ""
    
    local compliance_issues=0
    local security_concerns=0
    local policy_violations=0
    
    # Define enterprise policy rules
    local blocked_apps=("Games" "Social Media" "Dating")  # Example categories
    local required_apps=("Microsoft Word" "Microsoft Excel" "Slack")  # Example required apps
    local max_app_age_days=365  # Apps older than 1 year flagged
    local min_security_permissions=("Camera" "Microphone" "Location")
    
    echo "Analyzing App Store applications for compliance..."
    echo ""
    
    # Track required apps
    local found_required_apps=()
    local missing_required_apps=()
    
    # Analyze each App Store application
    while IFS= read -r -d '' app_path; do
        if [[ -d "$app_path" && -f "$app_path/Contents/_MASReceipt/receipt" ]]; then
            local app_name=$(basename "$app_path" .app)
            local bundle_id=""
            local version=""
            local category=""
            local install_date=""
            local days_since_install=""
            
            echo "Analyzing: $app_name"
            
            # Get app metadata
            if [[ -f "$app_path/Contents/Info.plist" ]]; then
                bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2>/dev/null || echo "unknown")
                version=$(defaults read "$app_path/Contents/Info.plist" CFBundleShortVersionString 2>/dev/null || echo "unknown")
                category=$(defaults read "$app_path/Contents/Info.plist" LSApplicationCategoryType 2>/dev/null || echo "unknown")
            fi
            
            # Get install date and calculate age
            if [[ -f "$app_path/Contents/_MASReceipt/receipt" ]]; then
                install_date=$(stat -f "%Sm" -t "%Y-%m-%d" "$app_path/Contents/_MASReceipt/receipt" 2>/dev/null || echo "unknown")
                if [[ "$install_date" != "unknown" ]]; then
                    local install_epoch=$(date -j -f "%Y-%m-%d" "$install_date" "+%s" 2>/dev/null || echo "0")
                    local current_epoch=$(date "+%s")
                    days_since_install=$(( (current_epoch - install_epoch) / 86400 ))
                fi
            fi
            
            # Check against blocked apps/categories
            local is_blocked=false
            for blocked_category in "${blocked_apps[@]}"; do
                if [[ "$category" =~ "$blocked_category" ]] || [[ "$app_name" =~ "$blocked_category" ]]; then
                    echo "  ❌ POLICY VIOLATION: App category '$category' is blocked"
                    is_blocked=true
                    ((policy_violations++))
                    break
                fi
            done
            
            # Check app age
            if [[ -n "$days_since_install" && "$days_since_install" -gt "$max_app_age_days" ]]; then
                echo "  ⚠️ COMPLIANCE: App is older than $max_app_age_days days ($days_since_install days)"
                ((compliance_issues++))
            fi
            
            # Check for security permissions
            local permission_count=0
            if [[ -f "$app_path/Contents/Info.plist" ]]; then
                if defaults read "$app_path/Contents/Info.plist" NSCameraUsageDescription >/dev/null 2>&1; then
                    echo "  🔒 SECURITY: App requests camera access"
                    ((permission_count++))
                fi
                
                if defaults read "$app_path/Contents/Info.plist" NSMicrophoneUsageDescription >/dev/null 2>&1; then
                    echo "  🔒 SECURITY: App requests microphone access"
                    ((permission_count++))
                fi
                
                if defaults read "$app_path/Contents/Info.plist" NSLocationUsageDescription >/dev/null 2>&1; then
                    echo "  🔒 SECURITY: App requests location access"
                    ((permission_count++))
                fi
                
                if [[ "$permission_count" -gt 2 ]]; then
                    echo "  ⚠️ SECURITY CONCERN: App requests multiple sensitive permissions"
                    ((security_concerns++))
                fi
            fi
            
            # Check against required apps
            for required_app in "${required_apps[@]}"; do
                if [[ "$app_name" =~ "$required_app" ]]; then
                    found_required_apps+=("$required_app")
                fi
            done
            
            # Code signing verification
            if command -v codesign >/dev/null 2>&1; then
                if ! codesign -v "$app_path" >/dev/null 2>&1; then
                    echo "  ❌ SECURITY: Invalid code signature"
                    ((security_concerns++))
                fi
            fi
            
            echo "  ✅ Analysis complete"
            echo ""
        fi
    done < <(find /Applications -name "*.app" -type d -print0 2>/dev/null)
    
    # Check for missing required apps
    for required_app in "${required_apps[@]}"; do
        local found=false
        for found_app in "${found_required_apps[@]}"; do
            if [[ "$found_app" == "$required_app" ]]; then
                found=true
                break
            fi
        done
        
        if [[ "$found" == "false" ]]; then
            missing_required_apps+=("$required_app")
        fi
    done
    
    # Generate compliance report
    echo "=== Compliance Analysis Report ==="
    echo "Policy violations: $policy_violations"
    echo "Compliance issues: $compliance_issues"
    echo "Security concerns: $security_concerns"
    echo ""
    
    if [[ ${#missing_required_apps[@]} -gt 0 ]]; then
        echo "Missing required applications:"
        for missing_app in "${missing_required_apps[@]}"; do
            echo "  - $missing_app"
        done
        echo ""
    fi
    
    if [[ ${#found_required_apps[@]} -gt 0 ]]; then
        echo "Found required applications:"
        for found_app in "${found_required_apps[@]}"; do
            echo "  ✅ $found_app"
        done
        echo ""
    fi
    
    # Overall compliance score
    local total_issues=$((policy_violations + compliance_issues + security_concerns + ${#missing_required_apps[@]}))
    if [[ "$total_issues" -eq 0 ]]; then
        echo "🎉 COMPLIANCE STATUS: FULLY COMPLIANT"
    elif [[ "$total_issues" -le 3 ]]; then
        echo "⚠️ COMPLIANCE STATUS: MINOR ISSUES ($total_issues issues)"
    else
        echo "❌ COMPLIANCE STATUS: MAJOR ISSUES ($total_issues issues)"
    fi
    
    return $total_issues
}

# Execute compliance analysis
analyze_app_compliance

Fleet Management and Reporting

Enterprise Fleet App Management

#!/bin/bash

# Enterprise fleet-wide App Store management
manage_fleet_app_inventory() {
    local operation="${1:-discover}"  # discover, report, compliance, update
    local fleet_config_file="${2:-/etc/macfleet/app_config.json}"
    local output_dir="${3:-/var/log/macfleet}"
    
    echo "🚀 MacFleet Enterprise App Store Management"
    echo "=========================================="
    echo "Operation: $operation"
    echo "Config file: $fleet_config_file"
    echo "Output directory: $output_dir"
    echo ""
    
    # Ensure output directory exists
    mkdir -p "$output_dir"
    
    # Device identification
    local device_id=$(system_profiler SPHardwareDataType | grep "Serial Number" | awk -F': ' '{print $2}' | xargs)
    local device_name=$(hostname)
    local timestamp=$(date +"%Y%m%d_%H%M%S")
    local report_file="$output_dir/fleet_app_report_${device_id}_${timestamp}.json"
    
    case "$operation" in
        "discover")
            echo "🔍 Fleet App Discovery Mode"
            echo "==========================="
            
            # Generate comprehensive app inventory
            local app_data=$(generate_comprehensive_app_data)
            
            # Create fleet report
            local fleet_report="{
                \"device_info\": {
                    \"device_id\": \"$device_id\",
                    \"device_name\": \"$device_name\",
                    \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\",
                    \"macos_version\": \"$(sw_vers -productVersion)\",
                    \"current_user\": \"$(stat -f%Su /dev/console 2>/dev/null || echo "$USER")\"
                },
                \"app_inventory\": $app_data,
                \"operation\": \"fleet_discovery\"
            }"
            
            echo "$fleet_report" > "$report_file"
            echo "✅ Fleet discovery report saved: $report_file"
            ;;
            
        "compliance")
            echo "📋 Fleet Compliance Check"
            echo "========================="
            
            # Run compliance analysis
            local compliance_result=$(analyze_fleet_compliance)
            
            # Create compliance report
            local compliance_report="{
                \"device_info\": {
                    \"device_id\": \"$device_id\",
                    \"device_name\": \"$device_name\",
                    \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"
                },
                \"compliance_results\": $compliance_result,
                \"operation\": \"compliance_check\"
            }"
            
            echo "$compliance_report" > "$report_file"
            echo "✅ Compliance report saved: $report_file"
            ;;
            
        "report")
            echo "📊 Fleet Reporting Mode"
            echo "======================"
            
            # Generate all report formats
            echo "Generating comprehensive fleet reports..."
            
            generate_app_store_inventory "json" "true" "$output_dir"
            generate_app_store_inventory "csv" "true" "$output_dir"
            generate_app_store_inventory "text" "true" "$output_dir"
            
            echo "✅ All fleet reports generated in: $output_dir"
            ;;
            
        "update")
            echo "🔄 Fleet Update Check"
            echo "===================="
            
            # Check for app updates
            check_app_store_updates
            ;;
            
        *)
            echo "❌ Unknown operation: $operation"
            echo "Available operations: discover, report, compliance, update"
            return 1
            ;;
    esac
    
    return 0
}

# Helper function to generate comprehensive app data
generate_comprehensive_app_data() {
    local apps_json="["
    local first_app=true
    
    while IFS= read -r -d '' app_path; do
        if [[ -d "$app_path" && -f "$app_path/Contents/_MASReceipt/receipt" ]]; then
            local app_name=$(basename "$app_path" .app)
            local bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2>/dev/null || echo "unknown")
            local version=$(defaults read "$app_path/Contents/Info.plist" CFBundleShortVersionString 2>/dev/null || echo "unknown")
            local size_bytes=$(du -sk "$app_path" 2>/dev/null | cut -f1 || echo "0")
            size_bytes=$((size_bytes * 1024))
            
            if [[ "$first_app" == "false" ]]; then
                apps_json="$apps_json,"
            fi
            
            apps_json="$apps_json{
                \"name\": \"$app_name\",
                \"bundle_id\": \"$bundle_id\",
                \"version\": \"$version\",
                \"size_bytes\": $size_bytes,
                \"path\": \"$app_path\"
            }"
            
            first_app=false
        fi
    done < <(find /Applications -name "*.app" -type d -print0 2>/dev/null)
    
    apps_json="$apps_json]"
    echo "$apps_json"
}

# Check for App Store updates
check_app_store_updates() {
    echo "Checking for available App Store updates..."
    
    # Use softwareupdate to check for available updates
    local updates_available=$(softwareupdate -l 2>/dev/null | grep -i "app store" || echo "")
    
    if [[ -n "$updates_available" ]]; then
        echo "📦 App Store updates available:"
        echo "$updates_available"
    else
        echo "✅ All App Store apps are up to date"
    fi
    
    # Also check Mac App Store directly if available
    if command -v mas >/dev/null 2>&1; then
        echo ""
        echo "Checking with mas-cli..."
        mas outdated 2>/dev/null || echo "mas-cli not available or no updates found"
    fi
}

# Usage examples
echo "Fleet Management Examples:"
echo "========================="
echo ""

echo "1. Discovery mode:"
manage_fleet_app_inventory "discover"
echo ""

echo "2. Compliance check:"
manage_fleet_app_inventory "compliance"
echo ""

echo "3. Generate all reports:"
manage_fleet_app_inventory "report"

App Store Connect Integration

Enterprise App Management with App Store Connect

#!/bin/bash

# Enterprise App Store Connect integration for license management
manage_app_store_licenses() {
    echo "📄 App Store Connect License Management"
    echo "======================================"
    echo ""
    
    # Note: This requires enterprise App Store Connect access
    # and proper API credentials configuration
    
    local license_report_file="/tmp/app_store_licenses_$(date +%Y%m%d_%H%M%S).json"
    
    echo "Analyzing App Store app licensing..."
    echo ""
    
    # Track apps that may require business licenses
    local business_apps=()
    local consumer_apps=()
    local unknown_apps=()
    
    while IFS= read -r -d '' app_path; do
        if [[ -d "$app_path" && -f "$app_path/Contents/_MASReceipt/receipt" ]]; then
            local app_name=$(basename "$app_path" .app)
            local bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2>/dev/null || echo "unknown")
            
            # Categories that typically require business licenses
            local business_categories=("Developer Tools" "Business" "Productivity" "Graphics & Design")
            local is_business_app=false
            
            # Check if app is business-oriented
            if [[ -f "$app_path/Contents/Info.plist" ]]; then
                local category=$(defaults read "$app_path/Contents/Info.plist" LSApplicationCategoryType 2>/dev/null || echo "unknown")
                
                for biz_cat in "${business_categories[@]}"; do
                    if [[ "$category" =~ "$biz_cat" ]]; then
                        is_business_app=true
                        break
                    fi
                done
                
                # Also check common business app patterns
                if [[ "$app_name" =~ (Microsoft|Adobe|Slack|Zoom|Teams|Office) ]]; then
                    is_business_app=true
                fi
            fi
            
            if [[ "$is_business_app" == "true" ]]; then
                business_apps+=("$app_name ($bundle_id)")
                echo "💼 Business app detected: $app_name"
            else
                # Consumer apps that might be acceptable
                if [[ "$app_name" =~ (Calculator|TextEdit|Safari|Mail|Photos) ]]; then
                    consumer_apps+=("$app_name ($bundle_id)")
                    echo "👤 Consumer app: $app_name"
                else
                    unknown_apps+=("$app_name ($bundle_id)")
                    echo "❓ Unknown category: $app_name"
                fi
            fi
        fi
    done < <(find /Applications -name "*.app" -type d -print0 2>/dev/null)
    
    # Generate license compliance report
    {
        echo "{"
        echo "  \"license_analysis\": {"
        echo "    \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\","
        echo "    \"device_id\": \"$(system_profiler SPHardwareDataType | grep "Serial Number" | awk -F': ' '{print $2}' | xargs)\","
        echo "    \"business_apps\": ["
        
        local first=true
        for app in "${business_apps[@]}"; do
            if [[ "$first" == "false" ]]; then echo ","; fi
            echo "      \"$app\""
            first=false
        done
        
        echo "    ],"
        echo "    \"consumer_apps\": ["
        
        first=true
        for app in "${consumer_apps[@]}"; do
            if [[ "$first" == "false" ]]; then echo ","; fi
            echo "      \"$app\""
            first=false
        done
        
        echo "    ],"
        echo "    \"unknown_apps\": ["
        
        first=true
        for app in "${unknown_apps[@]}"; do
            if [[ "$first" == "false" ]]; then echo ","; fi
            echo "      \"$app\""
            first=false
        done
        
        echo "    ],"
        echo "    \"summary\": {"
        echo "      \"total_business_apps\": ${#business_apps[@]},"
        echo "      \"total_consumer_apps\": ${#consumer_apps[@]},"
        echo "      \"total_unknown_apps\": ${#unknown_apps[@]},"
        echo "      \"requires_license_review\": $([[ ${#business_apps[@]} -gt 0 || ${#unknown_apps[@]} -gt 0 ]] && echo "true" || echo "false")"
        echo "    }"
        echo "  }"
        echo "}"
    } > "$license_report_file"
    
    echo ""
    echo "=== License Analysis Summary ==="
    echo "Business apps found: ${#business_apps[@]}"
    echo "Consumer apps found: ${#consumer_apps[@]}"
    echo "Unknown apps found: ${#unknown_apps[@]}"
    echo ""
    
    if [[ ${#business_apps[@]} -gt 0 || ${#unknown_apps[@]} -gt 0 ]]; then
        echo "⚠️ License review required for business/unknown applications"
        echo "📄 License report saved: $license_report_file"
    else
        echo "✅ No business licensing concerns detected"
    fi
    
    return 0
}

# Execute license management
manage_app_store_licenses

Important Notes

Enterprise Management Features

  • Comprehensive Inventory - Complete App Store application discovery and tracking
  • Compliance Monitoring - Policy enforcement and security analysis
  • License Management - Enterprise App Store Connect integration
  • Fleet Reporting - Multi-format reporting (JSON, CSV, text) with metadata
  • Security Analysis - Permission auditing and code signature verification
  • Update Management - App Store update detection and management

App Store Receipt System

  • MAS Receipts - Digital proof of App Store purchase located in Contents/_MASReceipt/receipt
  • Bundle Identification - Unique app tracking via CFBundleIdentifier
  • Version Tracking - App version and build number management
  • Installation Metadata - Install date and modification tracking
  • License Verification - Enterprise license compliance and allocation

Security and Compliance

  • Permission Auditing - Camera, microphone, location access monitoring
  • Code Signature Verification - Validate app integrity and authenticity
  • Policy Enforcement - Blocked app categories and required app validation
  • Age Analysis - Identify outdated applications requiring updates
  • Team Identifier Tracking - Developer team identification for enterprise policies

Usage Examples

# Basic App Store app discovery
find /Applications -path '*Contents/_MASReceipt/receipt' -maxdepth 4 -print | \
    sed 's#.app/Contents/_MASReceipt/receipt#.app#g; s#/Applications/##'

# Enterprise inventory with JSON output
generate_app_store_inventory "json" "true" "/tmp"

# Compliance analysis
analyze_app_compliance

# Fleet management - discovery mode
manage_fleet_app_inventory "discover"

# License management
manage_app_store_licenses

Tutorial

Nuevas actualizaciones y mejoras para Macfleet.

Configurando un Runner de GitHub Actions en un Mac Mini (Apple Silicon)

Runner de GitHub Actions

GitHub Actions es una plataforma poderosa de CI/CD que te permite automatizar tus flujos de trabajo de desarrollo de software. Aunque GitHub ofrece runners hospedados, los runners auto-hospedados proporcionan mayor control y personalización para tu configuración de CI/CD. Este tutorial te guía a través de la configuración y conexión de un runner auto-hospedado en un Mac mini para ejecutar pipelines de macOS.

Prerrequisitos

Antes de comenzar, asegúrate de tener:

  • Un Mac mini (regístrate en Macfleet)
  • Un repositorio de GitHub con derechos de administrador
  • Un gestor de paquetes instalado (preferiblemente Homebrew)
  • Git instalado en tu sistema

Paso 1: Crear una Cuenta de Usuario Dedicada

Primero, crea una cuenta de usuario dedicada para el runner de GitHub Actions:

# Crear la cuenta de usuario 'gh-runner'
sudo dscl . -create /Users/gh-runner
sudo dscl . -create /Users/gh-runner UserShell /bin/bash
sudo dscl . -create /Users/gh-runner RealName "GitHub runner"
sudo dscl . -create /Users/gh-runner UniqueID "1001"
sudo dscl . -create /Users/gh-runner PrimaryGroupID 20
sudo dscl . -create /Users/gh-runner NFSHomeDirectory /Users/gh-runner

# Establecer la contraseña para el usuario
sudo dscl . -passwd /Users/gh-runner tu_contraseña

# Agregar 'gh-runner' al grupo 'admin'
sudo dscl . -append /Groups/admin GroupMembership gh-runner

Cambia a la nueva cuenta de usuario:

su gh-runner

Paso 2: Instalar Software Requerido

Instala Git y Rosetta 2 (si usas Apple Silicon):

# Instalar Git si no está ya instalado
brew install git

# Instalar Rosetta 2 para Macs Apple Silicon
softwareupdate --install-rosetta

Paso 3: Configurar el Runner de GitHub Actions

  1. Ve a tu repositorio de GitHub
  2. Navega a Configuración > Actions > Runners

Runner de GitHub Actions

  1. Haz clic en "New self-hosted runner" (https://github.com/<username>/<repository>/settings/actions/runners/new)
  2. Selecciona macOS como imagen del runner y ARM64 como arquitectura
  3. Sigue los comandos proporcionados para descargar y configurar el runner

Runner de GitHub Actions

Crea un archivo .env en el directorio _work del runner:

# archivo _work/.env
ImageOS=macos15
XCODE_15_DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
  1. Ejecuta el script run.sh en tu directorio del runner para completar la configuración.
  2. Verifica que el runner esté activo y escuchando trabajos en la terminal y revisa la configuración del repositorio de GitHub para la asociación del runner y el estado Idle.

Runner de GitHub Actions

Paso 4: Configurar Sudoers (Opcional)

Si tus acciones requieren privilegios de root, configura el archivo sudoers:

sudo visudo

Agrega la siguiente línea:

gh-runner ALL=(ALL) NOPASSWD: ALL

Paso 5: Usar el Runner en Flujos de Trabajo

Configura tu flujo de trabajo de GitHub Actions para usar el runner auto-hospedado:

name: Flujo de trabajo de muestra

on:
  workflow_dispatch:

jobs:
  build:
    runs-on: [self-hosted, macOS, ARM64]
    steps:
      - name: Instalar NodeJS
        run: brew install node

El runner está autenticado en tu repositorio y etiquetado con self-hosted, macOS, y ARM64. Úsalo en tus flujos de trabajo especificando estas etiquetas en el campo runs-on:

runs-on: [self-hosted, macOS, ARM64]

Mejores Prácticas

  • Mantén tu software del runner actualizado
  • Monitorea regularmente los logs del runner para problemas
  • Usa etiquetas específicas para diferentes tipos de runners
  • Implementa medidas de seguridad apropiadas
  • Considera usar múltiples runners para balanceo de carga

Solución de Problemas

Problemas comunes y soluciones:

  1. Runner no conectando:

    • Verifica conectividad de red
    • Verifica validez del token de GitHub
    • Asegúrate de permisos apropiados
  2. Fallas de construcción:

    • Verifica instalación de Xcode
    • Verifica dependencias requeridas
    • Revisa logs del flujo de trabajo
  3. Problemas de permisos:

    • Verifica permisos de usuario
    • Verifica configuración de sudoers
    • Revisa permisos del sistema de archivos

Conclusión

Ahora tienes un runner auto-hospedado de GitHub Actions configurado en tu Mac mini. Esta configuración te proporciona más control sobre tu entorno de CI/CD y te permite ejecutar flujos de trabajo específicos de macOS de manera eficiente.

Recuerda mantener regularmente tu runner y mantenerlo actualizado con los últimos parches de seguridad y versiones de software.

Aplicación Nativa

Aplicación nativa de Macfleet

Guía de Instalación de Macfleet

Macfleet es una solución poderosa de gestión de flota diseñada específicamente para entornos de Mac Mini alojados en la nube. Como proveedor de hosting en la nube de Mac Mini, puedes usar Macfleet para monitorear, gestionar y optimizar toda tu flota de instancias Mac virtualizadas.

Esta guía de instalación te llevará a través de la configuración del monitoreo de Macfleet en sistemas macOS, Windows y Linux para asegurar una supervisión integral de tu infraestructura en la nube.

🍎 macOS

  • Descarga el archivo .dmg para Mac aquí
  • Haz doble clic en el archivo .dmg descargado
  • Arrastra la aplicación Macfleet a la carpeta Aplicaciones
  • Expulsa el archivo .dmg
  • Abre Preferencias del Sistema > Seguridad y Privacidad
    • Pestaña Privacidad > Accesibilidad
    • Marca Macfleet para permitir el monitoreo
  • Inicia Macfleet desde Aplicaciones
  • El seguimiento comienza automáticamente

🪟 Windows

  • Descarga el archivo .exe para Windows aquí
  • Haz clic derecho en el archivo .exe > "Ejecutar como administrador"
  • Sigue el asistente de instalación
  • Acepta los términos y condiciones
  • Permite en Windows Defender si se solicita
  • Concede permisos de monitoreo de aplicaciones
  • Inicia Macfleet desde el Menú Inicio
  • La aplicación comienza el seguimiento automáticamente

🐧 Linux

  • Descarga el paquete .deb (Ubuntu/Debian) o .rpm (CentOS/RHEL) aquí
  • Instala usando tu gestor de paquetes
    • Ubuntu/Debian: sudo dpkg -i Macfleet-linux.deb
    • CentOS/RHEL: sudo rpm -ivh Macfleet-linux.rpm
  • Permite permisos de acceso X11 si se solicita
  • Agrega el usuario a los grupos apropiados si es necesario
  • Inicia Macfleet desde el menú de Aplicaciones
  • La aplicación comienza el seguimiento automáticamente

Nota: Después de la instalación en todos los sistemas, inicia sesión con tus credenciales de Macfleet para sincronizar datos con tu panel de control.