plugins / diagnostic.sh
Tim's picture
Upload diagnostic.sh
d201ac4 verified
#!/bin/bash
#
# After Effects Plugin Diagnostic Tool
# Checks dependencies, code signing, quarantine, and plugin structure
#
# Usage: bash check_plugin.sh /path/to/Plugin.plugin
#
# Don't exit on error - we want to continue checking even if some commands fail
set +e
# Output file on Desktop
REPORT_TIMESTAMP=$(date +%Y%m%d_%H%M%S)
OUTPUT_FILE="$HOME/Desktop/plugin_diagnostic_$REPORT_TIMESTAMP.txt"
# Function to output to both console and file
log() {
echo -e "$1"
# Strip color codes for file output
echo -e "$1" | sed 's/\x1b\[[0-9;]*m//g' >> "$OUTPUT_FILE"
}
# Initialize output file
echo "# After Effects Plugin Diagnostic Report" > "$OUTPUT_FILE"
echo "# Generated: $(date)" >> "$OUTPUT_FILE"
echo "" >> "$OUTPUT_FILE"
# Colors (for console only)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Icons
PASS="βœ…"
FAIL="❌"
WARN="⚠️"
INFO="ℹ️"
print_header() {
log ""
log "${BLUE}═══════════════════════════════════════════════════════════${NC}"
log "${BLUE} $1${NC}"
log "${BLUE}═══════════════════════════════════════════════════════════${NC}"
}
print_section() {
log ""
log "${YELLOW}── $1 ──${NC}"
}
# Default plugin path for AIColorMatch
DEFAULT_PLUGIN="/Library/Application Support/Adobe/Common/Plug-ins/7.0/MediaCore/BSKL/AIColorMatch.plugin"
MEDIACORE_PATH="/Library/Application Support/Adobe/Common/Plug-ins/7.0/MediaCore"
# Check arguments - use default if not provided
if [ -z "$1" ]; then
if [ -d "$DEFAULT_PLUGIN" ]; then
PLUGIN_PATH="$DEFAULT_PLUGIN"
echo "Using default plugin path: $PLUGIN_PATH"
else
echo -e "${RED}Usage: bash check_plugin.sh /path/to/Plugin.plugin${NC}"
echo ""
echo "Default plugin not found at:"
echo " $DEFAULT_PLUGIN"
echo ""
echo "Please specify plugin path manually."
exit 1
fi
else
PLUGIN_PATH="$1"
fi
# Validate plugin exists
if [ ! -d "$PLUGIN_PATH" ]; then
log "${FAIL} Plugin not found: $PLUGIN_PATH"
exit 1
fi
# Get plugin name
PLUGIN_NAME=$(basename "$PLUGIN_PATH" .plugin)
print_header "After Effects Plugin Diagnostic Report"
log ""
log "Plugin: ${GREEN}$PLUGIN_NAME${NC}"
log "Path: $PLUGIN_PATH"
log "Date: $(date)"
log "macOS: $(sw_vers -productVersion) ($(sw_vers -buildVersion))"
log "Machine: $(uname -m)"
# ─────────────────────────────────────────────────────────────
# 0. ALL PLUGINS IN MEDIACORE
# ─────────────────────────────────────────────────────────────
print_section "All Plugins in MediaCore"
if [ -d "$MEDIACORE_PATH" ]; then
log "${BLUE}Scanning: $MEDIACORE_PATH${NC}"
log ""
# Find all .plugin bundles
PLUGIN_COUNT=0
while IFS= read -r plugin_bundle; do
PLUGIN_COUNT=$((PLUGIN_COUNT + 1))
plugin_name=$(basename "$plugin_bundle" .plugin)
plugin_modified=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$plugin_bundle" 2>/dev/null || echo "unknown")
plugin_size=$(du -sh "$plugin_bundle" 2>/dev/null | cut -f1 || echo "?")
plugin_perms=$(stat -f "%Sp" "$plugin_bundle" 2>/dev/null || echo "?")
plugin_owner=$(stat -f "%Su:%Sg" "$plugin_bundle" 2>/dev/null || echo "?")
# Check if binary exists
binary_path="$plugin_bundle/Contents/MacOS/$plugin_name"
if [ -f "$binary_path" ]; then
arch=$(lipo -archs "$binary_path" 2>/dev/null || echo "?")
binary_perms=$(stat -f "%Sp" "$binary_path" 2>/dev/null || echo "?")
# Check code signing
codesign_check=$(codesign -dv --verbose=2 "$plugin_bundle" 2>&1)
if echo "$codesign_check" | grep -q "Authority="; then
signer=$(echo "$codesign_check" | grep "Authority=" | head -1 | cut -d= -f2 | cut -c1-40)
sign_status="$PASS"
elif echo "$codesign_check" | grep -q "Signature=adhoc"; then
signer="ad-hoc"
sign_status="$WARN"
elif echo "$codesign_check" | grep -q "TeamIdentifier="; then
# Signed but no Authority line (Developer ID)
team=$(echo "$codesign_check" | grep "TeamIdentifier=" | cut -d= -f2)
signer="Team: $team"
sign_status="$PASS"
else
signer="unsigned"
sign_status="$FAIL"
fi
# Check notarization
if echo "$codesign_check" | grep -q "Notarization Ticket"; then
notarized="yes"
else
notarized="no"
fi
# Check quarantine
quarantine_check=$(xattr -p com.apple.quarantine "$plugin_bundle" 2>/dev/null)
if [ -n "$quarantine_check" ]; then
qflag="$WARN quarantined"
else
qflag=""
fi
log "$sign_status $plugin_name $qflag"
log " Modified: $plugin_modified | Size: $plugin_size | Arch: $arch"
log " Perms: $plugin_perms | Binary: $binary_perms | Owner: $plugin_owner"
log " Signed: $signer | Notarized: $notarized"
else
log "$WARN $plugin_name (binary missing)"
log " Modified: $plugin_modified | Size: $plugin_size"
log " Perms: $plugin_perms | Owner: $plugin_owner"
fi
log ""
done < <(find "$MEDIACORE_PATH" -maxdepth 2 -name "*.plugin" -type d 2>/dev/null | sort)
log "Total plugins found: $PLUGIN_COUNT"
else
log "$WARN MediaCore directory not found: $MEDIACORE_PATH"
fi
# ─────────────────────────────────────────────────────────────
# 1. PLUGIN STRUCTURE
# ─────────────────────────────────────────────────────────────
print_section "Plugin Structure"
BINARY_PATH="$PLUGIN_PATH/Contents/MacOS/$PLUGIN_NAME"
PLIST_PATH="$PLUGIN_PATH/Contents/Info.plist"
RSRC_PATH="$PLUGIN_PATH/Contents/Resources/$PLUGIN_NAME.rsrc"
if [ -f "$BINARY_PATH" ]; then
log "$PASS Binary: $BINARY_PATH"
BINARY_SIZE=$(du -h "$BINARY_PATH" | cut -f1)
log " Size: $BINARY_SIZE"
else
log "$FAIL Binary NOT FOUND: $BINARY_PATH"
fi
if [ -f "$PLIST_PATH" ]; then
log "$PASS Info.plist: exists"
else
log "$FAIL Info.plist NOT FOUND"
fi
if [ -f "$RSRC_PATH" ]; then
log "$PASS Resource file: $PLUGIN_NAME.rsrc"
else
log "$WARN Resource file not found (may be embedded)"
fi
# ─────────────────────────────────────────────────────────────
# 2. ARCHITECTURE
# ─────────────────────────────────────────────────────────────
print_section "Architecture"
if [ -f "$BINARY_PATH" ]; then
ARCHS=$(lipo -archs "$BINARY_PATH" 2>/dev/null || echo "unknown")
if [[ "$ARCHS" == *"arm64"* ]] && [[ "$ARCHS" == *"x86_64"* ]]; then
log "$PASS Universal Binary: $ARCHS"
elif [[ "$ARCHS" == *"arm64"* ]]; then
log "$WARN ARM64 only (no Intel support)"
elif [[ "$ARCHS" == *"x86_64"* ]]; then
log "$WARN Intel only (no Apple Silicon native)"
else
log "$FAIL Unknown architecture: $ARCHS"
fi
# Current machine
MACHINE_ARCH=$(uname -m)
log "$INFO Current machine: $MACHINE_ARCH"
fi
# ─────────────────────────────────────────────────────────────
# 3. DEPENDENCIES
# ─────────────────────────────────────────────────────────────
print_section "Dependencies"
if [ -f "$BINARY_PATH" ]; then
MISSING_DEPS=0
# Get unique dependencies (otool shows both archs for universal binary)
while IFS= read -r line; do
# Skip empty lines and the binary path itself
[[ -z "$line" ]] && continue
[[ "$line" == *"$BINARY_PATH"* ]] && continue
# Extract library path
LIB_PATH=$(echo "$line" | sed 's/^[[:space:]]*//' | cut -d' ' -f1)
# Skip if empty
[[ -z "$LIB_PATH" ]] && continue
# Categorize
# Note: Since macOS Big Sur, /usr/lib libraries are in shared cache
# and don't exist as physical files, but they still work
if [[ "$LIB_PATH" == /usr/lib/* ]]; then
log "$PASS System: $(basename "$LIB_PATH") (shared cache)"
elif [[ "$LIB_PATH" == /System/Library/* ]]; then
if [ -d "${LIB_PATH%/*}" ] || [ -f "$LIB_PATH" ]; then
log "$PASS Framework: $(echo "$LIB_PATH" | grep -o '[^/]*\.framework')"
else
log "$FAIL MISSING: $LIB_PATH"
MISSING_DEPS=$((MISSING_DEPS + 1))
fi
elif [[ "$LIB_PATH" == @rpath/* ]] || [[ "$LIB_PATH" == @loader_path/* ]] || [[ "$LIB_PATH" == @executable_path/* ]]; then
log "$WARN Relative: $LIB_PATH"
elif [ -f "$LIB_PATH" ]; then
log "$PASS External: $LIB_PATH"
else
log "$FAIL MISSING: $LIB_PATH"
MISSING_DEPS=$((MISSING_DEPS + 1))
fi
done < <(otool -L "$BINARY_PATH" 2>/dev/null | sort -u)
if [ $MISSING_DEPS -gt 0 ]; then
log ""
log "${RED}$FAIL Found $MISSING_DEPS missing dependencies!${NC}"
else
log ""
log "${GREEN}$PASS All dependencies found${NC}"
fi
fi
# ─────────────────────────────────────────────────────────────
# 4. CODE SIGNING & NOTARIZATION
# ─────────────────────────────────────────────────────────────
print_section "Code Signing & Notarization"
CODESIGN_OUTPUT=$(codesign -dv --verbose=4 "$PLUGIN_PATH" 2>&1)
SIGNING_ISSUE=0
# Check signing status
if echo "$CODESIGN_OUTPUT" | grep -q "Authority="; then
# Extract all authorities (certificate chain)
log "${BLUE}Certificate Chain:${NC}"
echo "$CODESIGN_OUTPUT" | grep "Authority=" | while read -r line; do
CERT=$(echo "$line" | cut -d= -f2)
log " $CERT"
done
# Team ID
TEAM_ID=$(echo "$CODESIGN_OUTPUT" | grep "TeamIdentifier=" | cut -d= -f2)
if [ -n "$TEAM_ID" ] && [ "$TEAM_ID" != "not set" ]; then
log "$PASS Team ID: $TEAM_ID"
else
log "$WARN Team ID not set"
fi
# Timestamp
SIGN_TIMESTAMP=$(echo "$CODESIGN_OUTPUT" | grep "Timestamp=" | cut -d= -f2)
if [ -n "$SIGN_TIMESTAMP" ]; then
log "$PASS Signed on: $SIGN_TIMESTAMP"
else
log "$WARN No timestamp (signature may expire with certificate)"
fi
# Runtime version (hardened runtime)
RUNTIME=$(echo "$CODESIGN_OUTPUT" | grep "Runtime Version=" | cut -d= -f2)
if [ -n "$RUNTIME" ]; then
log "$PASS Hardened Runtime: $RUNTIME"
else
log "$WARN Hardened runtime not enabled"
fi
# Notarization check
if echo "$CODESIGN_OUTPUT" | grep -q "Notarization Ticket"; then
log "$PASS Notarization ticket stapled"
else
log "$WARN Notarization ticket NOT stapled"
SIGNING_ISSUE=1
fi
elif echo "$CODESIGN_OUTPUT" | grep -q "adhoc"; then
log "$WARN Ad-hoc signed (development only)"
SIGNING_ISSUE=1
else
log "$FAIL NOT SIGNED"
SIGNING_ISSUE=1
fi
# Verify signature integrity
log ""
log "${BLUE}Signature Verification:${NC}"
VERIFY_OUTPUT=$(codesign --verify --deep --strict "$PLUGIN_PATH" 2>&1)
if [ $? -eq 0 ]; then
log "$PASS Signature integrity: valid"
else
log "$FAIL Signature integrity: INVALID"
log " $VERIFY_OUTPUT"
SIGNING_ISSUE=1
fi
# Gatekeeper assessment (spctl)
log ""
log "${BLUE}Gatekeeper Assessment:${NC}"
SPCTL_OUTPUT=$(spctl --assess --type execute -v "$PLUGIN_PATH" 2>&1)
SPCTL_EXIT=$?
if [ $SPCTL_EXIT -eq 0 ]; then
log "$PASS Gatekeeper: accepted"
elif echo "$SPCTL_OUTPUT" | grep -q "rejected"; then
# Check the reason
if echo "$SPCTL_OUTPUT" | grep -q "notarized"; then
log "$WARN Gatekeeper: requires notarization"
elif echo "$SPCTL_OUTPUT" | grep -q "not.*app"; then
log "$PASS Gatekeeper: valid code (not an app bundle)"
else
log "$FAIL Gatekeeper: rejected"
log " $SPCTL_OUTPUT"
fi
else
log "$INFO Gatekeeper: $SPCTL_OUTPUT"
fi
# Check if notarization can be verified online
log ""
log "${BLUE}Notarization Status (online check):${NC}"
STAPLER_OUTPUT=$(xcrun stapler validate "$PLUGIN_PATH" 2>&1)
if echo "$STAPLER_OUTPUT" | grep -q "valid"; then
log "$PASS Notarization: verified"
elif echo "$STAPLER_OUTPUT" | grep -q "not valid"; then
log "$FAIL Notarization: NOT valid or not notarized"
SIGNING_ISSUE=1
else
log "$INFO $STAPLER_OUTPUT"
fi
# ─────────────────────────────────────────────────────────────
# 5. QUARANTINE
# ─────────────────────────────────────────────────────────────
print_section "Quarantine Status"
QUARANTINE=$(xattr -p com.apple.quarantine "$PLUGIN_PATH" 2>/dev/null || echo "")
if [ -n "$QUARANTINE" ]; then
log "$WARN Quarantine flag is SET"
log " Value: $QUARANTINE"
log ""
log "${YELLOW}To remove quarantine, run:${NC}"
log " xattr -cr \"$PLUGIN_PATH\""
else
log "$PASS No quarantine flag"
fi
# ─────────────────────────────────────────────────────────────
# 6. INFO.PLIST CHECK
# ─────────────────────────────────────────────────────────────
print_section "Info.plist Analysis"
if [ -f "$PLIST_PATH" ]; then
# CFBundleIdentifier
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$PLIST_PATH" 2>/dev/null || echo "")
if [ -n "$BUNDLE_ID" ] && [ "$BUNDLE_ID" != "" ]; then
log "$PASS CFBundleIdentifier: $BUNDLE_ID"
else
log "$FAIL CFBundleIdentifier is EMPTY!"
log " ${YELLOW}This may cause loading issues on some systems${NC}"
fi
# CFBundleExecutable
BUNDLE_EXEC=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$PLIST_PATH" 2>/dev/null || echo "")
if [ "$BUNDLE_EXEC" = "$PLUGIN_NAME" ]; then
log "$PASS CFBundleExecutable: $BUNDLE_EXEC"
else
log "$WARN CFBundleExecutable: $BUNDLE_EXEC (expected: $PLUGIN_NAME)"
fi
# LSMinimumSystemVersion
MIN_VERSION=$(/usr/libexec/PlistBuddy -c "Print :LSMinimumSystemVersion" "$PLIST_PATH" 2>/dev/null || echo "not set")
log "$INFO Minimum macOS: $MIN_VERSION"
# Current macOS version
CURRENT_MACOS=$(sw_vers -productVersion)
log "$INFO Current macOS: $CURRENT_MACOS"
fi
# ─────────────────────────────────────────────────────────────
# 7. ENTRY POINT CHECK (via nm)
# ─────────────────────────────────────────────────────────────
print_section "Entry Point"
if [ -f "$BINARY_PATH" ]; then
if nm "$BINARY_PATH" 2>/dev/null | grep -q "_EffectMain"; then
log "$PASS EffectMain symbol exported"
elif nm "$BINARY_PATH" 2>/dev/null | grep -q "_main"; then
log "$WARN Found _main but not _EffectMain"
else
log "$FAIL No EffectMain symbol found!"
fi
fi
# ─────────────────────────────────────────────────────────────
# 8. FILE PERMISSIONS & OWNERSHIP
# ─────────────────────────────────────────────────────────────
print_section "File Permissions & Ownership"
PERM_ISSUES=0
CURRENT_USER=$(whoami)
log "${BLUE}All files in plugin bundle:${NC}"
log ""
# Check each file and directory
while IFS= read -r item; do
# Get permissions, owner, group
STAT_OUTPUT=$(stat -f "%Sp %Su %Sg" "$item" 2>/dev/null)
PERMS=$(echo "$STAT_OUTPUT" | cut -d' ' -f1)
OWNER=$(echo "$STAT_OUTPUT" | cut -d' ' -f2)
GROUP=$(echo "$STAT_OUTPUT" | cut -d' ' -f3)
# Get relative path for display
REL_PATH="${item#$PLUGIN_PATH}"
[ -z "$REL_PATH" ] && REL_PATH="/"
# Determine status icon
HAS_ISSUE=0
# Check if it's a directory or file
if [ -d "$item" ]; then
ITEM_TYPE="d"
# Directories should be readable and executable (r-x) by all
if [[ "$PERMS" != d*r?x*r?x*r?x* ]] && [[ "$PERMS" != d*r?x*r?x* ]]; then
HAS_ISSUE=1
fi
else
# Files should be readable
if [[ "$PERMS" != -*r* ]]; then
HAS_ISSUE=1
fi
# Binary should be executable
if [ "$item" = "$BINARY_PATH" ] && [[ "$PERMS" != -*x* ]]; then
HAS_ISSUE=1
fi
fi
# Always show the file with its permissions
if [ $HAS_ISSUE -eq 1 ]; then
log "$FAIL $PERMS $OWNER:$GROUP $REL_PATH"
PERM_ISSUES=$((PERM_ISSUES + 1))
else
log "$PASS $PERMS $OWNER:$GROUP $REL_PATH"
fi
done < <(find "$PLUGIN_PATH" -print 2>/dev/null)
# Summary of permissions
log ""
if [ $PERM_ISSUES -eq 0 ]; then
log "${GREEN}$PASS All permissions look correct${NC}"
else
log "${RED}$FAIL Found $PERM_ISSUES permission issue(s)${NC}"
log ""
log "${YELLOW}To fix permissions, run:${NC}"
log " sudo chmod -R a+rX \"$PLUGIN_PATH\""
log " sudo chmod a+x \"$BINARY_PATH\""
fi
# Show ownership summary
log ""
log "${BLUE}Ownership Summary:${NC}"
# Get unique owners
OWNERS=$(find "$PLUGIN_PATH" -print0 2>/dev/null | xargs -0 stat -f "%Su" | sort -u)
for owner in $OWNERS; do
COUNT=$(find "$PLUGIN_PATH" -user "$owner" 2>/dev/null | wc -l | tr -d ' ')
log " $owner: $COUNT items"
done
# ─────────────────────────────────────────────────────────────
# 9. EXTENDED ATTRIBUTES
# ─────────────────────────────────────────────────────────────
print_section "Extended Attributes"
# Check for any extended attributes on all files
XATTR_FILES=0
log "${BLUE}Checking for extended attributes on all files...${NC}"
log ""
while IFS= read -r item; do
XATTRS=$(xattr "$item" 2>/dev/null)
if [ -n "$XATTRS" ]; then
REL_PATH="${item#$PLUGIN_PATH}"
[ -z "$REL_PATH" ] && REL_PATH="/"
log "$WARN $REL_PATH"
echo "$XATTRS" | while read -r attr; do
if [ -n "$attr" ]; then
log " $attr"
fi
done
XATTR_FILES=$((XATTR_FILES + 1))
fi
done < <(find "$PLUGIN_PATH" -print 2>/dev/null)
if [ $XATTR_FILES -eq 0 ]; then
log "${GREEN}$PASS No extended attributes found${NC}"
else
log ""
log "${YELLOW}Found extended attributes on $XATTR_FILES file(s)${NC}"
log "${YELLOW}To remove all extended attributes, run:${NC}"
log " xattr -cr \"$PLUGIN_PATH\""
fi
# ─────────────────────────────────────────────────────────────
# SUMMARY
# ─────────────────────────────────────────────────────────────
print_header "Summary"
ISSUES=0
# Check for common issues
if [ -z "$BUNDLE_ID" ] || [ "$BUNDLE_ID" = "" ]; then
log "$FAIL Empty CFBundleIdentifier - may cause 'entry point' errors"
ISSUES=$((ISSUES + 1))
fi
if [ -n "$QUARANTINE" ]; then
log "$WARN Quarantine flag set - may block plugin loading"
ISSUES=$((ISSUES + 1))
fi
if [ $MISSING_DEPS -gt 0 ]; then
log "$FAIL Missing dependencies detected"
ISSUES=$((ISSUES + 1))
fi
if [ $SIGNING_ISSUE -gt 0 ]; then
log "$WARN Code signing or notarization issues detected"
ISSUES=$((ISSUES + 1))
fi
if [ $PERM_ISSUES -gt 0 ]; then
log "$FAIL Permission issues detected"
ISSUES=$((ISSUES + 1))
fi
if [ $ISSUES -eq 0 ]; then
log "${GREEN}$PASS No issues detected${NC}"
else
log ""
log "${YELLOW}Found $ISSUES potential issue(s)${NC}"
log ""
log "${BLUE}Quick fixes:${NC}"
if [ -n "$QUARANTINE" ]; then
log " Remove quarantine: xattr -cr \"$PLUGIN_PATH\""
fi
if [ $PERM_ISSUES -gt 0 ]; then
log " Fix permissions: sudo chmod -R a+rX \"$PLUGIN_PATH\""
fi
fi
# ─────────────────────────────────────────────────────────────
# OUTPUT FILE INFO
# ─────────────────────────────────────────────────────────────
log ""
log "═══════════════════════════════════════════════════════════"
log ""
log "${GREEN}πŸ“„ Report saved to:${NC}"
log " $OUTPUT_FILE"
log ""
log "${YELLOW}Please send this file to support for analysis.${NC}"
log ""
# Also print to console without colors for clarity
echo ""
echo "════════════════════════════════════════════════════════════"
echo ""
echo "πŸ“„ Report saved to Desktop:"
echo " $OUTPUT_FILE"
echo ""
echo "πŸ‘‰ Please send this file to BSKL support for analysis."
echo ""