Initial commit
This commit is contained in:
commit
104a8b8cfb
6 changed files with 162 additions and 0 deletions
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 SunRed
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
39
README.md
Normal file
39
README.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Incremental sync-snap with cleanup
|
||||||
|
|
||||||
|
These are simple scripts I use for incrementally backuping my system running on btrfs whenever I connect my USB drive to my computer(s).
|
||||||
|
|
||||||
|
One could of course also simply use other tools like [btrbk](https://github.com/digint/btrbk) to automatically backup/replicate btrfs snapshots. One would just have to change the commands used to `btrbk run` and let btrbk itself handle the cleanup locally and on the external drive.
|
||||||
|
For a desktop system I still like to be able to manually snapshot or cleanup using [snapper-gui](https://github.com/ricardomv/snapper-gui) and since snap-sync does not automatically prune old snapshots, we need [a simple cleanup script](https://gist.github.com/alanorth/fdaa3f3be16b58822a4a876afbd62604/) that runs after the snap-sync replication.
|
||||||
|
|
||||||
|
Based on this it's also possible to use a network target to simply replicate snapshots onto a NAS or similar.
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
You should have a subvolume present already on your backup drive using something like `snap-sync -c "root home" -d "latest incremental backup"` pointing to `snapshot_root` configured in [clean-snap-sync-external.sh](scripts/clean-snap-sync-external.sh). After first replication snap-sync automatically uses the same subvolume again so that you just have to set the UUID of your external drive you placed your initial snapshot on in the bash scripts in the [scripts](scripts) folder.
|
||||||
|
|
||||||
|
If you are going to enable `snap-sync-cleanup.service`, edit the snapshot root and the amount of snapshots to keep on the external drive in `clean-snap-sync-external.sh` as well.
|
||||||
|
|
||||||
|
Edit both services in the [services](services) folder and edit the path to the scripts. In my case I just keep them on the external drive my replicated snapshots reside on.
|
||||||
|
|
||||||
|
You _also_ have to edit the `.mount` target in all service files to point to your usb drive.
|
||||||
|
You can get the correct mount target by running `systemctl list-units -t mount`.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Place the services in the [services](services) folder in `/etc/systemd/system/`, run `systemctl daemon-reload` and enable each of them:
|
||||||
|
|
||||||
|
`snap-sync.service` for backuping a new snapshot onto your disk
|
||||||
|
|
||||||
|
`snap-sync-cleanup.service` for deleting any snapshots older than the amount you've configured (only runs if `snap-sync.service` succeeded)
|
||||||
|
|
||||||
|
The next time you connect your external drive a snap-sync notification should pop up, starting the replication process.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
|
||||||
|
* Proper config file
|
||||||
|
* setup script
|
||||||
|
* AUR package?
|
6
scripts/backup-snap-sync-incremental.sh
Executable file
6
scripts/backup-snap-sync-incremental.sh
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
readonly backup_uuid="b8f49636-2681-4c28-911e-63c63f16dbc8"
|
||||||
|
snap-sync --UUID $backup_uuid -c "root home" -d "latest incremental backup" -n
|
72
scripts/clean-snap-sync-external.sh
Executable file
72
scripts/clean-snap-sync-external.sh
Executable file
|
@ -0,0 +1,72 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# clean-snap-sync-external.sh v1.0.1 (2021-07-09)
|
||||||
|
#
|
||||||
|
# Changes
|
||||||
|
# -------
|
||||||
|
# 2021-07-09:
|
||||||
|
# - adjust logic to keep latest x, instead of delete oldest x
|
||||||
|
# - make output cleaner (hide btrfs subvolume delete output)
|
||||||
|
#
|
||||||
|
# Inspired by FraYoshi's original
|
||||||
|
# See: https://github.com/wesbarnett/snap-sync/issues/16
|
||||||
|
|
||||||
|
# Change these for your environment
|
||||||
|
readonly backup_uuid="b8f49636-2681-4c28-911e-63c63f16dbc8"
|
||||||
|
readonly snapshot_root="$(findmnt -rn -S UUID=$backup_uuid -o TARGET)/snapshots/$(hostname)"
|
||||||
|
readonly keep_latest=3
|
||||||
|
|
||||||
|
# Don't change these
|
||||||
|
readonly ansi_default="\e[39m"
|
||||||
|
readonly ansi_green="\e[32m"
|
||||||
|
readonly ansi_yellow="\e[33m"
|
||||||
|
|
||||||
|
echo -e "$ansi_green[DEBUG] Keeping the latest $keep_latest snapshots on $snapshot_root.$ansi_default"
|
||||||
|
|
||||||
|
# Use printf to get the filename without the path
|
||||||
|
find /etc/snapper/configs -mindepth 1 -maxdepth 1 -printf "%f\n" | while read config; do
|
||||||
|
echo -e " $ansi_green[DEBUG] Checking $snapshot_root/$config$ansi_default"
|
||||||
|
|
||||||
|
# Make sure the total number of snapshots is more than $keep_latest
|
||||||
|
if [ $(find "$snapshot_root/$config" -mindepth 1 -maxdepth 1 -type d | wc -l) -gt $keep_latest ]; then
|
||||||
|
# Find all snapshots for the current config, sort by snapshot number, and
|
||||||
|
# get the oldest. Note that I would prefer to do this:
|
||||||
|
#
|
||||||
|
# for snapshot in $(ls -1td /mnt/backup/root/*); do
|
||||||
|
#
|
||||||
|
# ... but parsing ls is apparently frowned upon. I will settle on using find
|
||||||
|
# and sorting numerically, since snapshot numbers should be integers.
|
||||||
|
#
|
||||||
|
# See: http://mywiki.wooledge.org/ParsingLs
|
||||||
|
#
|
||||||
|
# Again, we rely on printf to get the snapshot number without the path,
|
||||||
|
# then we sort the snapshot numbers in reverse numerical order, ie:
|
||||||
|
#
|
||||||
|
# 6601 (snapshot 1)
|
||||||
|
# 6600 (snapshot 2)
|
||||||
|
# 6599 (snapshot 3)
|
||||||
|
#
|
||||||
|
# Then we use `tail -n +` with $keep_latest + 1 to skip that many snap-
|
||||||
|
# shots, as tail's `-n +` syntax starts at line 1.
|
||||||
|
find "$snapshot_root/$config" -mindepth 1 -maxdepth 1 -type d -printf "%f\n" | sort -nr | tail -n +$(expr $keep_latest + 1) | while read snapnum; do
|
||||||
|
echo -e " $ansi_green[DEBUG] Found $snapshot_root/$config/$snapnum$ansi_default"
|
||||||
|
|
||||||
|
# Make sure this is not the latest incremental backup. grep will return
|
||||||
|
# with a non-zero exit status if there is *no match*, so we continue to
|
||||||
|
# delete matching snapshots if this command fails.
|
||||||
|
if ! snapper -c "$config" list --columns number,description | grep -E "^$snapnum" | grep 'latest incremental backup' >/dev/null; then
|
||||||
|
# Delete the snapshot itself
|
||||||
|
echo -e " $ansi_yellow[INFO] Deleting $snapshot_root/$config/$snapnum$ansi_default"
|
||||||
|
btrfs subvolume delete "$snapshot_root/$config/$snapnum/snapshot" >/dev/null
|
||||||
|
|
||||||
|
# Delete the snapper snapshot root (ie, where info.xml lives)
|
||||||
|
# SC2115: Use "${var:?}" to ensure this never expands to / .
|
||||||
|
rm -r "${snapshot_root:?}/$config/$snapnum"
|
||||||
|
else
|
||||||
|
echo -e " $ansi_green[DEBUG] Not deleting $snapshot_root/$config/$snapnum because it is the latest incremental backup.$ansi_default"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo -e " $ansi_green[DEBUG] Number of snapshots in $snapshot_root/$config should be more than $keep_latest.$ansi_default"
|
||||||
|
fi
|
||||||
|
done
|
12
services/snap-sync-cleanup.service
Normal file
12
services/snap-sync-cleanup.service
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Run snap-sync cleanup
|
||||||
|
Requires=run-media-usbdrive.mount
|
||||||
|
After=run-media-usbdrive.mount snap-sync.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=TZ=UTC
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/run/media/manuel/T7/snapshots/scripts/clean-snap-sync-external.sh
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=snap-sync.service
|
12
services/snap-sync.service
Normal file
12
services/snap-sync.service
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Run snap-sync backup
|
||||||
|
Requires=run-media-usbdrive.mount
|
||||||
|
After=run-media-usbdrive.mount
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=TZ=UTC
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/run/media/manuel/T7/snapshots/scripts/backup-snap-sync-incremental.sh
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=run-media-usbdrive.mount
|
Loading…
Reference in a new issue