mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 18:23:07 +02:00
git-subtree-dir: linux_configuration git-subtree-mainline:11427631cdgit-subtree-split:0762e3d07b
324 lines
8.2 KiB
Bash
Executable File
324 lines
8.2 KiB
Bash
Executable File
#!/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 "$@"
|