Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,52 @@ brews:
system "#{bin}/resticprofile", "backup", "testfile"
system "#{bin}/resticprofile", "restore", "latest", "-t", "#{testpath}/restore"
assert compare_file "testfile", "#{testpath}/restore/testfile"

nfpms:
-
builds:
- resticprofile_targz
formats:
- apk
- deb
- rpm
vendor: "creativeprojects"
homepage: "https://github.com/creativeprojects"
maintainer: "[email protected]"
description: "Configuration profiles for restic backup"
license: "GPL-3.0-only"
file_name_template: "{{.ProjectName}}_{{.Version}}-{{.Arch}}"
replacements:
amd64: 64bit
386: 32bit
arm: ARM
arm64: ARM64
linux: Linux
dependencies:
- restic
bindir: "/usr/local/bin"
scripts:
postinstall: "contrib/posix/post-install.sh"
contents:
- { type: config, src: contrib/posix/profiles.conf, dst: /etc/resticprofile/profiles.conf.dist }
- { type: config, src: contrib/posix/conf.d/backup.conf, dst: /etc/resticprofile/conf.d/backup.conf.dist }
- { type: config, src: contrib/posix/conf.d/check.conf, dst: /etc/resticprofile/conf.d/check.conf.dist }
- { type: config, src: contrib/posix/conf.d/hooks.conf, dst: /etc/resticprofile/conf.d/hooks.conf.dist }
- { type: config, src: contrib/posix/conf.d/metrics.conf, dst: /etc/resticprofile/conf.d/metrics.conf.dist }
- { type: config, src: contrib/posix/conf.d/prune.conf, dst: /etc/resticprofile/conf.d/prune.conf.dist }
- { type: config, src: contrib/posix/conf.d/z_overrides.conf, dst: /etc/resticprofile/conf.d/z_overrides.conf.dist }
- { type: config, src: contrib/posix/profiles.d/fs-snapshot.yaml.sample, dst: /etc/resticprofile/profiles.d/fs-snapshot.yaml.sample }
- { type: config, src: contrib/posix/profiles.d/minimal.conf.sample, dst: /etc/resticprofile/profiles.d/minimal.conf.sample }
- { type: config, src: contrib/posix/profiles.d/minimal.yaml.sample, dst: /etc/resticprofile/profiles.d/minimal.yaml.sample }
- { type: config, src: contrib/posix/profiles.d/system.conf, dst: /etc/resticprofile/profiles.d/system.conf.dist }
- { type: config, src: contrib/posix/repository.d/default.conf, dst: /etc/resticprofile/repository.d/default.conf.dist }
- { type: config, src: contrib/posix/repository.d/other.conf.sample, dst: /etc/resticprofile/repository.d/other.conf.sample }
- { type: config, src: contrib/posix/templates/default-host.conf, dst: /etc/resticprofile/templates/default-host.conf.dist }
- { type: config, src: contrib/posix/templates/default-tags.conf, dst: /etc/resticprofile/templates/default-tags.conf.dist }
- { type: config, src: contrib/posix/templates/systemd.timer.in, dst: /etc/resticprofile/templates/systemd.timer.in.dist }
- { type: config, src: contrib/posix/templates/systemd.unit.in, dst: /etc/resticprofile/templates/systemd.unit.in.dist }
- src: contrib/posix/resticprofile-send-error.rc
dst: /etc/resticprofile/resticprofile-send-error.rc.dist
- src: contrib/notification-scripts/resticprofile-send-error.sh
dst: /usr/local/bin/resticprofile-send-error
file_info: { mode: 0755, owner: root, group: root }
Original file line number Diff line number Diff line change
@@ -1,55 +1,31 @@
# Send an email on error (systemd schedule)
# Email with failure details - "resticprofile-send-error.sh"

In `profiles.yaml` you set:

```yaml
default:
...
run-after-fail:
- 'resticprofile-send-error.sh [email protected]'
- 'resticprofile-send-error.sh -s [email protected]'
```

With `/usr/local/bin/resticprofile-send-error.sh` being:
Usage:

