Since Arch Linux is a bleeding edge rolling release, there are a lot of updates and Arch users will also typically tinker with their system often, which can lead to non-bootable system. Although a broken system may present an opportunity to learn more about your OS, there are situations, where you rather boot up immeadiately in order to continue working on something important, than fixing your computer.
On this page, I try to outline my method of keeping my system bootable by mirroring the root file system and use the computer in a dual boot configuration using GRUB.
You can download the backup script at the bottom of this page.
A simple and reliable backup can be done by booting from a live image on a USB thumb drive, because it is guaranteed, that no process will write to the partition while the backup is being created, which may cause inconsistencies:
rsync -avxHAWX --delete /path/to/mounted/root /path/to/mounted/backup/folder
Rebooting constantly can be quite annoying, so I set out to find a method of doing something like this without having to close all my network connections and programs.
This how-to assumes a single root partition main-root
that contains evertything:
/dev/sda1 | /dev/sda2 | /dev/sda3 |
---|---|---|
swap | main-root | alt-root |
There are two classes of programs, that could write to disk: Services and user programs. My approach is to stop everything before running rsync.
Halting all user processes will freeze X, you won't be able to input anything to your terminal and output of the backup script will not be shown, until the user processes are being continued (that is, when the backup is done). Therefore, the backup script needs to be started from the console.
# pkill -e -STOP -u <user> # Halt all processes of user # pkill -e -CONT -u <user> # Resume all processes of user
This part is easy enough. But what about system programs? You can stop any service with:
Besides user programs, there are some services that update certain files every now and then. To make sure, everything stays clean, we can stop them as usual:
# systemctl stop <service> # Temporarily stop a service # systemctl start <service> # Restart the service
In order to find out, which programs are writing to disk (and therefore what needs to be stopped), I use inotifywatch:
inotifywait will show any files changed on disk. With system services it should be rather easy to figure out, which one wrote something:
inotifywait -e modify -e attrib -e move -e create -e delete -m -r /
In order to boot to the alternate copy of your system, you need to add an entry to GRUB's config file. You also need to change the entry in /etc/fstab of the mirrored system, so that the correct partition will be mounted as /.
Create a first backup of your current root file system, then adjust the fstab settings of your alternative root file system. Find the UUID of your alt-root with:
$ blkid
You can have your grub.cfg generated automatically after you made the initial backup and adjusting fstab. GRUB's os-prober module should find both Arch instances.
I prefer to have a small, clean grub.cfg file, though. Here is an example configuration:
insmod ext2 insmod gzio menuentry "cursor" { linux /vmlinuz-linux root=UUID=<UUID of main-root> rw initrd /intel-ucode.img /initramfs-linux.img } menuentry "cursor-alt" { linux /vmlinuz-linux root=UUID=<UUID of alt-root> rw initrd /intel-ucode.img /initramfs-linux.img }
In order to automate the whole process, I made a script, that does the following:
root
#!/bin/bash ############################################################################### # MIRROR SYSTEM TO ALTERNATE PARTITION ############################################################################### # Base settings exclude_dirs='{"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found","/etc/fstab"}' exclude_notify='(/dev|/proc|/sys|/tmp|/run|/mnt|/media|/lost+found|/data)' halt_users="hmw" # Space delimited list halt_services="vnstat.service httpd.service" # Space delimited list main_fs='main-root' alt_fs='alt-root' # Commands rsync_cmd="rsync -axHAWX --info=progress2 --delete --exclude=$exclude_dirs / /mnt" # Colored output TEXT_GREEN="$(tput setaf 2)" TEXT_YELLOW="$(tput setaf 3)" TEXT_RESET="$(tput sgr0)" sanity_checks() { if [ "$(whoami)" != "root" ] ; then echo "Error: This script must be run as root!" exit 1 fi if [ $DISPLAY ] ; then echo "Error: This script must not be run from an X terminal!" exit 2 fi if [ "$current_device" != "$main_fs" ] && [ "$current_device" != "$alt_fs" ] ; then echo "Error: Volume not recognised: '$current_device'" echo "Expecting '$main_fs' or '$alt_fs'." exit 3 fi } get_current_device() { mount | grep 'on / type' | awk '{ print $1; }' | awk -F '/' '{ print $3 }' } halt_user_processes() { echo "Halting user processes:${TEXT_YELLOW}" echo $halt_users | tr ' ' "\n" | xargs -I % sh -c 'echo -n "%: " ; pkill -e -STOP -u % | wc -l' echo "${TEXT_RESET}" } resume_user_processes() { echo "Resuming user processes:${TEXT_YELLOW}" echo $halt_users | tr ' ' "\n" | xargs -I % sh -c 'echo -n "%: " ; pkill -e -CONT -u % | wc -l' echo "${TEXT_RESET}" } do_backup() { echo "Mounting alternate system partition..." mount -v $target_device /mnt if [ "$?" != "0" ] ; then echo "Error while mounting $target_device, aborting." exit 4 fi echo "About to issue:" echo "${TEXT_YELLOW}${rsync_cmd}${TEXT_RESET}" halt_user_processes echo "Stopping ${TEXT_YELLOW}${halt_services}${TEXT_RESET}" systemctl stop $halt_services echo 999999 > /proc/sys/fs/inotify/max_user_watches inotifywait \ -e modify -e attrib -e move -e create -e delete -m -r / \ --exclude "$exclude_notify" \ > >(ts ' %H:%M:%S') & inotify_pid=$! echo "Wait for watches to be established, then press Enter to continue or Ctrl+C to abort" read tput cuu1 echo $(halt_user_processes) halted_user_processes=true echo "Backing up ${TEXT_BOLD}${TEXT_YELLOW}/${TEXT_RESET}" eval $rsync_cmd umount -v /mnt } cleanup() { kill $inotify_pid > /dev/null 2>&1 [ $halted_user_processes ] && echo $(resume_user_processes) echo "Starting ${TEXT_YELLOW}${halt_services}${TEXT_RESET}" systemctl start $halt_services echo "Done." exit } ############################################################################### # PROGRAM ENTRY POINT ############################################################################### current_device=$(get_current_device); if [ "$current_device" == "$main_fs" ] ; then target_device="$alt_fs" else target_device="$main_fs" fi source_device=/dev/$current_device target_device=/dev/$target_device echo -"${TEXT_GREEN}${TEXT_BOLD}$(basename $0)${TEXT_RESET} - System backup to mirror partition" sanity_checks echo -n "About to mirror the current system " echo -n "${TEXT_YELLOW}${TEXT_BOLD}${source_device}${TEXT_RESET}" echo " --> ${TEXT_YELLOW}${TEXT_BOLD}${target_device}${TEXT_RESET}" trap cleanup SIGINT do_backup cleanup