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
  • 910
  • 9
  • 29
  • 57

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,106
  • 1
  • 18
  • 46
  • Hi emk2203, thanks, that seems very useful. Does this command work on Ubuntu though when the Btrfs subvolume is named `@` and not by default `/`? Also, do you know if a timestamp is added automatically or how will the snapshots be differentiated from each other when they're all labeled "syschanges"? – Prototype700 May 11 '20 at 10:45
  • The linked webpage has lots of information if you need to learn more. The tag can apply to several snapshots, this makes sure that you can have other snapshots in there and they are unaffected by your wish to store only 3 snapshots of a given tag. The `/` refers to the standard root folder, so I assume with a subvolume named `@` you need to substitute if you want to save this subvolume only. But this depends on the specifics of your system, some quick tries yourself wouldn't hurt. – emk2203 May 11 '20 at 16:44
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
  • 88,764
  • 91
  • 205
  • 313
  • Hi Pilot6, thanks a lot for that explanation. I don't want to be too pedantic, but since this is extremely important to get right and I don't want to leave it up to chance if I misinterpret anything, could you give me an example of a one-line command that would do this in the correct order? Mounting, deleting snapshots, creating snapshots, unmounting and updating, I presume? Let's assume my update-command would be just the `upgrade`-alias, as a variable. Also is there a difference between using `btrfs sub...` and the aforementioned `btrfs-snp` by emk2203? – Prototype700 May 11 '20 at 10:41
  • Why would you want a one line command? It is much more convenient to do it by a script. Anyway you can do it as a long one line command instead. `btrfs-snp` is a custom command created in that script. I will rewrite the answer in the correct order. – Pilot6 May 11 '20 at 10:43
  • @Prototype700 Does it look better. You can create a script to do it as one command. – Pilot6 May 11 '20 at 10:50
  • Sure you can call a script by an alias. Nobody makes this type of things as a one line command directly. – Pilot6 May 11 '20 at 10:53
  • That does look better, I wanted to integrate it into my existing upgrade-alias so that I don't have to switch up anything in my upgrade habits, hence the one-line command. I will look up how to call a script via bash alias. Could you quickly explain what you mean by "incremental backups" in your answer? – Prototype700 May 11 '20 at 10:54
  • 1
    I'll add a script example. – Pilot6 May 11 '20 at 10:58
  • Thank you, that's very clear. I think this is the most simple solution. I just have one more question: Could you elaborate shortly on what the significance of mounting the partition first is? `/dev/sda1` in this case being the system root-partition. What if there is another Btrfs-partition mounted in `/mnt`, say, a regular data-partition? Since the path isn't specified, can there be any issues? – Prototype700 May 18 '20 at 11:40
  • It is easy. You need to temporarily mount the root of the btrfs filesystem somewhere. `/mnt` is an example. You can mount it anywhere you like. – Pilot6 May 18 '20 at 11:46
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
  • 998
  • 6
  • 9
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.

Akib Azmain Turja
  • 1,022
  • 1
  • 12
  • 28
  • looks good .. it might be better to delete the old snapshots after creating the new one if you care to always have at least a single snapshot at a time.. and the delete would become more involved in this case – bodrin Mar 19 '21 at 11:45