Created
November 8, 2025 17:00
-
-
Save samelie/af485df79f8034f834767724407986b2 to your computer and use it in GitHub Desktop.
Format external drives for Raspberry Pi and macOS compatibility
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| ############################################################################## | |
| # format-drive.sh - Format external drives for Raspberry Pi and macOS | |
| # Runs from macOS, formats drive with compatibility for both platforms | |
| ############################################################################## | |
| set -euo pipefail | |
| # Colors for output | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| NC='\033[0m' # No Color | |
| # Defaults | |
| FILESYSTEM="ext4" | |
| MOUNT_POINT="/mnt/external" | |
| FORCE_FORMAT=false | |
| INTERACTIVE=true | |
| DRY_RUN=false | |
| ############################################################################## | |
| # Helper Functions | |
| ############################################################################## | |
| log_info() { | |
| echo -e "${BLUE}ℹ${NC} $1" | |
| } | |
| log_success() { | |
| echo -e "${GREEN}✓${NC} $1" | |
| } | |
| log_warn() { | |
| echo -e "${YELLOW}⚠${NC} $1" | |
| } | |
| log_error() { | |
| echo -e "${RED}✗${NC} $1" | |
| } | |
| usage() { | |
| cat << EOF | |
| Usage: $0 [OPTIONS] | |
| Format external drives for Raspberry Pi and macOS compatibility. | |
| OPTIONS: | |
| -d, --device DEVICE Device path (e.g., /dev/disk4) | |
| -f, --filesystem FS Filesystem type: ext4, exfat, apfs (default: ext4) | |
| -m, --mount-point PATH Mount point on Pi (default: /mnt/external) | |
| -F, --force Force format without confirmation | |
| --dry-run Show what would be done, don't execute | |
| -h, --help Show this help message | |
| EXAMPLES: | |
| # Interactive mode - will prompt for device | |
| $0 | |
| # Format /dev/disk4 to ext4 | |
| $0 -d /dev/disk4 -f ext4 | |
| # Force format to exfat (macOS compatible) | |
| $0 -d /dev/disk4 -f exfat -F | |
| # Dry run to see what would happen | |
| $0 -d /dev/disk4 --dry-run | |
| EOF | |
| exit 0 | |
| } | |
| list_drives() { | |
| log_info "Available external drives:" | |
| diskutil list external | |
| } | |
| confirm_action() { | |
| local prompt="$1" | |
| local response | |
| if [ "$FORCE_FORMAT" = true ]; then | |
| return 0 | |
| fi | |
| read -p "$(echo -e ${YELLOW}?)${NC} $prompt (yes/no): " response | |
| if [[ "$response" =~ ^[Yy][Ee]?[Ss]?$ ]]; then | |
| return 0 | |
| else | |
| return 1 | |
| fi | |
| } | |
| ############################################################################## | |
| # macOS-specific Functions | |
| ############################################################################## | |
| get_device_info() { | |
| local device="$1" | |
| if [ ! -e "$device" ]; then | |
| log_error "Device not found: $device" | |
| return 1 | |
| fi | |
| log_info "Device information for $device:" | |
| diskutil info "$device" | |
| } | |
| unmount_device() { | |
| local device="$1" | |
| log_info "Unmounting $device..." | |
| # Get all partitions for this device | |
| local partitions | |
| partitions=$(diskutil list "$device" | grep -E "^\s+[0-9]+" | awk '{print $NF}' || true) | |
| if [ -z "$partitions" ]; then | |
| log_warn "No partitions found to unmount" | |
| return 0 | |
| fi | |
| for partition in $partitions; do | |
| if diskutil info "/dev/$partition" &>/dev/null; then | |
| local mount_point | |
| mount_point=$(diskutil info "/dev/$partition" | grep "Mount Point:" | awk '{$1=$2=""; print $0}' | xargs || true) | |
| if [ -n "$mount_point" ] && [ "$mount_point" != "Not mounted" ]; then | |
| if [ "$DRY_RUN" = true ]; then | |
| log_info "[DRY RUN] Would unmount: /dev/$partition from $mount_point" | |
| else | |
| diskutil unmount "/dev/$partition" || true | |
| fi | |
| fi | |
| fi | |
| done | |
| log_success "Unmount complete" | |
| } | |
| eject_device() { | |
| local device="$1" | |
| if [ "$DRY_RUN" = true ]; then | |
| log_info "[DRY RUN] Would eject: $device" | |
| else | |
| diskutil eject "$device" || log_warn "Could not eject device (may still be in use)" | |
| fi | |
| } | |
| ############################################################################## | |
| # Partitioning & Formatting | |
| ############################################################################## | |
| partition_drive() { | |
| local device="$1" | |
| log_info "Creating GPT partition table on $device..." | |
| log_info "Using exFAT as intermediate (ext4 not supported natively on macOS)" | |
| log_warn "You'll need to format to ext4 on Raspberry Pi if needed" | |
| if [ "$DRY_RUN" = true ]; then | |
| log_info "[DRY RUN] Would run: diskutil partitionDisk $device GPT exFAT PIEXT4 0b" | |
| return 0 | |
| fi | |
| # macOS doesn't support ext4 natively, use exFAT as workaround | |
| diskutil partitionDisk "$device" GPT "exFAT" "PIEXT4" 0b | |
| } | |
| partition_drive_exfat() { | |
| local device="$1" | |
| log_info "Creating GPT partition table with exFAT on $device..." | |
| if [ "$DRY_RUN" = true ]; then | |
| log_info "[DRY RUN] Would run: diskutil partitionDisk $device GPT exFAT PIEXFAT 0b" | |
| return 0 | |
| fi | |
| diskutil partitionDisk "$device" GPT "exFAT" "PIEXFAT" 0b | |
| } | |
| partition_drive_apfs() { | |
| local device="$1" | |
| log_info "Creating APFS partition on $device..." | |
| if [ "$DRY_RUN" = true ]; then | |
| log_info "[DRY RUN] Would run: diskutil partitionDisk $device APFS PIAPFS 0b" | |
| return 0 | |
| fi | |
| diskutil partitionDisk "$device" APFS "PIAPFS" 0b | |
| } | |
| ############################################################################## | |
| # Main Execution | |
| ############################################################################## | |
| main() { | |
| # Parse arguments | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| -d|--device) | |
| DEVICE="$2" | |
| shift 2 | |
| ;; | |
| -f|--filesystem) | |
| FILESYSTEM="$2" | |
| shift 2 | |
| ;; | |
| -m|--mount-point) | |
| MOUNT_POINT="$2" | |
| shift 2 | |
| ;; | |
| -F|--force) | |
| FORCE_FORMAT=true | |
| shift | |
| ;; | |
| --dry-run) | |
| DRY_RUN=true | |
| shift | |
| ;; | |
| -h|--help) | |
| usage | |
| ;; | |
| *) | |
| log_error "Unknown option: $1" | |
| usage | |
| ;; | |
| esac | |
| done | |
| # Check if running on macOS | |
| if [[ "$OSTYPE" != "darwin"* ]]; then | |
| log_error "This script is designed to run on macOS" | |
| exit 1 | |
| fi | |
| # Check for required tools | |
| if ! command -v diskutil &> /dev/null; then | |
| log_error "diskutil not found - this script requires macOS" | |
| exit 1 | |
| fi | |
| log_info "=== External Drive Formatter for Raspberry Pi & macOS ===" | |
| echo "" | |
| # List drives if not specified | |
| if [ -z "${DEVICE:-}" ]; then | |
| list_drives | |
| echo "" | |
| read -p "Enter device path (e.g., /dev/disk4): " DEVICE | |
| fi | |
| # Validate device | |
| if [ ! -e "$DEVICE" ]; then | |
| log_error "Device not found: $DEVICE" | |
| exit 1 | |
| fi | |
| # Show device info | |
| get_device_info "$DEVICE" | |
| echo "" | |
| # Validate filesystem | |
| case "$FILESYSTEM" in | |
| ext4|exfat|apfs) | |
| ;; | |
| *) | |
| log_error "Unsupported filesystem: $FILESYSTEM" | |
| log_info "Supported: ext4, exfat, apfs" | |
| exit 1 | |
| ;; | |
| esac | |
| # Show filesystem info | |
| log_info "Requested filesystem: $FILESYSTEM" | |
| if [ "$FILESYSTEM" = "ext4" ]; then | |
| log_warn "Note: macOS can't format ext4 natively" | |
| log_info "Will format as exFAT on macOS, reformat on Raspberry Pi if needed" | |
| elif [ "$FILESYSTEM" = "exfat" ]; then | |
| log_info "Best for: Cross-platform (macOS + Raspberry Pi)" | |
| log_warn "Slightly slower than native formats, no Unix permissions" | |
| elif [ "$FILESYSTEM" = "apfs" ]; then | |
| log_info "Best for: macOS only" | |
| log_error "Not recommended - Raspberry Pi has limited APFS support" | |
| fi | |
| echo "" | |
| # Confirm action | |
| if ! confirm_action "Format $DEVICE with $FILESYSTEM?"; then | |
| log_warn "Cancelled" | |
| exit 0 | |
| fi | |
| if [ "$DRY_RUN" = false ]; then | |
| log_warn "This will erase all data on $DEVICE!" | |
| if ! confirm_action "Are you absolutely sure?"; then | |
| log_warn "Cancelled" | |
| exit 0 | |
| fi | |
| fi | |
| echo "" | |
| log_info "Starting format process..." | |
| # Unmount device | |
| unmount_device "$DEVICE" | |
| echo "" | |
| # Format based on filesystem | |
| case "$FILESYSTEM" in | |
| ext4) | |
| partition_drive "$DEVICE" | |
| ;; | |
| exfat) | |
| partition_drive_exfat "$DEVICE" | |
| ;; | |
| apfs) | |
| partition_drive_apfs "$DEVICE" | |
| ;; | |
| esac | |
| if [ "$DRY_RUN" = false ]; then | |
| sleep 2 | |
| log_success "Format complete!" | |
| echo "" | |
| log_info "Drive info after format:" | |
| diskutil list "$DEVICE" | |
| echo "" | |
| # Get partition device | |
| local partition_device | |
| partition_device=$(diskutil list "$DEVICE" | grep -E "^\s+0:" | awk '{print $NF}') | |
| if [ -n "$partition_device" ]; then | |
| log_info "Partition created: /dev/$partition_device" | |
| log_info "To use on Raspberry Pi, copy this to your ansible playbook:" | |
| log_warn "ssd_device: \"/dev/sda\"" | |
| log_warn "mount_point: \"$MOUNT_POINT\"" | |
| log_warn "filesystem: \"$FILESYSTEM\"" | |
| fi | |
| else | |
| log_success "[DRY RUN] Format would complete successfully" | |
| fi | |
| echo "" | |
| log_success "Done!" | |
| } | |
| # Run main function | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment