mirror of
https://github.com/kuhyx/scripts.git
synced 2026-07-04 13:03:05 +02:00
feat: script for formatting sd card
This commit is contained in:
parent
aa462adc4f
commit
d32d305d3b
323
scripts/utils/format_sd_card.sh
Executable file
323
scripts/utils/format_sd_card.sh
Executable file
@ -0,0 +1,323 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# format_sd_card.sh
|
||||
#
|
||||
# Safely detect and format an SD card.
|
||||
#
|
||||
# Defaults:
|
||||
# * Detect removable disks via lsblk (TYPE=disk, RM=1)
|
||||
# * Interactive selection if multiple candidates found
|
||||
# * Unmount all partitions before formatting
|
||||
# * Create a single partition and format it as exfat by default
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./format_sd_card.sh # interactive detection + confirmation
|
||||
# sudo ./format_sd_card.sh /dev/sdX # format specific device
|
||||
# sudo ./format_sd_card.sh --dry-run # show what would happen, no changes
|
||||
# sudo ./format_sd_card.sh --help
|
||||
|
||||
DRY_RUN=false
|
||||
FILESYSTEM="exfat" # you can change to ext4, vfat, etc.
|
||||
DUMBPHONE_MODE=false # when true: MBR + ~30GiB FAT32 primary partition
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: sudo $(basename "$0") [OPTIONS] [DEVICE]
|
||||
|
||||
Safely detect and format an SD card.
|
||||
|
||||
Options:
|
||||
--dry-run Show actions without executing them
|
||||
--fs TYPE Filesystem type (default: ${FILESYSTEM})
|
||||
--dumbphone Use MBR and create a ~30GiB FAT32 partition for old phones
|
||||
-h, --help Show this help
|
||||
|
||||
If DEVICE is not provided, removable disks are detected automatically and you
|
||||
will be asked to pick one if multiple are found.
|
||||
|
||||
WARNING: This will ERASE ALL DATA on the selected device.
|
||||
EOF
|
||||
}
|
||||
|
||||
ensure_fs_tools() {
|
||||
case "$FILESYSTEM" in
|
||||
vfat | fat32)
|
||||
# Ensure mkfs.vfat is available
|
||||
if ! command -v mkfs.vfat > /dev/null 2>&1; then
|
||||
echo "mkfs.vfat not found. Attempting to install dosfstools..." >&2
|
||||
|
||||
# Detect package manager
|
||||
if command -v pacman > /dev/null 2>&1; then
|
||||
run "pacman -Sy --needed --noconfirm dosfstools"
|
||||
elif command -v apt-get > /dev/null 2>&1; then
|
||||
run "apt-get update"
|
||||
run "apt-get install -y dosfstools"
|
||||
else
|
||||
echo "Unsupported package manager. Please install 'dosfstools' (provides mkfs.vfat) manually." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Re-check
|
||||
if ! command -v mkfs.vfat > /dev/null 2>&1; then
|
||||
echo "mkfs.vfat is still not available after attempted installation." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
exfat)
|
||||
# exfat tools
|
||||
if ! command -v mkfs.exfat > /dev/null 2>&1; then
|
||||
echo "mkfs.exfat not found. Please install exfatprogs (Arch) or exfat-fuse/exfatprogs (Debian/Ubuntu)." >&2
|
||||
# Do not auto-install here to avoid too much magic across distros
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
ext4)
|
||||
if ! command -v mkfs.ext4 > /dev/null 2>&1; then
|
||||
echo "mkfs.ext4 not found. Please install e2fsprogs." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
|
||||
echo "This script must be run as root (use sudo)." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
run() {
|
||||
if [[ $DRY_RUN == true ]]; then
|
||||
log "DRY RUN: $*"
|
||||
else
|
||||
log "RUN: $*"
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
confirm() {
|
||||
local prompt="$1"
|
||||
read -r -p "$prompt [y/N]: " ans
|
||||
case "$ans" in
|
||||
y | Y | yes | YES) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
DEVICE=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--fs)
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "Missing value for --fs" >&2
|
||||
exit 1
|
||||
fi
|
||||
FILESYSTEM="$2"
|
||||
shift 2
|
||||
;;
|
||||
--dumbphone)
|
||||
# Force settings that are friendlier to older phones:
|
||||
# * MBR (dos) partition table
|
||||
# * Single ~30GiB FAT32 partition
|
||||
# * Leave remaining space unused
|
||||
DUMBPHONE_MODE=true
|
||||
FILESYSTEM="vfat"
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
/dev/*)
|
||||
DEVICE="$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
export DEVICE FILESYSTEM DRY_RUN DUMBPHONE_MODE
|
||||
}
|
||||
|
||||
detect_sd_card() {
|
||||
# List removable disks (RM=1, TYPE=disk)
|
||||
# Columns: NAME, RM, SIZE, MODEL, TRAN, TYPE
|
||||
local output
|
||||
output=$(lsblk -o NAME,RM,SIZE,MODEL,TRAN,TYPE -nr | awk '$2==1 && $6=="disk"') || true
|
||||
|
||||
if [[ -z $output ]]; then
|
||||
echo "No removable disks detected. Please provide device explicitly (e.g., /dev/sda)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t candidates < <(echo "$output")
|
||||
|
||||
if [[ ${#candidates[@]} -eq 1 ]]; then
|
||||
local name size model tran
|
||||
read -r name _ size model tran _ <<< "${candidates[0]}"
|
||||
DEVICE="/dev/${name}"
|
||||
log "Detected removable disk: $DEVICE (${size} ${model} ${tran})"
|
||||
else
|
||||
echo "Multiple removable disks detected:" >&2
|
||||
local i=1
|
||||
for line in "${candidates[@]}"; do
|
||||
local name size model tran
|
||||
read -r name _ size model tran _ <<< "$line"
|
||||
printf ' %d) /dev/%s %s %s %s\n' "$i" "$name" "$size" "$model" "$tran"
|
||||
((i++))
|
||||
done
|
||||
|
||||
while true; do
|
||||
read -r -p "Select device to format (1-${#candidates[@]}): " choice
|
||||
if [[ $choice =~ ^[0-9]+$ ]] && ((choice >= 1 && choice <= ${#candidates[@]})); then
|
||||
local sel="${candidates[choice - 1]}"
|
||||
local name size model tran
|
||||
read -r name _ size model tran _ <<< "$sel"
|
||||
DEVICE="/dev/${name}"
|
||||
log "Selected device: $DEVICE (${size} ${model} ${tran})"
|
||||
break
|
||||
else
|
||||
echo "Invalid choice." >&2
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
validate_device() {
|
||||
if [[ -z ${DEVICE:-} ]]; then
|
||||
detect_sd_card
|
||||
fi
|
||||
|
||||
if [[ ! -b $DEVICE ]]; then
|
||||
echo "Device $DEVICE does not exist or is not a block device." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extra safety: refuse clearly system disks by checking if rootfs lives there
|
||||
local root_dev
|
||||
root_dev=$(findmnt -no SOURCE / || true)
|
||||
if [[ $root_dev == "$DEVICE"* ]]; then
|
||||
echo "Refusing to operate on $DEVICE because it appears to contain the root filesystem ($root_dev)." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
unmount_partitions() {
|
||||
local dev base part
|
||||
dev="$DEVICE"
|
||||
base="${dev##*/}"
|
||||
|
||||
mapfile -t parts < <(lsblk -nr -o NAME,MOUNTPOINT "/dev/${base}" | awk 'NF==2 {print $1" "$2}') || true
|
||||
|
||||
for entry in "${parts[@]:-}"; do
|
||||
read -r part mp <<< "$entry"
|
||||
if [[ -n $mp ]]; then
|
||||
run "umount \"$mp\""
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
wipe_and_partition() {
|
||||
local dev="$DEVICE"
|
||||
|
||||
if ! confirm "About to WIPE ALL DATA on $dev and create a new ${FILESYSTEM} filesystem. Continue?"; then
|
||||
echo "Aborted by user." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Zap existing partition table
|
||||
run "wipefs -a \"$dev\""
|
||||
|
||||
# Create a new partition table + partition layout
|
||||
# Using sfdisk for non-interactive, reproducible layout
|
||||
local sfdisk_input
|
||||
|
||||
if [[ $DUMBPHONE_MODE == true ]]; then
|
||||
# Old phones often:
|
||||
# * only support MBR (dos)
|
||||
# * only support SD/SDHC (<=32GiB)
|
||||
# We create an MBR table and a single ~30GiB FAT32 partition, leaving the rest unused.
|
||||
# 30GiB ≈ 30 * 2^30 / 512 ≈ 62914560 sectors; start at 2048 for alignment.
|
||||
sfdisk_input=$'label: dos\n2048,62914560,c,*\n'
|
||||
if [[ $DRY_RUN == true ]]; then
|
||||
log "DRY RUN: echo -e '$sfdisk_input' | sfdisk $dev"
|
||||
else
|
||||
log "RUN: create MBR (dos) with ~30GiB FAT32 partition on $dev"
|
||||
echo -e "$sfdisk_input" | sfdisk "$dev"
|
||||
fi
|
||||
else
|
||||
# Default: GPT with one partition spanning the whole device
|
||||
sfdisk_input=$'label: gpt\n,;\n'
|
||||
if [[ $DRY_RUN == true ]]; then
|
||||
log "DRY RUN: echo -e '$sfdisk_input' | sfdisk $dev"
|
||||
else
|
||||
log "RUN: create GPT with one partition on $dev"
|
||||
echo -e "$sfdisk_input" | sfdisk "$dev"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Let the kernel re-read the partition table
|
||||
sleep 2
|
||||
}
|
||||
|
||||
format_filesystem() {
|
||||
local dev base part
|
||||
dev="$DEVICE"
|
||||
base="${dev##*/}"
|
||||
part="/dev/${base}1"
|
||||
|
||||
if [[ ! -b $part ]]; then
|
||||
echo "Expected partition $part not found after partitioning." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$FILESYSTEM" in
|
||||
exfat)
|
||||
run mkfs.exfat -n SDCARD "$part"
|
||||
;;
|
||||
vfat | fat32)
|
||||
run mkfs.vfat -F32 -n SDCARD "$part"
|
||||
;;
|
||||
ext4)
|
||||
run mkfs.ext4 -F -L SDCARD "$part"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported filesystem type: $FILESYSTEM" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log "Formatting completed on $part with filesystem $FILESYSTEM."
|
||||
}
|
||||
|
||||
main() {
|
||||
parse_args "$@"
|
||||
require_root
|
||||
ensure_fs_tools
|
||||
validate_device
|
||||
unmount_partitions
|
||||
wipe_and_partition
|
||||
format_filesystem
|
||||
|
||||
log "All done. You can now remove and reinsert the SD card or mount the new filesystem manually."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in New Issue
Block a user