feat: Log the backup stats summary to a file (#128)
* docs: Fix typos and minor improvements * feat: Log the backup stats summary to a CSV file The log records the added, removed and snapshot size after each backup. * feat: Add the snapshot ID to the stats log * docs: Update README with the stats log info * fix: Linter 1: Quote var * fix: Linter 2: Shellcheck declare and assign separately * feat: Turn the stats log into an opt-in
This commit is contained in:
21
README.md
21
README.md
@@ -26,16 +26,16 @@
|
|||||||
# Intro
|
# Intro
|
||||||
[restic](https://restic.net/) is a command-line tool for making backups, the right way. Check the official website for a feature explanation. As a storage backend, I recommend [Backblaze B2](https://www.backblaze.com/b2/cloud-storage.html) as restic works well with it, and it is (at the time of writing) very affordable for the hobbyist hacker! (anecdotal: I pay for my full-systems backups each month typically < 1 USD).
|
[restic](https://restic.net/) is a command-line tool for making backups, the right way. Check the official website for a feature explanation. As a storage backend, I recommend [Backblaze B2](https://www.backblaze.com/b2/cloud-storage.html) as restic works well with it, and it is (at the time of writing) very affordable for the hobbyist hacker! (anecdotal: I pay for my full-systems backups each month typically < 1 USD).
|
||||||
|
|
||||||
Unfortunately restic does not come pre-configured with a way to run automated backups, say every day. However it's possible to set this up yourself using built-in tools in your OS and some wrappers. For Linux with systemd, it's convenient to use systemd timers. For macOS systems, we can use built-in LaunchAgents. For Windows we can use ScheduledTasks. Any OS having something cron-like will also work!
|
Unfortunately restic does not come pre-configured with a way to run automated backups, say every day. However, it's possible to set this up yourself using built-in tools in your OS and some wrappers. For Linux with systemd, it's convenient to use systemd timers. For macOS systems, we can use built-in LaunchAgents. For Windows we can use ScheduledTasks. Any OS having something cron-like will also work!
|
||||||
|
|
||||||
Here follows a step-by step tutorial on how to set it up, with my sample script and configurations that you can modify to suit your needs.
|
Here follows a step-by step tutorial on how to set it up, with my sample script and configurations that you can modify to suit your needs.
|
||||||
|
|
||||||
Note, you can use any restic's supported [storage backends](https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html). The setup should be similar but you will have to use other configuration variables to match your backend of choice.
|
Note, you can use any restic's supported [storage backends](https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html). The setup should be similar, but you will have to use other configuration variables to match your backend of choice.
|
||||||
|
|
||||||
## Project Scope
|
## Project Scope
|
||||||
The scope for this is not to be a full-fledged super solution that solves all the problems and all possible setups. The aim is to be a hackable code base for you to start sewing up the perfect backup solution that fits your requirements!
|
The scope for this is not to be a full-fledged super solution that solves all the problems and all possible setups. The aim is to be a hackable code base for you to start sewing up the perfect backup solution that fits your requirements!
|
||||||
|
|
||||||
Nevertheless the project should work out of the box, be minimal but still open the doors for configuration and extensions by users.
|
Nevertheless, the project should work out of the box, be minimal but still open the doors for configuration and extensions by users.
|
||||||
|
|
||||||
To use a different storage backend than B2, you should only need to tweak a few settings variables in the backup profile as well as some restic arguments inside `restic_backup.sh`.
|
To use a different storage backend than B2, you should only need to tweak a few settings variables in the backup profile as well as some restic arguments inside `restic_backup.sh`.
|
||||||
|
|
||||||
@@ -442,12 +442,18 @@ To create a different backup and use you can do:
|
|||||||
# restic_backup.sh
|
# restic_backup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Optional: Summary stats log
|
||||||
|
|
||||||
|
When enabled, it will write to a CSV log file the stats after each backup. Can be enabled by uncommenting its env variable (`RESTIC_BACKUP_STATS_DIR`) on the global environment file or defining it on a specific profile.
|
||||||
|
|
||||||
|
The stats log (as well as) the desktop notifications incur in an additional run of `restic snapshots` and `restic diff`. This execution is shared with the notifications (no extra run).
|
||||||
|
|
||||||
### Optional: Desktop Notifications
|
### Optional: Desktop Notifications
|
||||||
<img src="img/macos_notification.png" align="right" />
|
<img src="img/macos_notification.png" align="right" />
|
||||||
|
|
||||||
It's a good idea to be on top of your backups to make sure that they don't increase a lot in size and incur high costs. However it's notoriously tricky to make GUI notifications correctly from a non-user process (e.g. root).
|
It's a good idea to be on top of your backups to make sure that they don't increase a lot in size and incur high costs. However, it's notoriously tricky to make GUI notifications correctly from a non-user process (e.g. root).
|
||||||
|
|
||||||
Therefore this project provides a lightweight solution for desktop notifications that works like this: Basically `restic_backup.sh` will append a summary line of the last backup to a user-owned file (the user running your OS's desktop environment) in a fire-and-forget fashion. Then the user has a process that reads this and forward each line as a new message to the desktop environment in use.
|
Therefore, this project provides a lightweight solution for desktop notifications that works like this: Basically `restic_backup.sh` will append a summary line of the last backup to a user-owned file (the user running your OS's desktop environment) in a fire-and-forget fashion. Then the user has a process that reads this and forward each line as a new message to the desktop environment in use.
|
||||||
|
|
||||||
To set desktop notifications up:
|
To set desktop notifications up:
|
||||||
1. Create a special FIFO file as your desktop user:
|
1. Create a special FIFO file as your desktop user:
|
||||||
@@ -456,12 +462,11 @@ To set desktop notifications up:
|
|||||||
```
|
```
|
||||||
1. In your profile, e.g. `/etc/restic/default.sh`, set:
|
1. In your profile, e.g. `/etc/restic/default.sh`, set:
|
||||||
```bash
|
```bash
|
||||||
RESTIC_NOTIFY_BACKUP_STATS=true
|
|
||||||
RESTIC_BACKUP_NOTIFICATION_FILE=/home/user/.cache/notification-queue
|
RESTIC_BACKUP_NOTIFICATION_FILE=/home/user/.cache/notification-queue
|
||||||
```
|
```
|
||||||
1. Create a listener on the notification queue file that forwards to desktop notifications
|
1. Create a listener on the notification queue file that forwards to desktop notifications
|
||||||
* Linux auto start + cross-platform notifier / notify-send
|
* Linux auto start + cross-platform notifier / notify-send
|
||||||
* [notification-queue-notifier](https://github.com/gerardbosch/dotfiles/blob/ddc1491056822eab45dedd131f1946308ef62135/home/bin/notification-queue-notifier)
|
* [notification-queue-notifier](https://github.com/gerardbosch/dotfiles/blob/2130d54daa827e7f885abac0d4f10b6f67d28ad3/home/bin/notification-queue-notifier)
|
||||||
* [notification-queue.desktop](https://github.com/gerardbosch/dotfiles-linux/blob/ea0f75bfd7a356945544ecaa42a2fc35c9fab3a1/home/.config/autostart/notification-queue.desktop)
|
* [notification-queue.desktop](https://github.com/gerardbosch/dotfiles-linux/blob/ea0f75bfd7a356945544ecaa42a2fc35c9fab3a1/home/.config/autostart/notification-queue.desktop)
|
||||||
* macOS auto start + [terminal-notifier](https://github.com/julienXX/terminal-notifier)
|
* macOS auto start + [terminal-notifier](https://github.com/julienXX/terminal-notifier)
|
||||||
* [notification-queue-notifier.sh](https://github.com/erikw/dotfiles/blob/8a942defe268292200b614951cdf433ddccf7170/bin/notification-queue-notifier.sh)
|
* [notification-queue-notifier.sh](https://github.com/erikw/dotfiles/blob/8a942defe268292200b614951cdf433ddccf7170/bin/notification-queue-notifier.sh)
|
||||||
@@ -554,7 +559,7 @@ $ source /etc/restic/default.env.sh
|
|||||||
$ bash -x /bin/restic_backup.sh
|
$ bash -x /bin/restic_backup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
To debug smaller portions of of the backup script, insert these lines at the top and bottom of the relevant code portions e.g.:
|
To debug smaller portions of the backup script, insert these lines at the top and bottom of the relevant code portions e.g.:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
set -x
|
set -x
|
||||||
|
|||||||
@@ -49,6 +49,30 @@ warn_on_missing_envvars() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Log the backup summary stats to a CSV file
|
||||||
|
logBackupStatsCsv() {
|
||||||
|
local snapId="$1" added="$2" removed="$3" snapSize="$4"
|
||||||
|
local logFile
|
||||||
|
logFile="${RESTIC_BACKUP_STATS_DIR}/$(date '+%Y')-stats.log.csv"
|
||||||
|
test -e "$logFile" || install -D -m 0644 <(echo "Date, Snapshot ID, Added, Removed, Snapshot size") "$logFile"
|
||||||
|
# DEV-NOTE: using `ex` due `sed` inconsistencies (GNU vs. BSD) and `awk` cannot edit in-place. `ex` does a good job
|
||||||
|
printf '1a\n%s\n.\nwq\n' "$(date '+%F %H:%M:%S'), ${snapId}, ${added}, ${removed}, ${snapSize}" | ex "$logFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Notify the backup summary stats to the user
|
||||||
|
notifyBackupStats() {
|
||||||
|
local statsMsg="$1"
|
||||||
|
if [ -w "$RESTIC_BACKUP_NOTIFICATION_FILE" ]; then
|
||||||
|
echo "$statsMsg" >> "$RESTIC_BACKUP_NOTIFICATION_FILE"
|
||||||
|
else
|
||||||
|
echo "[WARN] Couldn't write to the backup notification file. File not found or not writable: ${RESTIC_BACKUP_NOTIFICATION_FILE}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------
|
||||||
|
# === Main ===
|
||||||
|
# ------------
|
||||||
|
|
||||||
assert_envvars \
|
assert_envvars \
|
||||||
RESTIC_BACKUP_PATHS RESTIC_BACKUP_TAG \
|
RESTIC_BACKUP_PATHS RESTIC_BACKUP_TAG \
|
||||||
RESTIC_BACKUP_EXCLUDE_FILE RESTIC_BACKUP_EXTRA_ARGS RESTIC_REPOSITORY RESTIC_VERBOSITY_LEVEL \
|
RESTIC_BACKUP_EXCLUDE_FILE RESTIC_BACKUP_EXTRA_ARGS RESTIC_REPOSITORY RESTIC_VERBOSITY_LEVEL \
|
||||||
@@ -138,23 +162,22 @@ wait $!
|
|||||||
|
|
||||||
echo "Backup & cleaning is done."
|
echo "Backup & cleaning is done."
|
||||||
|
|
||||||
# (optionally) Notify about backup summary stats.
|
# (optional) Compute backup summary stats
|
||||||
if [ "$RESTIC_NOTIFY_BACKUP_STATS" = true ]; then
|
if [[ -n "$RESTIC_BACKUP_STATS_DIR" || -n "$RESTIC_BACKUP_NOTIFICATION_FILE" ]]; then
|
||||||
if [ -w "$RESTIC_BACKUP_NOTIFICATION_FILE" ]; then
|
echo 'Silently computing backup summary stats...'
|
||||||
echo 'Notifications are enabled: Silently computing backup summary stats...'
|
latest_snapshots=$(restic snapshots --tag "$RESTIC_BACKUP_TAG" --latest 2 --compact \
|
||||||
|
| grep -Ei "^[abcdef0-9]{8} " \
|
||||||
|
| awk '{print $1}' \
|
||||||
|
| tail -2 \
|
||||||
|
| tr '\n' ' ')
|
||||||
|
latest_snapshot_diff=$(echo "$latest_snapshots" | xargs restic diff)
|
||||||
|
added=$(echo "$latest_snapshot_diff" | grep -i 'added:' | awk '{print $2 " " $3}')
|
||||||
|
removed=$(echo "$latest_snapshot_diff" | grep -i 'removed:' | awk '{print $2 " " $3}')
|
||||||
|
snapshot_size=$(restic stats latest --tag "$RESTIC_BACKUP_TAG" | grep -i 'total size:' | cut -d ':' -f2 | xargs) # xargs acts as trim
|
||||||
|
snapshotId=$(echo "$latest_snapshots" | cut -d ' ' -f2)
|
||||||
|
statsMsg="Added: ${added}. Removed: ${removed}. Snap size: ${snapshot_size}"
|
||||||
|
|
||||||
snapshot_size=$(restic stats latest --tag "$RESTIC_BACKUP_TAG" | grep -i 'total size:' | cut -d ':' -f2 | xargs) # xargs acts as trim
|
echo "$statsMsg"
|
||||||
latest_snapshot_diff=$(restic snapshots --tag "$RESTIC_BACKUP_TAG" --latest 2 --compact \
|
test -n "$RESTIC_BACKUP_STATS_DIR" && logBackupStatsCsv "$snapshotId" "$added" "$removed" "$snapshot_size"
|
||||||
| grep -Ei "^[abcdef0-9]{8} " \
|
test -n "$RESTIC_BACKUP_NOTIFICATION_FILE" && notifyBackupStats "$statsMsg"
|
||||||
| awk '{print $1}' \
|
|
||||||
| tail -2 \
|
|
||||||
| tr '\n' ' ' \
|
|
||||||
| xargs restic diff)
|
|
||||||
added=$(echo "$latest_snapshot_diff" | grep -i 'added:' | awk '{print $2 " " $3}')
|
|
||||||
removed=$(echo "$latest_snapshot_diff" | grep -i 'removed:' | awk '{print $2 " " $3}')
|
|
||||||
|
|
||||||
echo "Added: ${added}. Removed: ${removed}. Snap size: ${snapshot_size}" >> "$RESTIC_BACKUP_NOTIFICATION_FILE"
|
|
||||||
else
|
|
||||||
echo "[WARN] Couldn't write the backup summary stats. File not found or not writable: ${RESTIC_BACKUP_NOTIFICATION_FILE}"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# shellcheck shell=sh
|
# shellcheck shell=sh
|
||||||
|
|
||||||
# Global envionment variables
|
# Global environment variables
|
||||||
# These variables are sourced FIRST, and any values inside of *.env.sh files for
|
# These variables are sourced FIRST, and any values inside of *.env.sh files for
|
||||||
# specific configurations will override if also defined there.
|
# specific configurations will override if also defined there.
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ export RESTIC_BACKUP_EXTRA_ARGS=
|
|||||||
# Override this value in a profile if needed.
|
# Override this value in a profile if needed.
|
||||||
export RESTIC_VERBOSITY_LEVEL=0
|
export RESTIC_VERBOSITY_LEVEL=0
|
||||||
|
|
||||||
# (optional) Desktop notifications. See restic_backup.sh for details on how to set this up.
|
# (optional, uncomment to enable) Backup summary stats log: snapshot size, etc. (empty/unset won't log)
|
||||||
export RESTIC_NOTIFY_BACKUP_STATS=false
|
#export RESTIC_BACKUP_STATS_DIR="$INSTALL_PREFIX/var/log/restic-automatic-backup-scheduler"
|
||||||
|
|
||||||
|
# (optional) Desktop notifications. See README and restic_backup.sh for details on how to set this up (empty/unset means disabled)
|
||||||
export RESTIC_BACKUP_NOTIFICATION_FILE=
|
export RESTIC_BACKUP_NOTIFICATION_FILE=
|
||||||
|
|||||||
Reference in New Issue
Block a user