Skip to content

Instantly share code, notes, and snippets.

@kidpixo
Last active June 7, 2024 10:21
Show Gist options
  • Save kidpixo/e3d74a9cbb9bb56865aa0cf6ddcb76f2 to your computer and use it in GitHub Desktop.
Save kidpixo/e3d74a9cbb9bb56865aa0cf6ddcb76f2 to your computer and use it in GitHub Desktop.
Script to create and handle rsync backup on crypted external usb as LVM on LUKS
#!/bin/bash
backup_check_disk() {
########################################################################
# Function to check the current external disk
#
# This function iterates over a list of paths of possible external disks
# and checks if each path exists. It returns the basename of the first
# path that exists.
#
# Returns:
# The basename of the first existing disk path, or an empty string if
# no path exists.
#
# Example usage:
# current_disk=$(backup_check_disk)
# echo "Current Disk: $current_disk"
########################################################################
# Array of possible external disk paths
local -a EXTERNAL_DISK_PATHS=(
# Black Toshiba 2.5" external disk
"/dev/disk/by-id/usb-TOSHIBA_EXTERNAL_USB_20231120008590F-0:0"
# 3.5" Toshiba external disk at work (dead?)
"/dev/disk/by-id/usb-TOSHIBA_External_USB_3.0_20151012015818-0:0"
# Blue Toshiba 2.5" external disk
"/dev/disk/by-id/usb-TOSHIBA_External_USB_3.0_20170317010805F-0:0"
# Black WD Elements 2.5", Saturn, 28.05.2024
"/dev/disk/by-id/usb-WD_Elements_2620_5758323241373335584E3555-0:0"
)
local CURRENT_DISK=""
# Iterate over each disk path
for path in "${EXTERNAL_DISK_PATHS[@]}"; do
# Check if the path exists
if [[ -e "$path" ]]; then
# Set the current disk to the basename of the path
local CURRENT_DISK=$(basename "$path")
# Break the loop after the first existing disk is found
break
fi
done
# Return the current disk or an empty string if no disk is found
echo "$CURRENT_DISK"
}
backup_mount_external_disks() {
########################################################################
# Function to mount the current external disk
#
# This function checks the current external disk and mounts it to the
# specified mount point.
#
# Returns:
# None
#
# Example usage:
# backup_mount_external_disks
#########################################################################
# Check the current external disk
local CURRENT_DISK=$(backup_check_disk)
# If the disk is found, mount it
if [ -n "$CURRENT_DISK" ]; then
echo "Current Disk : $CURRENT_DISK"
echo "Decrypt $CURRENT_DISK-part2"
# add this only if encrypted
# sudo cryptsetup isLuks "/dev/disk/by-id/$CURRENT_DISK"-part1 && echo "LUKS Encrypted" || echo "NOT Encrypted"
sudo cryptsetup open --type luks "/dev/disk/by-id/$CURRENT_DISK"-part2 cryptlvm_backup
echo "Activate LVM volume_backup (root,home,swap)"
sudo vgchange -a y volume_backup
echo "Mount /dev/volume_backup/root to /mnt/backup"
sudo mount /dev/volume_backup/root /mnt/backup
echo "Mount /dev/volume_backup/home to /mnt/backup/home"
sudo mount /dev/volume_backup/home /mnt/backup/home
echo "Mount $CURRENT_DISK-part1 to /mnt/backup/boot"
sudo mount "/dev/disk/by-id/$CURRENT_DISK"-part1 /mnt/backup/boot
else
echo "NO External Disk Present : STOPPING"
fi
}
backup_close_external_disks() {
#######################################################################
# Function to close the current external disk
#
# This function checks the current external disk and unmounts it from the
# specified mount point. It also deactivates the LVM volume group and
# closes the encrypted partition.
#
# Returns:
# None
#
# Example usage:
# backup_close_external_disks
#######################################################################
# Check the current external disk
local CURRENT_DISK=$(backup_check_disk)
# If the disk is found, unmount it
if [ -n "$CURRENT_DISK" ]; then
echo "Current Disk : $CURRENT_DISK"
echo "Umount everything under /mnt/backup"
# Unmount everything under /mnt/backup
sudo umount -R /mnt/backup
echo "Deactivate volume_backup"
# Deactivate the volume group
sudo vgchange -an volume_backup
echo "Close encrypted partition"
# Close the encrypted partition
sudo cryptsetup close cryptlvm_backup
else
# If no external disk is present, print a message and stop
echo "NO External Disk Present : STOPPING"
fi
}
### DEBUG : I am moving this to functions, it is used when you disconnect the disk and have dangling lvm/crypt volumes that won't close
# # list device-mapper devices: useful if cryptdevices are stuck (LVM not closed etc)
# sudo dmsetup table
# cryptlvm: 0 3905945600 ......
# volume-home: 0 3419340800 linear 254:0 486604799
# volume-root: 0 419430400 linear 254:0 67174399
# volume-swap: 0 67108864 linear 254:0 65535
#
# # info on cryptlvm_backup
# sudo cryptsetup --verbose --debug luksDump cryptlvm_backup
#
# # remove device-mapper
# sudo dmsetup remove volume_backup-{root,home,swap}
# # close the cryptlvm_backup device
# sudo cryptsetup close cryptlvm_backup
backup_rsync(){
#######################################################################
# Function to perform an rsync backup of the system to an external disk.
# This function checks if the desired mount points are present and if the
# external disk is connected. If both conditions are met, it performs the
# backup using rsync. Otherwise, it prints a message and stops.
#
# This function does not take any parameters.
#
# This function does not return any value
#######################################################################
# Define color codes for printing messages
local green='\033[0;32m' # Green color code
local red='\033[0;31m' # Red color code
local reset='\033[0m' # Reset color code
# Get the current disk
local CURRENT_DISK=$(backup_check_disk)
echo "Current Disk : $CURRENT_DISK"
# Check if the target mount points are present
if [[ $(backup_check_mountpoints) -eq 0 ]] && [[ -n "$CURRENT_DISK" ]] ; then
echo -e "${green}All desired mount points found!${reset}"
echo "Going on with rsync"
# Sync the /boot directory in place
echo sudo rsync -aAXHl --inplace --delete --info=progress2 --human-readable --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} /boot/ /mnt/backup/boot
sudo rsync -aAXHl --inplace --delete --info=progress2 --human-readable --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} /boot/ /mnt/backup/boot
# Copy local backuped systemd-boot entries and fstab to backup disk
echo sudo cp $CURRENT_DISK/boot/loader/entries/*conf /mnt/backup/boot/loader/entries/
sudo cp $CURRENT_DISK/boot/loader/entries/*conf /mnt/backup/boot/loader/entries/
echo sudo cp $CURRENT_DISK/etc/fstab /mnt/backup/etc/
sudo cp $CURRENT_DISK/etc/fstab /mnt/backup/etc/
# Sync everything BUT /boot to the current disk
echo sudo rsync -aAXHl --delete --info=progress2 --human-readable --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found","/boot"} / /mnt/backup/
sudo rsync -aAXHl --delete --info=progress2 --human-readable --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found","/boot"} / /mnt/backup/
else
echo "Some desired mount points are missing!"
echo "List what is present: "
backup_status
fi
}
backup_check_mountpoints() {
#######################################################################
# Function to check if the desired mount points are present
# This function checks if all desired mount points are present in the /proc/mounts file
# and returns 0 if all mounts are found, 1 otherwise
#
# This function does not take any parameters.
#
# This function does not return any value
#######################################################################
# Define the desired mount points as an array
# The desired mount points are "/mnt/backup", "/mnt/backup/home" and "/mnt/backup/boot"
local desired_mounts=("/mnt/backup" "/mnt/backup/home" "/mnt/backup/boot")
# Get all mount points targets from /proc/mounts
# The awk command '{print $2}' gets the second column from /proc/mounts file which contains the target mount points
local all_mounts=$(awk '{print $2}' /proc/mounts)
# Check if each desired mount point is present in the all_mounts list
# The '[[ ! $all_mounts =~ $mount_point ]]' checks if the mount point is not present in the all_mounts list
# If a mount point is not found, the function sets the 'found' variable to 1 and breaks the loop
local found=0
for mount_point in "${desired_mounts[@]}"; do
if [[ ! $all_mounts =~ $mount_point ]]; then
found=1
break
fi
done
# Return 0 if all mounts found, 1 otherwise
echo $found
return $found
}
backup_status(){
#######################################################################
# Function to get the status of the backup mount points and the current disk
#
# This function checks if the desired mount points are present in the /proc/mounts file
# and if the current disk is set. It prints the status of each mount point and the current disk value.
#
# This function does not take any parameters.
#
# This function does not return any value.
#######################################################################
# Get the current disk
local CURRENT_DISK=$(backup_check_disk)
# Define the desired mount points as an array
# The desired mount points are "/mnt/backup", "/mnt/backup/home" and "/mnt/backup/boot"
local desired_mounts=("/mnt/backup" "/mnt/backup/home" "/mnt/backup/boot")
# Define color codes for printing messages
local green='\033[0;32m' # Green color code
local red='\033[0;31m' # Red color code
local reset='\033[0m' # Reset color code
lsblk -fA
echo
# Loop through desired mount points
for mount_point in "${desired_mounts[@]}"; do
# Check if mount point is present in /proc/mounts
if grep -q "$mount_point" /proc/mounts; then
# Print message if mount point is mounted
echo -e "${green}'$mount_point' is mounted.${reset}"
else
# Print message if mount point is not mounted
echo -e "${red}'$mount_point' is not mounted.${reset}"
fi
done
# Check if the current disk value is empty
if [ -e "$CURRENT_DISK" ]; then
# Print message if current disk value is not empty
echo -e "${green}CURRENT_DISK value is '${CURRENT_DISK}'${reset}"
else
# Print message if current disk value is empty
echo -e "${red}CURRENT_DISK is empty.${reset}"
fi
}
#### SHORT ALIASES
alias b_check_disk='backup_check_disk'
alias b_mount_external_disks='backup_mount_external_disks'
alias b_close_external_disks='backup_close_external_disks'
alias b_rsync='backup_rsync'
alias b_status='backup_status'
_backup_completion() {
# Get a list of all functions starting with "backup_" (excluding this function)
local functions=( $(compgen -A function "${@:1}" | grep -E '^[backup_|b_]') )
# Add the function names to the completion list (COMPREPLY)
COMPREPLY=("${functions[@]}")
}
# Register the completion function for commands starting with "backup_"
complete -F _backup_completion backup_
#!/bin/bash
#### LINES TESTED
# mostly from [LVM on LUKS Arch installation with systemd-boot](https://gist.github.com/OdinsPlasmaRifle/e16700b83624ff44316f87d9cdbb5c94)
#
# format new disk as boot + LVM
sudo parted --script /dev/sdb \
mklabel gpt \
mkpart primary fat3 21MiB 489MiB \
set 1 esp on \
mkpart primary 489MiB 100% \
set 2 lvm on;
# print the new paritition table
sudo parted -s /dev/sdb print;
# create filesystem on parition
sudo mkfs.fat -F32 /dev/sdb1
sudo mkfs.ext4 /dev/sdb2
############## Create linux LVM on second parition
sudo pvcreate /dev/sdb2
sudo vgcreate volume_backup /dev/sdb2
###### create CRYPTed parition + on linux LVM
sudo cryptsetup luksFormat /dev/sdb2
# Open the disk with the password set above:
sudo cryptsetup open --type luks /dev/sdb2 cryptlvm_backup
# Check the lvm disk exists:
ls /dev/mapper/cryptlvm_backup
sudo pvcreate /dev/mapper/cryptlvm_backup
# Create a volume group:
sudo vgcreate volume_backup /dev/mapper/cryptlvm_backup
###### CRYPT + LVM ###################################################
# create logical partitions:
sudo lvcreate -L32G volume_backup -n swap
sudo lvcreate -L200G volume_backup -n root
sudo lvcreate -l 99%FREE volume_backup -n home
# format file system on logical partitions:
sudo mkfs.ext4 /dev/volume_backup/root
sudo mkfs.ext4 /dev/volume_backup/home
sudo mkswap /dev/volume_backup/swap
# show mounted
lsblk -f
# decrypt new toshiba
#sudo cryptsetup open --type luks /dev/disk/by-id/usb-TOSHIBA_EXTERNAL_USB_20231120008590F-0\:0-part2 cryptlvm_backup
# activate volume
sudo vgchange -a y volume_backup
# activate logical volumes
sudo lvchange -a y volume_backup/root
sudo lvchange -a y volume_backup/home
sudo lvchange -a y volume_backup/swap
sudo mkdir -p /mnt/swap_backup
# # those mountpoints should already exist
# # they exists for sure on the original system mounted under:
# disk
# ├── swap # swap lvm partiton /dev/volume/swap |
# └── / # root lvm partiton /dev/volume/root |- cryptsetup luks2 physical partition
# ├── /home # home lvm partiton /dev/volume/home |
# └── /boot # boot physical partition
#
# sudo mkdir -p /mnt/boot/backup
# sudo mkdir -p /mnt/backup/home
# create a /home in the external backup root
sudo mount /dev/volume_backup/root /mnt/backup
if [ ! -d /mnt/backup/home ]; then
sudo mkdir -p /mnt/backup/home;
fi
# create a /boot in the external backup root
sudo mount /dev/volume_backup/home /mnt/backup/home
# generic device
if [ ! -d /mnt/backup/boot ]; then
sudo mkdir -p /mnt/backup/boot;
fi
# sudo mount /dev/sdb1 /mnt/backup/boot
# # small toshiba old
# sudo mount /dev/disk/by-id/usb-TOSHIBA_External_USB_3.0_20170317010805F-0\:0-part1 /mnt/backup/boot
# sudo mount /dev/disk/by-uuid/3545-FC05 /mnt/backup/boot
# # small toshiba new
# sudo mount /dev/disk/by-id/usb-TOSHIBA_EXTERNAL_USB_20231120008590F-0:0-part1 /mnt/backup/boot
# sudo mount /dev/disk/by-uuid/C379-D5DF /mnt/backup/boot
#
# # sudo mount /dev/sdb1 /mnt/boot_backup
# # sudo swapon /dev/volume_backup/swap
#################
# close
#
# umount everything
sudo umount -R /mnt/backup
# deactivate volume
sudo vgchange -an volume_backup
# close encrypted partition
sudo cryptsetup close cryptlvm_backup
####### WARNING : WIP!!!! REFRACTORING
# Function to display available disks and prompt the user to select a disk
select_disk() {
local disks=$(lsblk --output NAME --noheadings)
local options=()
for disk in $disks; do
options+=("$disk")
done
echo "Available disks:"
select disk_name in "${options[@]}"; do
if [[ -n $disk_name ]]; then
echo "Selected disk: $disk_name"
echo "/dev/$disk_name"
break
else
echo "Invalid selection. Please try again."
fi
done
}
# Function to partition the new disk
partition_new_disk() {
local disk=$1
# Partition the new disk as boot + LVM
sudo parted --script "$disk" \
mklabel gpt \
mkpart primary 1MiB 512MiB \
set 1 esp on \
mkpart primary 512MiB 100% \
quit
# Create physical volumes for root, home, and swap
sudo pvcreate "$disk"2
# Create volume group for the new disk
sudo vgcreate volume_backup "$disk"2
# Create logical volumes for root, home, and swap
sudo lvcreate -L32G volume_backup -n swap
sudo lvcreate -L200G volume_backup -n root
sudo lvcreate -l 99%FREE volume_backup -n home
}
# Function to format the new disk
format_new_disk() {
local disk=$1
# Format the new disk
sudo mkfs.ext4 "$disk"volume_backup/root
sudo mkfs.ext4 "$disk"volume_backup/home
sudo mkswap "$disk"volume_backup/swap
}
# Function to mount the new disk
mount_new_disk() {
local disk=$1
# Create mount points for the new disk
sudo mkdir -p /mnt/root /mnt/home /mnt/swap
# Mount the new disk
sudo mount "$disk"volume_backup/root /mnt/root
sudo mount "$disk"volume_backup/home /mnt/home
sudo mount "$disk"volume_backup/swap /mnt/swap
sudo swapon "$disk"volume_backup/swap
}
# Function to copy files from the old disk to the new disk
copy_files() {
# Copy files from the old disk to the new disk
sudo rsync -aAX /mnt/old_root/ /mnt/root/
sudo rsync -aAX /mnt/old_home/ /mnt/home/
}
# Function to unmount the new disk
unmount_new_disk() {
local disk=$1
# Unmount the new disk
sudo umount /mnt/root
sudo umount /mnt/home
sudo umount /mnt/swap
}
# Function to update fstab
update_fstab() {
local disk=$1
# Update fstab with the UUID of the new disk
sudo blkid | grep volume_backup | awk '{print $2}' | sed 's/"//g' | sudo tee -a /etc/fstab
}
# Call the functions in the desired order
selected_disk=$(select_disk)
partition_new_disk "$selected_disk"
format_new_disk "$selected_disk"
mount_new_disk "$selected_disk"
copy_files
unmount_new_disk "$selected_disk"
update_fstab "$selected_disk"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment