diff --git a/.gitignore b/.gitignore index 9ea865d..7fa0aa3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ -# Prevent check-in of these sensitive files. Instead they are generated from the corresponding *.template file. -etc/restic/pw.txt -etc/restic/_global.env -etc/restic/default.env +# make install +/build # IntelliJ .idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index a84ccdd..8ffd3ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - `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@.timer + # systemctl enable restic-backup@.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 ### Fixed @@ -46,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - `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 ### Fixed diff --git a/Makefile b/Makefile index c8b81ed..40c5d6b 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,77 @@ -# Not file targets. -.PHONY: help install install-scripts install-conf install-systemd uninstall +### Notes ### +# 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 ### -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. -SRCS_CONF = $(sort $(patsubst %.template, %, $(wildcard etc/restic/*))) -SRCS_SYSTEMD = $(wildcard etc/systemd/system/*) +SRCS_CONF = $(sort $(patsubst %.template, %, $(wildcard $(DIR_CONF)/*))) +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 ### # target: all - Default target. @@ -24,33 +81,9 @@ all: install help: @egrep "#\starget:" [Mm]akefile | sed 's/\s-\s/\t\t\t/' | cut -d " " -f3- | sort -d -# target: install - Install all files -install: install-scripts install-conf install-systemd - - -# 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: clean - Remove build files. +clean: + $(RM) -r $(BUILD_DIR) # target: uninstall - Uninstall ALL files from the install targets. uninstall: @@ -58,3 +91,46 @@ uninstall: echo $(RM) $$file; \ $(RM) $$file; \ 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 $< $@ diff --git a/README.md b/README.md index cb0a511..90374be 100644 --- a/README.md +++ b/README.md @@ -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! 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. - * 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): ```console $ 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 ```` +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.: ```console $ yaourt -S restic-systemd-automatic-backup @@ -126,7 +138,7 @@ $ restic init ``` ## 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 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 $ sudo -i $ source /etc/restic/default.env -$ /usr/local/sbin/restic_backup.sh +$ /sbin/restic_backup.sh ```` ## 6. Verify the backup @@ -159,8 +171,7 @@ $ ls /mnt/restic ## 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! -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 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). @@ -204,7 +215,7 @@ $ journalctl -f -u restic-backup@default.service ## 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. -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. 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 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 $ 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: * `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 diff --git a/etc/cron.d/restic b/etc/cron.d/restic index b510fc0..eca6735 100644 --- a/etc/cron.d/restic +++ b/etc/cron.d/restic @@ -1,5 +1,5 @@ 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 # minute hour mday month wday command # Reference: https://www.freebsd.org/doc/handbook/configtuning-cron.html diff --git a/etc/restic/_global.env.template b/etc/restic/_global.env similarity index 88% rename from etc/restic/_global.env.template rename to etc/restic/_global.env index 5bd7c6c..b70a154 100644 --- a/etc/restic/_global.env.template +++ b/etc/restic/_global.env @@ -8,9 +8,9 @@ # 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 -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. # Restic environment variables are documented at https://restic.readthedocs.io/en/latest/040_backup.html#environment-variables diff --git a/etc/restic/default.env.template b/etc/restic/default.env similarity index 97% rename from etc/restic/default.env.template rename to etc/restic/default.env index 967d390..eec4e1a 100644 --- a/etc/restic/default.env.template +++ b/etc/restic/default.env @@ -9,7 +9,7 @@ # Thus you don't have to provide all the arguments like # $ restic --repo ... --password-file ... -source /etc/restic/_global.env +source $INSTALL_PREFIX/etc/restic/_global.env # Below envvar will override those in _global.env diff --git a/etc/restic/pw.txt.template b/etc/restic/pw.txt similarity index 100% rename from etc/restic/pw.txt.template rename to etc/restic/pw.txt diff --git a/usr/local/sbin/cron_mail b/sbin/cron_mail similarity index 100% rename from usr/local/sbin/cron_mail rename to sbin/cron_mail diff --git a/usr/local/sbin/nm-unmetered-connection.sh b/sbin/nm-unmetered-connection.sh similarity index 100% rename from usr/local/sbin/nm-unmetered-connection.sh rename to sbin/nm-unmetered-connection.sh diff --git a/usr/local/sbin/restic_backup.sh b/sbin/restic_backup.sh similarity index 100% rename from usr/local/sbin/restic_backup.sh rename to sbin/restic_backup.sh diff --git a/usr/local/sbin/restic_check.sh b/sbin/restic_check.sh similarity index 100% rename from usr/local/sbin/restic_check.sh rename to sbin/restic_check.sh diff --git a/usr/local/sbin/resticw b/sbin/resticw similarity index 100% rename from usr/local/sbin/resticw rename to sbin/resticw diff --git a/usr/local/sbin/systemd-email b/sbin/systemd-email similarity index 100% rename from usr/local/sbin/systemd-email rename to sbin/systemd-email diff --git a/etc/systemd/system/nm-unmetered-connection.service b/usr/lib/systemd/system/nm-unmetered-connection.service similarity index 63% rename from etc/systemd/system/nm-unmetered-connection.service rename to usr/lib/systemd/system/nm-unmetered-connection.service index e2ce521..9ae42d8 100644 --- a/etc/systemd/system/nm-unmetered-connection.service +++ b/usr/lib/systemd/system/nm-unmetered-connection.service @@ -3,5 +3,4 @@ Description=Check if the current NetworkManager connection is metered [Service] Type=oneshot -ExecStart=/usr/local/sbin/nm-unmetered-connection.sh - +ExecStart=$INSTALL_PREFIX/sbin/nm-unmetered-connection.sh diff --git a/etc/systemd/system/restic-backup@.service b/usr/lib/systemd/system/restic-backup@.service similarity index 77% rename from etc/systemd/system/restic-backup@.service rename to usr/lib/systemd/system/restic-backup@.service index a41b156..84d46d9 100644 --- a/etc/systemd/system/restic-backup@.service +++ b/usr/lib/systemd/system/restic-backup@.service @@ -10,4 +10,4 @@ Nice=10 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. # `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' diff --git a/etc/systemd/system/restic-backup@.timer b/usr/lib/systemd/system/restic-backup@.timer similarity index 100% rename from etc/systemd/system/restic-backup@.timer rename to usr/lib/systemd/system/restic-backup@.timer diff --git a/etc/systemd/system/restic-check@.service b/usr/lib/systemd/system/restic-check@.service similarity index 70% rename from etc/systemd/system/restic-check@.service rename to usr/lib/systemd/system/restic-check@.service index 6c6f745..30eca31 100644 --- a/etc/systemd/system/restic-check@.service +++ b/usr/lib/systemd/system/restic-check@.service @@ -8,4 +8,4 @@ Requires=nm-unmetered-connection.service Type=simple Nice=10 # `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' diff --git a/etc/systemd/system/restic-check@.timer b/usr/lib/systemd/system/restic-check@.timer similarity index 100% rename from etc/systemd/system/restic-check@.timer rename to usr/lib/systemd/system/restic-check@.timer diff --git a/etc/systemd/system/status-email-user@.service b/usr/lib/systemd/system/status-email-user@.service similarity index 82% rename from etc/systemd/system/status-email-user@.service rename to usr/lib/systemd/system/status-email-user@.service index a2897ed..697bad2 100644 --- a/etc/systemd/system/status-email-user@.service +++ b/usr/lib/systemd/system/status-email-user@.service @@ -6,6 +6,6 @@ Description=Send status email for %i to user [Service] Type=oneshot -ExecStart=/usr/local/sbin/systemd-email abc@gmail.com %i +ExecStart=$INSTALL_PREFIX/sbin/systemd-email abc@gmail.com %i User=root Group=systemd-journal