├── Readme.md ├── rsyncbtrfs └── sample.backup /Readme.md: -------------------------------------------------------------------------------- 1 | rsyncbtrfs is a small shell script that does incremental backups under 2 | linux using [rsync][] on [btrfs][] partitions. 3 | 4 | Requirements 5 | ============ 6 | 7 | You need to have [rsync][] and [btrfs-tools][] installed on your system. 8 | 9 | Setup 10 | ===== 11 | 12 | rsyncbtrfs is a standalone shell script. You can just copy it to a 13 | directory in your PATH: 14 | 15 | $ mkdir -p ~/bin 16 | $ curl 'https://raw.githubusercontent.com/oxplot/rsyncbtrfs/master/rsyncbtrfs' > ~/bin/rsyncbtrfs 17 | $ chmod a+x ~/bin/rsyncbtrfs 18 | 19 | Example 20 | ======= 21 | 22 | Best way to demo how to use rsyncbtrfs is with an example. Let's say we 23 | have a btrfs formatted backup partition mounted under `/backup`. We want 24 | to backup `/` and `/home/joe` separately to `/backup`. 25 | 26 | First, we structure the backup partition: 27 | 28 | $ mkdir /backup/sys 29 | $ mkdir /backup/joe 30 | 31 | Next, we need to initialize each backup directory: 32 | 33 | $ rsyncbtrfs init /backup/sys 34 | $ rsyncbtrfs init /backup/joe 35 | 36 | All this does is to create an empty file called `.rsyncbtrfs` under the 37 | given directory. `rsyncbtrfs backup` command aborts if it can't find 38 | `.rsyncbtrfs` file in the destination. Just a safety check. 39 | 40 | Time to backup: 41 | 42 | $ rsyncbtrfs backup / /backup/sys --exclude='/home/**' 43 | $ rsyncbtrfs backup /home/joe /backup/joe 44 | 45 | Simple enough. Just a note on `--exclude` option: any argument after the 46 | destination is passed onto rsync. Here, we're excluding `/home` because 47 | we don't want to duplicate the backup of `/home/joe`. 48 | 49 | After the backup above, this is what `/backup` would look like: 50 | 51 | $ ls /backup/sys 52 | 2014-07-17-13:06:17 cur 53 | $ ls /backup/joe 54 | 2014-07-17-14:09:09 cur 55 | 56 | The timestamped directories are btrfs subvolumes. `cur` is a symlink to 57 | the latest timestamped subvolume, in this case the only one. 58 | 59 | Running the backup again, this is what we get: 60 | 61 | $ ls /backup/sys 62 | 2014-07-17-13:06:17 2014-07-17-18:31:00 cur 63 | $ ls /backup/joe 64 | 2014-07-17-14:09:09 2014-07-17-18:32:11 cur 65 | 66 | Notes 67 | ===== 68 | 69 | rsyncbtrfs creates a new subvolume or snapshots an existing one under a 70 | temp directory in destination, named `.inprog-XXXXX`. It only renames 71 | the subvolume to its final timestamped location on success. Same applies 72 | to updating of the `cur` symlink. If the backup fails for any reason, 73 | nothing changes in the backup directory. rsyncbtrfs tries to cleanup 74 | after itself but worse comes to worst, it might leave the inprog 75 | directory behind, which should be cleaned up, although it's not 76 | necessary. 77 | 78 | You can use `--bind-mount` argument when backing up to instruct 79 | rsyncbtrfs to bind mount the source directory under a temp path. This is 80 | useful when you don't want to backup all the mount points under the 81 | source: 82 | 83 | $ rsyncbtrfs backup --bind-mount / /backup/sys 84 | 85 | To take full advantage of btrfs COW functionality, `--inplace` flag of 86 | rsync is used by default. This flag tells rsync to only write the 87 | updated data in a file instead of creating a new copy and moving it into 88 | place. Using this flag has several (possibly negative) side effects 89 | which you should be aware of. Consult rsync's man page for further 90 | details. 91 | 92 | [rsync]: http://rsync.samba.org/ 93 | [btrfs]: https://btrfs.wiki.kernel.org/index.php/Main_Page 94 | [btrfs-tools]: https://btrfs.wiki.kernel.org/index.php/Manpage/btrfs 95 | -------------------------------------------------------------------------------- /rsyncbtrfs: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # rsyncbtrfs - an rsync/btrfs combo backup system which uses the 3 | # subvolume snapshot facilities of btrfs to perform 4 | # incremental backups. 5 | # Copyright (C) 2014 Mansour Behabadi 6 | # 7 | 8 | usage() { 9 | echo "Usage: rsyncbtrfs {init|backup} ... 10 | 11 | init Initialize a backup directory. 12 | 13 | backup [--bind-mount] [ [ ...]] 14 | Backup src to dst, passing to rsync. 15 | Use --bind-mount to bind-mount under 16 | a temp directory before backing it up. 17 | " 18 | exit 255 19 | } 20 | 21 | case $1 in 22 | 23 | init) 24 | [ $# -ge 2 ] || usage 25 | touch "$2/.rsyncbtrfs" 26 | ;; 27 | 28 | backup) 29 | 30 | shift 1 31 | 32 | BIND_MOUNT=N 33 | if [ "$1" = '--bind-mount' ] 34 | then 35 | BIND_MOUNT=Y 36 | shift 1 37 | fi 38 | 39 | [ $# -ge 2 ] || usage 40 | BACKUP_SRC="$1" 41 | BACKUP_DST="$2" 42 | 43 | shift 2 44 | 45 | # We do a simple check to prevent situations where the backup dst is 46 | # mistyped. 47 | 48 | if [ ! -e "$BACKUP_DST/.rsyncbtrfs" ] 49 | then 50 | echo "dst is not initialized - run 'rsyncbtrfs init $BACKUP_DST'" 51 | exit 254 52 | fi 53 | 54 | TMP_INPROG="`mktemp -d "$BACKUP_DST/.inprog-XXXXXXX"`" 55 | TMP_MOUNT="`mktemp -d --tmpdir rsyncbtrfs-XXXXXXX`" 56 | NEW_SUBVOL="`date +%F-%T`" 57 | 58 | # Cleanup handlers 59 | 60 | trap ' 61 | umount "$TMP_MOUNT" >/dev/null 2>&1 62 | rmdir "$TMP_MOUNT" 63 | btrfs subvolume delete "$TMP_INPROG/vol" >/dev/null 2>&1 64 | rm -f "$TMP_INPROG/cur" 65 | rmdir "$TMP_INPROG" 66 | ' INT QUIT TERM EXIT 67 | 68 | if [ "$BIND_MOUNT" = 'Y' ] 69 | then 70 | mount --bind "$BACKUP_SRC" "$TMP_MOUNT" || exit 253 71 | RSYNC_SRC="$TMP_MOUNT" 72 | else 73 | RSYNC_SRC="$BACKUP_SRC" 74 | fi 75 | 76 | # Decide if the first subvolume is created or the current one is 77 | # snapshotted. 78 | 79 | if [ -L "$BACKUP_DST/cur" ] 80 | then 81 | btrfs subvolume snapshot \ 82 | "$BACKUP_DST/`readlink "$BACKUP_DST/cur"`" \ 83 | "$TMP_INPROG/vol" || exit 253 84 | else 85 | btrfs subvolume create "$TMP_INPROG/vol" || exit 253 86 | fi 87 | 88 | # Do the backup. 89 | 90 | rsync --delete --delete-before --delete-excluded --inplace \ 91 | --no-whole-file -a $@ \ 92 | "$RSYNC_SRC/" "$TMP_INPROG/vol" || exit 253 93 | 94 | # Move the new backup subvolume to its final path 95 | 96 | mv -Tn "$TMP_INPROG/vol" "$BACKUP_DST/$NEW_SUBVOL" 97 | 98 | if [ -d "$TMP_INPROG/vol" ] 99 | then 100 | echo "$NEW_SUBVOL already exists - discarding new backup" 101 | exit 253 102 | fi 103 | 104 | # Point cur symlink to the backup we just made. 105 | 106 | ln -s "$NEW_SUBVOL" "$TMP_INPROG/cur" || exit 253 107 | mv -Tf "$TMP_INPROG/cur" "$BACKUP_DST/cur" || exit 253 108 | 109 | ;; 110 | 111 | *) 112 | usage 113 | ;; 114 | esac 115 | 116 | exit 0 117 | -------------------------------------------------------------------------------- /sample.backup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # To protect backups *most* of the time, we remount the backup partition 4 | # as readwrite only when backing up and put it back to readonly mode 5 | # once we're finished. 6 | 7 | trap " 8 | mount -o remount,ro /backup; 9 | " INT QUIT TERM EXIT 10 | 11 | mount -o remount,rw /backup 12 | 13 | rsyncbtrfs backup --bind-mount / /backup/sys \ 14 | --exclude='/tmp/**' --exclude='/var/lib/mock/**' \ 15 | --exclude='/home/build/**' --exclude='/home/wine/**' \ 16 | --exclude='/var/cache/mock/**' --exclude='/var/tmp/**' \ 17 | --exclude='/var/cache/yum/**' --exclude='/media/**' \ 18 | --exclude='/backup/**' 19 | rsyncbtrfs backup /home/user /backup/home \ 20 | --exclude='/Downloads/**' --exclude='/tmp/**' 21 | rsyncbtrfs backup /boot /backup/boot 22 | --------------------------------------------------------------------------------