#!/usr/bin/env bash # # mail-wrapper -- Report results of a command by email # Copyright (c)2017 Alexander Barton (alex@barton.de) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # NAME=$(basename "$0") # Include "ax-common.sh": ax_common_sourced= for dir in "$HOME/lib" "$HOME/.ax" /usr/local /opt/ax /usr; do [ -z "$ax_common_sourced" ] || break ax_common="${dir}/lib/ax/ax-common.sh" # shellcheck source=/usr/local/lib/ax/ax-common.sh [ -r "$ax_common" ] && . "$ax_common" done if [ -z "$ax_common_sourced" ]; then echo "Error ($NAME): \"ax-common.sh\" not found, aborting!" >&2 echo "Please install 'ax-unix', \"Alex' UNIX Tools & Scripts\", and try again." exit 99 fi unset dir ax_common ax_common_sourced usage() { { echo echo "Usage:" echo " $NAME [--help|--usage]" echo " $NAME {parameters} [ [ [<…>]]]" echo echo " -C Use the \"C\" locale, no localized (error) messages." echo " --errors|-e Generate email on errors only." echo " --from|-f Email address of the sender of the email." echo " --stderr-is-warning|-W Exit code indicates error; stderr is only warning." echo " --subject|-s Subject for the email." echo " --to|-t
Email address to send the email to." echo echo "When no is given, $NAME reads from standard input." echo } >&2 exit "${1:-0}" } syntax_error() { ax_error -l "Syntax error!" usage 2 } clean_up() { if [[ -z "$proc_fd_works" ]]; then ax_debug "Cleaning temporary files ..." [[ -n "$buffer_file" ]] && rm -f "$buffer_file" [[ -n "$error_file" ]] && rm -f "$error_file" fi } case "$(uname)" in "Darwin") unset proc_fd_works ;; *) proc_fd_works=1 esac # Initialize internal state. buffer_file="" error_file="" error_level=0 host=$(hostname -f 2>/dev/null || hostname) trap clean_up EXIT # Some defaults (can be adjusted by command line parameters). unset do_errors_only unset stderr_is_warning unset subject from="${LOGNAME:-root} <${LOGNAME:-root}@$host>" to="$from" # Parse the command line ... while [[ $# -gt 0 ]]; do case "$1" in "-C") unset LANG export LC_ALL="C" ;; "--debug"|"-D") export DEBUG=1 ;; "--errors"|"-e") do_errors_only=1 ;; "--from"|"-f") shift [[ $# -gt 0 ]] || syntax_error from="$1" ;; "--help"|"--usage") usage ;; "--subject"|"-s") shift [[ $# -gt 0 ]] || syntax_error subject="$1" ;; "--stderr-is-warning"|"-W") stderr_is_warning=1 ;; "--suppress-empty") # Ignore this switch for compatibility with an other # "mail-wrapper" script. This is the default anyway! ;; "--to"|"-t") shift [[ $# -gt 0 ]] || syntax_error to="$1" ;; "-"*) syntax_error ;; *) # Command to execute follows in command line. break ;; esac shift done # Initialize the "buffer file" on file handle #3. This file will store all # output, stdout and stderr combined. The file is immediately unliked so that # we can't leak stale files. Afterwards this script accesses the "buffer file" # by its file descriptor only. buffer_file=$(mktemp) \ || ax_abort -l "Failed to create buffer file: \"$buffer_file\"!" ax_debug "buffer_file=\"$buffer_file\"" exec 3>"$buffer_file" \ || ax_abort -l "Failed to redirect FD #3 to buffer file!" if [[ -n "$proc_fd_works" ]]; then rm "$buffer_file" \ || ax_error -l "Failed to delete buffer file: \"$buffer_file\"!" buffer_file="/dev/fd/3" fi if [[ $# -gt 0 ]]; then # Execute command and save output in buffer file. # Use a sub-shell to not pollute our name space! error_file=$(mktemp) \ || ax_abort -l "Failed to create error buffer file: \"$error_file\"!" ax_debug "error_file=\"$error_file\"" exec 4>"$error_file" \ || ax_abort -l "Failed to redirect FD #4 to error file!" if [[ -n "$proc_fd_works" ]]; then rm "$error_file" \ || ax_error -l "Failed to delete error buffer file: \"$error_file\"!" error_file="/dev/fd/4" fi job=$(basename "$1") ax_debug "Running command \"$*\" ..." exit_code=$( "$@" 2>&1 1>&3 | tee "$error_file" >&3 echo "${PIPESTATUS[0]}" ) else # Read from stdin and save it to the buffer file. error_file="/dev/null" job="Job" ax_debug "Reading from stdin ..." while read -r line; do echo "$line" >&3 \ || ax_abort -l "Failed to write to buffer file!" done exit_code=0 fi ax_debug "exit_code=$exit_code" declare -i count_all count_err count_all=$(wc -l <"$buffer_file" || ax_abort -l "Failed to count buffer file!") count_err=$(wc -l <"$error_file" || ax_abort -l "Failed to count error file!") # Error or no error -- that's the question! An error is assumed when either the # exit code of the command was non-zero or there was output to stderr. # But when stderr_is_warning is set, messages on stderr result on a warning only! if [[ $exit_code -ne 0 ]]; then error_level=2 elif [[ $count_err -gt 0 ]]; then [[ -n $stderr_is_warning ]] && error_level=1 || error_level=2 else error_level=0 fi # Construct email subject ... [[ -z "$subject" ]] && subject="$host: $job report" if [[ "$error_level" -eq 0 ]]; then subject="$subject - success" elif [[ "$error_level" -eq 1 ]]; then subject="$subject - WARNING!" else subject="$subject - ERROR!" fi ax_debug "from=\"$from\"" ax_debug "to=\"$to\"" ax_debug "subject=$subject" if [[ -n "$DEBUG" ]]; then echo "--- stdout+stderr ---" cat "$buffer_file" echo "--- stderr ---" cat "$error_file" echo "---" fi ax_debug "count_all=$count_all" ax_debug "count_err=$count_err" ax_debug "error_level=$error_level" # No errors detected (exit code & stderr), and email should be sent on errors # only: so exit early! [[ "$error_level" -lt 2 && -n "$do_errors_only" ]] && exit $exit_code # No error detected and no output at all: skip email, exit early: [[ "$error_level" -eq 0 && $count_all -eq 0 ]] && exit $exit_code # Build the report mail. # Make sure to ignore all mail(1) configuration files, system wide /etc/mailrc # (by using the "-n" option) as well as ~/.mailrc (by setting the MAILRC # environment varialbe). export MAILRC=/dev/null ( echo "$job report:" echo echo " - Host: $host" echo " - User: $(id -un)" echo " - Exit code: $exit_code" echo if [[ $# -gt 0 ]]; then # A command name is known (not stdin), show it! echo "Command:" echo "$@" echo fi if [[ $count_err -gt 0 ]]; then # Prefix mail with all error messages. echo "Error summary:" echo "-----------------------------------------------------------------------------" cat "$error_file" \ || ax_abort -l "Failed to dump error file!" echo "-----------------------------------------------------------------------------" echo fi if [[ $count_all -ne $count_err ]]; then # Show full output when different to "error output" only. cat "$buffer_file" \ || ax_abort -l "Failed to dump buffer file!" fi ) | mail -n -a "From: $from" -s "$subject" "$to" \ || ax_abort -l "Failed to send email to \"$to\"!" exit $exit_code