```sh
#!/usr/bin/env bash
[[ -z "${PROFILE_NAME}" ]] || sendmail -t <<ERRMAIL
To: $1
From: "resticprofile $(hostname -f)" <$USER@$(hostname -f)>
Subject: restic failed: ${PROFILE_COMMAND} "${PROFILE_NAME}"
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=UTF-8

${ERROR}

----
COMMANDLINE:

${ERROR_COMMANDLINE}

----
STDERR:

${ERROR_STDERR}

----
DETAILS:

$(systemctl status --full "resticprofile-${PROFILE_COMMAND}@profile-${PROFILE_NAME}")

----
CONFIG:

$(resticprofile --name "${PROFILE_NAME}" show)

ERRMAIL
exit 0
```
resticprofile-send-error.sh [options] user1@domain user2@domain ...
Options:
-s Only send mail when operating on schedule (RESTICPROFILE_ON_SCHEDULE=1)
-o name,.. Only send mail when PROFILE_NAME is in the list of specified names
-c command Set the profile command (instead of PROFILE_COMMAND)
-n name Set the profile name (instead of PROFILE_NAME)
-p Print mail to stdout instead of sending it
-f Send mail even when no profile name is specified
```

## Quick installation

```sh
curl -ssL https://github.com/creativeprojects/resticprofile/raw/master/contrib/systemd/resticprofile-send-error.sh \
curl -ssL https://github.com/creativeprojects/resticprofile/raw/master/contrib/notification-scripts/resticprofile-send-error.sh \
> /usr/local/bin/resticprofile-send-error.sh \
&& chmod +x /usr/local/bin/resticprofile-send-error.sh
```
Expand All @@ -64,7 +40,7 @@ In this example, the failure is caused by a custom pre-script complaining about
Date: Fri, 23 Apr 2021 23:25:03 +0200
To: [email protected]
From: "resticprofile hyper1.domain.tl" <[email protected]>
Subject: restic failed: backup "vms"
Subject: restic failed: "backup" in "vms"

run-before backup on profile 'vms': exit status 1

Expand Down
124 changes: 124 additions & 0 deletions contrib/notification-scripts/resticprofile-send-error.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
#
# Error notification sendmail script
#
help() {
cat - <<HELP
Usage $1 [options] user1@domain user2@domain ...
Options:
-s Only send mail when operating on schedule (RESTICPROFILE_ON_SCHEDULE=1)
-o name,.. Only send mail when PROFILE_COMMAND is in the list of specified names
-c command Set the profile command (instead of PROFILE_COMMAND)
-n name Set the profile name (instead of PROFILE_NAME)
-p Print mail to stdout instead of sending it
-f Send mail even when no profile name is specified
HELP
}

# Parse CLI args
FORCE_SENDING=0
SEND_COMMAND=""
LIMIT_COMMAND_NAMES=""
while getopts 'c:fhn:o:ps' flag ; do
case "${flag}" in
c) PROFILE_COMMAND="${OPTARG}" ;;
o) LIMIT_COMMAND_NAMES="${OPTARG}" ;;
f) FORCE_SENDING=1 ;;
n) PROFILE_NAME="${OPTARG}" ;;
p) SEND_COMMAND="cat -" ;;
s) (( ${RESTICPROFILE_ON_SCHEDULE:-0} > 0 )) || exit 0 ;;
*) help "$0" ; exit 0 ;;
esac
done
shift $((OPTIND-1))

# Parameters
MAIL_TO=""
MAIL_FROM="\"resticprofile $(hostname -f)\" <$USER@$(hostname -f)>"
MAIL_SUBJECT="restic failed: \"${PROFILE_COMMAND}\" in \"${PROFILE_NAME}\""

SEND_COMMAND="${SEND_COMMAND:-sendmail -t}"

DETAILS_COMMAND_RESULT=""
DETAILS_COMMAND=""

# Get command to capture output from scheduler ( if in use )
if [[ -d /etc/systemd/ ]] \
&& (( ${RESTICPROFILE_ON_SCHEDULE:-0} > 0 )) \
&& resticprofile --name "${PROFILE_NAME}" show | grep -v -q -E "scheduler:\s*cron" ; then
DETAILS_COMMAND="systemctl status --full \"resticprofile-${PROFILE_COMMAND:-*}@profile-${PROFILE_NAME:-*}\""
fi

# Load parameter overrides
RC_FILE="/etc/resticprofile/$(basename "$0").rc}"
[[ -f "${RC_FILE}" ]] && source "${RC_FILE}"

main() {
if can_send ; then
if [[ -n "${DETAILS_COMMAND}" ]] ; then
DETAILS_COMMAND_RESULT="$(${DETAILS_COMMAND})"
fi

for email in "$@" "${MAIL_TO}" ; do
if [[ "${email}" =~ ^[a-zA-Z0-9_.%+-]+@[a-zA-Z0-9_]+[a-zA-Z0-9_.-]+$ ]] ; then
send_mail "${email}" || echo "Failed sending to \"${email}\" using '${SEND_COMMAND}' exit code $?"
elif [[ -n "${email}" ]] ; then
echo "Skipping notification for invalid address \"${email}\""
fi
done
fi
return 0
}

can_send() {
if [[ -n "${PROFILE_NAME}" ]] ; then
if [[ -n "${LIMIT_COMMAND_NAMES}" ]] ; then
local IFS=",; "
for cmd in ${LIMIT_COMMAND_NAMES} ; do
[[ "${PROFILE_COMMAND}" == "$cmd" ]] && return 0
done
else
return 0
fi
fi

[[ "${FORCE_SENDING}" == "1" ]]
return $?
}

send_mail() {
${SEND_COMMAND} <<ERRMAIL
To: $1
From: $MAIL_FROM
Subject: $MAIL_SUBJECT
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=UTF-8

${ERROR:-No error information available}

----
COMMANDLINE:

${ERROR_COMMANDLINE:-N/A}

----
STDERR:

${ERROR_STDERR:-N/A}

----
DETAILS:

${DETAILS_COMMAND_RESULT:-N/A}

----
CONFIG:

$(resticprofile --name "${PROFILE_NAME}" show)

ERRMAIL
}

# Invoke main and exit without error to ensure other error handlers run
main "$@"
exit 0
52 changes: 52 additions & 0 deletions contrib/posix/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Default configuration for POSIX systems

**Layout for `/etc/resticprofile`**:

* `conf.d/*` - default configuration and config overrides
* `profiles.conf` - main configuration file
* `profiles.d/*` - host centric backup profiles (`*.toml` & `*.yaml`)
* `repositories.d/*` - restic repository configuration
* `templates/*` - reusable config blocks and system templates

The layout is used in `deb`, `rpm` and `apk` packages of `resticprofile`

**Generated files**:
* `repositories.d/default-repository.secret` - during installation, only if missing

**Referenced files and paths**:
* `repositories.d/default-repository-self-signed-pub.pem` - TLS public cert (self-signed only)
* `repositories.d/default-repository-client.pem` - TLS client cert
* `/var/lib/prometheus/node-exporter/resticprofile-*.prom` - Prometheus files
* `$TMPDIR/resticprofile-*` - Status and lock files

# Quick Start

## Installation

* RPM: `rpm -i "resticprofile-VERSION-ARCH.rpm"`
* DEB: `dpkg -i "resticprofile-VERSION-ARCH.deb"`

## Configuration
Setup repository and validate system backup profile:
```shell
cd /etc/resticprofile/
vim repositories.d/default.conf
vim profiles.d/system.toml
```

## Verify configuration, backup & restore
```shell
resticprofile root.show
resticprofile --dry-run root.backup
resticprofile root.backup
resticprofile root.snapshots
resticprofile root.mount /mnt/restore &
```

## Maintenance (check & prune)
```shell
resticprofile maintenance.check
resticprofile maintenance.prune
resticprofile maintenance.schedule
resticprofile maintenance.unschedule
```
Loading