Move /sbin to /bin
As more users would have /bin than /sbin in their PATH
This commit is contained in:
24
bin/cron_mail
Executable file
24
bin/cron_mail
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env sh
|
||||
# vi: ft=sh
|
||||
#
|
||||
# To be called by a cron job as a wrapper that sends stdour and stderr via the mail program.
|
||||
# Why? Because of FreeBSD the system cron uses sendmail, and I want to use ssmtp.
|
||||
# Make your crontab files like:
|
||||
#SHELL=/bin/sh
|
||||
#PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin:$INSTALL_PREFIX/bin
|
||||
#@daily root cron_mail freebsd-update cron
|
||||
|
||||
mail_target=root
|
||||
scriptname=${0##*/}
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "No program to run given!" >&2
|
||||
exit 1
|
||||
fi
|
||||
cmd="$*"
|
||||
|
||||
body=$(eval "$cmd" 2>&1)
|
||||
|
||||
if [ -n "$body" ];then
|
||||
echo "$body" | mail -s "${scriptname}: ${cmd}" $mail_target
|
||||
fi
|
||||
19
bin/nm-unmetered-connection.sh
Normal file
19
bin/nm-unmetered-connection.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
systemctl is-active dbus.service >/dev/null 2>&1 || exit 0
|
||||
systemctl is-active NetworkManager.service >/dev/null 2>&1 || exit 0
|
||||
|
||||
metered_status=$(dbus-send --system --print-reply=literal \
|
||||
--system --dest=org.freedesktop.NetworkManager \
|
||||
/org/freedesktop/NetworkManager \
|
||||
org.freedesktop.DBus.Properties.Get \
|
||||
string:org.freedesktop.NetworkManager string:Metered \
|
||||
| grep -o ".$")
|
||||
|
||||
if [[ $metered_status =~ (1|3) ]]; then
|
||||
echo Current connection is metered
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
|
||||
103
bin/restic_backup.sh
Normal file
103
bin/restic_backup.sh
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# Make a backup with restic to Backblaze B2.
|
||||
#
|
||||
# This script is typically run (as root user) either like:
|
||||
# - from restic service/timer: $PREFIX/etc/systemd/system/restic-backup.{service,timer}
|
||||
# - from a cronjob: $PREFIX/etc/cron.d/restic
|
||||
# - manually by a user. For it to work, the environment variables must be set in the shell where this script is executed
|
||||
# $ source $PREFIX/etc/default.env
|
||||
# $ restic_backup.sh
|
||||
|
||||
# Exit on error, unset var, pipe failure
|
||||
set -euo pipefail
|
||||
|
||||
# Clean up lock if we are killed.
|
||||
# If killed by systemd, like $(systemctl stop restic), then it kills the whole cgroup and all it's subprocesses.
|
||||
# However if we kill this script ourselves, we need this trap that kills all subprocesses manually.
|
||||
exit_hook() {
|
||||
echo "In exit_hook(), being killed" >&2
|
||||
jobs -p | xargs kill
|
||||
restic unlock
|
||||
}
|
||||
trap exit_hook INT TERM
|
||||
|
||||
|
||||
# Assert that all needed environment variables are set.
|
||||
# TODO in future if this grows, move this to a restic_lib.sh
|
||||
assert_envvars() {
|
||||
local varnames=("$@")
|
||||
for varname in "${varnames[@]}"; do
|
||||
if [ -z ${!varname+x} ]; then
|
||||
printf "%s must be set for this script to work.\n\nDid you forget to source a /etc/restic/*.env profile in the current shell before executing this script?\n" "$varname" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
assert_envvars \
|
||||
B2_ACCOUNT_ID B2_ACCOUNT_KEY B2_CONNECTIONS \
|
||||
RESTIC_BACKUP_PATHS RESTIC_BACKUP_TAG \
|
||||
RESTIC_BACKUP_EXCLUDE_FILE RESTIC_BACKUP_EXTRA_ARGS RESTIC_PASSWORD_FILE RESTIC_REPOSITORY RESTIC_VERBOSITY_LEVEL \
|
||||
RESTIC_RETENTION_DAYS RESTIC_RETENTION_MONTHS RESTIC_RETENTION_WEEKS RESTIC_RETENTION_YEARS
|
||||
|
||||
|
||||
# Convert to arrays, as arrays should be used to build command lines. See https://github.com/koalaman/shellcheck/wiki/SC2086
|
||||
IFS=':' read -ra backup_paths <<< "$RESTIC_BACKUP_PATHS"
|
||||
IFS=' ' read -ra extra_args <<< "$RESTIC_BACKUP_EXTRA_ARGS"
|
||||
|
||||
|
||||
# Set up exclude files: global + path-specific ones
|
||||
# NOTE that restic will fail the backup if not all listed --exclude-files exist. Thus we should only list them if they are really all available.
|
||||
## Global backup configuration.
|
||||
exclusion_args=(--exclude-file "$RESTIC_BACKUP_EXCLUDE_FILE")
|
||||
## Self-contained backup exclusion files per backup path. E.g. having an USB disk at /mnt/media in RESTIC_BACKUP_PATHS,
|
||||
# then a file /mnt/media/.backup_exclude.txt will automatically be detected and used:
|
||||
for backup_path in "${backup_paths[@]}"; do
|
||||
if [ -f "$backup_path/.backup_exclude.txt" ]; then
|
||||
exclusion_args=("${exclusion_args[@]}" --exclude-file "$backup_path/.backup_exclude.txt")
|
||||
fi
|
||||
done
|
||||
|
||||
# NOTE start all commands in background and wait for them to finish.
|
||||
# Reason: bash ignores any signals while child process is executing and thus the trap exit hook is not triggered.
|
||||
# However if put in subprocesses, wait(1) waits until the process finishes OR signal is received.
|
||||
# Reference: https://unix.stackexchange.com/questions/146756/forward-sigterm-to-child-in-bash
|
||||
|
||||
# Remove locks from other stale processes to keep the automated backup running.
|
||||
restic unlock &
|
||||
wait $!
|
||||
|
||||
# Do the backup!
|
||||
# See restic-backup(1) or http://restic.readthedocs.io/en/latest/040_backup.html
|
||||
# --one-file-system makes sure we only backup exactly those mounted file systems specified in $RESTIC_BACKUP_PATHS, and thus not directories like /dev, /sys etc.
|
||||
# --tag lets us reference these backups later when doing restic-forget.
|
||||
restic backup \
|
||||
--verbose="$RESTIC_VERBOSITY_LEVEL" \
|
||||
--one-file-system \
|
||||
--tag "$RESTIC_BACKUP_TAG" \
|
||||
--option b2.connections="$B2_CONNECTIONS" \
|
||||
"${exclusion_args[@]}" \
|
||||
"${extra_args[@]}" \
|
||||
"${backup_paths[@]}" &
|
||||
wait $!
|
||||
|
||||
# Dereference and delete/prune old backups.
|
||||
# See restic-forget(1) or http://restic.readthedocs.io/en/latest/060_forget.html
|
||||
# --group-by only the tag and path, and not by hostname. This is because I create a B2 Bucket per host, and if this hostname accidentially change some time, there would now be multiple backup sets.
|
||||
restic forget \
|
||||
--verbose="$RESTIC_VERBOSITY_LEVEL" \
|
||||
--tag "$RESTIC_BACKUP_TAG" \
|
||||
--option b2.connections="$B2_CONNECTIONS" \
|
||||
--prune \
|
||||
--group-by "paths,tags" \
|
||||
--keep-daily "$RESTIC_RETENTION_DAYS" \
|
||||
--keep-weekly "$RESTIC_RETENTION_WEEKS" \
|
||||
--keep-monthly "$RESTIC_RETENTION_MONTHS" \
|
||||
--keep-yearly "$RESTIC_RETENTION_YEARS" &
|
||||
wait $!
|
||||
|
||||
# Check repository for errors.
|
||||
# NOTE this takes much time (and data transfer from remote repo?), do this in a separate systemd.timer which is run less often.
|
||||
#restic check &
|
||||
#wait $!
|
||||
|
||||
echo "Backup & cleaning is done."
|
||||
42
bin/restic_check.sh
Normal file
42
bin/restic_check.sh
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
# Check the backups made with restic to Backblaze B2 for errors.
|
||||
# See restic_backup.sh on how this script is run (as it's analogous for this script).
|
||||
|
||||
# Exit on error, unset var, pipe failure
|
||||
set -euo pipefail
|
||||
|
||||
# Clean up lock if we are killed.
|
||||
# If killed by systemd, like $(systemctl stop restic), then it kills the whole cgroup and all it's subprocesses.
|
||||
# However if we kill this script ourselves, we need this trap that kills all subprocesses manually.
|
||||
exit_hook() {
|
||||
echo "In exit_hook(), being killed" >&2
|
||||
jobs -p | xargs kill
|
||||
restic unlock
|
||||
}
|
||||
trap exit_hook INT TERM
|
||||
|
||||
# Assert that all needed environment variables are set.
|
||||
assert_envvars() {
|
||||
local varnames=("$@")
|
||||
for varname in "${varnames[@]}"; do
|
||||
if [ -z ${!varname+x} ]; then
|
||||
printf "%s must be set for this script to work.\n\nDid you forget to source a /etc/restic/*.env profile in the current shell before executing this script?\n" "$varname" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
assert_envvars \
|
||||
B2_ACCOUNT_ID B2_ACCOUNT_KEY B2_CONNECTIONS \
|
||||
RESTIC_PASSWORD_FILE RESTIC_REPOSITORY RESTIC_VERBOSITY_LEVEL
|
||||
|
||||
|
||||
# Remove locks from other stale processes to keep the automated backup running.
|
||||
# NOTE nope, don't unlock like restic_backup.sh. restic_backup.sh should take precedence over this script.
|
||||
#restic unlock &
|
||||
#wait $!
|
||||
|
||||
# Check repository for errors.
|
||||
restic check \
|
||||
--option b2.connections="$B2_CONNECTIONS" \
|
||||
--verbose="$RESTIC_VERBOSITY_LEVEL" &
|
||||
wait $!
|
||||
146
bin/resticw
Executable file
146
bin/resticw
Executable file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DOC="A little wrapper over restic just to handle profiles and environment loading.
|
||||
|
||||
Usage:
|
||||
resticw [options] <restic_arguments_line>...
|
||||
|
||||
The <restic_arguments_line> is just the regular unwrapped restic command arguments, e.g. stats latest
|
||||
|
||||
Options:
|
||||
-p --profile=<name> Specify the profile to load or use default [default: default].
|
||||
|
||||
Examples:
|
||||
resticw --profile profileA snapshots
|
||||
resticw stats latest # this will use the profile: default
|
||||
"
|
||||
|
||||
# The following argument parser is generated with docopt.sh from the above docstring.
|
||||
# See https://github.com/andsens/docopt.sh. If the DOC is updated or new options are added, refresh the parser!
|
||||
|
||||
# docopt parser below, refresh this parser with `docopt.sh resticw`
|
||||
# shellcheck disable=2016,1075,2154
|
||||
docopt() { parse() { if ${DOCOPT_DOC_CHECK:-true}; then local doc_hash
|
||||
if doc_hash=$(printf "%s" "$DOC" | (sha256sum 2>/dev/null || shasum -a 256)); then
|
||||
if [[ ${doc_hash:0:5} != "$digest" ]]; then
|
||||
stderr "The current usage doc (${doc_hash:0:5}) does not match \
|
||||
what the parser was generated with (${digest})
|
||||
Run \`docopt.sh\` to refresh the parser."; _return 70; fi; fi; fi
|
||||
local root_idx=$1; shift; argv=("$@"); parsed_params=(); parsed_values=()
|
||||
left=(); testdepth=0; local arg; while [[ ${#argv[@]} -gt 0 ]]; do
|
||||
if [[ ${argv[0]} = "--" ]]; then for arg in "${argv[@]}"; do
|
||||
parsed_params+=('a'); parsed_values+=("$arg"); done; break
|
||||
elif [[ ${argv[0]} = --* ]]; then parse_long
|
||||
elif [[ ${argv[0]} = -* && ${argv[0]} != "-" ]]; then parse_shorts
|
||||
elif ${DOCOPT_OPTIONS_FIRST:-false}; then for arg in "${argv[@]}"; do
|
||||
parsed_params+=('a'); parsed_values+=("$arg"); done; break; else
|
||||
parsed_params+=('a'); parsed_values+=("${argv[0]}"); argv=("${argv[@]:1}"); fi
|
||||
done; local idx; if ${DOCOPT_ADD_HELP:-true}; then
|
||||
for idx in "${parsed_params[@]}"; do [[ $idx = 'a' ]] && continue
|
||||
if [[ ${shorts[$idx]} = "-h" || ${longs[$idx]} = "--help" ]]; then
|
||||
stdout "$trimmed_doc"; _return 0; fi; done; fi
|
||||
if [[ ${DOCOPT_PROGRAM_VERSION:-false} != 'false' ]]; then
|
||||
for idx in "${parsed_params[@]}"; do [[ $idx = 'a' ]] && continue
|
||||
if [[ ${longs[$idx]} = "--version" ]]; then stdout "$DOCOPT_PROGRAM_VERSION"
|
||||
_return 0; fi; done; fi; local i=0; while [[ $i -lt ${#parsed_params[@]} ]]; do
|
||||
left+=("$i"); ((i++)) || true; done
|
||||
if ! required "$root_idx" || [ ${#left[@]} -gt 0 ]; then error; fi; return 0; }
|
||||
parse_shorts() { local token=${argv[0]}; local value; argv=("${argv[@]:1}")
|
||||
[[ $token = -* && $token != --* ]] || _return 88; local remaining=${token#-}
|
||||
while [[ -n $remaining ]]; do local short="-${remaining:0:1}"
|
||||
remaining="${remaining:1}"; local i=0; local similar=(); local match=false
|
||||
for o in "${shorts[@]}"; do if [[ $o = "$short" ]]; then similar+=("$short")
|
||||
[[ $match = false ]] && match=$i; fi; ((i++)) || true; done
|
||||
if [[ ${#similar[@]} -gt 1 ]]; then
|
||||
error "${short} is specified ambiguously ${#similar[@]} times"
|
||||
elif [[ ${#similar[@]} -lt 1 ]]; then match=${#shorts[@]}; value=true
|
||||
shorts+=("$short"); longs+=(''); argcounts+=(0); else value=false
|
||||
if [[ ${argcounts[$match]} -ne 0 ]]; then if [[ $remaining = '' ]]; then
|
||||
if [[ ${#argv[@]} -eq 0 || ${argv[0]} = '--' ]]; then
|
||||
error "${short} requires argument"; fi; value=${argv[0]}; argv=("${argv[@]:1}")
|
||||
else value=$remaining; remaining=''; fi; fi; if [[ $value = false ]]; then
|
||||
value=true; fi; fi; parsed_params+=("$match"); parsed_values+=("$value"); done
|
||||
}; parse_long() { local token=${argv[0]}; local long=${token%%=*}
|
||||
local value=${token#*=}; local argcount; argv=("${argv[@]:1}")
|
||||
[[ $token = --* ]] || _return 88; if [[ $token = *=* ]]; then eq='='; else eq=''
|
||||
value=false; fi; local i=0; local similar=(); local match=false
|
||||
for o in "${longs[@]}"; do if [[ $o = "$long" ]]; then similar+=("$long")
|
||||
[[ $match = false ]] && match=$i; fi; ((i++)) || true; done
|
||||
if [[ $match = false ]]; then i=0; for o in "${longs[@]}"; do
|
||||
if [[ $o = $long* ]]; then similar+=("$long"); [[ $match = false ]] && match=$i
|
||||
fi; ((i++)) || true; done; fi; if [[ ${#similar[@]} -gt 1 ]]; then
|
||||
error "${long} is not a unique prefix: ${similar[*]}?"
|
||||
elif [[ ${#similar[@]} -lt 1 ]]; then
|
||||
[[ $eq = '=' ]] && argcount=1 || argcount=0; match=${#shorts[@]}
|
||||
[[ $argcount -eq 0 ]] && value=true; shorts+=(''); longs+=("$long")
|
||||
argcounts+=("$argcount"); else if [[ ${argcounts[$match]} -eq 0 ]]; then
|
||||
if [[ $value != false ]]; then
|
||||
error "${longs[$match]} must not have an argument"; fi
|
||||
elif [[ $value = false ]]; then
|
||||
if [[ ${#argv[@]} -eq 0 || ${argv[0]} = '--' ]]; then
|
||||
error "${long} requires argument"; fi; value=${argv[0]}; argv=("${argv[@]:1}")
|
||||
fi; if [[ $value = false ]]; then value=true; fi; fi; parsed_params+=("$match")
|
||||
parsed_values+=("$value"); }; required() { local initial_left=("${left[@]}")
|
||||
local node_idx; ((testdepth++)) || true; for node_idx in "$@"; do
|
||||
if ! "node_$node_idx"; then left=("${initial_left[@]}"); ((testdepth--)) || true
|
||||
return 1; fi; done; if [[ $((--testdepth)) -eq 0 ]]; then
|
||||
left=("${initial_left[@]}"); for node_idx in "$@"; do "node_$node_idx"; done; fi
|
||||
return 0; }; optional() { local node_idx; for node_idx in "$@"; do
|
||||
"node_$node_idx"; done; return 0; }; oneormore() { local i=0
|
||||
local prev=${#left[@]}; while "node_$1"; do ((i++)) || true
|
||||
[[ $prev -eq ${#left[@]} ]] && break; prev=${#left[@]}; done
|
||||
if [[ $i -ge 1 ]]; then return 0; fi; return 1; }; value() { local i
|
||||
for i in "${!left[@]}"; do local l=${left[$i]}
|
||||
if [[ ${parsed_params[$l]} = "$2" ]]; then
|
||||
left=("${left[@]:0:$i}" "${left[@]:((i+1))}")
|
||||
[[ $testdepth -gt 0 ]] && return 0; local value
|
||||
value=$(printf -- "%q" "${parsed_values[$l]}"); if [[ $3 = true ]]; then
|
||||
eval "var_$1+=($value)"; else eval "var_$1=$value"; fi; return 0; fi; done
|
||||
return 1; }; stdout() { printf -- "cat <<'EOM'\n%s\nEOM\n" "$1"; }; stderr() {
|
||||
printf -- "cat <<'EOM' >&2\n%s\nEOM\n" "$1"; }; error() {
|
||||
[[ -n $1 ]] && stderr "$1"; stderr "$usage"; _return 1; }; _return() {
|
||||
printf -- "exit %d\n" "$1"; exit "$1"; }; set -e; trimmed_doc=${DOC:0:450}
|
||||
usage=${DOC:79:53}; digest=dc31d; shorts=(-p); longs=(--profile); argcounts=(1)
|
||||
node_0(){ value __profile 0; }; node_1(){ value _restic_arguments_line_ a true
|
||||
}; node_2(){ optional 0; }; node_3(){ optional 2; }; node_4(){ oneormore 1; }
|
||||
node_5(){ required 3 4; }; node_6(){ required 5; }; cat <<<' docopt_exit() {
|
||||
[[ -n $1 ]] && printf "%s\n" "$1" >&2; printf "%s\n" "${DOC:79:53}" >&2; exit 1
|
||||
}'; unset var___profile var__restic_arguments_line_; parse 6 "$@"
|
||||
local prefix=${DOCOPT_PREFIX:-''}; unset "${prefix}__profile" \
|
||||
"${prefix}_restic_arguments_line_"
|
||||
eval "${prefix}"'__profile=${var___profile:-default}'
|
||||
if declare -p var__restic_arguments_line_ >/dev/null 2>&1; then
|
||||
eval "${prefix}"'_restic_arguments_line_=("${var__restic_arguments_line_[@]}")'
|
||||
else eval "${prefix}"'_restic_arguments_line_=()'; fi; local docopt_i=1
|
||||
[[ $BASH_VERSION =~ ^4.3 ]] && docopt_i=2; for ((;docopt_i>0;docopt_i--)); do
|
||||
declare -p "${prefix}__profile" "${prefix}_restic_arguments_line_"; done; }
|
||||
# docopt parser above, complete command for generating this parser is `docopt.sh resticw`
|
||||
|
||||
# Parse arguments
|
||||
eval "$(docopt "$@")" # See https://github.com/andsens/docopt.sh for the magic :)
|
||||
|
||||
# --^^^-- END OF GENERATED COMMAND LINE PARSING STUFF --^^^--
|
||||
#
|
||||
# --vvv-- ACTUAL SCRIPT BELOW --vvv--
|
||||
|
||||
# Exit on error, unbound variable, pipe error
|
||||
set -euo pipefail
|
||||
ENV_DIR=/etc/restic
|
||||
|
||||
ERR_NO_SUCH_PROFILE=2
|
||||
ERR_PROFILE_NO_READ_PERM=3
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
profile_file="${ENV_DIR}/${__profile}.env"
|
||||
|
||||
[[ ! -f "$profile_file" ]] && echo "Invalid profile: No such environment file ${profile_file}" && exit "$ERR_NO_SUCH_PROFILE"
|
||||
|
||||
if [[ ! -r "$profile_file" ]]; then
|
||||
echo "Error: could not read the environment file ${profile_file}. Are you running this script as the correct user? Maybe try sudo with the right user."
|
||||
exit "$ERR_PROFILE_NO_READ_PERM"
|
||||
fi
|
||||
|
||||
echo -e "‣ Using profile: ${__profile} -- (${profile_file})\n"
|
||||
|
||||
# shellcheck disable=SC2154,SC1090
|
||||
source "$profile_file" && restic "$_restic_arguments_line_"
|
||||
61
bin/systemd-email
Normal file
61
bin/systemd-email
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
# Send email notification from systemd.
|
||||
# Source: https://serverfault.com/questions/876233/how-to-send-an-email-if-a-systemd-service-is-restarted
|
||||
# Source: https://wiki.archlinux.org/index.php/Systemd/Timers#MAILTO
|
||||
# Usage: systemd-email <recipient-email> <failed-systemd-unit-name>
|
||||
|
||||
|
||||
# According to
|
||||
# http://www.flashissue.com/blog/gmail-sending-limits/
|
||||
# Gmail blocks your account if you send more than 500 emails per day, which is one email every
|
||||
# (24 * 60 * 60) / 500 = 172.8 second => choose a min wait time which is significantly longer than this to be on the safe time to not exceed 500 emails per day.
|
||||
# However this source
|
||||
# https://group-mail.com/sending-email/email-send-limits-and-options/
|
||||
# says the limit when not using the Gmail webinterface but going directly to the SMTP server is 100-150 per day, which yelds maximum one email every
|
||||
# (24 * 60 * 60) / 100 = 864 second
|
||||
# One option that I used with my old Axis cameras it to use my gmx.com accunt for sending emails instead, as there are (no?) higher limits there.
|
||||
MIN_WAIT_TIME_S=900
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
LAST_RUN_FILE="/tmp/${SCRIPT_NAME}_last_run.txt"
|
||||
|
||||
last_touch() {
|
||||
stat -c %Y "$1"
|
||||
}
|
||||
|
||||
waited_long_enough() {
|
||||
retval=1
|
||||
if [ -e "$LAST_RUN_FILE" ]; then
|
||||
now=$(date +%s)
|
||||
last=$(last_touch "$LAST_RUN_FILE")
|
||||
wait_s=$((now - last))
|
||||
if [ "$wait_s" -gt "$MIN_WAIT_TIME_S" ]; then
|
||||
retval=0
|
||||
fi
|
||||
else
|
||||
retval=0
|
||||
fi
|
||||
|
||||
[ $retval -eq 0 ] && touch "$LAST_RUN_FILE"
|
||||
return $retval
|
||||
}
|
||||
|
||||
|
||||
# Make sure that my Gmail account dont' get shut down because of sending too many emails!
|
||||
if ! waited_long_enough; then
|
||||
echo "Systemd email was not sent, as it's less than ${MIN_WAIT_TIME_S} seconds since the last one was sent."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
recipient=$1
|
||||
system_unit=$2
|
||||
|
||||
sendmail -t <<ERRMAIL
|
||||
To: $recipient
|
||||
From: systemd <root@$HOSTNAME>
|
||||
Subject: [systemd-email] ${system_unit}
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
|
||||
$(systemctl status --full "$system_unit")
|
||||
ERRMAIL
|
||||
Reference in New Issue
Block a user