48 Commits
v7.4.0 ... main

Author SHA1 Message Date
Erik Westrup
4158ee155a Update devcontainer.json 2025-05-02 04:05:47 -07:00
Erik Westrup
248d8f7d0a Update README.md 2025-04-21 10:11:01 -07:00
Erik Westrup
3797979dd3 Update linter.yml 2025-04-21 10:09:51 -07:00
Erik Westrup
232dc7e90f Update README.md 2025-03-12 00:01:44 -07:00
Erik Westrup
478ea152ac Use markdown alerts 2025-03-12 00:01:14 -07:00
Erik Westrup
db0a00bdaa Update README.md 2025-03-04 23:19:27 -08:00
Erik Westrup
679569f633 init semver-cli 2025-03-04 16:40:47 +00:00
Erik Westrup
3089709038 Add semver-cli and devcontainers 2025-03-04 15:14:59 +00:00
Erik Westrup
d36a5e850d Update README.md 2025-02-12 10:19:40 -08:00
Erik Westrup
950564d433 Update README.md
Fixes #143
2025-02-09 02:52:08 -08:00
Erik Westrup
43f00b699c Update README.md 2025-02-04 22:30:36 -08:00
Erik Westrup
aa22ced34a Update README.md 2025-02-04 22:30:09 -08:00
Erik Westrup
5f9a0ba5ad Update README sections screenshot 2025-02-05 06:28:42 +00:00
Erik Westrup
01076031c4 Replace broken tokei SLOC 2025-01-16 23:21:17 -08:00
vwkd
b96f01e823 Update README.md (#142) 2024-11-21 05:22:14 +05:45
Erik Westrup
d6f512cb56 Update README.md 2024-10-12 01:42:28 +05:45
giuaig
4deb4481cc FIX Readme typo on 'Email Notification' (#132) 2023-12-14 03:09:11 +05:30
Gerard Bosch
de28285741 fix: Change INSTALL_PREFIX placeholder notation to templating notation (#131)
* refactor: Change INSTALL_PREFIX placeholder notation to templating notation

The former shell notation `$INSTALL_PREFIX` could result confusing. As
this is a placeholder that's replaced in the `make`, we switch it to a
template notation `{{ INSTALL_PREFIX }}`.

* docs: Small fix on README/manual setup

* docs(email-notif): Document the `{{ INSTALL_PREFIX }}` placeholder

co-authored-by: giuaig <13609224+giuaig@users.noreply.github.com>

* Update README.md

Co-authored-by: Gerard Bosch <30733556+gerardbosch@users.noreply.github.com>

---------

Co-authored-by: giuaig <13609224+giuaig@users.noreply.github.com>
Co-authored-by: Erik Westrup <erik.westrup@icloud.com>
2023-12-13 23:49:41 +05:30
Gerard Bosch
849181bfb8 docs: Update README with resticw last addition (#130) 2023-12-12 20:49:35 +05:30
Gerard Bosch
9f9a4044a9 feat(resticw): Add a switch to run a diff on the latest snapshot (#129)
There's no obvious way to simply run a restic diff for the latest
snapshot against the previous one.

This simplifies the task to inspect the last snapshot changes. The user
only needs to run `sudo resticw --diff-latest` (optionally specifying a
profile).
2023-12-12 20:26:09 +05:30
Erik Westrup
a001840c0e Update README.md 2023-12-12 20:09:35 +05:30
Erik Westrup
e28973a103 Project is feature complete! 2023-12-12 20:08:39 +05:30
Gerard Bosch
69e21d586c 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
2023-12-12 20:05:13 +05:30
Erik Westrup
e681b8fb6a Style fix 2023-11-01 15:27:08 +01:00
Erik Westrup
e5e5cea35e Windows: prevent backing up current directory
Don't process the array if there's no values to prevent issues on
Windows. See #125

Fixes #125
2023-11-01 15:22:31 +01:00
Erik Westrup
1c0ba09d70 Install pwsh with scoop
Fixes #124
2023-10-31 09:19:23 +01:00
Erik Westrup
48ec2f9cf7 remove echo 2023-10-31 09:13:31 +01:00
Erik Westrup
b363e736d1 format 2023-10-31 08:20:51 +01:00
Erik Westrup
f1b29db5fd format 2023-10-31 08:20:24 +01:00
Erik Westrup
2973f64dd1 Add debugging section 2023-10-31 08:15:41 +01:00
Erik Westrup
4e6afce8d2 Add stale action 2023-10-03 19:17:23 +02:00
Erik Westrup
2873f6a315 rename var for clarity 2023-09-25 18:34:47 +02:00
Erik Westrup
3cdf1503ae Revert "Allow extra args for non-backup commands (#115)"
This reverts commit 2406f078ef.

As of #120
2023-09-25 18:33:33 +02:00
Erik Westrup
3f05091edb Revert "Update README.md: add $RESTIC_EXTRA_ARGS (#118)" (#119)
This reverts commit 6d154890e7.
2023-08-02 09:28:39 +02:00
Sven Gottwald
6d154890e7 Update README.md: add $RESTIC_EXTRA_ARGS (#118)
Add `$RESTIC_EXTRA_ARGS` to all restic calls.
2023-08-02 09:26:47 +02:00
Erik Westrup
a870259eb2 Debug scripts with TRACE envvar 2023-06-02 22:32:09 +02:00
nettnikl
2406f078ef Allow extra args for non-backup commands (#115)
* Allow extra args for non-backup commands

* Add example in doc

* Include global args in backup command

* Fix indent

* Restore set

* Modified: CHANGELOG.md

---------

Co-authored-by: Erik Westrup <erik.westrup@gmail.com>
2023-06-02 22:28:52 +02:00
nettnikl
e072b6db14 Fix invalid syntax (#114) 2023-05-24 18:42:55 +02:00
Erik Westrup
184e426ec8 Allow spaces in extra args (#112)
* Allow spaces in extra args

Fixes #111

* Fix linter

* Preserve space by iterating
2023-05-21 23:19:43 +02:00
Erik Westrup
20b5fd301e Modified: CHANGELOG.md 2023-05-21 10:32:10 +02:00
Erik Westrup
0dcb315296 Make check work with 'set -u' 2023-05-21 10:31:19 +02:00
nettnikl
e7fe75ca47 Make one env optional and warn about optional missing envs (#110)
* Make one env optional and warn about optional missing envs

* Restrict visibility of var

* Fix warning

* Dont print empty warning list
2023-05-21 10:31:11 +02:00
Erik Westrup
085d722747 Add lang shield 2023-03-26 23:45:15 +02:00
Erik Westrup
91ddab850b Clean up linter.yml 2023-03-08 12:06:38 +01:00
Erik Westrup
46ed2e4af8 Ignore resticw from linter 2023-03-08 11:49:14 +01:00
Erik Westrup
620acf82cf Ignore resticw from linter 2023-03-08 11:45:54 +01:00
Erik Westrup
803f5605a7 Ignore resticw from linter 2023-03-08 11:43:29 +01:00
Erik Westrup
987bc39d00 Ignore resticw from linter 2023-03-08 11:39:13 +01:00
23 changed files with 364 additions and 137 deletions

View 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"
]
}
}
}

View File

@@ -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
View 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
View File

@@ -0,0 +1,4 @@
alpha: 0
beta: 0
rc: 0
release: v7.4.0

View File

@@ -5,6 +5,11 @@ 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

View File

@@ -12,12 +12,12 @@
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>source $INSTALL_PREFIX/etc/restic/default.env.sh &amp;&amp; $INSTALL_PREFIX/bin/restic_backup.sh >>$HOME/$LOG_OUT 2>>$HOME/$LOG_ERR</string>
<string>source {{ INSTALL_PREFIX }}/etc/restic/default.env.sh &amp;&amp; {{ 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>

View File

@@ -8,12 +8,12 @@
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>source $INSTALL_PREFIX/etc/restic/default.env.sh &amp;&amp; $INSTALL_PREFIX/bin/restic_check.sh >>$HOME/$LOG_OUT 2>>$HOME/$LOG_ERR</string>
<string>source {{ INSTALL_PREFIX }}/etc/restic/default.env.sh &amp;&amp; {{ 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>

View File

@@ -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)/%

129
README.md
View File

@@ -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*
[![GitHub Stars](https://img.shields.io/github/stars/erikw/restic-automatic-backup-scheduler?style=social)](#)
[![GitHub Forks](https://img.shields.io/github/forks/erikw/restic-automatic-backup-scheduler?style=social)](#)
<br>
[![Lint Code Base](https://github.com/erikw/restic-automatic-backup-scheduler/actions/workflows/linter.yml/badge.svg)](https://github.com/erikw/restic-automatic-backup-scheduler/actions/workflows/linter.yml)
[![Lint](https://github.com/erikw/restic-automatic-backup-scheduler/actions/workflows/linter.yml/badge.svg)](https://github.com/erikw/restic-automatic-backup-scheduler/actions/workflows/linter.yml)
[![Latest tag](https://img.shields.io/github/v/tag/erikw/restic-automatic-backup-scheduler)](https://github.com/erikw/restic-automatic-backup-scheduler/tags)
[![AUR version](https://img.shields.io/aur/version/restic-automatic-backup-scheduler)](https://aur.archlinux.org/packages/restic-automatic-backup-scheduler/)
[![AUR maintainer](https://img.shields.io/aur/maintainer/restic-automatic-backup-scheduler?label=AUR%20maintainer)](https://aur.archlinux.org/packages/restic-automatic-backup-scheduler/)
@@ -14,7 +14,8 @@
[![Closed PRs](https://img.shields.io/github/issues-pr-closed/erikw/restic-automatic-backup-scheduler?color=success)](https://github.com/erikw/restic-automatic-backup-scheduler/pulls?q=is%3Apr+is%3Aclosed)
[![License](https://img.shields.io/badge/license-BSD--3-blue)](LICENSE)
[![OSS Lifecycle](https://img.shields.io/osslifecycle/erikw/restic-automatic-backup-scheduler)](https://github.com/Netflix/osstracker)
[![SLOC](https://img.shields.io/tokei/lines/github/erikw/restic-automatic-backup-scheduler?logo=codefactor&logoColor=lightgrey)](#)
[![SLOC](https://sloc.xyz/github/erikw/restic-automatic-backup-scheduler?lower=true)](#)
[![Top programming languages used](https://img.shields.io/github/languages/top/erikw/restic-automatic-backup-scheduler)](#)
<br>
[![Contributors](https://img.shields.io/github/contributors/erikw/restic-automatic-backup-scheduler)](https://github.com/erikw/restic-automatic-backup-scheduler/graphs/contributors) including these top contributors:
@@ -22,26 +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.
![README Sections](img/readme_sections.png)
***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
@@ -62,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.
@@ -112,7 +125,8 @@ 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 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).
> [!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 a LaunchAgent to be run as your normal user for starting regular backups.
@@ -136,6 +150,7 @@ Many Linux distributions nowadays use [Systemd](https://en.wikipedia.org/wiki/Sy
```
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:
@@ -206,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
@@ -258,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.
@@ -305,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
@@ -441,12 +457,18 @@ To create a different backup and use you can do:
# 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:
@@ -455,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)
@@ -475,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
```
@@ -509,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
@@ -526,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 |
@@ -544,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
@@ -556,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)

View File

@@ -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."

View File

@@ -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

113
bin/restic_backup.sh Normal file → Executable file
View 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,24 +29,75 @@ 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 $INSTALL_PREFIX/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"
PRE_SCRIPT="{{ INSTALL_PREFIX }}/etc/restic/pre_backup.sh"
test -x "$PRE_SCRIPT" && "$PRE_SCRIPT"
# Set up exclude files: global + path-specific ones
@@ -81,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 $!
@@ -93,7 +145,7 @@ 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" \
@@ -110,23 +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}' \
| 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
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
View 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 $INSTALL_PREFIX/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 $!

View File

@@ -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

View File

@@ -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

View File

@@ -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=

View 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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View 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

View File

@@ -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

View File

@@ -14,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=/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'
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'

View File

@@ -12,4 +12,4 @@ 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=/bin/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'

View File

@@ -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