Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4158ee155a | ||
|
|
248d8f7d0a | ||
|
|
3797979dd3 | ||
|
|
232dc7e90f | ||
|
|
478ea152ac | ||
|
|
db0a00bdaa | ||
|
|
679569f633 | ||
|
|
3089709038 | ||
|
|
d36a5e850d | ||
|
|
950564d433 | ||
|
|
43f00b699c | ||
|
|
aa22ced34a | ||
|
|
5f9a0ba5ad | ||
|
|
01076031c4 | ||
|
|
b96f01e823 | ||
|
|
d6f512cb56 | ||
|
|
4deb4481cc | ||
|
|
de28285741 | ||
|
|
849181bfb8 | ||
|
|
9f9a4044a9 | ||
|
|
a001840c0e | ||
|
|
e28973a103 | ||
|
|
69e21d586c | ||
|
|
e681b8fb6a | ||
|
|
e5e5cea35e | ||
|
|
1c0ba09d70 | ||
|
|
48ec2f9cf7 | ||
|
|
b363e736d1 | ||
|
|
f1b29db5fd | ||
|
|
2973f64dd1 | ||
|
|
4e6afce8d2 | ||
|
|
2873f6a315 | ||
|
|
3cdf1503ae | ||
|
|
3f05091edb | ||
|
|
6d154890e7 | ||
|
|
a870259eb2 | ||
|
|
2406f078ef | ||
|
|
e072b6db14 | ||
|
|
184e426ec8 | ||
|
|
20b5fd301e | ||
|
|
0dcb315296 | ||
|
|
e7fe75ca47 | ||
|
|
085d722747 | ||
|
|
91ddab850b | ||
|
|
46ed2e4af8 | ||
|
|
620acf82cf | ||
|
|
803f5605a7 | ||
|
|
987bc39d00 | ||
|
|
7bc0e33779 | ||
|
|
86d841a72b | ||
|
|
4fba6a3f10 | ||
|
|
347322d9c9 | ||
|
|
f5c50a18ad | ||
|
|
eee3c4659b | ||
|
|
ef58c46fde | ||
|
|
3aa88ebb45 | ||
|
|
9e60df6caa | ||
|
|
1b4d5ea24e | ||
|
|
b7f250f345 | ||
|
|
fc6bd5a0be | ||
|
|
9fa01a10f7 | ||
|
|
811a5c3c8c | ||
|
|
49cc054eeb | ||
|
|
a35a9977f3 | ||
|
|
f17848bed2 | ||
|
|
d5497b04b4 | ||
|
|
855997d7d7 |
17
.devcontainer/devcontainer.json
Normal file
17
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
|
||||
"packages": ["shellcheck"]
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "bash scripts/devcontainer_postCreateCommand.sh",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"rogalmic.bash-debug",
|
||||
"timonwong.shellcheck",
|
||||
"mads-hartmann.bash-ide-vscode"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
42
.github/workflows/linter.yml
vendored
42
.github/workflows/linter.yml
vendored
@@ -1,33 +1,47 @@
|
||||
name: Lint Code Base
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: main
|
||||
paths:
|
||||
- '**.sh'
|
||||
- '.github/workflows/linter.yml'
|
||||
- 'bin/**'
|
||||
- "**.sh"
|
||||
- ".github/workflows/linter.yml"
|
||||
- "bin/**"
|
||||
pull_request:
|
||||
branches: main
|
||||
paths:
|
||||
- '**.sh'
|
||||
- '.github/workflows/linter.yml'
|
||||
- 'bin/**'
|
||||
- "**.sh"
|
||||
- ".github/workflows/linter.yml"
|
||||
- "bin/**"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Lint Code Base
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
# To report GitHub Actions status checks
|
||||
statuses: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Full git history is needed to get a proper list of changed files within `super-linter`
|
||||
# super-linter needs the full git history to get the
|
||||
# list of files that changed across commits
|
||||
fetch-depth: 0
|
||||
- name: Lint Code Base
|
||||
uses: github/super-linter@v4
|
||||
|
||||
- name: Super-linter
|
||||
uses: super-linter/super-linter@v7.3.0
|
||||
env:
|
||||
# To report GitHub Actions status checks
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DEFAULT_BRANCH: main
|
||||
VALIDATE_ALL_CODEBASE: true
|
||||
VALIDATE_BASH: true
|
||||
DEFAULT_BRANCH: main
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
IGNORE_GENERATED_FILES: true
|
||||
|
||||
17
.github/workflows/stale.yml
vendored
Normal file
17
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: "Close stale issues and PRs"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * 0"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
days-before-stale: 180
|
||||
exempt-issue-labels: "NotStale"
|
||||
exempt-pr-labels: "NotStale"
|
||||
stale-issue-message: "Issue is stale; will soon close."
|
||||
stale-pr-message: "PR is stale; will soon close."
|
||||
4
.semver.yaml
Normal file
4
.semver.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
alpha: 0
|
||||
beta: 0
|
||||
rc: 0
|
||||
release: v7.4.0
|
||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -5,6 +5,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Debug scripts by setting `TRACE=1`.
|
||||
- Add semver-cli for git tagging.
|
||||
### Changed
|
||||
- Warn on certain unset envvars instead of error-exit.
|
||||
|
||||
## [7.4.0] - 2023-03-08
|
||||
### Added
|
||||
- Support saving hourly snapshots. [#98](https://github.com/erikw/restic-automatic-backup-scheduler/pull/98)
|
||||
- Support for pre backup script at /etc/restic/pre_backup.sh [107](https://github.com/erikw/restic-automatic-backup-scheduler/pull/107)
|
||||
|
||||
### Fixed
|
||||
- Full path to `/bin/bash` in sytemd services. [#96](https://github.com/erikw/restic-automatic-backup-scheduler/issues/96)
|
||||
|
||||
## [7.3.4] - 2022-04-29
|
||||
### Fixed
|
||||
- Backup stats notifications: fix issue where `restic snapshots --latest 2` will show more than two snapshots due to different backup paths used.
|
||||
|
||||
## [7.3.3] - 2022-04-14
|
||||
### Fixed
|
||||
- Trying to fix broken Homebrew bottles due to GitHub API issues.
|
||||
|
||||
## [7.3.2] - 2022-04-11
|
||||
### Fixed
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
<array>
|
||||
<string>/bin/bash</string>
|
||||
<string>-c</string>
|
||||
<string>source $INSTALL_PREFIX/etc/restic/default.env.sh && $INSTALL_PREFIX/bin/restic_backup.sh >>$HOME/$LOG_OUT 2>>$HOME/$LOG_ERR</string>
|
||||
<string>source {{ INSTALL_PREFIX }}/etc/restic/default.env.sh && {{ INSTALL_PREFIX }}/bin/restic_backup.sh >>$HOME/$LOG_OUT 2>>$HOME/$LOG_ERR</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>$INSTALL_PREFIX/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
<string>{{ INSTALL_PREFIX }}/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
<key>LOG_OUT</key>
|
||||
<string>/Library/Logs/restic/backup_stdout.log</string>
|
||||
<key>LOG_ERR</key>
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
<array>
|
||||
<string>/bin/bash</string>
|
||||
<string>-c</string>
|
||||
<string>source $INSTALL_PREFIX/etc/restic/default.env.sh && $INSTALL_PREFIX/bin/restic_check.sh >>$HOME/$LOG_OUT 2>>$HOME/$LOG_ERR</string>
|
||||
<string>source {{ INSTALL_PREFIX }}/etc/restic/default.env.sh && {{ INSTALL_PREFIX }}/bin/restic_check.sh >>$HOME/$LOG_OUT 2>>$HOME/$LOG_ERR</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>$INSTALL_PREFIX/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
<string>{{ INSTALL_PREFIX }}/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
<key>LOG_OUT</key>
|
||||
<string>/Library/Logs/restic/check_stdout.log</string>
|
||||
<key>LOG_ERR</key>
|
||||
|
||||
10
Makefile
10
Makefile
@@ -1,14 +1,14 @@
|
||||
#### Notes ####################################################################
|
||||
# This build process is done in three stages (out-of-source build):
|
||||
# 1. copy source files to the local build directory.
|
||||
# 2. build dir: replace the string "$INSTALL_PREFIX" with the value of $PREFIX
|
||||
# 2. build dir: replace the string "{{ INSTALL_PREFIX }}" with the value of $PREFIX
|
||||
# 3. install files from the build directory to the target directory.
|
||||
#
|
||||
# Why this dance?
|
||||
# * To fully support that a user can install this project to a custom path e.g.
|
||||
# $(PREFIX=/usr/local make install), we need to modify the files that refer
|
||||
# to other files on disk. We do this by having a placeholder
|
||||
# "$INSTALL_PREFIX" that is substituted with the value of $PREFIX when
|
||||
# "{{ INSTALL_PREFIX }}" that is substituted with the value of $PREFIX when
|
||||
# installed.
|
||||
# * We don't want to modify the files that are controlled by git, thus let's
|
||||
# copy them to a build directory and then modify.
|
||||
@@ -57,7 +57,7 @@ LAUNCHAGENT_CHECK = com.github.erikw.restic-check
|
||||
LAUNCHAGENT_TARGET_BACKUP = gui/$(UID)/$(LAUNCHAGENT_BACKUP)
|
||||
LAUNCHAGENT_TARGET_CHECK = gui/$(UID)/$(LAUNCHAGENT_CHECK)
|
||||
|
||||
# What to substitute $INSTALL_PREFIX in sources to.
|
||||
# What to substitute {{ INSTALL_PREFIX }} in sources to.
|
||||
# This can be useful to set to empty on commandline when building e.g. an AUR
|
||||
# package in a separate build directory (PREFIX).
|
||||
INSTALL_PREFIX := $(PREFIX)
|
||||
@@ -192,11 +192,11 @@ install-targets-schedtask: $(BUILD_DIR_SCHEDTASK)/$(SCHEDTASK_INSTALL)
|
||||
uninstall-targets-schedtask: $(BUILD_DIR_SCHEDTASK)/$(SCHEDTASK_UNINSTALL)
|
||||
test $(CUR_OS) != Windows || ./$<
|
||||
|
||||
# Copies sources to build directory & replace "$INSTALL_PREFIX".
|
||||
# Copies sources to build directory & replace "{{ INSTALL_PREFIX }}".
|
||||
$(BUILD_DIR)/% : %
|
||||
@${MKDIR_PARENTS} $@
|
||||
cp $< $@
|
||||
sed -i.bak -e 's|$$INSTALL_PREFIX|$(INSTALL_PREFIX)|g' $@; rm $@.bak
|
||||
sed -i.bak -e 's|{{ INSTALL_PREFIX }}|$(INSTALL_PREFIX)|g' $@; rm $@.bak
|
||||
|
||||
# Install destination script files.
|
||||
$(DEST_DIR_SCRIPT)/%: $(BUILD_DIR_SCRIPT)/%
|
||||
|
||||
143
README.md
143
README.md
@@ -1,10 +1,10 @@
|
||||
# Automatic restic Backups using Native OS Task Schedulers
|
||||
# Automatic Restic Backups Using Native OS Task Schedulers
|
||||
*formerly named restic-systemd-automatic-backup*
|
||||
|
||||
[](#)
|
||||
[](#)
|
||||
<br>
|
||||
[](https://github.com/erikw/restic-automatic-backup-scheduler/actions/workflows/linter.yml)
|
||||
[](https://github.com/erikw/restic-automatic-backup-scheduler/actions/workflows/linter.yml)
|
||||
[](https://github.com/erikw/restic-automatic-backup-scheduler/tags)
|
||||
[](https://aur.archlinux.org/packages/restic-automatic-backup-scheduler/)
|
||||
[](https://aur.archlinux.org/packages/restic-automatic-backup-scheduler/)
|
||||
@@ -14,6 +14,8 @@
|
||||
[](https://github.com/erikw/restic-automatic-backup-scheduler/pulls?q=is%3Apr+is%3Aclosed)
|
||||
[](LICENSE)
|
||||
[](https://github.com/Netflix/osstracker)
|
||||
[](#)
|
||||
[](#)
|
||||
<br>
|
||||
|
||||
[](https://github.com/erikw/restic-automatic-backup-scheduler/graphs/contributors) including these top contributors:
|
||||
@@ -21,24 +23,37 @@
|
||||
<img src = "https://contrib.rocks/image?repo=erikw/restic-automatic-backup-scheduler&max=24"/>
|
||||
</a>
|
||||
|
||||
<p align="center">
|
||||
<!-- Ref: https://dev.to/azure/adding-a-github-codespace-button-to-your-readme-5f6l -->
|
||||
<a href="https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=129436975" title="Open in GitHub Codespaces" ><img alt="Open in GitHub Codespaces" src="https://github.com/codespaces/badge.svg"></a>
|
||||
</p>
|
||||
|
||||
# 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).
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
> [!NOTE]
|
||||
> **Update:** this project is feature complete (see reasoning below). Only bug fixes will be accepted. Feel free to fork if you want to add more features; being a forking base was the initial scope of this project!
|
||||
|
||||
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`.
|
||||
|
||||
## Notes
|
||||
* Tip: Navigate this document easily from the Section icon in the top left corner.
|
||||

|
||||
* ☝ **Note**: in the command listing in this document, `$` means a user shell and `#` means a root shell (or use `sudo`).
|
||||
> [!TIP]
|
||||
> Navigate this document easily from the Section icon in the top left corner.
|
||||
<img src="img/readme_sections.png" alt="README.md sections" width="30%" />
|
||||
|
||||
> [!NOTE]
|
||||
> In the command listing in this document, `$` means a user shell and `#` means a root shell (or use `sudo`).
|
||||
|
||||
|
||||
# Requirements
|
||||
@@ -59,7 +74,8 @@ Depending on your system, the setup will look different. Choose one of:
|
||||
## Setup Linux Systemd
|
||||
<img height="64" width="64" src="https://unpkg.com/simple-icons@v6/icons/linux.svg" />
|
||||
|
||||
☝ **Note** The Linux setup here will assume an installation to `/`.
|
||||
> [!NOTE]
|
||||
> The Linux setup here will assume an installation to `/`.
|
||||
|
||||
Many Linux distributions nowadays use [Systemd](https://en.wikipedia.org/wiki/Systemd), which features good support for running services and scheduled jobs. If your distribution is no on Systemd, check out the [cron setup](#setup-cron) instead.
|
||||
|
||||
@@ -109,9 +125,10 @@ Many Linux distributions nowadays use [Systemd](https://en.wikipedia.org/wiki/Sy
|
||||
## Setup macOS LaunchAgent
|
||||
<img height="64" width="64" src="https://unpkg.com/simple-icons@v6/icons/apple.svg" />
|
||||
|
||||
☝ **Note** The macOS setup here will assume an installation to `/usr/local`, as [custom](https://docs.brew.sh/FAQ#why-does-homebrew-say-sudo-is-bad) with Homebrew installations.
|
||||
> [!NOTE]
|
||||
> The macOS setup here will assume a Homebrew installation to the [recommended default location](https://docs.brew.sh/FAQ#why-should-i-install-homebrew-in-the-default-location). This is [`$HOMEBREW_PREFIX` (`brew --prefix`)](https://docs.brew.sh/Formula-Cookbook#variables-for-directory-locations) , which is `/usr/local` on Intel Macs and `/opt/homebrew` on [Apple Silicon](https://docs.brew.sh/FAQ#why-is-the-default-installation-prefix-opthomebrew-on-apple-silicon).
|
||||
|
||||
[Launchd](https://www.launchd.info/) is the modern built-in service scheduler in macOS. It has support for running services as root (Daemon) or as a normal user (Agent). Here we set up an LauchAgent to be run as your normal user for starting regular backups.
|
||||
[Launchd](https://www.launchd.info/) is the modern built-in service scheduler in macOS. It has support for running services as root (Daemon) or as a normal user (Agent). Here we set up a LaunchAgent to be run as your normal user for starting regular backups.
|
||||
|
||||
**TL;DR setup**
|
||||
1. [Create](#1-create-backblaze-b2-account-bucket-and-keys) B2 bucket + credentials
|
||||
@@ -122,17 +139,18 @@ Many Linux distributions nowadays use [Systemd](https://en.wikipedia.org/wiki/Sy
|
||||
```
|
||||
* Using `make`:
|
||||
```console
|
||||
$ make PREFIX=/usr/local install-launchagent
|
||||
$ make PREFIX=$(brew --prefix) install-launchagent
|
||||
```
|
||||
1. Fill out [configuration values](#2-configure-b2-credentials-locally) in `/usr/local/etc/restic`.
|
||||
1. Fill out [configuration values](#2-configure-b2-credentials-locally) in `$(brew --prefix)/etc/restic`.
|
||||
1. [Initialize](#3-initialize-remote-repo) the remote repo.
|
||||
Source the profile to make all needed configuration available to `restic(1)`. All commands after this assumes the profile is sourced in the current shell.
|
||||
```console
|
||||
$ source /usr/local/etc/restic/default.env.sh
|
||||
$ source $(brew --prefix)/etc/restic/default.env.sh
|
||||
$ restic init
|
||||
```
|
||||
1. Configure [how often](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/ScheduledJobs.html#//apple_ref/doc/uid/10000172i-CH1-SW1) backups should be done. If needed, edit `OnCalendar` in
|
||||
* Homebrew install: `~/Library/LaunchAgents/homebrew.mxcl.restic-automatic-backup-scheduler.plist`.
|
||||
* Note that with Homebrew install, this file will only be available after running the `$ brew services start [...]` command in the next step. Run that command and come back here.
|
||||
* `make` install: `~/Library/LaunchAgents/com.github.erikw.restic-backup.plist`.
|
||||
1. Enable automated backup for starting with the system & make the first backup:
|
||||
* Homebrew install:
|
||||
@@ -203,9 +221,9 @@ I describe here one of may ways you can get restic and this backup script workin
|
||||
|
||||
**TL;DR setup**
|
||||
1. Install [scoop](https://scoop.sh/)
|
||||
1. Install dependencies from a PowerShell with *administrator privileges*:
|
||||
1. Install dependencies from a PowerShell with *administrator privileges*. `pwsh` should be installed to be able to run powershell in shebang scripts.
|
||||
```console
|
||||
powershell> scoop install restic make git
|
||||
powershell> scoop install restic make git pwsh
|
||||
```
|
||||
1. In a *non-privileged* PowerShell, start git-bash and clone this repo
|
||||
```console
|
||||
@@ -255,7 +273,8 @@ With `taskschd.msc` you can easily start, stop, delete and configure the schedul
|
||||
## Setup Cron
|
||||
<img height="64" width="64" src="https://unpkg.com/simple-icons@v6/icons/clockify.svg" />
|
||||
|
||||
☝ **Note** There are many different cron [implementations](https://wiki.archlinux.org/title/Cron) out there and they all work slightly different.
|
||||
> [!NOTE]
|
||||
> There are many different cron [implementations](https://wiki.archlinux.org/title/Cron) out there and they all work slightly different.
|
||||
|
||||
Any system that has a cron-like system can easily setup restic backups as well. However if you system supports any of the previous setups, those are recommended over cron as they provide more features and reliability for your backups.
|
||||
|
||||
@@ -302,17 +321,17 @@ $ git clone https://github.com/erikw/restic-automatic-backup-scheduler.git && cd
|
||||
|
||||
Make a quick search-and-replace in the source files:
|
||||
```console
|
||||
$ find etc bin -type f -exec sed -i.bak -e 's|$INSTALL_PREFIX||g' {} \; -exec rm {}.bak \;
|
||||
$ find bin etc usr Library ScheduledTask -type f -exec sed -i.bak -e 's|{{ INSTALL_PREFIX }}||g' {} \; -exec rm {}.bak \;
|
||||
```
|
||||
and you should now see that all files have been changed like e.g.
|
||||
```diff
|
||||
-export RESTIC_PASSWORD_FILE="$INSTALL_PREFIX/etc/restic/pw.txt"
|
||||
-export RESTIC_PASSWORD_FILE="{{ INSTALL_PREFIX }}/etc/restic/pw.txt"
|
||||
+export RESTIC_PASSWORD_FILE="/etc/restic/pw.txt"
|
||||
```
|
||||
|
||||
Why? The OS specific TL;DR setups above all use the [Makefile](Makefile) or a package manager to install these files. The placeholder string `$INSTALL_PREFIX` is in the source files for portability reasons, so that the Makefile can support all different operating systems. `make` users can set a different `$PREFIX` when installing like `PREFIX=/usr/local make install-systemd`.
|
||||
Why? The OS specific TL;DR setups above all use the [Makefile](Makefile) or a package manager to install these files. The placeholder string `{{ INSTALL_PREFIX }}` is in the source files for portability reasons, so that the Makefile can support all different operating systems. `make` users can set a different `$PREFIX` when installing like `PREFIX=/usr/local make install-systemd`.
|
||||
|
||||
In this detailed manual setup we will copy all files manually to `/etc`and `/bin`. Thus we need to remove the placeholder string `$INSTALL_PREFIX` in the source files as a first step.
|
||||
In this detailed manual setup we will copy all files manually to `/etc`and `/bin`. Thus, we need to remove the placeholder string `{{ INSTALL_PREFIX }}` in the source files as a first step.
|
||||
|
||||
|
||||
#### 1. Create Backblaze B2 Account, Bucket and Keys
|
||||
@@ -433,17 +452,23 @@ To have different backup jobs having e.g. different buckets, backup path of sche
|
||||
To create a different backup and use you can do:
|
||||
```console
|
||||
# cp /etc/restic/default.env.sh /etc/restic/other.env.sh
|
||||
# vim /etc/restic/default.other.sh # Set backup path, bucket etc.
|
||||
# source /etc/restic/default.other.sh
|
||||
# vim /etc/restic/other.env.sh # Set backup path, bucket etc.
|
||||
# source /etc/restic/other.env.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
|
||||
<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:
|
||||
1. Create a special FIFO file as your desktop user:
|
||||
@@ -452,12 +477,11 @@ To set desktop notifications up:
|
||||
```
|
||||
1. In your profile, e.g. `/etc/restic/default.sh`, set:
|
||||
```bash
|
||||
RESTIC_NOTIFY_BACKUP_STATS=true
|
||||
RESTIC_BACKUP_NOTIFICATION_FILE=/home/user/.cache/notification-queue
|
||||
```
|
||||
1. Create a listener on the notification queue file that forwards to desktop notifications
|
||||
* 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)
|
||||
* 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)
|
||||
@@ -472,10 +496,10 @@ We want to be aware when the automatic backup fails, so we can fix it. Since my
|
||||
Put this file in `/bin`:
|
||||
* `systemd-email`: Sends email using sendmail(1). This script also features time-out for not spamming Gmail servers and getting my account blocked.
|
||||
|
||||
Put this files in `/etc/systemd/system/`:
|
||||
* `status-email-user@.service`: A service that can notify you via email when a systemd service fails. Edit the target email address in this file.
|
||||
Put this file in `/etc/systemd/system/`:
|
||||
* `status-email-user@.service`: A service that can notify you via email when a systemd service fails. Edit the target email address in this file, and replace or remove `{{ INSTALL_PREFIX }}` according to your installation.
|
||||
|
||||
Now edit `restic-backup@.service` and `status-email-user@.service` to call this service failure.
|
||||
Now edit `/usr/lib/systemd/system/restic-backup@.service` and `/usr/lib/systemd/system/restic-check@.service` to call this service failure.
|
||||
```
|
||||
OnFailure=status-email-user@%n.service
|
||||
```
|
||||
@@ -495,6 +519,7 @@ For a laptop, it can make sense to not do heavy backups when your on a metered c
|
||||
1. Edit `restic-backup@.service` and `restic-check@.service` to require the new service to be in success state:
|
||||
```
|
||||
Requires=nm-unmetered-connection.service
|
||||
After=nm-unmetered-connection.service
|
||||
```
|
||||
1. Copy and paste the command below, it will install the following files and refresh systemd daemon:
|
||||
1. Put this file in `/etc/systemd/system/`:
|
||||
@@ -505,8 +530,10 @@ For a laptop, it can make sense to not do heavy backups when your on a metered c
|
||||
```console
|
||||
# systemctl daemon-reload
|
||||
```
|
||||
> [!TIP]
|
||||
> All steps but the first can be done in one go if you use the Makefile. Set `$PREFIX` as needed or leave empty for install to `/`.
|
||||
|
||||
|
||||
☝ **Tip**: All steps but the first can be done in one go if you use the Makefile. Set `$PREFIX` as needed or leave empty for install to `/`.
|
||||
```bash
|
||||
sudo bash -c 'export PREFIX=
|
||||
make build/usr/lib/systemd/system/nm-unmetered-connection.service
|
||||
@@ -522,14 +549,16 @@ straightforward (it needs to run with sudo to read environment). Just run:
|
||||
|
||||
* `sudo resticw WHATEVER` (e.g. `sudo resticw snapshots`) to use the default profile.
|
||||
* You can run the wrapper by passing a specific profile: `resticw -p anotherprofile snapshots`.
|
||||
* The wrapper has extras on top of `restic` like `--diff-latest` option.
|
||||
|
||||
Useful commands:
|
||||
| Command | Description |
|
||||
|---------------------------------------------------|-------------------------------------------------------------------|
|
||||
| `resticw snapshots` | List backup snapshots |
|
||||
| `resticw diff <snapshotId-1> <snapshotId-2>` | Show the changes between backup snapshots |
|
||||
| `resticw stats` / `resticw stats snapshotId ...` | Show the statistics for the whole repo or the specified snapshots |
|
||||
| `resticw mount /mnt/restic` | Mount your remote repository |
|
||||
| Command | Description |
|
||||
|---------------------------------------------------|---------------------------------------------------------------------------------------|
|
||||
| `resticw snapshots` | List backup snapshots |
|
||||
| `resticw diff <snapshotId-1> <snapshotId-2>` | Show the changes between backup snapshots |
|
||||
| `resticw stats` / `resticw stats snapshotId ...` | Show the statistics for the whole repo or the specified snapshots |
|
||||
| `resticw mount /mnt/restic` | Mount your remote repository |
|
||||
| `resticw --diff-latest` | Show latest snapshot changes: Runs `restic diff` after finding the latest 2 snapshots |
|
||||
|
||||
|
||||
|
||||
@@ -540,6 +569,32 @@ There is a make target to remove all files (scripts and **configs)** that were i
|
||||
$ sudo make uninstall
|
||||
```
|
||||
|
||||
|
||||
# Debugging
|
||||
The best way to debug what's going on is to run the `restic_backup.sh` script with bash's trace function. You can activate it by running the script with `bash -x`:
|
||||
|
||||
```consle
|
||||
$ source /etc/restic/default.env.sh
|
||||
$ bash -x /bin/restic_backup.sh
|
||||
```
|
||||
|
||||
To debug smaller portions of the backup script, insert these lines at the top and bottom of the relevant code portions e.g.:
|
||||
|
||||
```bash
|
||||
set -x
|
||||
exec 2>/tmp/restic-automatic-backup-scheduler.log
|
||||
<code to debug>
|
||||
set +x
|
||||
```
|
||||
|
||||
and then inspect the outputs like
|
||||
|
||||
```shell
|
||||
$ less /tmp/restic-automatic-backup-scheduler.log
|
||||
$ tail -f /tmp/restic-automatic-backup-scheduler.log # or follow output like this.
|
||||
```
|
||||
|
||||
|
||||
# Development
|
||||
* To not mess up your real installation when changing the `Makefile` simply install to a `$PREFIX` like
|
||||
```console
|
||||
@@ -552,13 +607,13 @@ $ sudo make uninstall
|
||||
```
|
||||
|
||||
# Releasing
|
||||
To make a new release:
|
||||
1. Create a new tag:
|
||||
```console
|
||||
$ vi CHANGELOG.md && git commit -am "Update CHANGELOG.md"
|
||||
$ git tag vX.Y.Z
|
||||
$ git push && git push --tags
|
||||
```
|
||||
1. Create a new version of this project by using [semver-cli](https://github.com/maykonlsf/semver-cli).
|
||||
```shell
|
||||
vi CHANGELOG.md
|
||||
semver up minor
|
||||
ver=$(semver get release)
|
||||
git commit -am "Bump version to $ver" && git tag $ver && git push --atomic origin main $ver
|
||||
```
|
||||
1. Update version in the AUR [PKGBUILD](https://aur.archlinux.org/packages/restic-automatic-backup-scheduler/)
|
||||
1. Update version in the Homebrew Formulas (see the repo README):
|
||||
* [restic-automatic-backup-scheduler](https://github.com/erikw/homebrew-tap/blob/main/Formula/restic-automatic-backup-scheduler.rb)
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
|
||||
# Install restic_backup.sh
|
||||
$action = New-ScheduledTaskAction -Execute "$(scoop prefix git)\git-bash.exe" -Argument '-l -c "source $INSTALL_PREFIX/etc/restic/default.env.sh && $INSTALL_PREFIX/bin/restic_backup.sh"'
|
||||
$action = New-ScheduledTaskAction -Execute "$(scoop prefix git)\git-bash.exe" -Argument '-l -c "source {{ INSTALL_PREFIX }}/etc/restic/default.env.sh && {{ INSTALL_PREFIX }}/bin/restic_backup.sh"'
|
||||
$trigger = New-ScheduledTaskTrigger -Daily -At 7pm
|
||||
Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "restic_backup" -Description "Daily backup to B2 with restic."
|
||||
|
||||
# Install restic_check.sh
|
||||
$action = New-ScheduledTaskAction -Execute "$(scoop prefix git)\git-bash.exe" -Argument '-l -c "source $INSTALL_PREFIX/etc/restic/default.env.sh && /INSTALL_PREFIX/bin/restic_check.sh"'
|
||||
$action = New-ScheduledTaskAction -Execute "$(scoop prefix git)\git-bash.exe" -Argument '-l -c "source {{ INSTALL_PREFIX }}/etc/restic/default.env.sh && {{ INSTALL_PREFIX }}/bin/restic_check.sh"'
|
||||
$trigger = New-ScheduledTaskTrigger -Weekly -WeeksInterval 4 -DaysOfWeek Sunday -At 8pm -RandomDelay 128
|
||||
Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "restic_check" -Description "Check B2 backups with restic."
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# Why? Because of FreeBSD the system cron uses sendmail, and I want to use ssmtp.
|
||||
# Make your crontab files like:
|
||||
#SHELL=/bin/sh
|
||||
#PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin:$INSTALL_PREFIX/bin
|
||||
#PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin:{{ INSTALL_PREFIX }}/bin
|
||||
#@daily root cron_mail freebsd-update cron
|
||||
|
||||
mail_target=root
|
||||
|
||||
114
bin/restic_backup.sh
Normal file → Executable file
114
bin/restic_backup.sh
Normal file → Executable file
@@ -8,8 +8,9 @@
|
||||
# $ source $PREFIX/etc/default.env.sh
|
||||
# $ restic_backup.sh
|
||||
|
||||
# Exit on error, unset var, pipe failure
|
||||
set -euo pipefail
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
[[ "${TRACE-0}" =~ ^1|t|y|true|yes$ ]] && set -o xtrace
|
||||
|
||||
# Clean up lock if we are killed.
|
||||
# If killed by systemd, like $(systemctl stop restic), then it kills the whole cgroup and all it's subprocesses.
|
||||
@@ -28,22 +29,76 @@ assert_envvars() {
|
||||
local varnames=("$@")
|
||||
for varname in "${varnames[@]}"; do
|
||||
if [ -z ${!varname+x} ]; then
|
||||
printf "%s must be set for this script to work.\n\nDid you forget to source a /etc/restic/*.env.sh profile in the current shell before executing this script?\n" "$varname" >&2
|
||||
printf "%s must be set for this script to work.\n\nDid you forget to source a {{ INSTALL_PREFIX }}/etc/restic/*.env.sh profile in the current shell before executing this script?\n" "$varname" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
assert_envvars \
|
||||
B2_ACCOUNT_ID B2_ACCOUNT_KEY B2_CONNECTIONS \
|
||||
RESTIC_BACKUP_PATHS RESTIC_BACKUP_TAG \
|
||||
RESTIC_BACKUP_EXCLUDE_FILE RESTIC_BACKUP_EXTRA_ARGS RESTIC_PASSWORD_FILE RESTIC_REPOSITORY RESTIC_VERBOSITY_LEVEL \
|
||||
RESTIC_RETENTION_DAYS RESTIC_RETENTION_MONTHS RESTIC_RETENTION_WEEKS RESTIC_RETENTION_YEARS
|
||||
|
||||
warn_on_missing_envvars() {
|
||||
local unset_envs=()
|
||||
local varnames=("$@")
|
||||
for varname in "${varnames[@]}"; do
|
||||
if [ -z "${!varname-}" ]; then
|
||||
unset_envs=("${unset_envs[@]}" "$varname")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#unset_envs[@]} -gt 0 ]; then
|
||||
printf "The following env variables are recommended, but have not been set. This script may not work as expected: %s\n" "${unset_envs[*]}" >&2
|
||||
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 \
|
||||
RESTIC_BACKUP_PATHS RESTIC_BACKUP_TAG \
|
||||
RESTIC_BACKUP_EXCLUDE_FILE RESTIC_BACKUP_EXTRA_ARGS RESTIC_REPOSITORY RESTIC_VERBOSITY_LEVEL \
|
||||
RESTIC_RETENTION_HOURS RESTIC_RETENTION_DAYS RESTIC_RETENTION_MONTHS RESTIC_RETENTION_WEEKS RESTIC_RETENTION_YEARS
|
||||
|
||||
warn_on_missing_envvars \
|
||||
B2_ACCOUNT_ID B2_ACCOUNT_KEY B2_CONNECTIONS \
|
||||
RESTIC_PASSWORD_FILE
|
||||
|
||||
# Convert to arrays, as arrays should be used to build command lines. See https://github.com/koalaman/shellcheck/wiki/SC2086
|
||||
IFS=':' read -ra backup_paths <<< "$RESTIC_BACKUP_PATHS"
|
||||
IFS=' ' read -ra extra_args <<< "$RESTIC_BACKUP_EXTRA_ARGS"
|
||||
|
||||
# Convert to array, an preserve spaces. See #111
|
||||
backup_extra_args=( )
|
||||
if [ -n "$RESTIC_BACKUP_EXTRA_ARGS" ]; then
|
||||
while IFS= read -r -d ''; do
|
||||
backup_extra_args+=( "$REPLY" )
|
||||
done < <(xargs printf '%s\0' <<<"$RESTIC_BACKUP_EXTRA_ARGS")
|
||||
fi
|
||||
|
||||
B2_ARG=
|
||||
[ -z "${B2_CONNECTIONS+x}" ] || B2_ARG=(--option b2.connections="$B2_CONNECTIONS")
|
||||
|
||||
# If you need to run some commands before performing the backup; create this file, put them there and make the file executable.
|
||||
PRE_SCRIPT="{{ INSTALL_PREFIX }}/etc/restic/pre_backup.sh"
|
||||
test -x "$PRE_SCRIPT" && "$PRE_SCRIPT"
|
||||
|
||||
# Set up exclude files: global + path-specific ones
|
||||
# NOTE that restic will fail the backup if not all listed --exclude-files exist. Thus we should only list them if they are really all available.
|
||||
@@ -78,9 +133,9 @@ restic backup \
|
||||
--verbose="$RESTIC_VERBOSITY_LEVEL" \
|
||||
$FS_ARG \
|
||||
--tag "$RESTIC_BACKUP_TAG" \
|
||||
--option b2.connections="$B2_CONNECTIONS" \
|
||||
"${B2_ARG[@]}" \
|
||||
"${exclusion_args[@]}" \
|
||||
"${extra_args[@]}" \
|
||||
"${backup_extra_args[@]}" \
|
||||
"${backup_paths[@]}" &
|
||||
wait $!
|
||||
|
||||
@@ -90,9 +145,10 @@ wait $!
|
||||
restic forget \
|
||||
--verbose="$RESTIC_VERBOSITY_LEVEL" \
|
||||
--tag "$RESTIC_BACKUP_TAG" \
|
||||
--option b2.connections="$B2_CONNECTIONS" \
|
||||
"${B2_ARG[@]}" \
|
||||
--prune \
|
||||
--group-by "paths,tags" \
|
||||
--keep-hourly "$RESTIC_RETENTION_HOURS" \
|
||||
--keep-daily "$RESTIC_RETENTION_DAYS" \
|
||||
--keep-weekly "$RESTIC_RETENTION_WEEKS" \
|
||||
--keep-monthly "$RESTIC_RETENTION_MONTHS" \
|
||||
@@ -106,22 +162,22 @@ wait $!
|
||||
|
||||
echo "Backup & cleaning is done."
|
||||
|
||||
# (optionally) Notify about backup summary stats.
|
||||
if [ "$RESTIC_NOTIFY_BACKUP_STATS" = true ]; then
|
||||
if [ -w "$RESTIC_BACKUP_NOTIFICATION_FILE" ]; then
|
||||
echo 'Notifications are enabled: Silently computing backup summary stats...'
|
||||
# (optional) Compute backup summary stats
|
||||
if [[ -n "$RESTIC_BACKUP_STATS_DIR" || -n "$RESTIC_BACKUP_NOTIFICATION_FILE" ]]; then
|
||||
echo '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
|
||||
latest_snapshot_diff=$(restic snapshots --tag "$RESTIC_BACKUP_TAG" --latest 2 --compact \
|
||||
| grep -Ei "^[abcdef0-9]{8} " \
|
||||
| awk '{print $1}' \
|
||||
| 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
|
||||
echo "$statsMsg"
|
||||
test -n "$RESTIC_BACKUP_STATS_DIR" && logBackupStatsCsv "$snapshotId" "$added" "$removed" "$snapshot_size"
|
||||
test -n "$RESTIC_BACKUP_NOTIFICATION_FILE" && notifyBackupStats "$statsMsg"
|
||||
fi
|
||||
|
||||
32
bin/restic_check.sh
Normal file → Executable file
32
bin/restic_check.sh
Normal file → Executable file
@@ -2,8 +2,9 @@
|
||||
# Check the backups made with restic to Backblaze B2 for errors.
|
||||
# See restic_backup.sh on how this script is run (as it's analogous for this script).
|
||||
|
||||
# Exit on error, unset var, pipe failure
|
||||
set -euo pipefail
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
[[ "${TRACE-0}" =~ ^1|t|y|true|yes$ ]] && set -o xtrace
|
||||
|
||||
# Clean up lock if we are killed.
|
||||
# If killed by systemd, like $(systemctl stop restic), then it kills the whole cgroup and all it's subprocesses.
|
||||
@@ -20,15 +21,34 @@ assert_envvars() {
|
||||
local varnames=("$@")
|
||||
for varname in "${varnames[@]}"; do
|
||||
if [ -z ${!varname+x} ]; then
|
||||
printf "%s must be set for this script to work.\n\nDid you forget to source a /etc/restic/*.env.sh profile in the current shell before executing this script?\n" "$varname" >&2
|
||||
printf "%s must be set for this script to work.\n\nDid you forget to source a {{ INSTALL_PREFIX }}/etc/restic/*.env.sh profile in the current shell before executing this script?\n" "$varname" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
assert_envvars \
|
||||
B2_ACCOUNT_ID B2_ACCOUNT_KEY B2_CONNECTIONS \
|
||||
|
||||
warn_on_missing_envvars() {
|
||||
local unset_envs=()
|
||||
local varnames=("$@")
|
||||
for varname in "${varnames[@]}"; do
|
||||
if [ -z "${!varname}" ]; then
|
||||
unset_envs=("${unset_envs[@]}" "$varname")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#unset_envs[@]} -gt 0 ]; then
|
||||
printf "The following env variables are recommended, but have not been set. This script may not work as expected: %s\n" "${unset_envs[*]}" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
assert_envvars\
|
||||
RESTIC_PASSWORD_FILE RESTIC_REPOSITORY RESTIC_VERBOSITY_LEVEL
|
||||
|
||||
warn_on_missing_envvars \
|
||||
B2_ACCOUNT_ID B2_ACCOUNT_KEY B2_CONNECTIONS
|
||||
|
||||
B2_ARG=
|
||||
[ -z "${B2_CONNECTIONS+x}" ] || B2_ARG=(--option b2.connections="$B2_CONNECTIONS")
|
||||
|
||||
# Remove locks from other stale processes to keep the automated backup running.
|
||||
# NOTE nope, don't unlock like restic_backup.sh. restic_backup.sh should take precedence over this script.
|
||||
@@ -37,6 +57,6 @@ assert_envvars \
|
||||
|
||||
# Check repository for errors.
|
||||
restic check \
|
||||
--option b2.connections="$B2_CONNECTIONS" \
|
||||
"${B2_ARG[@]}" \
|
||||
--verbose="$RESTIC_VERBOSITY_LEVEL" &
|
||||
wait $!
|
||||
|
||||
78
bin/resticw
78
bin/resticw
@@ -1,18 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# @generated
|
||||
|
||||
DOC="A little wrapper over restic just to handle profiles and environment loading.
|
||||
DOC="A little wrapper over restic just to handle profiles and environment loading, with small extensions.
|
||||
|
||||
Usage:
|
||||
resticw [options] <restic_arguments_line>...
|
||||
resticw [--profile <name>] <restic_arguments_line>...
|
||||
resticw [--profile <name>] --diff-latest
|
||||
|
||||
The <restic_arguments_line> is just the regular unwrapped restic command arguments, e.g. stats latest
|
||||
The <restic_arguments_line> is just the regular unwrapped restic command arguments, e.g. \`stats latest\`.
|
||||
|
||||
Options:
|
||||
-p --profile=<name> Specify the profile to load or use default [default: default].
|
||||
--diff-latest Show latest snapshot changes: Runs \`restic diff\` after finding the latest 2 snapshots.
|
||||
|
||||
Examples:
|
||||
resticw --profile profileA snapshots
|
||||
resticw stats latest # this will use the profile: default
|
||||
resticw -p profileB --diff-latest
|
||||
|
||||
💡 You may need to run it with sudo to source the profile environment.
|
||||
"
|
||||
|
||||
# The following argument parser is generated with docopt.sh from the above docstring.
|
||||
@@ -85,35 +91,51 @@ local node_idx; ((testdepth++)) || true; for node_idx in "$@"; do
|
||||
if ! "node_$node_idx"; then left=("${initial_left[@]}"); ((testdepth--)) || true
|
||||
return 1; fi; done; if [[ $((--testdepth)) -eq 0 ]]; then
|
||||
left=("${initial_left[@]}"); for node_idx in "$@"; do "node_$node_idx"; done; fi
|
||||
return 0; }; optional() { local node_idx; for node_idx in "$@"; do
|
||||
"node_$node_idx"; done; return 0; }; oneormore() { local i=0
|
||||
local prev=${#left[@]}; while "node_$1"; do ((i++)) || true
|
||||
return 0; }; either() { local initial_left=("${left[@]}"); local best_match_idx
|
||||
local match_count; local node_idx; ((testdepth++)) || true
|
||||
for node_idx in "$@"; do if "node_$node_idx"; then
|
||||
if [[ -z $match_count || ${#left[@]} -lt $match_count ]]; then
|
||||
best_match_idx=$node_idx; match_count=${#left[@]}; fi; fi
|
||||
left=("${initial_left[@]}"); done; ((testdepth--)) || true
|
||||
if [[ -n $best_match_idx ]]; then "node_$best_match_idx"; return 0; fi
|
||||
left=("${initial_left[@]}"); return 1; }; optional() { local node_idx
|
||||
for node_idx in "$@"; do "node_$node_idx"; done; return 0; }; oneormore() {
|
||||
local i=0; local prev=${#left[@]}; while "node_$1"; do ((i++)) || true
|
||||
[[ $prev -eq ${#left[@]} ]] && break; prev=${#left[@]}; done
|
||||
if [[ $i -ge 1 ]]; then return 0; fi; return 1; }; value() { local i
|
||||
if [[ $i -ge 1 ]]; then return 0; fi; return 1; }; switch() { local i
|
||||
for i in "${!left[@]}"; do local l=${left[$i]}
|
||||
if [[ ${parsed_params[$l]} = "$2" ]]; then
|
||||
left=("${left[@]:0:$i}" "${left[@]:((i+1))}")
|
||||
[[ $testdepth -gt 0 ]] && return 0; if [[ $3 = true ]]; then
|
||||
eval "((var_$1++))" || true; else eval "var_$1=true"; fi; return 0; fi; done
|
||||
return 1; }; value() { local i; for i in "${!left[@]}"; do local l=${left[$i]}
|
||||
if [[ ${parsed_params[$l]} = "$2" ]]; then
|
||||
left=("${left[@]:0:$i}" "${left[@]:((i+1))}")
|
||||
[[ $testdepth -gt 0 ]] && return 0; local value
|
||||
value=$(printf -- "%q" "${parsed_values[$l]}"); if [[ $3 = true ]]; then
|
||||
eval "var_$1+=($value)"; else eval "var_$1=$value"; fi; return 0; fi; done
|
||||
return 1; }; stdout() { printf -- "cat <<'EOM'\n%s\nEOM\n" "$1"; }; stderr() {
|
||||
printf -- "cat <<'EOM' >&2\n%s\nEOM\n" "$1"; }; error() {
|
||||
[[ -n $1 ]] && stderr "$1"; stderr "$usage"; _return 1; }; _return() {
|
||||
printf -- "exit %d\n" "$1"; exit "$1"; }; set -e; trimmed_doc=${DOC:0:450}
|
||||
usage=${DOC:79:53}; digest=dc31d; shorts=(-p); longs=(--profile); argcounts=(1)
|
||||
node_0(){ value __profile 0; }; node_1(){ value _restic_arguments_line_ a true
|
||||
}; node_2(){ optional 0; }; node_3(){ optional 2; }; node_4(){ oneormore 1; }
|
||||
node_5(){ required 3 4; }; node_6(){ required 5; }; cat <<<' docopt_exit() {
|
||||
[[ -n $1 ]] && printf "%s\n" "$1" >&2; printf "%s\n" "${DOC:79:53}" >&2; exit 1
|
||||
}'; unset var___profile var__restic_arguments_line_; parse 6 "$@"
|
||||
local prefix=${DOCOPT_PREFIX:-''}; unset "${prefix}__profile" \
|
||||
"${prefix}_restic_arguments_line_"
|
||||
printf -- "exit %d\n" "$1"; exit "$1"; }; set -e; trimmed_doc=${DOC:0:751}
|
||||
usage=${DOC:102:105}; digest=a9466; shorts=(-p '')
|
||||
longs=(--profile --diff-latest); argcounts=(1 0); node_0(){ value __profile 0; }
|
||||
node_1(){ switch __diff_latest 1; }; node_2(){
|
||||
value _restic_arguments_line_ a true; }; node_3(){ optional 0; }; node_4(){
|
||||
oneormore 2; }; node_5(){ required 3 4; }; node_6(){ required 3 1; }; node_7(){
|
||||
either 5 6; }; node_8(){ required 7; }; cat <<<' docopt_exit() {
|
||||
[[ -n $1 ]] && printf "%s\n" "$1" >&2; printf "%s\n" "${DOC:102:105}" >&2
|
||||
exit 1; }'; unset var___profile var___diff_latest var__restic_arguments_line_
|
||||
parse 8 "$@"; local prefix=${DOCOPT_PREFIX:-''}; unset "${prefix}__profile" \
|
||||
"${prefix}__diff_latest" "${prefix}_restic_arguments_line_"
|
||||
eval "${prefix}"'__profile=${var___profile:-default}'
|
||||
eval "${prefix}"'__diff_latest=${var___diff_latest:-false}'
|
||||
if declare -p var__restic_arguments_line_ >/dev/null 2>&1; then
|
||||
eval "${prefix}"'_restic_arguments_line_=("${var__restic_arguments_line_[@]}")'
|
||||
else eval "${prefix}"'_restic_arguments_line_=()'; fi; local docopt_i=1
|
||||
[[ $BASH_VERSION =~ ^4.3 ]] && docopt_i=2; for ((;docopt_i>0;docopt_i--)); do
|
||||
declare -p "${prefix}__profile" "${prefix}_restic_arguments_line_"; done; }
|
||||
declare -p "${prefix}__profile" "${prefix}__diff_latest" \
|
||||
"${prefix}_restic_arguments_line_"; done; }
|
||||
# docopt parser above, complete command for generating this parser is `docopt.sh resticw`
|
||||
|
||||
# Parse arguments - See https://github.com/andsens/docopt.sh for the magic :)
|
||||
@@ -126,11 +148,21 @@ eval "$(docopt "$@")"
|
||||
|
||||
# Exit on error, unbound variable, pipe error
|
||||
set -euo pipefail
|
||||
ENV_DIR=$INSTALL_PREFIX/etc/restic
|
||||
ENV_DIR="{{ INSTALL_PREFIX }}/etc/restic"
|
||||
|
||||
ERR_NO_SUCH_PROFILE=2
|
||||
ERR_PROFILE_NO_READ_PERM=3
|
||||
|
||||
# Compute the latest 2 snapshots and run the diff
|
||||
latestSnapshotDiff() {
|
||||
restic snapshots --tag "$RESTIC_BACKUP_TAG" --latest 2 --compact \
|
||||
| grep -Ei "^[abcdef0-9]{8} " \
|
||||
| awk '{print $1}' \
|
||||
| tail -2 \
|
||||
| tr '\n' ' ' \
|
||||
| xargs restic diff
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
profile_file="${ENV_DIR}/${__profile}.env.sh"
|
||||
|
||||
@@ -142,6 +174,12 @@ if [[ ! -r "$profile_file" ]]; then
|
||||
fi
|
||||
|
||||
echo -e "‣ Using profile: ${__profile} -- (${profile_file})\n"
|
||||
# shellcheck disable=SC1090
|
||||
source "$profile_file"
|
||||
|
||||
# shellcheck disable=SC2154,SC1090
|
||||
source "$profile_file" && restic "${_restic_arguments_line_[@]}"
|
||||
# shellcheck disable=SC2154
|
||||
if [[ "${__diff_latest}" == true ]]; then
|
||||
latestSnapshotDiff
|
||||
else
|
||||
restic "${_restic_arguments_line_[@]}"
|
||||
fi
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
SHELL=/bin/sh
|
||||
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin/:$INSTALL_PREFIX/bin/
|
||||
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin/:{{ INSTALL_PREFIX }}/bin/
|
||||
# Order of crontab fields
|
||||
# minute hour mday month wday command
|
||||
# Reference: https://www.freebsd.org/doc/handbook/configtuning-cron.html
|
||||
# Reference: crontab(5).
|
||||
|
||||
@midnight root . $INSTALL_PREFIX/etc/restic/default.env.sh && restic_backup.sh
|
||||
@monthly root . $INSTALL_PREFIX/etc/restic/default.env.sh && restic_check.sh
|
||||
@midnight root . {{ INSTALL_PREFIX }}/etc/restic/default.env.sh && restic_backup.sh
|
||||
@monthly root . {{ INSTALL_PREFIX }}/etc/restic/default.env.sh && restic_check.sh
|
||||
|
||||
# Email notification version. Make sure bin/cron_mail is in the above $PATH
|
||||
#@midnight root . $INSTALL_PREFIX/etc/restic/default.env.sh && cron_mail restic_backup.sh
|
||||
#@monthly root . $INSTALL_PREFIX/etc/restic/default.env.sh && cron_mail restic_check.sh
|
||||
#@midnight root . {{ INSTALL_PREFIX }}/etc/restic/default.env.sh && cron_mail restic_backup.sh
|
||||
#@monthly root . {{ INSTALL_PREFIX }}/etc/restic/default.env.sh && cron_mail restic_check.sh
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# shellcheck shell=sh
|
||||
|
||||
# Global envionment variables
|
||||
# Global environment variables
|
||||
# These variables are sourced FIRST, and any values inside of *.env.sh files for
|
||||
# specific configurations will override if also defined there.
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
|
||||
# The restic repository encryption key
|
||||
export RESTIC_PASSWORD_FILE="$INSTALL_PREFIX/etc/restic/pw.txt"
|
||||
export RESTIC_PASSWORD_FILE="{{ INSTALL_PREFIX }}/etc/restic/pw.txt"
|
||||
# The global restic exclude file
|
||||
export RESTIC_BACKUP_EXCLUDE_FILE="$INSTALL_PREFIX/etc/restic/backup_exclude.txt"
|
||||
export RESTIC_BACKUP_EXCLUDE_FILE="{{ INSTALL_PREFIX }}/etc/restic/backup_exclude.txt"
|
||||
|
||||
# Backblaze B2 credentials keyID & applicationKey pair.
|
||||
# Restic environment variables are documented at https://restic.readthedocs.io/en/latest/040_backup.html#environment-variables
|
||||
@@ -30,6 +30,8 @@ export RESTIC_BACKUP_EXTRA_ARGS=
|
||||
# Override this value in a profile if needed.
|
||||
export RESTIC_VERBOSITY_LEVEL=0
|
||||
|
||||
# (optional) Desktop notifications. See restic_backup.sh for details on how to set this up.
|
||||
export RESTIC_NOTIFY_BACKUP_STATS=false
|
||||
# (optional, uncomment to enable) Backup summary stats log: snapshot size, etc. (empty/unset won't log)
|
||||
#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=
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# $ restic --repo ... --password-file ...
|
||||
|
||||
# shellcheck source=etc/restic/_global.env.sh
|
||||
. "$INSTALL_PREFIX/etc/restic/_global.env.sh"
|
||||
. "{{ INSTALL_PREFIX }}/etc/restic/_global.env.sh"
|
||||
|
||||
# Envvars below will override those in _global.env.sh if present.
|
||||
|
||||
@@ -32,6 +32,7 @@ export RESTIC_BACKUP_TAG=systemd.timer
|
||||
|
||||
# Retention policy - How many backups to keep.
|
||||
# See https://restic.readthedocs.io/en/stable/060_forget.html?highlight=month#removing-snapshots-according-to-a-policy
|
||||
export RESTIC_RETENTION_HOURS=1
|
||||
export RESTIC_RETENTION_DAYS=14
|
||||
export RESTIC_RETENTION_WEEKS=16
|
||||
export RESTIC_RETENTION_MONTHS=18
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 22 KiB |
8
scripts/devcontainer_postCreateCommand.sh
Executable file
8
scripts/devcontainer_postCreateCommand.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Devcontainer postCreateCommand.
|
||||
# Install dependencies for running this project in GitHub Codespaces.
|
||||
|
||||
set -eux
|
||||
|
||||
# For git version tagging:
|
||||
go install github.com/maykonlsf/semver-cli/cmd/semver@latest
|
||||
@@ -3,4 +3,4 @@ Description=Check if the current NetworkManager connection is metered
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=$INSTALL_PREFIX/bin/nm-unmetered-connection.sh
|
||||
ExecStart={{ INSTALL_PREFIX }}/bin/nm-unmetered-connection.sh
|
||||
|
||||
@@ -4,6 +4,7 @@ Description=Backup with restic to Backblaze B2
|
||||
#OnFailure=status-email-user@%n.service
|
||||
# Prevent backup on unmetered connection. Needs special setup. See README.md.
|
||||
#Requires=nm-unmetered-connection.service
|
||||
#After=nm-unmetered-connection.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
@@ -13,4 +14,4 @@ Environment="HOME=/root"
|
||||
# pipefail: so that redirecting stderr from the script to systemd-cat does not hide the failed command from OnFailure above.
|
||||
# Random sleep (in seconds): in the case of multiple backup profiles. Many restic instances started at the same time could case high load or network bandwith usage.
|
||||
# `systemd-cat` allows showing the restic output to the systemd journal
|
||||
ExecStart=bash -c 'set -o pipefail; ps cax | grep -q restic && sleep $(shuf -i 0-300 -n 1); source $INSTALL_PREFIX/etc/restic/%I.env.sh && $INSTALL_PREFIX/bin/restic_backup.sh 2>&1 | systemd-cat'
|
||||
ExecStart=/bin/bash -c 'set -o pipefail; ps cax | grep -q restic && sleep $(shuf -i 0-300 -n 1); source {{ INSTALL_PREFIX }}/etc/restic/%I.env.sh && {{ INSTALL_PREFIX }}/bin/restic_backup.sh 2>&1 | systemd-cat'
|
||||
|
||||
@@ -5,10 +5,11 @@ Description=Check restic backup Backblaze B2 for errors
|
||||
Conflicts=restic-backup.service
|
||||
# Prevent backup on unmetered connection. Needs special setup. See README.md.
|
||||
#Requires=nm-unmetered-connection.service
|
||||
#After=nm-unmetered-connection.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Nice=10
|
||||
# pipefail: so that redirecting stderr from the script to systemd-cat does not hide the failed command from OnFailure above.
|
||||
# `systemd-cat`: allows showing the restic output to the systemd journal
|
||||
ExecStart=bash -c 'set -o pipefail; source $INSTALL_PREFIX/etc/restic/%I.env.sh && $INSTALL_PREFIX/bin/restic_check.sh 2>&1 | systemd-cat'
|
||||
ExecStart=/bin/bash -c 'set -o pipefail; source {{ INSTALL_PREFIX }}/etc/restic/%I.env.sh && {{ INSTALL_PREFIX }}/bin/restic_check.sh 2>&1 | systemd-cat'
|
||||
|
||||
@@ -6,6 +6,6 @@ Description=Send status email for %i to user
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=$INSTALL_PREFIX/bin/systemd-email abc@gmail.com %i
|
||||
ExecStart={{ INSTALL_PREFIX }}/bin/systemd-email abc@gmail.com %i
|
||||
User=root
|
||||
Group=systemd-journal
|
||||
|
||||
Reference in New Issue
Block a user