Navigation überspringen

Harald Markus Wirth


Seiteninhalt:

Keeping Arch Linux Bootable

Backup of a running system

Requirements:

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.

To Reboot or to Not Reboot?

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
-a
archive mode; equals -rlptgoD (no -H,-A,-X)
-v
increase verbosity (Show each copied file)
-x
don't cross filesystem boundaries (regarding symlinks)
-H
preserve hard links
-A
preserve ACLs (implies -p)
-W
copy files whole (w/o delta-xfer algorithm)
-X
preserve extended attributes
--delete
Remove files from the backup, if they do not exist in the source

Partitioning scheme

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

Preventing changes while the backup is running

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

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:

Halting services

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:

Finding out, who is still writing to disk

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 /
-e
Event
-m
I have no idea, why my script contains this. Do you? The man page has no such entry.
-r
Recursive

Prepare for dual-boot

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

/boot/grub/grub.cfg

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
}

The backup script

In order to automate the whole process, I made a script, that does the following:

#!/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


Content Management:

μCMS α1.6