Skip to content

Instantly share code, notes, and snippets.

@bkuri
Last active September 6, 2024 10:18
Show Gist options
  • Save bkuri/804813969fe0919bf3b8ce3f4e3e6fc9 to your computer and use it in GitHub Desktop.
Save bkuri/804813969fe0919bf3b8ce3f4e3e6fc9 to your computer and use it in GitHub Desktop.
Arch Linux Boot Backup to USB Script
#!/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
@bkuri
Copy link
Author

bkuri commented Sep 6, 2024

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:

  • Backs up existing USB contents before formatting
  • Handles partitioned and partitionless USB drives
  • Supports LUKS encrypted and LVM root partitions
  • Checks for adequate USB drive space
  • Copies current boot files and initramfs
  • Installs and configures systemd-boot on the USB drive
  • Customizes bootloader entry for easy identification

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment