Truly support custom PREFIX= install

* Before, doing `$ PREFIX=/usr/local make install` would install files to`/usr/local/usr/local..` which is wrong
* With this PR, files will be installed to the expected location e.g. `/usr/local/etc/restic`
* `Makefile` almost completely rewritten
   * As e.g. `default.env` would source `_global.env`, `default.env` must be edited to find the right location of `_global.env` depending on what `$PREFIX` was set to.
   * see documented build stages in the `Makefile` itself.
   *  Made sure that the rules are correct so that only modifed files are installed, not all at once unnecessarily like before.
* A sub-goal was that the [PKGBUILD](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=restic-systemd-automatic-backup#n20) for Arch should not need to do any custom install configuration, to keep everything easier to maintain. `$ make install` should work out of the box for Arch.
* Additionally added the `-b` flag to `install(1)` that makes a backup of existing `etc/restic/*` files before installing a newer version.

Fixes #49
This commit is contained in:
Erik Westrup
2022-01-18 20:22:54 +01:00
parent 3852e305b6
commit 9760cd05ec
20 changed files with 158 additions and 60 deletions

6
.gitignore vendored
View File

@@ -1,7 +1,5 @@
# Prevent check-in of these sensitive files. Instead they are generated from the corresponding *.template file. # make install
etc/restic/pw.txt /build
etc/restic/_global.env
etc/restic/default.env
# IntelliJ # IntelliJ
.idea/ .idea/

View File

@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- `resticw` wrapper for working with different profiles without the need to source the profiles first. - `resticw` wrapper for working with different profiles without the need to source the profiles first.
- `$make install` will now make a timestamped backup of any existing `/etc/restic/*` files before installing a newer version.
### Changed
- **BREAKING CHANGE** moved systemd installation with makefile from `/etc/systemd/system` to `/usr/lib/systemd/system` as this is what packages should do. This is to be able to simplify the arch [PKGBUILD](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=restic-systemd-automatic-backup) so that it does not need to do anything else than `make install`.
- If you upgrade form an existing install, you should disable and then re-enable the timer, so that the symlink is pointing to the new location of the timer.
```console
# systemctl disable restic-backup@<profile>.timer
# systemctl enable restic-backup@<profile>.timer
```
- **BREAKING CHANGE** moved script installation with makefile from `/usr/local/sbin` to `/sbin` to have a simpler interface to work with `$PREFIX`.
### Fixed
- Installation with custom `PREFIX` now works properly with Make: `$ PREFIX=/usr/local make install` whill now install everything at the expected location. With this, it's easy to use this script as non-root user on e.g. an macOS system.
## [4.0.0] - 2022-02-01 ## [4.0.0] - 2022-02-01
### Fixed ### Fixed
@@ -46,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- `restic_backup.sh` now finds `.backup_exclude` files on each backup path as intended. - `restic_backup.sh` now finds `.backup_exclude` files on each backup path as intended.
- Install executeables to `$PREFIX/sbin` instead of `$PREFIX/user/local/sbin`, so that `$ PREFIX=/usr/local make install` does what is expected.
## [1.0.1] - 2021-12-03 ## [1.0.1] - 2021-12-03
### Fixed ### Fixed

154
Makefile
View File

@@ -1,20 +1,77 @@
# Not file targets. ### Notes ###
.PHONY: help install install-scripts install-conf install-systemd uninstall # This build process is done in three stages:
# 1. copy source files to the local build directory.
# 2. replace the string "$INSTALL_PREFIX" with the value of $PREFIX
# 3. copy 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 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.
### Non-file targets ###
.PHONY: help install install-scripts install-conf install-systemd clean uninstall
### Macros ### ### Macros ###
SRCS_SCRIPTS = $(filter-out %cron_mail, $(wildcard usr/local/sbin/*)) NOW := $(shell date +%Y-%m-%d_%H:%M:%S)
# Source: https://stackoverflow.com/a/14777895/265508
ifeq ($(OS),Windows_NT)
CUR_OS := Windows
else
CUR_OS := $(shell uname)
endif
# GNU install and macOS install have incompatible command line arguments.
ifeq ($(CUR_OS),Darwin)
BAK_SUFFIX = -B .$(NOW).bak
else
BAK_SUFFIX = --suffix=.$(NOW).bak
endif
# Create parent directories of a file, if not existing.
# Reference: https://stackoverflow.com/a/25574592/265508
MKDIR_PARENTS=sh -c '\
dir=$$(dirname $$1); \
test -d $$dir || mkdir -p $$dir \
' MKDIR_PARENTS
# Source directories.
DIR_SCRIPTS = sbin
DIR_CONF = etc/restic
DIR_SYSTEMD = usr/lib/systemd/system
# Source files.
SRCS_SCRIPTS = $(filter-out %cron_mail, $(wildcard $(DIR_SCRIPTS)/*))
# $(sort) remove duplicates that comes from running make install >1 times. # $(sort) remove duplicates that comes from running make install >1 times.
SRCS_CONF = $(sort $(patsubst %.template, %, $(wildcard etc/restic/*))) SRCS_CONF = $(sort $(patsubst %.template, %, $(wildcard $(DIR_CONF)/*)))
SRCS_SYSTEMD = $(wildcard etc/systemd/system/*) SRCS_SYSTEMD = $(wildcard $(DIR_SYSTEMD)/*)
# To change the installation root path, set the PREFIX variable in your shell's environment, like:
# $ PREFIX=/usr/local make install
# $ PREFIX=/tmp/test make install
DEST_SCRIPTS = $(PREFIX)/usr/local/sbin
DEST_CONF = $(PREFIX)/etc/restic
DEST_SYSTEMD = $(PREFIX)/etc/systemd/system
INSTALLED_FILES = $(addprefix $(PREFIX)/, $(SRCS_SCRIPTS) $(SRCS_CONF) $(SRCS_SYSTEMD)) # Local build directory. Sources will be copied here, modified and then installed from this directory.
BUILD_DIR := build
BUILD_DIR_SCRIPTS = $(BUILD_DIR)/$(DIR_SCRIPTS)
BUILD_DIR_CONF = $(BUILD_DIR)/$(DIR_CONF)
BUILD_DIR_SYSTEMD = $(BUILD_DIR)/$(DIR_SYSTEMD)
# Sources copied to build directory.
BUILD_SRCS_SCRIPTS = $(addprefix $(BUILD_DIR)/, $(SRCS_SCRIPTS))
BUILD_SRCS_CONF = $(addprefix $(BUILD_DIR)/, $(SRCS_CONF))
BUILD_SRCS_SYSTEMD = $(addprefix $(BUILD_DIR)/, $(SRCS_SYSTEMD))
# Destination directories
DEST_DIR_SCRIPTS = $(PREFIX)/$(DIR_SCRIPTS)
DEST_DIR_CONF = $(PREFIX)/$(DIR_CONF)
DEST_DIR_SYSTEMD = $(PREFIX)/$(DIR_SYSTEMD)
# Destination files.
DEST_SCRIPTS = $(addprefix $(PREFIX)/, $(SRCS_SCRIPTS))
DEST_CONF = $(addprefix $(PREFIX)/, $(SRCS_CONF))
DEST_SYSTEMD = $(addprefix $(PREFIX)/, $(SRCS_SYSTEMD))
INSTALLED_FILES = $(DEST_SCRIPTS) $(DEST_CONF) $(DEST_SYSTEMD)
### Targets ### ### Targets ###
# target: all - Default target. # target: all - Default target.
@@ -24,33 +81,9 @@ all: install
help: help:
@egrep "#\starget:" [Mm]akefile | sed 's/\s-\s/\t\t\t/' | cut -d " " -f3- | sort -d @egrep "#\starget:" [Mm]akefile | sed 's/\s-\s/\t\t\t/' | cut -d " " -f3- | sort -d
# target: install - Install all files # target: clean - Remove build files.
install: install-scripts install-conf install-systemd clean:
$(RM) -r $(BUILD_DIR)
# target: install-scripts - Install executables.
install-scripts:
install -d $(DEST_SCRIPTS)
install -m 0744 $(filter-out %/resticw, $(SRCS_SCRIPTS)) $(DEST_SCRIPTS)
install -m 0755 usr/local/sbin/resticw $(DEST_SCRIPTS)
# Copy templates to new files with restricted permissions.
# Why? Because the non-template files are git-ignored to prevent that someone who clones or forks this repo checks in their sensitive data like the B2 password!
etc/restic/_global.env etc/restic/default.env etc/restic/pw.txt:
install -m 0600 $@.template $@
# target: install-conf - Install restic configuration files.
# will create these files locally only if they don't already exist
# `|` means that dependencies are order-only, i.e. only created if they don't already exist.
install-conf: | $(SRCS_CONF)
install -d $(DEST_CONF)
install -b -m 0600 $(SRCS_CONF) $(DEST_CONF)
$(RM) etc/restic/_global.env etc/restic/default.env etc/restic/pw.txt
# target: install-systemd - Install systemd timer and service files.
install-systemd:
install -d $(DEST_SYSTEMD)
install -m 0644 $(SRCS_SYSTEMD) $(DEST_SYSTEMD)
# target: uninstall - Uninstall ALL files from the install targets. # target: uninstall - Uninstall ALL files from the install targets.
uninstall: uninstall:
@@ -58,3 +91,46 @@ uninstall:
echo $(RM) $$file; \ echo $(RM) $$file; \
$(RM) $$file; \ $(RM) $$file; \
done done
# To change the installation root path, set the PREFIX variable in your shell's environment, like:
# $ PREFIX=/usr/local make install
# $ PREFIX=/tmp/test make install
# target: install - Install all files
install: install-scripts install-conf install-systemd
# Install targets - add build sources to prereqa as well, so that build dir is re-created if deleted (expected behaviour).
# target: install-scripts - Install executables.
install-scripts: $(DEST_SCRIPTS)
# target: install-conf - Install restic configuration files.
install-conf: $(DEST_CONF) $(BUILD_SRCS_CONF)
# target: install-systemd - Install systemd timer and service files.
install-systemd: $(DEST_SYSTEMD)
# Copies sources to build directory & replace "$INSTALL_PREFIX"
# dir= line needs to be in the same subshell to use shared envvars. Reference: https://stackoverflow.com/a/36419671/265508
$(BUILD_DIR)/% : %
${MKDIR_PARENTS} $@
cp $< $@
sed -i.bak -e "s|\$$INSTALL_PREFIX|$$PREFIX|g" $@; rm $@.bak
# For the destination files to be built, build-files must exist.
#$(DEST_SCRIPTS): $(BUILD_SRCS_SCRIPTS)
#$(DEST_CONF): $(BUILD_SRCS_CONF)
#$(DEST_SYSTEMD): $(BUILD_SRCS_SYSTEMD)
# Install destination script files.
$(DEST_DIR_SCRIPTS)/%: $(BUILD_DIR_SCRIPTS)/%
${MKDIR_PARENTS} $@
install -m 0744 $< $@
# Install destination conf files. Additionally backup existing files.
$(DEST_DIR_CONF)/%: $(BUILD_DIR_CONF)/%
${MKDIR_PARENTS} $@
install -m 0600 -b $(BAK_SUFFIX) $< $@
# Install destination script files.
$(DEST_DIR_SYSTEMD)/%: $(BUILD_DIR_SYSTEMD)/%
${MKDIR_PARENTS} $@
install -m 0644 $< $@

View File

@@ -52,7 +52,7 @@ Nevertheless the project should work out of the box, be minimal but still open t
* `/etc/restic/backup_exclude.txt` - List of file patterns to ignore. This will trim down your backup size and the speed of the backup a lot when done properly! * `/etc/restic/backup_exclude.txt` - List of file patterns to ignore. This will trim down your backup size and the speed of the backup a lot when done properly!
1. Initialize remote repo as described [below](#3-initialize-remote-repo) 1. Initialize remote repo as described [below](#3-initialize-remote-repo)
1. Configure [how often](https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events) back up should be made. 1. Configure [how often](https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events) back up should be made.
* Edit if needed `OnCalendar` in `/etc/systemd/system/restic-check@.timer`. * Edit if needed `OnCalendar` in `/usr/lib/systemd/system/restic-check@.timer`.
1. Enable automated backup for starting with the system (`enable` creates symlinks): 1. Enable automated backup for starting with the system (`enable` creates symlinks):
```console ```console
$ sudo systemctl start restic-backup@default.timer $ sudo systemctl start restic-backup@default.timer
@@ -91,6 +91,18 @@ $ git clone https://github.com/erikw/restic-systemd-automatic-backup.git && cd $
$ sudo make install $ sudo make install
```` ````
If you want to install everything manually, we will install files to `/etc`, `/sbin`, and not use the `$make install` command, then you need to clean up a placeholder `$INSTALL_PREFIX` in the souce files first by running:
```console
$ find etc sbin -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="/etc/restic/pw.txt"
```
This prefix is there so that make users can set a different `$PREFIX` when installing like `PREFIX=/usr/local make install`. So if we don't use the makefile, we need to remove this prefix with the command above just.
Arch Linux users can install the aur package [restic-systemd-automatic-backup](https://aur.archlinux.org/packages/restic-systemd-automatic-backup/) e.g.: Arch Linux users can install the aur package [restic-systemd-automatic-backup](https://aur.archlinux.org/packages/restic-systemd-automatic-backup/) e.g.:
```console ```console
$ yaourt -S restic-systemd-automatic-backup $ yaourt -S restic-systemd-automatic-backup
@@ -126,7 +138,7 @@ $ restic init
``` ```
## 4. Script for doing the backup ## 4. Script for doing the backup
Put this file in `/usr/local/sbin`: Put this file in `/sbin`:
* `restic_backup.sh`: A script that defines how to run the backup. The intention is that you should not need to edit this script yourself, but be able to control everything from the `*.env` profiles. * `restic_backup.sh`: A script that defines how to run the backup. The intention is that you should not need to edit this script yourself, but be able to control everything from the `*.env` profiles.
Restic support exclude files. They list file pattern paths to exclude from you backups, files that just occupy storage space, backup-time, network and money. `restic_backup.sh` allows for a few different exclude files. Restic support exclude files. They list file pattern paths to exclude from you backups, files that just occupy storage space, backup-time, network and money. `restic_backup.sh` allows for a few different exclude files.
@@ -139,7 +151,7 @@ Now see if the backup itself works, by running as root
```console ```console
$ sudo -i $ sudo -i
$ source /etc/restic/default.env $ source /etc/restic/default.env
$ /usr/local/sbin/restic_backup.sh $ /sbin/restic_backup.sh
```` ````
## 6. Verify the backup ## 6. Verify the backup
@@ -159,8 +171,7 @@ $ ls /mnt/restic
## 7. Backup automatically; systemd service + timer ## 7. Backup automatically; systemd service + timer
Now we can do the modern version of a cron-job, a systemd service + timer, to run the backup every day! Now we can do the modern version of a cron-job, a systemd service + timer, to run the backup every day!
Put these files in `/etc/systemd/system/`: Put these files in `/etc/systemd/system` (note that the Makefile installs as package to `/usr/lib/systemd/system`)
* `restic-backup@.service`: A service that calls the backup script with the specified profile. The profile is specified * `restic-backup@.service`: A service that calls the backup script with the specified profile. The profile is specified
by the value after `@` when running it (see below). by the value after `@` when running it (see below).
* `restic-backup@.timer`: A timer that starts the former backup every day (same thing about profile here). * `restic-backup@.timer`: A timer that starts the former backup every day (same thing about profile here).
@@ -204,7 +215,7 @@ $ journalctl -f -u restic-backup@default.service
## 8. Email notification on failure ## 8. Email notification on failure
We want to be aware when the automatic backup fails, so we can fix it. Since my laptop does not run a mail server, I went for a solution to set up my laptop to be able to send emails with [postfix via my Gmail](https://easyengine.io/tutorials/linux/ubuntu-postfix-gmail-smtp/). Follow the instructions over there. We want to be aware when the automatic backup fails, so we can fix it. Since my laptop does not run a mail server, I went for a solution to set up my laptop to be able to send emails with [postfix via my Gmail](https://easyengine.io/tutorials/linux/ubuntu-postfix-gmail-smtp/). Follow the instructions over there.
Put this file in `/usr/local/sbin`: Put this file in `/sbin`:
* `systemd-email`: Sends email using sendmail(1). This script also features time-out for not spamming Gmail servers and getting my account blocked. * `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/`: Put this files in `/etc/systemd/system/`:
@@ -216,7 +227,7 @@ As you maybe noticed already before, `restic-backup.service` is configured to st
## 9. Optional: automated backup checks ## 9. Optional: automated backup checks
Once in a while it can be good to do a health check of the remote repository, to make sure it's not getting corrupt. This can be done with `$ restic check`. Once in a while it can be good to do a health check of the remote repository, to make sure it's not getting corrupt. This can be done with `$ restic check`.
There is companion scripts, service and timer (`*check*`) to restic-backup.sh that checks the restic backup for errors; look in the repo in `etc/systemd/system` and `usr/local/sbin` and copy what you need over to their corresponding locations. There is companion scripts, service and timer (`*check*`) to restic-backup.sh that checks the restic backup for errors; look in the repo in `usr/lib/systemd/system/` and `sbin/` and copy what you need over to their corresponding locations.
```console ```console
$ sudo -i $ sudo -i
@@ -245,7 +256,7 @@ straightforward (it needs to run with sudo to read environment). Just run:
If you want to run an all-classic cron job instead, do like this: If you want to run an all-classic cron job instead, do like this:
* `etc/cron.d/restic`: Depending on your system's cron, put this in `/etc/cron.d/` or similar, or copy the contents to $(sudo crontab -e). The format of this file is tested under FreeBSD, and might need adaptions depending on your cron. * `etc/cron.d/restic`: Depending on your system's cron, put this in `/etc/cron.d/` or similar, or copy the contents to $(sudo crontab -e). The format of this file is tested under FreeBSD, and might need adaptions depending on your cron.
* `usr/local/sbin/cron_mail`: A wrapper for running cron jobs, that sends output of the job as an email using the mail(1) command. * `sbin/cron_mail`: A wrapper for running cron jobs, that sends output of the job as an email using the mail(1) command.
# Uninstall # Uninstall

View File

@@ -1,5 +1,5 @@
SHELL=/bin/sh SHELL=/bin/sh
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin/:/usr/local/sbin/ PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin/:$INSTALL_PREFIX/sbin/
# Order of crontab fields # Order of crontab fields
# minute hour mday month wday command # minute hour mday month wday command
# Reference: https://www.freebsd.org/doc/handbook/configtuning-cron.html # Reference: https://www.freebsd.org/doc/handbook/configtuning-cron.html

View File

@@ -8,9 +8,9 @@
# The restic repository encryption key # The restic repository encryption key
export RESTIC_PASSWORD_FILE="/etc/restic/pw.txt" export RESTIC_PASSWORD_FILE="$INSTALL_PREFIX/etc/restic/pw.txt"
# The global restic exclude file # The global restic exclude file
export RESTIC_BACKUP_EXCLUDE_FILE="/etc/restic/backup_exclude.txt" export RESTIC_BACKUP_EXCLUDE_FILE="$INSTALL_PREFIX/etc/restic/backup_exclude.txt"
# Backblaze B2 credentials keyID & applicationKey pair. # Backblaze B2 credentials keyID & applicationKey pair.
# Restic environment variables are documented at https://restic.readthedocs.io/en/latest/040_backup.html#environment-variables # Restic environment variables are documented at https://restic.readthedocs.io/en/latest/040_backup.html#environment-variables

View File

@@ -9,7 +9,7 @@
# Thus you don't have to provide all the arguments like # Thus you don't have to provide all the arguments like
# $ restic --repo ... --password-file ... # $ restic --repo ... --password-file ...
source /etc/restic/_global.env source $INSTALL_PREFIX/etc/restic/_global.env
# Below envvar will override those in _global.env # Below envvar will override those in _global.env

View File

@@ -3,5 +3,4 @@ Description=Check if the current NetworkManager connection is metered
[Service] [Service]
Type=oneshot Type=oneshot
ExecStart=/usr/local/sbin/nm-unmetered-connection.sh ExecStart=$INSTALL_PREFIX/sbin/nm-unmetered-connection.sh

View File

@@ -10,4 +10,4 @@ Nice=10
Environment="HOME=/root" Environment="HOME=/root"
# The random sleep (in seconds) is in the case of multiple backup profiles. Many restic instances started at the same time could case high load or network bandwith usage. # The random sleep (in seconds) is 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 # `systemd-cat` allows showing the restic output to the systemd journal
ExecStart=bash -c 'sleep $(shuf -i 0-300 -n 1) && source /etc/restic/%I.env && /usr/local/sbin/restic_backup.sh | systemd-cat' ExecStart=bash -c 'sleep $(shuf -i 0-300 -n 1) && source $INSTALL_PREFIX/etc/restic/%I.env && $INSTALL_PREFIX/sbin/restic_backup.sh | systemd-cat'

View File

@@ -8,4 +8,4 @@ Requires=nm-unmetered-connection.service
Type=simple Type=simple
Nice=10 Nice=10
# `systemd-cat` allows showing the restic output to the systemd journal # `systemd-cat` allows showing the restic output to the systemd journal
ExecStart=bash -c 'source /etc/restic/%I.env && /usr/local/sbin/restic_check.sh | systemd-cat' ExecStart=bash -c 'source $INSTALL_PREFIX/etc/restic/%I.env && $INSTALL_PREFIX/sbin/restic_check.sh | systemd-cat'

View File

@@ -6,6 +6,6 @@ Description=Send status email for %i to user
[Service] [Service]
Type=oneshot Type=oneshot
ExecStart=/usr/local/sbin/systemd-email abc@gmail.com %i ExecStart=$INSTALL_PREFIX/sbin/systemd-email abc@gmail.com %i
User=root User=root
Group=systemd-journal Group=systemd-journal