diff --git a/.goreleaser.yml b/.goreleaser.yml index acbd3c503..9cb364ec6 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -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: "fred@creativeprojects.tech" + 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 } diff --git a/contrib/systemd/README.md b/contrib/notification-scripts/README.md similarity index 83% rename from contrib/systemd/README.md rename to contrib/notification-scripts/README.md index cd964432d..0bcb62325 100644 --- a/contrib/systemd/README.md +++ b/contrib/notification-scripts/README.md @@ -1,4 +1,4 @@ -# Send an email on error (systemd schedule) +# Email with failure details - "resticprofile-send-error.sh" In `profiles.yaml` you set: @@ -6,50 +6,26 @@ In `profiles.yaml` you set: default: ... run-after-fail: - - 'resticprofile-send-error.sh name@domain.tl' + - 'resticprofile-send-error.sh -s name@domain.tl' ``` -With `/usr/local/bin/resticprofile-send-error.sh` being: +Usage: -```sh -#!/usr/bin/env bash -[[ -z "${PROFILE_NAME}" ]] || sendmail -t < -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 ``` @@ -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: admins@domain.tl From: "resticprofile hyper1.domain.tl" -Subject: restic failed: backup "vms" +Subject: restic failed: "backup" in "vms" run-before backup on profile 'vms': exit status 1 diff --git a/contrib/notification-scripts/resticprofile-send-error.sh b/contrib/notification-scripts/resticprofile-send-error.sh new file mode 100755 index 000000000..8ad179409 --- /dev/null +++ b/contrib/notification-scripts/resticprofile-send-error.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# +# Error notification sendmail script +# +help() { + cat - < 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} <>> "profiles.d/example.conf" +# +# [profiles.example] +# description = "Backup example" +# inherit = "base" +# +# [profiles.example.backup] +# schedule = "daily" +# source = [ +# "/path/to/backup", +# "/other/path/to/backup", +# ] +# +# <<< +# + + +## +# Backup defaults +[profiles.default.backup] + +# Hostname to identify backup snapshots in the repository from this host +{{ template "conf:default-host" . }} + +# +# Tags (besides host & path) are used to identify snapshots belonging to +# a certain backup. +# +# Multiple tags can be defined, but profile name should always be included, +# the expression "{{ .Profile.Name }}" resolves to the currently active +# profile name. +# +# Notes: +# +# - Set the same tags for "backup" and "retention" unless you know what you +# are doing. Retention uses the tags defined in the backup section when +# "tag = true" is set in the retention configuration and this default +# setup should not be changed. +# +# - Most of the command sections should use "tag = true" to copy tags from +# backup so that restore, mount, etc. relate to the selected profile. +# +tag = [ "{{ .Profile.Name }}" ] + +# Exclude known cache files & folders from backups +exclude-caches = true + +# Exclude nested filesystems +# Prefer overriding this option in dedicated backup profiles instead of +# globally as it can greatly increase the volume if nested FS mounts are +# contained in backup source paths. +one-file-system = true + +# Toggle whether a failure in reading a backup source is considered an error +no-error-on-warning = false + +# Wait on acquiring locks when running the profile on a schedule +schedule-lock-wait = "45m" + +# Specify the user that runs profile tasks on a schedule +# "system" - root runs the profile tasks +# "user" - user that created the schedule runs the profile tasks +schedule-permission = "system" + +# Toggle verbose output for troubleshooting +#verbose = false + +# Toggles immediate repository check before and after backup. +# Checks can be heavy on resources. Consider scheduling "maintenance" +# (see "conf.d/check.conf") instead of enabling checks here. +check-before = false +check-after = false + + +## +# Snapshot retention defaults +[profiles.default.retention] +# Remove obsolete snapshots prior to starting a backup +before-backup = false +# Remove obsolete snapshots after a successful backup +after-backup = true + +# +# Note: Retention operates on host, path and tag filters to identify snapshots +# to retain or remove. In most cases these filters should be in sync with +# the backup configuration of a profile so that snapshots will be removed +# that truely belong to a profile's backup. +# + +# Host filter +# Copying "host" block to identify snapshots by hostname +{{ template "conf:default-host" . }} + +# Tag filter +# Retention allows to build tag filter from backup with 'true'. It is strongly +# advised not to change this as tags are the primary filter besides hostname. +tag = true + +# Path filter +# Set to "true" to copy source paths from backup, "false" or a list of paths +# to disable or customize the path filter. +# +# Note: Path filters match literally on the absolute source paths recorded +# when a snapshot was created. If sources are changed, specified relative or +# with wildcards, snapshots may no longer be matched. Tag and host filters are +# better suited to identify all snapshots of certain profile. +path = false + +# Specify the snapshots to keep when checking for obsolete snapshots +# Snapshots that do not match any condition are removed +keep-tag = [ "forever" ] +keep-last = 3 +#keep-hourly = 1 +#keep-daily = 1 +#keep-weekly = 1 +#keep-monthly = 1 +#keep-yearly = 1 +#keep-within = "30d" + +# Use compact format for listing snapshots +#compact = false + +# Toggles immediate prune of the repository as snapshots are removed. +# +# While removing snapshots is a light operation, prune (reclaim space) can +# be heavy on resources as it rewrites parts of the repository. +# Consider scheduling "maintenance" (see "conf.d/prune.conf") instead of +# enabling prune here. +# +# Also an attempt to recover a removed snapshot with "resticprofile recover" +# only works as long as the repository was not yet pruned. +prune = false + + +## +# Defaults for operations on repository snapshots of this host +# Usage: +# - "resticprofile snapshots" - view snapshots +# - "resticprofile mount /mnt/restore" - mount snapshots +# - "resticprofile ls latest /" - list files in a snapshot +# - "resticprofile dump latest /file" - dump a file to stdout +# - "resticprofile find PATTERN..." - find files in snapshots +# - "resticprofile copy --repo2=..." - copy snapshots to repo2 +# - "resticprofile restore --target=/to/dir --include=PATTERN... latest" +[profiles.default.copy] +{{ template "conf:default-host" . }} +schedule-lock-wait = "1h30m" +[profiles.default.dump] +{{ template "conf:default-host" . }} +[profiles.default.find] +{{ template "conf:default-host" . }} +[profiles.default.forget] +{{ template "conf:default-host" . }} +[profiles.default.ls] +{{ template "conf:default-host" . }} +[profiles.default.mount] +{{ template "conf:default-host" . }} +[profiles.default.restore] +{{ template "conf:default-host" . }} +[profiles.default.snapshots] +{{ template "conf:default-host" . }} +[profiles.default.stats] +{{ template "conf:default-host" . }} +[profiles.default.tag] +{{ template "conf:default-host" . }} + + +## +# Setup operations on repository snapshots for profiles deriving from "base" +# Usage: +# - "resticprofile profileName.snapshots" - view snapshots +# - "resticprofile profileName.mount /mnt/restore" - mount snapshots +# - "resticprofile profileName.ls latest /" - list files in a snapshot +# - "resticprofile profileName.dump latest /file" - dump a file to stdout +# - "resticprofile profileName.find PATTERN..." - find files in snapshots +# - "resticprofile profileName.copy --repo2=..." - copy snapshots to repo2 +# - "resticprofile profileName.restore --target=/to/dir latest" +[profiles.base.copy] +tag = true +[profiles.base.dump] +tag = true +[profiles.base.find] +tag = true +[profiles.base.forget] +tag = true +[profiles.base.ls] +tag = true +[profiles.base.mount] +tag = true +[profiles.base.restore] +tag = true +[profiles.base.snapshots] +tag = true +[profiles.base.stats] +tag = true +[profiles.base.tag] +tag = true + + +# ----------------------------------------------------------------------------- diff --git a/contrib/posix/conf.d/check.conf b/contrib/posix/conf.d/check.conf new file mode 100644 index 000000000..6dcd1e789 --- /dev/null +++ b/contrib/posix/conf.d/check.conf @@ -0,0 +1,52 @@ +# ----------------------------------------------------------------------------- +## +# Repository check defaults +# +# Usage +# +# Run check on the default profile: +# - "resticprofile check" +# Run check on all maintenance profiles: +# - "resticprofile maintenance-all.check" +# +# Schedule the default maintenance profile: +# - "resticprofile maintenance.schedule" +# - "resticprofile maintenance.unschedule" +# Schedule all maintenance profiles: +# - "resticprofile maintenance-all.schedule" +# - "resticprofile maintenance-all.unschedule" +# + + +## +# Defaults for repository checks +[profiles.default.check] +# Read actual repository data for verification +read-data = false +# Read a subset of the repository for verification (when read-data = true) +read-data-subset = "33%" +# +# Check cache setup +# "check" uses a separate local one-time repo cache to detect problems. +# The default is to create this cache below "/tmp/" and it may grow to several +# GB during check execution, adjust path if needed. +cache-dir = "{{ .TempDir }}/restic-check-cache.resticprofile" +with-cache = true +# Allow longer lock waits for check schedules to ensure checks run as other +# tasks take longer to complete +schedule-lock-wait = "8h" +# Only root may schedule check +schedule-permission = "system" + + +## +# Configuring "check" schedule in maintenance profile +[profiles.maintenance.check] +# Schedule at 5th, 10th, 15th, 20th, 25th and 30th day in month at 04:15 +schedule = "*-*-5,10,15,20,25,30 04:15:00" +# Read repository data for verification every 15th day in month +{{ if .Now.Day | eq 15 -}} +read-data = true +{{ end -}} + +# ----------------------------------------------------------------------------- diff --git a/contrib/posix/conf.d/hooks.conf b/contrib/posix/conf.d/hooks.conf new file mode 100644 index 000000000..0b6434f90 --- /dev/null +++ b/contrib/posix/conf.d/hooks.conf @@ -0,0 +1,53 @@ +# ----------------------------------------------------------------------------- +## +# Base configuration for action hooks that run before, after, after failure or +# always after a set of profile tasks (profile task = restic command or +# action hook) +# +# The following additional environment variables are defined in an action: +# +# - PROFILE_NAME - Name of the active profile +# - PROFILE_COMMAND - Name of the requested command +# - ERROR - Error message if the requested command failed +# - ERROR_EXIT_CODE - Exit code of the failing command or action hook +# - ERROR_COMMANDLINE - Commandline of the failing command or action hook +# - ERROR_STDERR - Stderr of the failing command or action hook +# + +## +# Action hooks for profiles that derive from "base" +[profiles.base] +## +# Actions to run before restic +run-before = [ + #'echo ">>> ${PROFILE_NAME} - BEGIN ${PROFILE_COMMAND}"', +] + +## +# Actions to run after restic (only if "run-before" and "restic" succeeded) +run-after = [ + #'echo "<<< ${PROFILE_NAME} - END ${PROFILE_COMMAND}"', +] + +## +# Actions to run when a profile task has failed (triggers as "run-before", +# "restic" or "run-after" failed) +# +# ERROR variables are set and contain details on the failed task +run-after-fail = [ + # Example: Print error + 'echo "!!! ${PROFILE_NAME} - FAILED ${PROFILE_COMMAND} - ERROR: ${ERROR}"', + + # Send mail (see /etc/resticprofile/resticprofile-send-error.rc) + 'resticprofile-send-error -s -o "check,copy,forget,backup,prune"', +] + +## +# Actions to run in any case after all other actions. +# +# On failure ERROR variables are set and contain details on the failed task +run-finally = [ + '["${PROFILE_COMMAND}" == "check"] && rm -rf "{{ .TempDir }}/restic-check-cache.resticprofile"', +] + +# ----------------------------------------------------------------------------- diff --git a/contrib/posix/conf.d/metrics.conf b/contrib/posix/conf.d/metrics.conf new file mode 100644 index 000000000..73f2034db --- /dev/null +++ b/contrib/posix/conf.d/metrics.conf @@ -0,0 +1,32 @@ +# ----------------------------------------------------------------------------- +## +# Metrics collection configuration +# +# Resticprofile can create metrics for prometheus or as a custom JSON format. +# In order to create metrics, the option "extended-status = true" must be set +# for "backup" along with metrics output configuration in the profile. +# + + +## +# Toggle metrics collection in "backup" for profiles that derive from "base" +[profiles.base.backup] +# Toggles full "restic" output capture to allow collecting backup metrics +# for "status-file" and "prometheus-(save-to-file|push)" +#extended-status = true + + +## +# Metric configuration for profiles that derive from "base" +[profiles.base] +## +# Write backup metrics as JSON (requires extended-status = true) +#status-file = "{{ .TempDir }}/resticprofile-{{ .Profile.Name }}-status.json" + +## +# Export backup metrics to Prometheus (requires extended-status = true) +#prometheus-save-to-file = "/var/lib/prometheus/node-exporter/{{ .Profile.Name }}.prom" +#prometheus-push = "http://host:9091/" + + +# ----------------------------------------------------------------------------- diff --git a/contrib/posix/conf.d/prune.conf b/contrib/posix/conf.d/prune.conf new file mode 100644 index 000000000..adcd1071d --- /dev/null +++ b/contrib/posix/conf.d/prune.conf @@ -0,0 +1,56 @@ +# ----------------------------------------------------------------------------- +## +# Repository prune defaults +# +# Usage +# +# Run prune on the default profile: +# - "resticprofile prune" +# Run prune on all maintenance profiles: +# - "resticprofile maintenance-all.prune" +# +# Schedule the default maintenance profile: +# - "resticprofile maintenance.schedule" +# - "resticprofile maintenance.unschedule" +# Schedule all maintenance profiles: +# - "resticprofile maintenance-all.schedule" +# - "resticprofile maintenance-all.unschedule" +# + + +## +# Defaults for repository prune +[profiles.default.prune] +## +# Auto remove old cache entries from the local cache that are no longer +# needed as data was pruned from the repository. +cleanup-cache = true + +## +# Repacking (repository compaction) configuration. +# Limited by: +# - "max-unused" (higher precentage, less repacking) +# - "max-repack-size" (lower size value, less repacking) +# +# Repacking requires up to "max-repack-size" temporary space in the repository, +# ensure it has the configured size available at least before running prune. +# Using a smaller size means more iterations are needed until all space is +# reclaimed. +max-repack-size = "3g" +# Tolerate given limit of unused data that is not reclaimed (default 5%) +# A higher percentage performs less repacking which will speedup prune. +max-unused = "5%" + +## +# Wait on acquiring locks when running "prune" on a schedule +schedule-lock-wait = "1h30m" +# Only root may schedule prune +schedule-permission = "system" + + +## +# Configuring "prune" schedule in maintenance profile +[profiles.maintenance.prune] +schedule = "daily" + +# ----------------------------------------------------------------------------- diff --git a/contrib/posix/conf.d/z_overrides.conf b/contrib/posix/conf.d/z_overrides.conf new file mode 100644 index 000000000..a307da0e8 --- /dev/null +++ b/contrib/posix/conf.d/z_overrides.conf @@ -0,0 +1,64 @@ +# ----------------------------------------------------------------------------- +## +# Is included as last conf file and may override any settings made earlier. +# +# Prefer this file to customize resticprofile's default configuration as +# this file is not updated during installation. +# + +## +# Example: Override default retention +# +# Note: Retention settings are inherited like all other parameters one-by-one. +# Anything that is set here must be explicitly configured in profiles +# if the default should not apply. +# +# [profiles.default.retention] +# keep-tag = [ "forever" ] +# keep-last = 10 +# keep-hourly = 0 +# keep-daily = 14 +# keep-weekly = 2 +# keep-monthly = 0 +# keep-yearly = 0 +# keep-within = "30d" + + +## +# Example: Global repository access +# +# Enable to access the default repository without host or tag filter +# +# [profiles.global.copy] +# host = false +# tag = false +# [profiles.global.dump] +# host = false +# tag = false +# [profiles.global.find] +# host = false +# tag = false +# [profiles.global.forget] +# host = false +# tag = false +# [profiles.global.ls] +# host = false +# tag = false +# [profiles.global.mount] +# host = false +# tag = false +# [profiles.global.restore] +# host = false +# tag = false +# [profiles.global.snapshots] +# host = false +# tag = false +# [profiles.global.stats] +# host = false +# tag = false +# [profiles.global.tag] +# host = false +# tag = false + + +# ----------------------------------------------------------------------------- diff --git a/contrib/posix/post-install.sh b/contrib/posix/post-install.sh new file mode 100755 index 000000000..0a66707bf --- /dev/null +++ b/contrib/posix/post-install.sh @@ -0,0 +1,170 @@ +#!/usr/bin/env sh +ROOT_PATH="${ROOT_PATH:-}" + +FILES_OWNER="root:root" +if [ -n "${ROOT_PATH}" ] && [ "${ROOT_PATH}" != "/usr/local" ] ; then + FILES_OWNER="$(id -u):$(id -g)" +fi + +# Temp dir (using fixed path to ensure "rm -rf" will not have side effects) +TEMP_PATH="${ROOT_PATH}/tmp/.resticprofile-setup" +if [ -d "$TEMP_PATH" ] ; then + rm -rf "${TEMP_PATH}" +fi +mkdir -p "$TEMP_PATH" +trap "rm -rf \"${TEMP_PATH}\"" EXIT INT TERM + +# Paths +CACHE_PATH="${TEMP_PATH}/cache" +CONFIG_PATH="${ROOT_PATH}/etc/resticprofile" +CONFIG_CACHE_FILE="${CONFIG_PATH}/.dist.cache" +REPOSITORY_SECRET="repositories.d/default.secret" +SECRET_FILE="${CONFIG_PATH}/${REPOSITORY_SECRET}" + +# Config files that are merged with .dist files when already existing +MERGEABLES="conf.d/backup.conf conf.d/check.conf conf.d/hooks.conf" +MERGEABLES="${MERGEABLES} conf.d/metrics.conf conf.d/prune.conf" +MERGEABLES="${MERGEABLES} profiles.conf repositories.d/default.conf" + +# Search path of dirs to install shell completions (only first match will be used) +COMPLETION_DIRS="${ROOT_PATH}/usr/share/bash-completion/completions" +COMPLETION_DIRS="${COMPLETION_DIRS} ${ROOT_PATH}/usr/share/bash-completion/bash_completion" +COMPLETION_DIRS="${COMPLETION_DIRS} ${ROOT_PATH}/usr/local/etc/bash_completion.d" +COMPLETION_DIRS="${COMPLETION_DIRS} ${ROOT_PATH}/etc/bash_completion.d" + +# Fix permissions (only root may edit and read since password & tokens can be in any of the files) +set_permission() { + _path="${CONFIG_PATH}/$1" + + if [ -e "${_path}" ] ; then + echo "Setting perms on ${_path}" + chown "${FILES_OWNER}" "${_path}" || return 1 + fi + + if [ -d "${_path}" ] || echo "${_path}" | grep -q -E '.rc$' ; then + chmod 0755 "${_path}" + elif [ -f "${_path}" ] ; then + echo "$1" | grep -q .secret \ + && chmod 0400 "${_path}" \ + || chmod 0640 "${_path}" + fi +} + +if cd "${CONFIG_PATH}" ; then + for file in *.dist \ + conf.d conf.d/*.dist \ + profiles.d profiles.d/*.dist \ + repositories.d repositories.d/*.dist ${REPOSITORY_SECRET} \ + templates templates/*.dist \ + ${MERGEABLES} ; do + set_permission "${file}" + done +else + echo "config path (${CONFIG_PATH}) not found" + exit 1 +fi + +# Check installation +if [ ! -e "$(which resticprofile)" ] || ! resticprofile version >/dev/null ; then + echo "resticprofile not found or not executable" + exit 1 +fi + +# Generate default-repo secret if missing +if [ ! -f "${SECRET_FILE}" ] ; then + echo "Generating ${SECRET_FILE}" + if resticprofile random-key > "${SECRET_FILE}" ; then + set_permission "${REPOSITORY_SECRET}" + else + exit 1 + fi +fi + +# Change scheduler to crond when systemd is missing (Alpine) +if [ ! -d "${ROOT_PATH}/etc/systemd/" ] ; then + sed -iE 's/#scheduler = "systemd"/scheduler = "crond"/g' "${CONFIG_PATH}/profiles.conf.dist" \ + || exit 1 +fi + +# Generate bash completions +for completion_path in ${COMPLETION_DIRS} ; do + completion_file="${completion_path}/resticprofile" + + if [ -d "${completion_path}" ] ; then + echo "Generating ${completion_file}" + if [ -f "${completion_file}" ] ; then + chmod u+w "${completion_file}" + fi + resticprofile completion-script --bash > "${completion_file}" \ + && chown root:root "${completion_file}" \ + && chmod 0555 "${completion_file}" + + break # install only in the first path + fi +done + +# Merge configuration updates with existing files +if [ -e "$(which diff3)" ] ; then + echo "Merging updates to config files" + + # Extract previous .dist files from .dist.cache + if [ -s "${CONFIG_CACHE_FILE}" ] ; then + cd "${CACHE_PATH}" \ + && tar -xzf "${CONFIG_CACHE_FILE}" + fi + + new_dist_cache_list="${TEMP_PATH}/dist_cache.list" + + cd "${CONFIG_PATH}" || exit 1 + + # Merge existing config with updates from .dist files + for file in $MERGEABLES ; do + target_file="${file}" + new_file="${file}.dist" + cached_file="${CACHE_PATH}/${new_file}" + output="${TEMP_PATH}/merged.conf" + + if [ -e "${new_file}" ] ; then + echo "${new_file}" >> "${new_dist_cache_list}" + fi + + if [ -e "${target_file}" ] && [ -e "${new_file}" ] && [ -e "${cached_file}" ] ; then + + diff3 --easy-only --merge "${new_file}" "${cached_file}" "${target_file}" > "${output}" + + if [ "$?" = "0" ] || [ "$?" = "1" ] ; then + if [ "$?" = "1" ] ; then + backup="${target_file}.prev" + cp -f "${target_file}" "${backup}" \ + && set_permission "${backup}" + echo "Conflicts found in \"${target_file}\", please verify. Created ${backup}" + fi + if [ -s "${output}" ] ; then + mv -f "${output}" "${target_file}" \ + && set_permission "${target_file}" + fi + else + echo "Failed merging \"${new_file}\" \"${cached_file}\" \"${target_file}\"" + fi + fi + done + + # Create new .dist.cache from current .dist files + if [ -s "${new_dist_cache_list}" ] ; then + tar -c --files-from "${new_dist_cache_list}" -zf "${CONFIG_CACHE_FILE}" + fi +fi + +# Unwrap remaining dist files where target file does not exist already +cd "${CONFIG_PATH}" || exit 1 + +for file in *.dist conf.d/*.dist profiles.d/*.dist repositories.d/*.dist templates/*.dist ; do + target_file="$(dirname "${file}")/$(basename -s ".dist" "${file}")" + + if [ -e "${target_file}" ] ; then + rm "${file}" + else + mv -f "${file}" "${target_file}" \ + && set_permission "${target_file}" + fi +done diff --git a/contrib/posix/post-install_test.sh b/contrib/posix/post-install_test.sh new file mode 100755 index 000000000..26424e1a5 --- /dev/null +++ b/contrib/posix/post-install_test.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd -P )" + +TEMP_PATH="$(mktemp -d)" +if [[ -z ${TEMP_PATH} || ! -d "${TEMP_PATH}" ]] ; then + exit 1 +fi +export ROOT_PATH="${TEMP_PATH}" + +trap 'cleanup remove ; cd "${SCRIPT_PATH}"' EXIT INT TERM + +function cleanup() { + echo "Cleaning '${TEMP_PATH}' $1" + rm -rf "${TEMP_PATH}" + [[ "$1" == "remove" ]] || mkdir -p "${TEMP_PATH}" +} + +function invoke() { + cd "${SCRIPT_PATH}" && ./post-install.sh +} + +function setup() { + cleanup "" + local config="${TEMP_PATH}/etc/resticprofile" + mkdir -p "${config}" \ + && cp -R *.conf *.rc conf.d profiles.d repositories.d templates "${config}" \ + && find "${config}" -name "*.conf" -exec mv {} {}.dist \; + find "${config}" +} + +# TODO tests +setup && invoke || exit 1 +find "${TEMP_PATH}" + diff --git a/contrib/posix/profiles.conf b/contrib/posix/profiles.conf new file mode 100644 index 000000000..197afa35d --- /dev/null +++ b/contrib/posix/profiles.conf @@ -0,0 +1,118 @@ + +# ----------------------------------------------------------------------------- +# Resticprofile backup profiles for "restic" backups +# See https://github.com/creativeprojects/resticprofile +# +# Note: This configuration file should not be changed directly. +# Configure resticprofile in "(conf|repositories).d" and add +# profiles to "profiles.d". +# ----------------------------------------------------------------------------- + +## Configuration file format version +version = 2 + +## +# Loading config from "(conf|repositories).d" and profiles from "profiles.d": +includes = [ + 'templates/*.conf', + 'conf.d/*.conf', + 'repositories.d/*.conf', + 'profiles.d/*.conf', + 'profiles.d/*.toml', + 'profiles.d/*.yaml', +] + +## +# The global section +[global] +# Initialize a repository if none exists at the specified location? +# Is configured in profiles, global setting should remain 'false'. +initialize = false + +# restic IO priority +ionice = false +#ionice-class = 2 +#ionice-level = 6 + +# restic CPU-priority ( idle | background | low | normal | high ) +priority = "low" + +# scheduler to use: "systemd" (default) or "crond" +#scheduler = "systemd" + +# systemd unit & timer template +systemd-unit-template = "templates/systemd.unit.in" +systemd-timer-template = "templates/systemd.timer.in" + +# what to run when no command is specified +default-command = "profiles" + +# Optional: Specify path to restic +#restic-binary = "/usr/local/bin/restic" + + +## +# The "default" profile +# +# This profile is choosen when no profile is explicitly specified and +# is configured to support commands on the default repository like +# "snapshots", "check", "prune" & "mount". +# +# It is also the parent to the "base" profile which means that profiles +# inheriting from "base" also inherit all settings from "default" +# +# The default profile should not be used directly for running backups +# nor should it contain schedules. +[profiles.default] +description = "Profile defaults for this host" +## Locks +# Prevent concurrent invocation of a profile +lock = "{{.TempDir}}/resticprofile-{{.Profile.Name}}.lock" +# Detect stale locks and unlock automatically +force-inactive-lock = true + + +## +# The "base" profile is the dedicated parent to all other profiles and it +# extends "default" with additional settings for backup, restore & maintenance +# +# Note: Profiles that do not inherit from "base" will run on built-in +# defaults instead and have to take care of a full profile setup. +[profiles.base] +description = "Base config for derived profiles" +# Inherit all settings from the "default" profile +inherit = "default" +initialize = true + + +## +# The "maintenance" profile is used to perform ondemand or scheduled +# repository maintenance tasks like prune and check. +# +# See also "conf.d/check.conf" & "conf.d/prune.conf" +[profiles.maintenance] +description = "Maintenance for the default repository" +inherit = "base" +# We never initialize a new repository for maintenance +initialize = false + + +## +# Group for profiles that check & verify repositories +# Usage +# - "resticprofile maintenance-all.check" +# - "resticprofile maintenance-all.prune" +# - "resticprofile maintenance-all.cache" +# - "resticprofile maintenance-all.schedule" +# - "resticprofile maintenance-all.unschedule" +[groups.maintenance-all] +description = "Group of check and cleanup profiles" +profiles = [ "maintenance" ] + +## +# Group for profiles that perform actual backups +# Usage +# - "resticprofile backup-all.backup" +[groups.backup-all] +description = "Group of backup profiles" +profiles = [ ] diff --git a/contrib/posix/profiles.d/fs-snapshot.yaml.sample b/contrib/posix/profiles.d/fs-snapshot.yaml.sample new file mode 100644 index 000000000..1dd0040b3 --- /dev/null +++ b/contrib/posix/profiles.d/fs-snapshot.yaml.sample @@ -0,0 +1,65 @@ +# ----------------------------------------------------------------------------- +## +# Example profile for applications whose files must be backed-up from a +# readonly snapshot. +# +profiles: + applications: + description: Applications backup + inherit: base + + backup: + source: + - /opt/apps/_backup + - /mnt/data_backup + - /opt/vms/my-vm1.xml + - /opt/vms/my-vm1.qcow2 + + # Create snapshots of supported sources prior to running a backup + run-before: + # Snapshot on Btrfs (mounted on /opt/apps/_backup) + - btrfs subvolume snapshot -r /opt/apps/ /opt/apps/_backup + + # Snapshot on LVM (mounted on /opt/apps/_backup) + - lvcreate -l100%FREE -s -n data_backup /dev/vg00/data && mount /dev/vg00/data_backup /mnt/data_backup + + # Snapshot & config dump for libvirt VMs (VM disk images are readonly during backup) + - virsh dumpxml "my-vm1" > /opt/vms/my-vm1.xml + - {{ template "libvirt-create-snapshot" "my-vm1" }} + + # Release snapshots after backup + run-finally: + # Cleanup on Btrfs + - btrfs subvolume delete /opt/apps/_backup + + # Cleanup on LVM + - umount /mnt/data_backup && lvremove -f /dev/vg00/data_backup + + # Cleanup for libvirt VMs (VM disk image receives changes from livedata file) + - {{ template "libvirt-delete-snapshot" "my-vm1" }} + + +groups: + backup-all: + profiles: [ "applications" ] + +##### +# Config templates +# Notes: +# - "resticprofile applications.show" shows rendered template output +# - Could also be saved to "templates/my-libvirt-utils.conf" and shared +# +{{ define "libvirt-create-snapshot" -}} + virsh snapshot-create-as --domain "{{.}}" --name "{{.}}-backup" {{- " " -}} + --diskspec "vda,file=/var/db/{{.}}-livedata.qcow2" {{- " " -}} + --disk-only --atomic --no-metadata --quiesce +{{- end }} +# +{{ define "libvirt-delete-snapshot" -}} + virsh blockcommit --domain "{{.}}" vda --wait --active {{- " " -}} + && virsh blockjob --domain "{{.}}" "/var/db/{{.}}-livedata.qcow2" --pivot {{- " " -}} + && rm -f /var/db/{{.}}-livedata.qcow2 +{{- end }} +##### + +# ----------------------------------------------------------------------------- diff --git a/contrib/posix/profiles.d/minimal.conf.sample b/contrib/posix/profiles.d/minimal.conf.sample new file mode 100644 index 000000000..d1814242b --- /dev/null +++ b/contrib/posix/profiles.d/minimal.conf.sample @@ -0,0 +1,27 @@ +## +# Minimal backup configuration example +# +# Usage: +# - "resticprofile minimal.show" +# - "resticprofile --dry-run minimal.backup" +# - "resticprofile minimal.backup" +# - "resticprofile minimal.snapshots" +# - "resticprofile minimal.mount /restore" +# - "resticprofile minimal.status" +# - "resticprofile minimal.schedule" +# - "resticprofile minimal.unschedule" +# +[profiles.minimal] +description = "Minimal backup example" +inherit = "base" + +[profiles.minimal.backup] +schedule = "daily" +source = [ + "/path/to/backup", + "/other/path/to/backup", +] + + +[groups.backup-all] +profiles = [ "minimal" ] diff --git a/contrib/posix/profiles.d/minimal.yaml.sample b/contrib/posix/profiles.d/minimal.yaml.sample new file mode 100644 index 000000000..02b546796 --- /dev/null +++ b/contrib/posix/profiles.d/minimal.yaml.sample @@ -0,0 +1,28 @@ +## +# Minimal backup configuration example +# +# Usage: +# - "resticprofile minimal.show" +# - "resticprofile --dry-run minimal.backup" +# - "resticprofile minimal.backup" +# - "resticprofile minimal.snapshots" +# - "resticprofile minimal.mount /restore" +# - "resticprofile minimal.status" +# - "resticprofile minimal.schedule" +# - "resticprofile minimal.unschedule" +# +profiles: + minimal: + description: Minimal backup example + inherit: base + + backup: + schedule: daily + source: + - /path/to/backup + - /other/path/to/backup + + +groups: + backup-all: + profiles: [ "minimal" ] diff --git a/contrib/posix/profiles.d/system.conf b/contrib/posix/profiles.d/system.conf new file mode 100644 index 000000000..197663ede --- /dev/null +++ b/contrib/posix/profiles.d/system.conf @@ -0,0 +1,55 @@ +# ----------------------------------------------------------------------------- +## +# The "root" profile to backup the system +# +# Backup $HOME of root and 'etc' folders once a day +# +# Usage: +# - "resticprofile root.show" +# - "resticprofile --dry-run root.backup" +# - "resticprofile root.backup" +# - "resticprofile root.snapshots" +# - "resticprofile root.mount /restore" +# - "resticprofile root.status" +# - "resticprofile root.schedule" +# - "resticprofile root.unschedule" +# +[profiles.root] +description = "System backup" +inherit = "base" + +## +# Backup sources and schedule +[profiles.root.backup] +no-error-on-warning = false +schedule = "daily" +source = [ + "/etc", + "/usr/local/etc", + "/opt/local/etc", + "~root/", +] +exclude = [ + "*.swp", +] +#exclude-larger-than = "2g" +#include = [ +# "**", +#] + +## +# Keep last 14 days and the latest from each of last 8 weeks and 6 months +# Total snapshots to keep: 24 +# (14 days, weeks not covered by days and months not covered by weeks) +[profiles.root.retention] +keep-last = true +keep-daily = 14 +keep-weekly = 8 +keep-monthly = 6 + +## +# Add to "backup-all" +[groups.backup-all] +profiles = [ "root" ] + +# ----------------------------------------------------------------------------- \ No newline at end of file diff --git a/contrib/posix/repositories.d/default.conf b/contrib/posix/repositories.d/default.conf new file mode 100644 index 000000000..2877e0bd5 --- /dev/null +++ b/contrib/posix/repositories.d/default.conf @@ -0,0 +1,54 @@ +# ----------------------------------------------------------------------------- +## +# Repository configuration +# See https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html +# + +## +# Default repository (used in all derived profiles unless redefined) +[profiles.default] +# Repository password file +password-file = "repositories.d/default.secret" + +## +# Local: Repository created in local path +repository = "local:/backup" +#run-before = [ 'mountpoint -q /backup' ] + +## +# SFTP: (requires password-less public-key auth for the user running restic) +#repository = "sftp:user@host:/restic-repo" +#repository = "sftp://user@[::1]:2222//restic-repo" + +## +# REST server: (https://github.com/restic/rest-server) +#repository = "rest:https://user:pass@host:8000/my_backup_repo/" +#cacert = "repositories.d/default-repository-self-signed-pub.pem" +#tls-client-cert = "repositories.d/default-repository-client.pem" + +## +# S3 storage (see [default.env]) +#repository = "s3:s3.amazonaws.com/bucket_name" +#repository = "s3:http://host:9000/bucket_name" +#repository = "s3:https://host/bucket_name" +#cacert = "repositories.d/default-repository-self-signed-pub.pem" + +## +# Azure storage (see [default.env]) +#repository = "azure:container_name:/" + + +## +# Environment variables to pass to "restic" +[profiles.default.env] +## +# S3 Storage (AWS, Minio, etc.) +#AWS_ACCESS_KEY_ID = "id" +#AWS_SECRET_ACCESS_KEY = "key" + +## +# Azure Blob Storage +#AZURE_ACCOUNT_NAME = "storage_account" +#AZURE_ACCOUNT_KEY = "key" + +# ----------------------------------------------------------------------------- diff --git a/contrib/posix/repositories.d/other.conf.sample b/contrib/posix/repositories.d/other.conf.sample new file mode 100644 index 000000000..2221d6935 --- /dev/null +++ b/contrib/posix/repositories.d/other.conf.sample @@ -0,0 +1,69 @@ +## +# Example: Secondary repository +# +# Usage: +# +# [profiles.my-profile-other] +# inherit = "base-other" +# [profiles.my-profile-other.backup] +# source = "/path" +# + + +# ----------------------------------------------------------------------------- +## +# Secondary repository for profiles inheriting from "other-base" +[profiles.base-other] +# Inherit all settings from base +inherit = "base" + +# +# Repository password for the secondary repository +# +# Generate with: +# - "resticprofile random-key > repositories.d/other-repository.secret" +password-file = "repositories.d/other-repository.secret" + +# +# See "default.conf" for more examples +repository = "local:/backup-other" + +# ----------------------------------------------------------------------------- + + +# ----------------------------------------------------------------------------- +## +# Setup repository maintenance for "base-other" +[profiles.maintenance-other] +description = "Maintenance for other repository" +inherit = "base-other" +initialize = false + + +## +# Configuring "check" schedule in maintenance profile +[profiles.maintenance-other.check] + +# Schedule at 5th, 10th, 15th, 20th, 25th and 30th day in month at 04:15 +schedule = "*-*-5,10,15,20,25,30 04:15:00" + +# Read repository data for verification every 15th day in month +{{ if .Now.Day | eq 15 -}} +read-data = true +{{ end -}} +#read-data-subset = "100%" + + +## +# Configuring "prune" schedule in maintenance profile +[profiles.maintenance-other.prune] +schedule = "daily" +#schedule = "weekly" + + +## +# Add "maintenance-other" to "maintenance-all" +[groups.maintenance-all] +profiles = [ "maintenance-other" ] + +# ----------------------------------------------------------------------------- diff --git a/contrib/posix/resticprofile-send-error.rc b/contrib/posix/resticprofile-send-error.rc new file mode 100644 index 000000000..7acff1e6c --- /dev/null +++ b/contrib/posix/resticprofile-send-error.rc @@ -0,0 +1,39 @@ +# ----------------------------------------------------------------------------- +# +# Configuration for "resticprofile-send-error" +# +# Usage, see: +# resticprofile-send-error -h +# +# Examples: +# +# Test mail sending: +# resticprofile-send-error -f user@domain.org +# +# Send if PROFILE_NAME is set +# resticprofile-send-error user@domain.org +# +# Send if PROFILE_NAME & MAIL_TO are set +# resticprofile-send-error +# +# Send if PROFILE_COMMAND is "check" "backup" or "prune": +# resticprofile-send-error -o check,backup,prune user@domain.org +# +# Send only if RESTICPROFILE_ON_SCHEDULE is set to 1 +# resticprofile-send-error -s user@domain.org +# +# ----------------------------------------------------------------------------- + +## +# Sender address +#MAIL_FROM="\"resticprofile $(hostname -f)\" <$USER@$(hostname -f)>" + +## +# Subject line +#MAIL_SUBJECT="restic failed: \"${PROFILE_COMMAND}\" in \"${PROFILE_NAME}\"" + +## +# Recipients (are combined with recipients specified in CLI) +# Use space as delimiter when declaring more than one recipient +#MAIL_TO="admin1@mynet.org admin2@mynet.org ..." +#MAIL_TO="" diff --git a/contrib/posix/templates/default-host.conf b/contrib/posix/templates/default-host.conf new file mode 100644 index 000000000..decb73dae --- /dev/null +++ b/contrib/posix/templates/default-host.conf @@ -0,0 +1,7 @@ +{{ define "conf:default-host" }} + +# Specify a hostname to set in snapshots created by this system. +# Use 'true' for the current hostname of the system +host = true + +{{ end }} diff --git a/contrib/posix/templates/systemd.timer.in b/contrib/posix/templates/systemd.timer.in new file mode 100644 index 000000000..01cbffdfe --- /dev/null +++ b/contrib/posix/templates/systemd.timer.in @@ -0,0 +1,17 @@ +{{/* +# Template for generating systemd timers +# Used to schedule commands when "scheduler=systemd" (default) +*/}} + +[Unit] +Description={{ .TimerDescription }} + +[Timer] +{{ range .OnCalendar -}} +OnCalendar={{ . }} +{{ end -}} +Unit={{ .SystemdProfile }} +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/contrib/posix/templates/systemd.unit.in b/contrib/posix/templates/systemd.unit.in new file mode 100644 index 000000000..22ecfce98 --- /dev/null +++ b/contrib/posix/templates/systemd.unit.in @@ -0,0 +1,22 @@ +{{/* +# Template for generating systemd units +# Used to schedule commands when "scheduler=systemd" (default) +*/}} + +[Unit] +Description={{ .JobDescription }} + +# Send mail when resticprofile fails (requires "unit-status-mail") +#OnFailure=unit-status-mail@%n.service + +[Service] +Type=notify +WorkingDirectory={{ .WorkingDirectory }} +ExecStart={{ .CommandLine }} +{{ if .Nice -}} +Nice={{ .Nice }} +{{ end -}} +Environment="RESTICPROFILE_ON_SCHEDULE=1" +{{ range .Environment -}} +Environment="{{ . }}" +{{ end -}} diff --git a/contrib/systemd/resticprofile-send-error.sh b/contrib/systemd/resticprofile-send-error.sh deleted file mode 100644 index 9ba8ec229..000000000 --- a/contrib/systemd/resticprofile-send-error.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -[[ -z "${PROFILE_NAME}" ]] || sendmail -t < -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 diff --git a/systemd/generate.go b/systemd/generate.go index 317be6dfb..5d73c0cc1 100644 --- a/systemd/generate.go +++ b/systemd/generate.go @@ -1,4 +1,5 @@ -//+build !darwin,!windows +//go:build !darwin && !windows +// +build !darwin,!windows package systemd @@ -29,6 +30,7 @@ Type=notify WorkingDirectory={{ .WorkingDirectory }} ExecStart={{ .CommandLine }} {{ if .Nice }}Nice={{ .Nice }}{{ end }} +Environment="RESTICPROFILE_ON_SCHEDULE=1" {{ range .Environment -}} Environment="{{ . }}" {{ end -}} diff --git a/systemd/generate_test.go b/systemd/generate_test.go index 76b35ab84..d276d3c58 100644 --- a/systemd/generate_test.go +++ b/systemd/generate_test.go @@ -1,4 +1,5 @@ -//+build !darwin,!windows +//go:build !darwin && !windows +// +build !darwin,!windows package systemd @@ -51,6 +52,7 @@ Type=notify WorkingDirectory=workdir ExecStart=commandLine Nice=5 +Environment="RESTICPROFILE_ON_SCHEDULE=1" Environment="HOME=%s" ` const expectedTimer = `[Unit]