Add all 44 scripts, update CI: error severity baseline, PowerShell validation, multi-distro testing
Amp-Thread-ID: https://ampcode.com/threads/T-019cc404-c628-759e-a50b-f5eeea35b91f Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Executable
+319
@@ -0,0 +1,319 @@
|
||||
#!/bin/bash
|
||||
|
||||
#############################################################
|
||||
#### Expand Drive ####
|
||||
#### Auto-expand partitions and filesystems ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### License: MIT ####
|
||||
#### Version: 2.3 ####
|
||||
#### ####
|
||||
#### Usage: sudo ./expand-drive.sh ####
|
||||
#############################################################
|
||||
|
||||
# Set strict error handling:
|
||||
# -e: Exit immediately if a command exits with a non-zero status
|
||||
# -u: Treat unset variables as an error when substituting
|
||||
# -o pipefail: The return value of a pipeline is the status of the last command to exit with a non-zero status
|
||||
set -euo pipefail
|
||||
|
||||
# Constants - Define paths to required system binaries (use command names, let PATH resolve)
|
||||
readonly BLKID_PATH="blkid" # Tool to locate/print block device attributes
|
||||
readonly LSBLK_PATH="lsblk" # Tool to list block devices
|
||||
readonly LOG_FILE="/var/log/expand_drive.log" # Location for script log output
|
||||
|
||||
# Configuration - Runtime behavior settings
|
||||
readonly DRY_RUN=${DRY_RUN:-false} # If true, show what would be done without making changes
|
||||
readonly REQUIRED_COMMANDS=("growpart" "xfs_growfs" "resize2fs") # Commands that must be available
|
||||
readonly SUPPORTED_FILESYSTEMS=("xfs" "ext2" "ext3" "ext4") # Filesystem types we can expand
|
||||
|
||||
# Exit codes - Standardized exit status values
|
||||
readonly EXIT_SUCCESS=0 # Script completed successfully
|
||||
readonly EXIT_ERROR=1 # General error occurred
|
||||
readonly EXIT_ROOT_REQUIRED=2 # Script must be run as root user
|
||||
readonly EXIT_MISSING_DEPS=3 # Required dependencies are missing
|
||||
|
||||
# Function to log messages with timestamp to both console and log file
|
||||
log_message() {
|
||||
echo "$(date): $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Function to log error messages with timestamp to both console, log file, and stderr
|
||||
log_error() {
|
||||
echo "$(date): ERROR: $1" | tee -a "$LOG_FILE" >&2
|
||||
}
|
||||
|
||||
# Function to check if a command exists in the system PATH
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Function to handle script interruption (SIGINT/SIGTERM) and perform cleanup
|
||||
cleanup() {
|
||||
# shellcheck disable=SC2317 # Suppress warning about unreachable code
|
||||
log_message "Script interrupted, cleaning up..."
|
||||
# shellcheck disable=SC2317 # Suppress warning about unreachable code
|
||||
exit "$EXIT_ERROR"
|
||||
}
|
||||
|
||||
# Function to validate prerequisites before script execution
|
||||
validate_prerequisites() {
|
||||
# Check if script is run as root (required for partition/filesystem operations)
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Error: This script must be run as root"
|
||||
exit "$EXIT_ROOT_REQUIRED"
|
||||
fi
|
||||
|
||||
# Ensure log directory exists and is writable
|
||||
local log_dir
|
||||
log_dir=$(dirname "$LOG_FILE")
|
||||
if [ ! -d "$log_dir" ]; then
|
||||
mkdir -p "$log_dir" || {
|
||||
echo "Error: Cannot create log directory $log_dir"
|
||||
exit "$EXIT_ERROR"
|
||||
}
|
||||
fi
|
||||
|
||||
# Verify all required system commands are available
|
||||
for cmd in "${REQUIRED_COMMANDS[@]}"; do
|
||||
if ! command_exists "$cmd"; then
|
||||
log_error "Required command '$cmd' not found. Please install it."
|
||||
exit "$EXIT_MISSING_DEPS"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Function to check if filesystem type is supported by this script
|
||||
is_supported_filesystem() {
|
||||
local fs_type="$1"
|
||||
# Loop through supported filesystem types array
|
||||
for supported in "${SUPPORTED_FILESYSTEMS[@]}"; do
|
||||
if [[ "$fs_type" == "$supported" ]]; then
|
||||
return 0 # Filesystem type is supported
|
||||
fi
|
||||
done
|
||||
return 1 # Filesystem type is not supported
|
||||
}
|
||||
|
||||
# Function to expand filesystem based on type (XFS or EXT variants)
|
||||
expand_filesystem() {
|
||||
local partition="$1" # Block device path (e.g., /dev/sda1)
|
||||
local fs_type="$2" # Filesystem type (xfs, ext2, ext3, ext4)
|
||||
local mount_point="$3" # Where the filesystem is mounted
|
||||
|
||||
# Validate filesystem type is one we support
|
||||
if ! is_supported_filesystem "$fs_type"; then
|
||||
log_error "Unsupported filesystem type $fs_type on $partition"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Handle different filesystem types with appropriate expansion commands
|
||||
case $fs_type in
|
||||
"xfs")
|
||||
log_message "Expanding XFS filesystem on $partition"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
log_message "DRY RUN: Would expand XFS filesystem on $partition"
|
||||
return 0
|
||||
# XFS uses xfs_growfs and requires the mount point as argument
|
||||
elif xfs_growfs "$mount_point" >/dev/null 2>&1; then
|
||||
log_message "Successfully expanded XFS filesystem on $partition"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to expand XFS filesystem on $partition"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
"ext2" | "ext3" | "ext4")
|
||||
log_message "Expanding EXT filesystem on $partition"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
log_message "DRY RUN: Would expand EXT filesystem on $partition"
|
||||
return 0
|
||||
# EXT filesystems use resize2fs and require the device path as argument
|
||||
elif resize2fs "$partition" >/dev/null 2>&1; then
|
||||
log_message "Successfully expanded EXT filesystem on $partition"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to expand EXT filesystem on $partition"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Function to expand partition to use available disk space
|
||||
expand_partition() {
|
||||
local disk="$1" # Parent disk device (e.g., /dev/sda)
|
||||
local partition="$2" # Partition device (e.g., /dev/sda1)
|
||||
local part_num="$3" # Partition number (e.g., 1)
|
||||
|
||||
# Check if partition can be expanded using growpart dry-run
|
||||
if ! growpart "$disk" "$part_num" --dry-run 2>/dev/null; then
|
||||
log_message "Partition $partition doesn't need expansion or cannot be expanded, skipping..."
|
||||
return 1 # Not an error, just nothing to do
|
||||
fi
|
||||
|
||||
# Perform the actual partition expansion
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
log_message "DRY RUN: Would expand partition $partition"
|
||||
return 0
|
||||
elif growpart "$disk" "$part_num" >/dev/null 2>&1; then
|
||||
log_message "Successfully expanded partition $partition"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to expand partition $partition"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Set up signal trap to handle interruptions gracefully
|
||||
trap cleanup INT TERM
|
||||
|
||||
# Initialize script by validating prerequisites
|
||||
validate_prerequisites
|
||||
|
||||
# Function to process a single partition (expand partition and filesystem)
|
||||
process_partition() {
|
||||
local partition="$1" # Partition device path (e.g., /dev/sda1)
|
||||
local disk="$2" # Parent disk device path (e.g., /dev/sda)
|
||||
|
||||
log_message "Processing partition $partition"
|
||||
|
||||
# Check if the filesystem is currently mounted (required for filesystem expansion)
|
||||
local mount_point
|
||||
mount_point=$(findmnt -n -o TARGET "$partition" 2>/dev/null)
|
||||
if [ -z "$mount_point" ]; then
|
||||
log_message "Warning: $partition is not mounted, skipping filesystem resize"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Extract partition number from device path (e.g., extract "1" from "/dev/sda1")
|
||||
local part_num
|
||||
part_num=$(echo "$partition" | grep -o '[0-9]\+$' | tail -1)
|
||||
if [ -z "$part_num" ]; then
|
||||
log_error "Could not extract partition number from $partition"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# First expand the partition to use available disk space
|
||||
if ! expand_partition "$disk" "$partition" "$part_num"; then
|
||||
return 0 # Not an error if partition doesn't need expansion
|
||||
fi
|
||||
|
||||
# Detect the filesystem type using blkid
|
||||
local fs_type
|
||||
fs_type=$($BLKID_PATH -s TYPE -o value "$partition")
|
||||
if [ -z "$fs_type" ]; then
|
||||
log_message "Warning: Could not detect filesystem type for $partition, skipping..."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Get current filesystem size before expansion
|
||||
local current_size
|
||||
current_size=$(df -h "$mount_point" | awk 'NR==2 {print $2}')
|
||||
log_message "Current filesystem size on $partition: $current_size"
|
||||
|
||||
# Expand the filesystem to use the newly available partition space
|
||||
expand_filesystem "$partition" "$fs_type" "$mount_point"
|
||||
|
||||
# Show new size after expansion
|
||||
local new_size
|
||||
new_size=$(df -h "$mount_point" | awk 'NR==2 {print $2}')
|
||||
log_message "New filesystem size on $partition: $new_size"
|
||||
}
|
||||
|
||||
# Function to process a disk with direct filesystem (no partitions)
|
||||
process_direct_filesystem() {
|
||||
local disk="$1" # Disk device path (e.g., /dev/nvme3n1)
|
||||
local mount_point="$2" # Where the filesystem is mounted
|
||||
|
||||
log_message "Processing direct filesystem on $disk mounted at $mount_point"
|
||||
|
||||
# Detect the filesystem type using blkid
|
||||
local fs_type
|
||||
fs_type=$($BLKID_PATH -s TYPE -o value "$disk")
|
||||
if [ -z "$fs_type" ]; then
|
||||
log_message "Warning: Could not detect filesystem type for $disk, skipping..."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Get current filesystem size before expansion
|
||||
local current_size
|
||||
current_size=$(df -h "$mount_point" | awk 'NR==2 {print $2}')
|
||||
log_message "Current filesystem size on $disk: $current_size"
|
||||
|
||||
# Expand the filesystem to use the full disk space
|
||||
expand_filesystem "$disk" "$fs_type" "$mount_point"
|
||||
|
||||
# Show new size after expansion
|
||||
local new_size
|
||||
new_size=$(df -h "$mount_point" | awk 'NR==2 {print $2}')
|
||||
log_message "New filesystem size on $disk: $new_size"
|
||||
}
|
||||
|
||||
# Function to process all partitions on a single disk
|
||||
process_disk() {
|
||||
local disk="$1" # Disk device path (e.g., /dev/sda)
|
||||
|
||||
log_message "Checking partitions on $disk..."
|
||||
|
||||
# Get list of partitions for the current disk using lsblk
|
||||
# Filter for partition type and extract device names
|
||||
local partitions
|
||||
local lsblk_output
|
||||
lsblk_output=$($LSBLK_PATH -pln -o NAME,TYPE "$disk" 2>&1) || {
|
||||
log_error "lsblk command failed for $disk: $lsblk_output"
|
||||
return 1
|
||||
}
|
||||
partitions=$(echo "$lsblk_output" | grep "part" | cut -d' ' -f1 || true)
|
||||
|
||||
if [ -z "$partitions" ]; then
|
||||
# Check if the disk itself has a filesystem (no partition table)
|
||||
local mount_point
|
||||
mount_point=$(findmnt -n -o TARGET "$disk" 2>/dev/null)
|
||||
if [ -n "$mount_point" ]; then
|
||||
log_message "No partitions found on $disk, but disk has direct filesystem. Processing disk directly..."
|
||||
process_direct_filesystem "$disk" "$mount_point"
|
||||
else
|
||||
log_message "No partitions found on $disk, skipping..."
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Process each partition found on this disk
|
||||
for partition in $partitions; do
|
||||
process_partition "$partition" "$disk"
|
||||
done
|
||||
}
|
||||
|
||||
# Main execution function - orchestrates the entire drive expansion process
|
||||
main() {
|
||||
log_message "Starting drive expansion process..."
|
||||
|
||||
# Get list of all disk devices in the system using lsblk
|
||||
# Filter for disk type and extract device names
|
||||
local devices
|
||||
devices=$($LSBLK_PATH -pln -o NAME,TYPE | grep "disk" | cut -d' ' -f1)
|
||||
|
||||
# Verify we found at least one disk device
|
||||
if [ -z "$devices" ]; then
|
||||
log_error "No disk devices found"
|
||||
exit "$EXIT_ERROR"
|
||||
fi
|
||||
|
||||
# Process each disk device found
|
||||
for disk in $devices; do
|
||||
# Verify device is actually a block device before processing
|
||||
if [ ! -b "$disk" ]; then
|
||||
log_error "Device $disk is not a block device, skipping..."
|
||||
continue
|
||||
fi
|
||||
process_disk "$disk"
|
||||
done
|
||||
|
||||
log_message "Drive expansion completed"
|
||||
exit "$EXIT_SUCCESS"
|
||||
}
|
||||
|
||||
# Execute the main function to start the script
|
||||
main
|
||||
Reference in New Issue
Block a user