6

I've installed Ubuntu 20.04 on a Btrfs root-partition for its snapshot functionality.

To keep it as simple as possible, I would like to integrate the creation of a Btrfs snapshot into my upgrade-alias command, which currently looks like this:

sudo apt update && sudo apt upgrade -y && sudo flatpak update -y && sudo snap refresh

How would I best add a snapshot before the updates so I can roll back if anything goes wrong?

Is there also a possiblity to remove older snapshots at the same time? (My root-partition is filled less than 10%, so I could copy my entire system multiple times, but I suppose it will fill up quickly with weekly updates?)

Prototype700
  • 1,120

4 Answers4

2

I would build on the nice script by Ignacio Nunez Hernanz:

#!/bin/bash

#
# Script that creates BTRFS snapshots, manually or from cron
#
# Usage:
#          sudo btrfs-snp  <dir> (<tag>) (<limit>) (<seconds>) (<destdir>)
#
# Copyleft 2017 by Ignacio Nunez Hernanz <nacho _a_t_ ownyourbits _d_o_t_ com>
# GPL licensed (see end of file) * Use at your own risk!
#
# Based on btrfs-snap by Birger Monsen
#
# More at https://ownyourbits.com
#

function btrfs-snp()
{
  local   BIN="${0##*/}"
  local   DIR="${1}"
  local   TAG="${2:-snapshot}"
  local LIMIT="${3:-0}"
  local  TIME="${4:-0}"
  local   DST="${5:-.snapshots}"

  ## usage
  [[ "$*" == "" ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]] && {
echo "Usage: $BIN <dir> (<tag>) (<limit>) (<seconds>) (<destdir>)

  dir     │ create snapshot of <dir>
  tag     │ name the snapshot <tag>_<timestamp>
  limit   │ keep <limit> snapshots with this tag. 0 to disable
  seconds │ don't create snapshots before <seconds> have passed from last with this tag. 0 to disable
  destdir │ store snapshot in <destdir>, relative to <dir>

Cron example: Hourly snapshot for one day, daily for one week, weekly for one month, and monthly for one year.

cat > /etc/cron.hourly/$BIN <<EOF
#!/bin/bash
/usr/local/sbin/$BIN /home hourly  24 3600
/usr/local/sbin/$BIN /home daily    7 86400
/usr/local/sbin/$BIN /home weekly   4 604800
/usr/local/sbin/$BIN /     weekly   4 604800
/usr/local/sbin/$BIN /home monthly 12 2592000
EOF
chmod +x /etc/cron.hourly/$BIN"
return 0
  }

  ## checks
  local SNAPSHOT=${TAG}_$( date +%F_%H%M%S )

  [[ ${EUID} -ne 0  ]] && { echo "Must be run as root. Try 'sudo $BIN'" ; return 1; }
  [[ -d "$SNAPSHOT" ]] && { echo "$SNAPSHOT already exists"             ; return 1; }

  mount -t btrfs | cut -d' ' -f3 | grep -q "^${DIR}$" || {
btrfs subvolume show "$DIR" | grep -q "${DIR}$" || {
  echo "$DIR is not a BTRFS mountpoint or snapshot"
  return 1
}
  }

  DST="$DIR/$DST"
  mkdir -p "$DST"
  local SNAPS=( $( btrfs subvolume list -s --sort=gen "$DST" | awk '{ print $14 }' | grep "${TAG}_" ) )

  ## check time of the last snapshot for this tag
  [[ "$TIME" != 0 ]] && [[ "${#SNAPS[@]}" != 0 ]] && {
local LATEST=$( sed -r "s|.*_(.*_.*)|\\1|;s|_([0-9]{2})([0-9]{2})([0-9]{2})| \\1:\\2:\\3|" <<< "${SNAPS[-1]}" )
LATEST=$( date +%s -d "$LATEST" ) || return 1

[[ $(( LATEST + TIME )) -gt $( date +%s ) ]] && { echo "No new snapshot needed for $TAG"; return 0; }
  }

  ## do it
  btrfs subvolume snapshot -r "$DIR" "$DST/$SNAPSHOT" || return 1

  ## prune older backups
  [[ "$LIMIT" != 0 ]] && \
  [[ ${#SNAPS[@]} -ge $LIMIT ]] && \
echo "Pruning old snapshots..." && \
for (( i=0; i <= $(( ${#SNAPS[@]} - LIMIT )); i++ )); do
  btrfs subvolume delete "$DIR/${SNAPS[$i]}"
done

  echo "snapshot $SNAPSHOT generated"
}

btrfs-snp "$@"

# License
#
# This script is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This script is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this script; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330,
# Boston, MA  02111-1307  USA

Relevant usage info:

# btrfs-snp
Usage: btrfs-snp <dir> (<tag>) (<limit>) (<seconds>) (<destdir>)

dir     │ create snapshot of <dir>
tag     │ name the snapshot <tag>_<timestamp>
limit   │ keep <limit> snapshots with this tag. 0 to disable
seconds │ don't create snapshots before <seconds> have passed from last with this tag. 0 to disable
destdir │ store snapshot in <destdir>, relative to <dir>

Your upgrade alias would need to look like this:

btrfs-snp / syschanges 3 600 && ... which generates a snapshot with the tag syschanges in /.snapshots, but not if there is already one in the last 5 minutes, and keeps maximum 3 of these.

This gives you a 5-minute window to do repeat operations without cluttering, for example, if you want to install from different repos or ppas in one install step, not only upgrades.

Then, you can use and restore these snapshots as per btrfs best practice.

emk2203
  • 4,393
  • 1
  • 26
  • 52
1

It is quite easy to make a snapshot in btrfs.

First mount your partitition containing the btrfs filesystem to e.g. /mnt. We are assuming it is /dev/sda1.

sudo mount /dev/sda1 /mnt
cd /mnt

If you have a standard Ubuntu install with / at @ and /home at @home, running ls will show two items: @ and @home.

Also if you previosly created snapshots, they will be shown there too.

To create snapshots of your / and /home , run the command:

sudo btrfs sub snap @ @-BACKUP && sudo btrfs sub snap @home @home-BACKUP

If you want to remove existing backups before you create a new one the command will be:

sudo btrfs sub del @-BACKUP && sudo btrfs sub del @home-BACKUP

As simple as that.

After you finish with that unmount your partition from /mnt by:

sudo umount /mnt

In addition I can add that you can create snapshots with timestamp or do incremental backups. But it is a bit out of scope of the question.

You can combine these commands into a text file like backup.sh.

Example:

#!/bin/sh
mount /dev/sda1 /mnt
cd /mnt
[ -d @-BACKUP ] && sudo btrfs sub del @-BACKUP #Checks is backup exists and deletes it
[ -d @home-BACKUP ] && sudo btrfs sub del @home-BACKUP
btrfs sub snap @ @-BACKUP
btrfs sub snap @home @home-BACKUP
cd /
umount /mnt

The script should be run with sudo.

Pilot6
  • 92,041
1

I have several ideas for you. Pick, choose and combine.

  1. Purpose-built, off-the-shelf software like apt-btrfs-snapshot or etckeeper that does this automatically behind the scenes. That's probably the best way going forward.
  2. To replicate a custom version of what the above packages do you hook into apt to run a script after every update. Above packages can of course aid your learning of best practice how this is done and an example of a sophisticated script was given in another answer. But it could be something as simple as btrfs sub snapshot -r /mnt/btrfsroot/@/ /mnt/btrfsroot/snapshots/root-$(date +%y%m%d) as well.
  3. In either case, while there are such hooks for apt/dpkg, there are none for snap and I am not sure about the others. But that should not really be a problem since you run apt first as per your question.
  4. You will end up with many snapshots, so you will also need a way to either manually or automatically remove obsolete snapshots. To give you an idea how this might be done assuming that your automatic snapshots have the following paths /mnt/btrfsroot/snapshots/@*-apthook-YYMMDDHH then you would for example run a cronjob on every 12th of the month as 34 03 12 * * btrfs sub delete /mnt/btrfsroot/snapshots/@*-apthook-$(date --date='15 days ago' +\%y\%m)*. Check the man pages for man 5 crontab and man date for further info.

I hope this gets you going in the right direction. Again, I would suggest simply going with apt-btrfs-snapshot and be done with it. Please be aware that as of now, apt-btrfs-snapshot assumes that your root partition is named @. This is the default for Ubuntu and a number of other distributions.

Feel free to ask follow-up questions in case something is unclear.

PS: Do you understand the difference between / (the root of your running system) and the btrfs-root?

leggewie
  • 1,087
0

You can easily do this with this shell script.

Create a shell script with this content:

# Directory for saving snapshots
SNAPDIR=/snapshots
export SNAPDIR

# Delete snapshots
sudo btrfs subvolume delete /mnt/btrfs/backup_*

# Ask user for the name of snapshot
echo -n "What will be the name of snapshot? "
read SNAPNAME

# Create the snapshot
sudo btrfs subvolume snapshot /mnt/btrfs/ $SNAPDIR/backup_$SNAPNAME

# Check if the snapshot created successfully, if not then exit
if [ $? -ne 0 ]
then
echo "Failed to create snapshot"
exit 1
fi

# Commands to execute after creating snapshot
sudo apt update && sudo apt upgrade -y && sudo flatpak update -y && sudo snap refresh

After creating the file, replace /snapshots with your desired snapshot directory where snapshots will be saved. Then place it anywhere.

Now make it executable by executing:

chmod +x /path/to/shell/script.sh

Now change the command for your upgrade alias so it points the script.

Now running your alias will first delete snapshots with backup_ prefix then it will take a snapshot of the filesystem saved with name starting with backup_ .

Note that when first time run, it can show error. But ignore it as first time when run, there is no backup so there is nothing to delete. Also don't make subvolumes or snapshot with name starting with backup_ at at the directory where backup snapshot will be saved. It will cause that to be deleted when the script is run. Also snapshot will not include files from other snapshots, subvolumes and mounted partitions.