User:Rdeckard/Btrfs - Tips and tricks

From ArchWiki

Snapshots

Automatic snapshots on each boot

It is possible to automatically save the then current state of a btrfs subvolume during system boot. Two files are needed to accomplish this, a script which snapshots btrfs subvolumes and a systemd unit file to run the script during the boot process.

Requirements

The root filesystem must reside on its own subvolume with the toplevel subvolume as its parent, and the toplevel (id=5) subvolume must be accessible by mounting it at some mount point, for example at /run/btrfs-root. In this example the layout is as follows:

subvolid=5 (/dev/sdaX, mounted as /run/btrfs-toplevel)
   |
   ├── @ (mounted as /)
   |       |
   |       ├── /bin (directory)
   |       |
   |       ├── /home (mounted @home subvolume)
   |       |
   |       ├── /usr (directory)
   |       |
   |       ├── /var/cache/pacman/pkg (nested subvolume)
   |       |
   |       ├── ... (other directories and nested subvolumes)
   |
   ├── @home (mounted as /home)
   |
   ├── @successful_boot (used in the script below)
   |
   └── @... (additional subvolumes you wish to use as mount points)

https://wiki.archlinux.org/index.php/Btrfs_-_Tips_and_tricks#Automatic_snapshots_on_each_boot

Snapshot script

An example script to snapshot all subvolumes is listed further below. The script will automatically detects subvolumes mounted or nested under / and creates snapshots of them under a directory specified by the caller.

The script deletes all snapshots from the state the system was in at last boot, makes new snapshots, and alters the /etc/fstab file of the snapshot of the root subvolume to allow it to be booted without manual configuration.

When called like this ...

# bash /usr/local/bin/snapshot_current_system_state.sh '/run/btrfs-toplevel' '@' '/run/btrfs/top-level/last_successful_boot'

the following structure will result:

# btrfs subvolume list '/'
ID 257 gen 1134 top level 5 path @
ID 258 gen 1135 top level 257 path @/var/cache/pacman/pkg
ID 260 gen 1137 top level 5 path @home
ID 277 gen 1128 top level 5 path @successful_boot/@
ID 280 gen 1130 top level 5 path @successful_boot/@home
ID 281 gen 1131 top level 5 path @successful-boot/@/var/cache/pacman/pkg

Note that var, opt and home are subvolumes created by the caller in this example and mounted under '/'. The script detected and snapshotted them automatically. The resulting directory structure is:

/run/btrfs-toplevel/@
/run/btrfs-toplevel/@/var/cache/pacman/pkg
/run/btrfs-toplevel/@home
/run/btrfs-toplevel/@successful_boot/@
/run/btrfs-toplevel/@successful_boot/@/var/cache/pacman/pkg
/run/btrfs-toplevel/@successful_boot/@home

The script below takes 3 parameters:

  1. The absolute path of the btrfs toplevel subvolume, for example, /run/btrfs-toplevel.
  2. The name of the btrfs root subvolume as specified in /etc/fstab, for example, @,
  3. The absolute path where the newly created snapshots will reside under the btrfs toplevel, for example /run/btrfs-toplevel/@successful_boot.
Warning: This script will delete all snapshots of the same names as the regular subvolume names in the path its third parameter is pointing to. Be careful that the 3rd parameter is not pointing at the place where your subvolumes reside in --- in this example, /run/btrfs-toplevel/ as the 3rd parameter would be incorrect and lead to data loss.
/usr/local/bin/snapshot_current_system_state.sh
#!/bin/bash

# example call: # ./snapshot_current_system_state '/run/btrfs-toplevel' '@' '/run/btrfs-toplevel/successful_boot' 

if [[ $EUID != 0 ]]; then
    echo "This script must be run as root."
    exit 0
fi

if [[ $# -ne 3 ]]; then
    echo "Usage:"
    echo
    echo "    $(basename $0) toplevel_path root_subvol_name output_path"
    echo 
    echo -e "    toplevel_path\tThe path of the mounted toplevel subvolume. For example, '/run/btrfs-toplevel'."
    echo -e "    root_subvol_name\tThe name of the btrfs root volume as specified in /etc/fstab. For exmaple, '@'."
    echo -e "    output_path\t\tAbsolute path where the newly created snapshots will reside. For example, '/run/btrfs-toplevel/successful_boot'."
    echo
    echo "    CAUTION: This script will delete all snapshots of the same name in 'output_path'."
    exit 0
fi

declare -r btrfs_toplevel=$(readlink -f $1); shift
declare -r root_subvolume_path=$1; shift
declare -r output_path=$(readlink -f $1)

if [[ "$btrfs_toplevel" == "$output_path" ]]; then
    echo "ERROR! You have set the output directory to be the same as your toplevel btrfs directory. Refusing to overwrite your toplevel subvolumes!"
    exit 1
fi

exit 0

if [[ -e "/SNAPSHOT-TIMESTAMP" ]]; then
    echo "You're booted into a previous snapshot. Refusing to take snapshots."
    exit 0
fi

# anti recursive snapshots
for subvolume in $(btrfs subvolume list '/' | awk '{print $NF}'); do

    path_to_snapshot="$output_path/$subvolume"
    [[ -d "${path_to_snapshot}" ]] && btrfs subvolume delete "${path_to_snapshot}"

done

subvolumes="$(btrfs subvolume list '/' | awk '{print $NF}')" # rescan

for subvolume in $subvolumes; do

    snapshot_directory="$output_path/$subvolume"

    [[ ! -d "$snapshot_directory" ]] && mkdir -p "$snapshot_directory" 
 
    btrfs subvolume snapshot "$btrfs_toplevel/$subvolume" "$snapshot_directory"

    if [[ "$subvolume" == "$root_subvolume_path" ]]; then

        timestamp="$(date +%d.%m.%Y-%H:%M:%S)"
        echo -e "Arch Linux --- state at last successful boot (nonpersistent) [$timestamp]\n" > "$output_path/$root_subvolume_path/etc/issue"
        echo "$timestamp" > "$output_path/$root_subvolume_path/SNAPSHOT-TIMESTAMP"
        sed_output_path="$(echo $output_path | sed --posix --regexp-extended 's/\//\\\//g')"
        for subvolumeX in $(echo $subvolumes | /usr/bin/sed --posix --regexp-extended 's/\//\\\//g'); do
            sed --posix --regexp-extended "s/subvol=$subvolumeX/subvol=$sed_output_path\/$subvolumeX/g" --in-place "$output_path/$root_subvolume_path/etc/fstab"
        done

    fi

done

sync

Systemd unit file

The following systemd unit file will run the script every time the system manages to successfully boot into multi-user.target:

[Unit]
Description=Takes a snapshot of each btrfs subvolume mounted under / after multi-user.target has been reached.
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/snapshot_current_system_state '/run/btrfs-toplevel' '@' '/run/btrfs-toplevel/@successful_boot'

[Install]
WantedBy=multi-user.target

Enable snapshot_current_system_state_upon_boot.service to have the script run on startup.

Booting into snapshots

In order to boot into a subvolume the rootflags=subvol= option has to be used on the kernel line. The subvol= mount options in /etc/fstab of the snapshot to boot into also have to be specified correctly.

GRUB

You can manually create a GRUB#GNU/Linux menu entry with the rootflags=subvol= argument. Alternatively, you can automatically populate your GRUB menu with btrfs snapshots when regenerating the GRUB configuration file by using grub-btrfsAUR or grub-btrfs-gitAUR.