diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1484851 --- /dev/null +++ b/Makefile @@ -0,0 +1,123 @@ + +prefix=/usr +bindir=$(prefix)/bin +libdir=$(prefix)/lib +sysconfdir=/etc +unitdir=$(libdir)/systemd/system +localstatedir=/var +cachedir=$(localstatedir)/cache/restic + +RESTIC_BIN = restic +RESTIC_USER = restic +RESTIC_GROUP = restic +RESTIC_BACKUP = $(bindir)/restic-backup +BINSCRIPTS = restic-backup restic-helper +INSTALL = install + +SERVICES = \ + restic-backup@.service \ + restic-check@.service \ + restic-forget@.service \ + restic-prune@.service \ + restic-unlock@.service + +TIMERS = \ + restic-backup-hourly@.timer \ + restic-backup-daily@.timer \ + restic-backup-weekly@.timer \ + restic-backup-monthly@.timer \ + restic-check-hourly@.timer \ + restic-check-daily@.timer \ + restic-check-weekly@.timer \ + restic-check-monthly@.timer \ + restic-forget-hourly@.timer \ + restic-forget-daily@.timer \ + restic-forget-weekly@.timer \ + restic-forget-monthly@.timer \ + restic-prune-hourly@.timer \ + restic-prune-daily@.timer \ + restic-prune-weekly@.timer \ + restic-prune-monthly@.timer + +all: $(TIMERS) $(SERVICES) $(BINSCRIPTS) + +### BINSCRIPTS +restic-backup: restic-backup.in + @echo generating $@ + @sed "s~@RESTIC_BIN@~$(bindir)/$(RESTIC_BIN)~g;s~@RESTIC_CACHE_DIR@~$(cachedir)~g" $< > $@ || rm -f $@ + +restic-helper: restic-helper.in + @echo generating $@ + @sed "s~@RESTIC_BIN@~$(bindir)/$(RESTIC_BIN)~g;s~@RESTIC_CACHE_DIR@~$(cachedir)~g" $< > $@ || rm -f $@ + +restic-tmpfiles.conf: restic-tmpfiles.conf.in + @echo generating $@ + @sed "s~@RESTIC_USER@~$(RESTIC_USER)~g;s~@RESTIC_GROUP@~$(RESTIC_GROUP)~g;s~@RESTIC_CACHE_DIR@~$(cachedir)~g" $< > $@ || rm -f $@ + +restic-sysusers.conf: restic-sysusers.conf.in + @echo generating $@ + @sed "s~@RESTIC_USER@~$(RESTIC_USER)~g;s~@RESTIC_GROUP@~$(RESTIC_GROUP)~g;s~@RESTIC_CACHE_DIR@~$(cachedir)~g" $< > $@ || rm -f $@ + +### SERVICES +restic-backup@.service: restic-backup@.service.in + @echo generating $@ + @sed "s~@RESTIC_USER@~$(RESTIC_USER)~g;s~@RESTIC_CACHE_DIR@~$(cachedir)~g;s~@RESTIC_BACKUP@~$(RESTIC_BACKUP)~g" $< > $@ || rm -f $@ + +restic-check@.service: restic-check@.service.in + @echo generating $@ + @sed "s~@RESTIC_USER@~$(RESTIC_USER)~g;s~@RESTIC_CACHE_DIR@~$(cachedir)~g;s~@RESTIC_BACKUP@~$(RESTIC_BACKUP)~g" $< > $@ || rm -f $@ + +restic-forget@.service: restic-forget@.service.in + @echo generating $@ + @sed "s~@RESTIC_USER@~$(RESTIC_USER)~g;s~@RESTIC_CACHE_DIR@~$(cachedir)~g;s~@RESTIC_BACKUP@~$(RESTIC_BACKUP)~g" $< > $@ || rm -f $@ + +restic-prune@.service: restic-prune@.service.in + @echo generating $@ + @sed "s~@RESTIC_USER@~$(RESTIC_USER)~g;s~@RESTIC_CACHE_DIR@~$(cachedir)~g;s~@RESTIC_BACKUP@~$(RESTIC_BACKUP)~g" $< > $@ || rm -f $@ + +restic-unlock@.service: restic-unlock@.service.in + @echo generating $@ + @sed "s~@RESTIC_USER@~$(RESTIC_USER)~g;s~@RESTIC_CACHE_DIR@~$(cachedir)~g;s~@RESTIC_BACKUP@~$(RESTIC_BACKUP)~g" $< > $@ || rm -f $@ + +### TIMER +restic-backup-%@.timer: restic-backup-schedule.timer + @echo generating $@ + @schedule=$(shell echo $@ | cut -f1 -d@ | cut -f3 -d-); \ + sed "s/@schedule@/$$schedule/g" $< > $@ || rm -f $@ + +restic-check-%@.timer: restic-check-schedule.timer + @echo generating $@ + @schedule=$(shell echo $@ | cut -f1 -d@ | cut -f3 -d-); \ + sed "s/@schedule@/$$schedule/g" $< > $@ || rm -f $@ + +restic-forget-%@.timer: restic-forget-schedule.timer + @echo generating $@ + @schedule=$(shell echo $@ | cut -f1 -d@ | cut -f3 -d-); \ + sed "s/@schedule@/$$schedule/g" $< > $@ || rm -f $@ + +restic-prune-%@.timer: restic-prune-schedule.timer + @echo generating $@ + @schedule=$(shell echo $@ | cut -f1 -d@ | cut -f3 -d-); \ + sed "s/@schedule@/$$schedule/g" $< > $@ || rm -f $@ + +### INSTALL +install: restic-backup restic-helper restic-tmpfiles.conf restic-sysusers.conf $(BINSCRIPTS) $(TIMERS) $(SERVICES) + $(INSTALL) -d -m 755 $(DESTDIR)$(sysconfdir)/restic + $(INSTALL) -m 644 restic-template.conf $(DESTDIR)$(sysconfdir)/restic/restic.tmpl + $(INSTALL) -d -m 755 $(DESTDIR)$(bindir) + for SCRIPTS in $(BINSCRIPTS); do \ + $(INSTALL) -m 755 $$SCRIPTS $(DESTDIR)$(bindir); \ + done + $(INSTALL) -m 755 -d $(DESTDIR)$(libdir)/sysusers.d + $(INSTALL) -m 644 restic-sysusers.conf $(DESTDIR)$(libdir)/sysusers.d/restic.conf + $(INSTALL) -m 755 -d $(DESTDIR)$(libdir)/tmpfiles.d + $(INSTALL) -m 644 restic-tmpfiles.conf $(DESTDIR)$(libdir)/tmpfiles.d/restic.conf + $(INSTALL) -m 755 -d $(DESTDIR)$(unitdir) + for unit in $(TIMERS) $(SERVICES) ; do \ + $(INSTALL) -m 644 $$unit $(DESTDIR)$(unitdir) ; \ + done + +clean: + rm -f $(BINSCRIPTS) + rm -f $(SERVICES) + rm -f $(TIMERS) diff --git a/restic-backup-schedule.timer b/restic-backup-schedule.timer new file mode 100644 index 0000000..0042500 --- /dev/null +++ b/restic-backup-schedule.timer @@ -0,0 +1,10 @@ +[Unit] +Description=@schedule@ backup of %i + +[Timer] +OnCalendar=@schedule@ +RandomizedDelaySec=500s +Unit=restic-backup@%i.service + +[Install] +WantedBy=timers.target diff --git a/restic-backup.in b/restic-backup.in new file mode 100644 index 0000000..1a9e3dc --- /dev/null +++ b/restic-backup.in @@ -0,0 +1,34 @@ +#!/usr/bin/bash + +: ${RESTIC_PRUNE_DOW:=0} +: ${RESTIC_BIN:=@RESTIC_BIN@} +: ${RESTIC_CACHE_DIR:=@RESTIC_CACHE_DIR@} + +export RESTIC_CACHE_DIR + +for required in BACKUP_DIR RESTIC_REPOSITORY; do + if [[ -z ${!required} ]]; then + echo "ERROR: $required is undefined" >&2 + exit 1 + fi +done + +( +set -e + +echo "* Starting backup of $BACKUP_DIR to $RESTIC_REPOSITORY" +$RESTIC_BIN $RESTIC_COMMON_ARGS backup $RESTIC_BACKUP_ARGS $BACKUP_DIR + +today=$(date +%w) +if (( today == RESTIC_PRUNE_DOW )); then + echo "* Pruning old backups from $BACKUP_DIR in $RESTIC_REPOSITORY" + $RESTIC_BIN $RESTIC_COMMON_ARGS forget --prune $RESTIC_FORGET_ARGS +fi +) + +retcode=$? + +echo "* Ensuring that repository $RESTIC_REPOSITORY is unlocked" +$RESTIC_BIN $RESTIC_COMMON_ARGS unlock + +exit $retcode diff --git a/restic-backup@.service.in b/restic-backup@.service.in new file mode 100644 index 0000000..020875e --- /dev/null +++ b/restic-backup@.service.in @@ -0,0 +1,12 @@ +[Unit] +Description=restic backup for %i + +[Service] +Type=oneshot +User=@RESTIC_USER@ +Environment=XDG_CACHE_HOME=@RESTIC_CACHE_DIR@ +EnvironmentFile=-/etc/restic/restic.conf +EnvironmentFile=/etc/restic/%i/restic.conf +WorkingDirectory=/etc/restic/%i +IOSchedulingClass=idle +ExecStart=@RESTIC_BACKUP@ $RESTIC_COMMON_ARGS backup $RESTIC_BACKUP_ARGS $BACKUP_DIR diff --git a/restic-check-schedule.timer b/restic-check-schedule.timer new file mode 100644 index 0000000..4831108 --- /dev/null +++ b/restic-check-schedule.timer @@ -0,0 +1,10 @@ +[Unit] +Description=@schedule@ check of %i + +[Timer] +OnCalendar=@schedule@ +RandomizedDelaySec=500s +Unit=restic-check@%i.service + +[Install] +WantedBy=timers.target diff --git a/restic-check@.service.in b/restic-check@.service.in new file mode 100644 index 0000000..d399f04 --- /dev/null +++ b/restic-check@.service.in @@ -0,0 +1,12 @@ +[Unit] +Description=restic check for %i + +[Service] +Type=oneshot +User=@RESTIC_USER@ +Environment=XDG_CACHE_HOME=@RESTIC_CACHE_DIR@ +EnvironmentFile=-/etc/restic/restic.conf +EnvironmentFile=/etc/restic/%i/restic.conf +WorkingDirectory=/etc/restic/%i +IOSchedulingClass=idle +ExecStart=@RESTIC_BACKUP@ $RESTIC_COMMON_ARGS check $RESTIC_CHECK_ARGS diff --git a/restic-forget-schedule.timer b/restic-forget-schedule.timer new file mode 100644 index 0000000..ebcb824 --- /dev/null +++ b/restic-forget-schedule.timer @@ -0,0 +1,10 @@ +[Unit] +Description=@schedule@ forget of %i + +[Timer] +OnCalendar=@schedule@ +RandomizedDelaySec=500s +Unit=restic-forget@%i.service + +[Install] +WantedBy=timers.target diff --git a/restic-forget@.service.in b/restic-forget@.service.in new file mode 100644 index 0000000..7440fef --- /dev/null +++ b/restic-forget@.service.in @@ -0,0 +1,13 @@ +[Unit] +Description=restic forget for %i +After=restic-backup@%i.service + +[Service] +Type=oneshot +User=@RESTIC_USER@ +Environment=XDG_CACHE_HOME=@RESTIC_CACHE_DIR@ +EnvironmentFile=-/etc/restic/restic.conf +EnvironmentFile=/etc/restic/%i/restic.conf +WorkingDirectory=/etc/restic/%i +IOSchedulingClass=idle +ExecStart=@RESTIC_BACKUP@ $RESTIC_COMMON_ARGS forget $RESTIC_FORGET_ARGS diff --git a/restic-helper.in b/restic-helper.in new file mode 100644 index 0000000..eb1319a --- /dev/null +++ b/restic-helper.in @@ -0,0 +1,12 @@ +#!/usr/bin/bash + +set -a +for CONF in /etc/restic/restic.conf ./restic.conf; do + if [[ -f $CONF ]]; then + echo "* reading configuration from $CONF" >&2 + . $CONF + fi +done +set +a + +exec "$@" diff --git a/restic-prune-schedule.timer b/restic-prune-schedule.timer new file mode 100644 index 0000000..2e833c4 --- /dev/null +++ b/restic-prune-schedule.timer @@ -0,0 +1,10 @@ +[Unit] +Description=@schedule@ prune of %i + +[Timer] +OnCalendar=@schedule@ +RandomizedDelaySec=500s +Unit=restic-prune@%i.service + +[Install] +WantedBy=timers.target diff --git a/restic-prune@.service.in b/restic-prune@.service.in new file mode 100644 index 0000000..1f91212 --- /dev/null +++ b/restic-prune@.service.in @@ -0,0 +1,13 @@ +[Unit] +Description=restic prune for %i +After=restic-forget@%i.service + +[Service] +Type=oneshot +User=@RESTIC_USER@ +Environment=XDG_CACHE_HOME=@RESTIC_CACHE_DIR@ +EnvironmentFile=-/etc/restic/restic.conf +EnvironmentFile=/etc/restic/%i/restic.conf +WorkingDirectory=/etc/restic/%i +IOSchedulingClass=idle +ExecStart=@RESTIC_BACKUP@ $RESTIC_COMMON_ARGS prune $RESTIC_PRUNE_ARGS diff --git a/restic-sysusers.conf b/restic-sysusers.conf new file mode 100644 index 0000000..651b272 --- /dev/null +++ b/restic-sysusers.conf @@ -0,0 +1,2 @@ +#Type Name ID GECOS Home directory Shell +u restic - - - - diff --git a/restic-sysusers.conf.in b/restic-sysusers.conf.in new file mode 100644 index 0000000..8f92e3d --- /dev/null +++ b/restic-sysusers.conf.in @@ -0,0 +1,2 @@ +#Type Name ID GECOS Home directory Shell +u @RESTIC_USER@ - - - - diff --git a/restic-template.conf b/restic-template.conf new file mode 100644 index 0000000..bf036b9 --- /dev/null +++ b/restic-template.conf @@ -0,0 +1,29 @@ +# What to back up? +BACKUP_DIR=/home + +# A pointer to your restic password. This can be set globally here +# or per-backup profile. +# RESTIC_PASSWORD_FILE=/etc/restic/password +# RESTIC_PASSWORD= + +# Where to store backups? +RESTIC_REPOSITORY=/mnt/backups + +# Arguments for s3 server (Amazon S3, Minio, Wasabi, etc.): +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= + +# Arguments for the all commands +RESTIC_COMMON_ARGS="" + +# Arguments for the backup command +RESTIC_BACKUP_ARGS="--tag home" + +# Arguments for the check command +RESTIC_CHECK_ARGS="--read-data" + +# Arguments for the forget command +RESTIC_FORGET_ARGS="--tag home --keep-daily 2 --keep-weekly 2 --keep-monthly 1" + +# Arguments for the prune command +RESTIC_PRUNE_ARGS="--tag home --keep-daily 2 --keep-weekly 2 --keep-monthly 1" diff --git a/restic-tmpfiles.conf b/restic-tmpfiles.conf new file mode 100644 index 0000000..166a40b --- /dev/null +++ b/restic-tmpfiles.conf @@ -0,0 +1,2 @@ +D /run/restic 0700 restic restic - +D /var/cache/restic 0700 restic restic - diff --git a/restic-tmpfiles.conf.in b/restic-tmpfiles.conf.in new file mode 100644 index 0000000..7cf754c --- /dev/null +++ b/restic-tmpfiles.conf.in @@ -0,0 +1,2 @@ +D /run/restic 0700 @RESTIC_USER@ @RESTIC_GROUP@ - +D @RESTIC_CACHE_DIR@ 0700 @RESTIC_USER@ @RESTIC_GROUP@ - diff --git a/restic-unlock@.service.in b/restic-unlock@.service.in new file mode 100644 index 0000000..c8af007 --- /dev/null +++ b/restic-unlock@.service.in @@ -0,0 +1,13 @@ +[Unit] +Description=restic forget for %I +After=restic-backup@%i.service restic-forget@%i.service + +[Service] +Type=oneshot +User=@RESTIC_USER@ +Environment=XDG_CACHE_HOME=@RESTIC_CACHE_DIR@ +EnvironmentFile=-/etc/restic/restic.conf +EnvironmentFile=/etc/restic/%i/restic.conf +WorkingDirectory=/etc/restic/%i +IOSchedulingClass=idle +ExecStart=@RESTIC_BACKUP@ $RESTIC_COMMON_ARGS unlock