Skip to content

Instantly share code, notes, and snippets.

@dangayle
Created December 25, 2025 01:47
Show Gist options
  • Select an option

  • Save dangayle/82ffaa200b700a77b69fe5db00bcd191 to your computer and use it in GitHub Desktop.

Select an option

Save dangayle/82ffaa200b700a77b69fe5db00bcd191 to your computer and use it in GitHub Desktop.
macOS bulk installer for audio plugins, etc.
#!/bin/bash
# Global variables for cleanup
MOUNT_POINTS=()
TEMP_DIRS=()
INSTALLED=()
FAILED=()
# Verbose flag (default is quiet). Use `--verbose` or `-v` to enable.
VERBOSE=0
# Helper: verbose-only echo
v_echo() {
if [ "$VERBOSE" -eq 1 ]; then
echo "$@"
fi
}
# Cleanup function
cleanup() {
v_echo "Cleaning up..."
# Unmount any mounted .dmg files
for mount_point in "${MOUNT_POINTS[@]}"; do
v_echo "Unmounting $mount_point..."
hdiutil detach "$mount_point" >/dev/null 2>&1 || v_echo "Failed to unmount $mount_point"
done
# Remove any temporary directories
for temp_dir in "${TEMP_DIRS[@]}"; do
v_echo "Removing temporary directory $temp_dir..."
rm -rf "$temp_dir"
done
v_echo "Cleanup complete. Exiting."
exit 1
}
# Trap SIGINT (Command+C) and call cleanup
trap cleanup SIGINT
# Parse args (allow `--verbose` / `-v` and optional base dir)
while [[ $# -gt 0 ]]; do
case "$1" in
--verbose|-v)
VERBOSE=1
shift
;;
*)
base_dir="$1"
shift
;;
esac
done
# Function to install .pkg files
install_pkg() {
local pkg_file="$1"
echo "Installing $pkg_file..."
if sudo installer -pkg "$pkg_file" -target /; then
INSTALLED+=("$pkg_file")
else
FAILED+=("$pkg_file")
fi
}
# Function to process standalone installers
process_standalone_installers() {
local dir="$1"
v_echo "Looking for standalone installers in $dir..."
# Collect candidate files: executable bit or inside an app's Contents/MacOS
mapfile -t candidates < <(find "$dir" -type f \( -perm -111 -o -path '*/Contents/MacOS/*' \) 2>/dev/null)
for exec_file in "${candidates[@]}"; do
# Skip helper scripts like installbuilder.sh
if [[ "$exec_file" == *installbuilder.sh ]]; then
v_echo "Skipping helper script: $exec_file"
continue
fi
# Skip obvious resource files by extension
case "$exec_file" in
*.png|*.jpg|*.jpeg|*.gif|*.bmp|*.tiff|*.ico|*.plist|*.lproj/*|*.txt|*.md|*.html|*.css|*.pdf)
v_echo "Skipping resource file: $exec_file"
continue
;;
esac
# Use `file` to classify the candidate and skip non-executables
if ! file_out=$(file -b --mime-encoding --mime-type "$exec_file" 2>/dev/null); then
v_echo "Unable to determine file type for $exec_file; skipping"
continue
fi
# Disallow common image/text mime types
if echo "$file_out" | grep -qiE 'image/|text/html|text/css|application/xml|application/json'; then
v_echo "Skipping non-executable mime type ($file_out): $exec_file"
continue
fi
# For plain text we require a shebang to treat it as executable script
if echo "$file_out" | grep -qi '^text/' ; then
first_line=$(head -n1 "$exec_file" 2>/dev/null || true)
if [[ "$first_line" != "#!"* ]]; then
v_echo "Skipping text file without shebang: $exec_file"
continue
fi
fi
# Secondary human-readable `file` check
human_desc=$(file -b "$exec_file" 2>/dev/null || true)
if ! echo "$human_desc" | grep -qiE 'Mach-O|executable|shell script|Python script|Perl script|Bourne-Again shell script|script'; then
v_echo "Skipping (not runnable): $exec_file ($human_desc)"
continue
fi
v_echo "Candidate installer: $exec_file (type: $human_desc)"
# Prefer to run binaries inside an app bundle's Contents/MacOS
if [[ "$exec_file" == */Contents/MacOS/* ]]; then
if [ "$VERBOSE" -eq 1 ]; then
echo "Running app-bundle executable: $exec_file"
fi
if sudo "$exec_file" --mode unattended --unattendedmodeui none 2>/dev/null; then
INSTALLED+=("$exec_file")
else
FAILED+=("$exec_file")
fi
continue
fi
# InstallBuilder unattended attempt
if strings "$exec_file" 2>/dev/null | grep -qi 'InstallBuilder'; then
v_echo "Detected InstallBuilder binary: $exec_file (attempting unattended)"
if sudo "$exec_file" --mode unattended --unattendedmodeui none 2>/dev/null; then
INSTALLED+=("$exec_file")
else
FAILED+=("$exec_file")
fi
continue
fi
# Otherwise, only attempt to run if the file is clearly an executable binary
if echo "$human_desc" | grep -qiE 'Mach-O|executable'; then
v_echo "Running executable: $exec_file"
if sudo "$exec_file" 2>/dev/null; then
INSTALLED+=("$exec_file")
else
FAILED+=("$exec_file")
fi
else
v_echo "Skipping $exec_file (not a runnable binary)"
fi
done
}
# Helper: attempt to mount a dmg and return the mount point (or empty string on failure)
# Note: simplified DMG mount helper removed — use direct hdiutil attach in process_dmg
# Function to process .dmg files
process_dmg() {
local dmg_file="$1"
dmg_file=$(realpath "$dmg_file") # Normalize the path
v_echo "Processing $dmg_file..."
# Attach using plist output so we can reliably extract device and mount-point
local plist_out
plist_out=$(hdiutil attach -plist -nobrowse "$dmg_file" 2>/dev/null) || plist_out=""
local dev
local mount_point
if command -v xmllint >/dev/null 2>&1 && [ -n "$plist_out" ]; then
dev=$(echo "$plist_out" | xmllint --xpath 'string(//key[. = "dev-entry"]/following-sibling::string[1])' - 2>/dev/null || true)
mount_point=$(echo "$plist_out" | xmllint --xpath 'string(//key[. = "mount-point"]/following-sibling::string[1])' - 2>/dev/null || true)
else
# Fallback to plain attach output
local attach_out
attach_out=$(hdiutil attach -nobrowse "$dmg_file" 2>&1)
# try to pull mount path (first /Volumes/... occurrence)
mount_point=$(echo "$attach_out" | sed -n 's/.*\(/Volumes\/[^ ]*\).*/\1/p' | head -n1)
# try to pull device (first dev entry like /dev/diskXsY)
dev=$(echo "$attach_out" | awk '/\/dev\//{for(i=1;i<=NF;i++) if ($i ~ /^\/dev\//) print $i; exit}' )
fi
# If we couldn't determine mount_point yet, try a quick parse of non-plist output
if [ -z "$mount_point" ]; then
mount_point=$(hdiutil info | awk '/\/Volumes\//{for(i=1;i<=NF;i++) if ($i ~ /^\/Volumes\//) print $i}' | head -n1)
fi
if [ -z "$dev" ] && [ -n "$mount_point" ]; then
# try to find dev from mount table
dev=$(mount | awk -v mp="$mount_point" '$3==mp {print $1; exit}') || true
fi
# Wait briefly for filesystem to appear if mount_point exists but is not accessible yet
if [ -n "$mount_point" ]; then
local retries=0
while [ $retries -lt 5 ] && [ ! -d "$mount_point" ]; do
sleep 0.2
retries=$((retries+1))
done
fi
if [ -z "$mount_point" ] || [ ! -d "$mount_point" ]; then
v_echo "Failed to mount $dmg_file (mountpoint not found)"
FAILED+=("$dmg_file")
# if we have a device value, try to detach it
if [ -n "$dev" ]; then
hdiutil detach "$dev" >/dev/null 2>&1 || true
fi
return 1
fi
v_echo "Mounted $dmg_file at $mount_point"
# store the device entry (or mount_point) for cleanup/detach
if [ -n "$dev" ]; then
MOUNT_POINTS+=("$dev")
else
MOUNT_POINTS+=("$mount_point")
fi
# Process .pkg files inside the mounted .dmg (only regular files)
find "$mount_point" -name "*.pkg" -type f | while read pkg_file; do
install_pkg "$pkg_file"
done
# Process standalone installers inside the mounted .dmg
process_standalone_installers "$mount_point"
# Unmount the .dmg by device if we have it, otherwise by mount point
v_echo "Unmounting $dmg_file..."
if [ -n "$dev" ]; then
hdiutil detach "$dev" >/dev/null 2>&1 || v_echo "Failed to detach $dev"
# remove from tracking array
MOUNT_POINTS=("${MOUNT_POINTS[@]/$dev}")
else
hdiutil detach "$mount_point" >/dev/null 2>&1 || v_echo "Failed to detach $mount_point"
MOUNT_POINTS=("${MOUNT_POINTS[@]/$mount_point}")
fi
return 0
}
# Function to process .zip files
process_zip() {
local zip_file="$1"
v_echo "Processing $zip_file..."
# Create a temporary directory for extraction
local temp_dir
temp_dir=$(mktemp -d)
TEMP_DIRS+=("$temp_dir") # Track temp directory for cleanup
unzip -q "$zip_file" -d "$temp_dir"
# Recursively process extracted files
process_directory "$temp_dir"
# Clean up the temporary directory
rm -rf "$temp_dir"
TEMP_DIRS=("${TEMP_DIRS[@]/$temp_dir}") # Remove from tracked temp directories
}
# Recursive function to process directories
process_directory() {
local dir="$1"
v_echo "Processing directory $dir..."
# Process .pkg files
find "$dir" -name "*.pkg" | while read pkg_file; do
install_pkg "$pkg_file"
done
# Process .dmg files
find "$dir" -name "*.dmg" | while read dmg_file; do
process_dmg "$dmg_file"
done
# Process .zip files
find "$dir" -name "*.zip" | while read zip_file; do
process_zip "$zip_file"
done
}
# Main script execution
# The directory to process must be provided as the first positional argument
if [ -z "$base_dir" ]; then
echo "Usage: $0 [--verbose|-v] <directory>"
exit 2
fi
# Ensure directory exists and expand to absolute path
if [ ! -d "$base_dir" ]; then
echo "Error: directory '$base_dir' does not exist"
exit 2
fi
base_dir="$(cd "$base_dir" && pwd)"
process_directory "$base_dir"
# Final summary: either Done or list items that failed
if [ ${#FAILED[@]} -ne 0 ]; then
echo
echo "Failed installs/mounts:"
for f in "${FAILED[@]}"; do
echo " - $f"
done
exit 1
else
echo "Done."
fi
@dangayle
Copy link
Author

@pdxstudio Probably forgot to add sudo, I updated my comment above

@pdxstudio
Copy link

@dangayle thanks for checking on this. Error was:
It was not possible to perform the installation with administrator privileges (status: 4294907265).
Please execute:
sudo /Volumes/UltraChannel/UltraChannel-3.11.4-osx-installer.app/Contents/MacOS/--mode --mode text
as an alternative to acheive the same effect

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment