Last active
September 6, 2024 10:18
-
-
Save bkuri/804813969fe0919bf3b8ce3f4e3e6fc9 to your computer and use it in GitHub Desktop.
Arch Linux Boot Backup to USB Script
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 | |
# Script to backup the current boot environment to a USB drive for systemd-boot on Arch Linux | |
# Ensure the script is run as root | |
if [ "$(id -u)" -ne 0 ]; then | |
echo "This script must be run as root" >&2 | |
exit 1 | |
fi | |
# Set variables | |
BACKUP_DIR="/tmp/usb_backup_$(date +%Y%m%d_%H%M%S)" | |
BOOT_DIR="/boot" | |
ENTRY_NAME="Backup USB Boot ($(date +%Y-%m-%d))" | |
USB_BOOT=$(mktemp -d) | |
backup_partition() { | |
local device=$1 | |
if [[ $(blkid -s TYPE -o value "$device") == "swap" ]]; then | |
echo "Skipping swap partition $device" | |
return | |
fi | |
MOUNT_POINT=$(mktemp -d) | |
if mount -o ro "$device" "$MOUNT_POINT" 2>/dev/null; then | |
rsync -av "$MOUNT_POINT/" "$BACKUP_DIR/$(basename $device)/" | |
umount "$MOUNT_POINT" | |
echo "Backed up contents of $device" | |
else | |
echo "Failed to mount $device. Its contents won't be backed up." | |
echo | |
read -p "Do you wish to continue regardless? (y/N) " -n 1 -r | |
echo | |
if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
echo "Operation cancelled." | |
exit 1 | |
fi | |
fi | |
rmdir "$MOUNT_POINT" | |
} | |
# Backup existing USB contents | |
backup_usb_contents() { | |
echo "Backing up existing USB contents…" | |
mkdir -p "$BACKUP_DIR" | |
# Check if the device has partitions | |
if [[ -b "${USB_DEVICE}1" ]] || [[ -b "${USB_DEVICE}p1" ]]; then | |
# Device has partitions, backup each partition | |
for partition in ${USB_DEVICE}*; do | |
if [[ -b "$partition" ]] && [[ "$partition" != "$USB_DEVICE" ]]; then | |
backup_partition "$partition" | |
fi | |
done | |
else | |
# Device doesn't have partitions, try to backup the entire device | |
backup_partition "$USB_DEVICE" | |
fi | |
echo "USB contents backed up to $BACKUP_DIR" | |
echo "Please ensure to copy this backup to a safe location before proceeding." | |
} | |
get_required_space() { | |
if [ -d "$BOOT_DIR" ]; then | |
local boot_size=$(du -s "$BOOT_DIR" | cut -f1) | |
# Add some extra space for the EFI partition and other potential files | |
echo $((boot_size + 50000)) # 50MB extra, adjust as needed | |
else | |
echo "Error: $BOOT_DIR does not exist or is not accessible." | |
exit 1 | |
fi | |
} | |
# Function to get the current root partition | |
get_root_partition() { | |
local root_partition | |
root_partition=$(findmnt -n -o SOURCE /) | |
# If the root is on a LVM or LUKS, we need to handle it differently | |
if [[ $root_partition == /dev/mapper/* ]]; then | |
# For LUKS (check this first) | |
if cryptsetup status $root_partition &>/dev/null; then | |
local backing_device=$(cryptsetup status $root_partition | awk '/device:/ {print $2}') | |
if [[ -n $backing_device ]]; then | |
echo "LUKS:$backing_device" | |
else | |
echo "LUKS:$root_partition" | |
fi | |
# For LVM | |
elif lvs $root_partition &>/dev/null; then | |
local vg_name=$(lvs --noheadings -o vg_name $root_partition | tr -d '[:space:]') | |
local lv_name=$(lvs --noheadings -o lv_name $root_partition | tr -d '[:space:]') | |
root_partition="/dev/$vg_name/$lv_name" | |
echo "LVM:$root_partition" | |
else | |
echo $root_partition | |
fi | |
else | |
echo $root_partition | |
fi | |
} | |
get_usb_size() { | |
local size_kb=$(lsblk -ndo SIZE -b "$USB_DEVICE" | awk '{print $1/1024}') | |
if [ -z "$size_kb" ]; then | |
echo "Error: Failed to get USB device size" >&2 | |
exit 1 | |
fi | |
echo "${size_kb%.*}" # Remove decimal part | |
} | |
# Function to select USB drive | |
select_usb_drive() { | |
echo "Available USB drives:" | |
lsblk -ndo NAME,SIZE,MODEL | grep -E "sd[b-z]|mmcblk[0-9]" | |
read -p "Enter the device name of your USB drive (e.g., sdb): " USB_DRIVE | |
USB_DEVICE="/dev/$USB_DRIVE" | |
if [ ! -b "$USB_DEVICE" ]; then | |
echo "Invalid device. Exiting…" | |
exit 1 | |
fi | |
} | |
# Function to unmount all partitions of a device | |
unmount_all() { | |
for partition in ${1}*; do | |
if mountpoint -q "$partition"; then | |
echo "Unmounting $partition…" | |
umount -f "$partition" || { echo "Failed to unmount $partition"; exit 1; } | |
fi | |
done | |
} | |
# Get the current root partition | |
CURRENT_ROOT=$(get_root_partition) | |
# Select USB drive | |
select_usb_drive | |
# Check if BOOT_DIR exists and is accessible | |
if [ ! -d "$BOOT_DIR" ]; then | |
echo "Error: Boot directory $BOOT_DIR does not exist or is not accessible." | |
exit 1 | |
fi | |
# Check USB drive size | |
REQUIRED_SPACE=$(get_required_space) | |
USB_SIZE=$(get_usb_size) | |
if [ "$USB_SIZE" -lt "$REQUIRED_SPACE" ]; then | |
echo "Error: The selected USB drive is too small." | |
echo "Required space: $((REQUIRED_SPACE / 1024)) MB" | |
echo "USB drive size: $((USB_SIZE / 1024)) MB" | |
echo "Please select a larger USB drive and try again." | |
exit 1 | |
fi | |
echo "USB drive size is adequate. Proceeding with backup…" | |
# Confirm with the user | |
read -p "This will erase all data on $USB_DEVICE. Do you want to backup its contents first? (Y/n) " -n 1 -r | |
echo | |
if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then | |
backup_usb_contents | |
fi | |
read -p "Are you sure you want to continue with creating the boot USB? (y/N) " -n 1 -r | |
echo | |
if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
echo "Operation cancelled." | |
exit 1 | |
fi | |
# Ask for root partition with the current one as default | |
read -p "Enter the root partition (default: $CURRENT_ROOT): " ROOT_PARTITION | |
ROOT_PARTITION=${ROOT_PARTITION:-$CURRENT_ROOT} | |
# Validate the root partition | |
if [ -z "$ROOT_PARTITION" ]; then | |
echo "Error: Root partition cannot be empty" | |
exit 1 | |
fi | |
if [ ! -b "$ROOT_PARTITION" ]; then | |
echo "Warning: $ROOT_PARTITION is not a block device. Make sure this is correct." | |
read -p "Press Enter to continue or Ctrl+C to exit" | |
fi | |
# Unmount all partitions of the USB device | |
unmount_all "$USB_DEVICE" | |
# Partition the USB drive | |
echo "Partitioning $USB_DEVICE…" | |
dd if=/dev/zero of="$USB_DEVICE" bs=512 count=1 conv=fsync | |
partprobe "$USB_DEVICE" | |
sleep 2 | |
parted -s "$USB_DEVICE" mklabel gpt | |
parted -s "$USB_DEVICE" mkpart primary fat32 1 100% | |
parted -s "$USB_DEVICE" set 1 esp on | |
partprobe "$USB_DEVICE" | |
sleep 2 | |
# Format partition | |
echo "Formatting partition…" | |
mkfs.fat -F32 "${USB_DEVICE}1" | |
# Mount partition | |
echo "Mounting partition…" | |
mount "${USB_DEVICE}1" "$USB_BOOT" || { echo "Failed to mount ${USB_DEVICE}1"; exit 1; } | |
# Copy boot files | |
echo "Copying boot files…" | |
rsync -av --no-owner --no-group "$BOOT_DIR"/* "$USB_BOOT/" | |
# Check if necessary modules are in initramfs | |
echo "Checking initramfs configuration…" | |
if [[ $ROOT_PARTITION == LUKS:* ]] || [[ $ROOT_PARTITION == LVM:* ]]; then | |
# Check if mkinitcpio.conf exists in the system | |
if [ -f "/etc/mkinitcpio.conf" ]; then | |
# Check for encrypt and lvm2 hooks | |
if ! grep -q "encrypt" "/etc/mkinitcpio.conf" || ! grep -q "lvm2" "/etc/mkinitcpio.conf"; then | |
echo "Error: encrypt and/or lvm2 hooks are missing from /etc/mkinitcpio.conf." | |
echo "Please add these hooks manually in the correct order and regenerate the initramfs before running this script again." | |
echo "More info here: https://wiki.archlinux.org/title/Mkinitcpio#HOOKS" | |
exit 1 | |
else | |
echo "Required hooks (encrypt and lvm2) found in mkinitcpio.conf. Proceeding…" | |
fi | |
else | |
echo "Error: mkinitcpio.conf not found in /etc." | |
echo "Please ensure your initramfs includes encrypt and lvm2 hooks before running this script again." | |
exit 1 | |
fi | |
fi | |
# Copy the current initramfs to the USB drive | |
echo "Copying current initramfs to USB drive…" | |
cp /boot/initramfs-linux* "$USB_BOOT/" | |
# Install systemd-boot on USB drive | |
echo "Installing systemd-boot on USB drive…" | |
if ! bootctl --path="$USB_BOOT" install; then | |
echo "Error: Failed to install systemd-boot on the USB drive." | |
exit 1 | |
fi | |
# Update bootloader configuration | |
echo "Updating bootloader configuration…" | |
for conf in "$USB_BOOT/loader/entries"/*.conf; do | |
# Comment out the existing root= option | |
sed -i 's/^options.*root=/#&/' "$conf" | |
# Check if root is on LUKS | |
if [[ $ROOT_PARTITION == LUKS:* ]]; then | |
LUKS_DEVICE=${ROOT_PARTITION#LUKS:} | |
LVM_ROOT=$(findmnt -n -o SOURCE /) | |
VG_NAME=$(lvs --noheadings -o vg_name $LVM_ROOT | tr -d '[:space:]') | |
# Add LUKS and LVM options | |
echo "options cryptdevice=$LUKS_DEVICE:cryptlvm root=/dev/$VG_NAME/root rd.luks.name=$(basename $LUKS_DEVICE)=cryptlvm rw add_efi_memmap" >> "$conf" | |
elif [[ $ROOT_PARTITION == LVM:* ]]; then | |
LVM_ROOT=${ROOT_PARTITION#LVM:} | |
echo "options root=$LVM_ROOT rw add_efi_memmap" >> "$conf" | |
else | |
# Add a new options line with the specified root partition | |
echo "options root=$ROOT_PARTITION rw add_efi_memmap" >> "$conf" | |
fi | |
# Add a note about this being a backup boot option | |
echo "# This is a backup boot option created on $(date +%Y-%m-%d)." >> "$conf" | |
echo "# Root partition: $ROOT_PARTITION" >> "$conf" | |
done | |
echo "Customizing bootloader entry name…" | |
echo "timeout 3" > "$USB_BOOT/loader/loader.conf" | |
echo "default @saved" >> "$USB_BOOT/loader/loader.conf" | |
echo "console-mode max" >> "$USB_BOOT/loader/loader.conf" | |
echo "editor no" >> "$USB_BOOT/loader/loader.conf" | |
echo "title $ENTRY_NAME" >> "$USB_BOOT/loader/loader.conf" | |
# Set proper permissions for random seed file | |
chmod 700 "$USB_BOOT/loader" | |
chmod 600 "$USB_BOOT/loader/random-seed" | |
# Unmount partition | |
echo "Unmounting partition…" | |
umount "$USB_BOOT" | |
rmdir "$USB_BOOT" | |
sync | |
echo "Backup completed. You can now use this USB drive as a backup boot device." | |
echo "The boot menu entry will be named '$ENTRY_NAME'." | |
echo "The root partition is set to $ROOT_PARTITION. If you need to change this, you can edit the boot entries manually." | |
echo "Please test the USB drive to ensure it boots correctly before relying on it as a backup." | |
echo |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Arch Linux Boot Backup to USB Script
This Bash script creates a bootable USB drive with a backup of your current Arch Linux boot environment using systemd-boot. It's designed to work with various system configurations, including LUKS encryption and LVM setups.
Key features:
Usage: Run as root. The script will guide you through the process, prompting for necessary information and confirmations.
Note: Always test the created USB drive to ensure it boots correctly before relying on it as a backup.