]> arthur.barton.de Git - ax-unix.git/blob - mail/wrapper/mail-wrapper
Add new "mail-wrapper" script
[ax-unix.git] / mail / wrapper / mail-wrapper
1 #!/usr/bin/env bash
2 #
3 # mail-wrapper -- Report results of a command by email
4 # Copyright (c)2017 Alexander Barton (alex@barton.de)
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11
12 NAME=$(basename "$0")
13
14 # Include "ax-common.sh":
15 ax_common_sourced=
16 for dir in "$HOME/lib" "$HOME/.ax" /usr/local /opt/ax /usr; do
17         [ -z "$ax_common_sourced" ] || break
18         ax_common="${dir}/lib/ax/ax-common.sh"
19         # shellcheck source=/usr/local/lib/ax/ax-common.sh
20         [ -r "$ax_common" ] && . "$ax_common"
21 done
22 if [ -z "$ax_common_sourced" ]; then
23         echo "Error ($(basename "$0")): \"ax-common.sh\" not found, aborting!"
24         echo "Please install 'ax-unix', \"Alex' UNIX Tools & Scripts\", and try again."
25         exit 99
26 fi
27 unset dir ax_common ax_common_sourced
28
29 usage() {
30         echo >&2
31         echo "Usage:" >&2
32         echo "  $NAME [--help|--usage]" >&2
33         echo "  $NAME {parameters} [<command> [<arg> [<…>]]]" >&2
34         echo >&2
35         echo "  -C                      Use the \"C\" locale, no localized (error) messages." >&2
36         echo "  --errors|-e             Generate email on errors only." >&2
37         echo "  --from|-f               Email address of the sender of the email." >&2
38         echo "  --subject|-s <subject>  Subject for the email." >&2
39         echo "  --to|-t <address>       Email address to send the email to." >&2
40         echo >&2
41         echo "When no <command> is given, $NAME reads from standard input." >&2
42         echo >&2
43         exit "${1:-0}"
44 }
45
46 syntax_error() {
47         ax_error -l "Syntax error!"
48         usage 2
49 }
50
51 # Initialize internal state.
52 unset is_error
53 host=$(hostname -f 2>/dev/null || hostname)
54
55 # Some defaults (can be adjusted by command line parameters).
56 unset do_errors_only
57 unset subject
58 from="${LOGNAME:-root} <${LOGNAME:-root}@$host>"
59 to="$from"
60
61 # Parse the command line ...
62 while [[ $# -gt 0 ]]; do
63         case "$1" in
64                 "-C")
65                         unset LANG
66                         export LC_ALL="C"
67                         ;;
68                 "--debug"|"-D")
69                         export DEBUG=1
70                         ;;
71                 "--errors"|"-e")
72                         do_errors_only=1
73                         ;;
74                 "--from"|"-f")
75                         shift
76                         [[ $# -gt 0 ]] || syntax_error
77                         from="$1"
78                         ;;
79                 "--help"|"--usage")
80                         usage
81                         ;;
82                 "--subject"|"-s")
83                         shift
84                         [[ $# -gt 0 ]] || syntax_error
85                         subject="$1"
86                         ;;
87                 "--to"|"-t")
88                         shift
89                         [[ $# -gt 0 ]] || syntax_error
90                         to="$1"
91                         ;;
92                 "-"*)
93                         syntax_error
94                         ;;
95                 *)
96                         # Command to execute follows in command line.
97                         break
98                         ;;
99         esac
100         shift
101 done
102
103 # Initialize the "buffer file" on file handle #3. This file will store all
104 # output, stdout and stderr combined. The file is immediately unliked so that
105 # we can't leak stale files. Afterwards this script accesses the "buffer file"
106 # by its file descriptor only.
107 buffer_file=$(mktemp) \
108         || ax_abort -l "Failed to create buffer file: \"$buffer_file\"!"
109 ax_debug "buffer_file=\"$buffer_file\""
110 exec 3>"$buffer_file" \
111         || ax_abort -l "Failed to redirect FD #3 to buffer file!"
112 rm "$buffer_file" \
113         || ax_error -l "Failed to delete buffer file: \"$buffer_file\"!"
114 buffer_file="/dev/fd/3"
115
116 if [[ $# -gt 0 ]]; then
117         # Execute command and save output in buffer file.
118         # Use a sub-shell to not pollute our name space!
119         error_file=$(mktemp) \
120                 || ax_abort -l "Failed to create error buffer file: \"$error_file\"!"
121         ax_debug "error_file=\"$error_file\""
122         exec 4>"$error_file" \
123                 || ax_abort -l "Failed to redirect FD #4 to error file!"
124         rm "$error_file" \
125                 || ax_error -l "Failed to delete error buffer file: \"$error_file\"!"
126         error_file="/dev/fd/4"
127
128         job=$(basename "$1")
129
130         ax_debug "Running command \"$*\" ..."
131         exit_code=$(
132                 "$@" 2>&1 1>&3 | tee "$error_file" >&3
133                 echo "${PIPESTATUS[0]}"
134         )
135 else
136         # Read from stdin and save it to the buffer file.
137         error_file="/dev/null"
138         job="Job"
139
140         ax_debug "Reading from stdin ..."
141         while read -r line; do
142                 echo "$line" >&3 \
143                         || ax_abort -l "Failed to write to buffer file!"
144         done
145         exit_code=0
146 fi
147
148 ax_debug "exit_code=$exit_code"
149
150 count_all=$(wc -l <"$buffer_file" || ax_abort -l "Failed to count buffer file!")
151 count_err=$(wc -l <"$error_file" || ax_abort -l "Failed to count error file!")
152
153 # Error or no error -- that's the question! An error is assumed when either the
154 # exit code of the command was non-zero or there was output to stderr.
155 [[ $count_err -gt 0 || $exit_code -ne 0 ]] && is_error=1
156
157 # Construct email subject ...
158 [[ -z "$subject" ]] && subject="$host: $job report"
159 [[ -n "$is_error" ]] && subject="$subject - ERROR!" || subject="$subject - success"
160
161 ax_debug "from=\"$from\""
162 ax_debug "to=\"$to\""
163 ax_debug "subject=$subject"
164
165 if [[ -n "$DEBUG" ]]; then
166         echo "--- stdout+stderr ---"
167         cat "$buffer_file"
168         echo "--- stderr ---"
169         cat "$error_file"
170         echo "---"
171 fi
172
173 ax_debug "count_all=$count_all"
174 ax_debug "count_err=$count_err"
175 ax_debug "is_error=$is_error"
176
177 # No errors detected (exit code & stderr), and email should be sent on errors
178 # only: so exit early!
179 [[ -z "$is_error" && -n "$do_errors_only" ]] && exit $exit_code
180
181 # No error detected and no output at all: skip email, exit early:
182 [[ -z "$is_error" && $count_all -eq 0 ]] && exit $exit_code
183
184 # Build the report mail.
185 # Make sure to ignore all mail(1) configuration files, system wide /etc/mailrc
186 # (by using the "-n" option) as well as ~/.mailrc (by setting the MAILRC
187 # environment varialbe).
188 export MAILRC=/dev/null
189 (
190         echo "$job report:"
191         echo
192         echo " - Host: $host"
193         echo " - User: $(id -un)"
194         echo " - Exit code: $exit_code"
195         echo
196         if [[ $# -gt 0 ]]; then
197                 # A command name is known (not stdin), show it!
198                 echo "Command:"
199                 echo "$@"
200                 echo
201         fi
202         if [[ $count_err -gt 0 ]]; then
203                 # Prefix mail with all error messages.
204                 echo "Error summary:"
205                 echo "-----------------------------------------------------------------------------"
206                 cat "$error_file" \
207                         || ax_abort -l "Failed to dump error file!"
208                 echo "-----------------------------------------------------------------------------"
209                 echo
210         fi
211         if [[ $count_all -ne $count_err ]]; then
212                 # Show full output when different to "error output" only.
213                 cat "$buffer_file" \
214                         || ax_abort -l "Failed to dump buffer file!"
215         fi
216 ) | mail -n -a "From: $from" -s "$subject" "$to" \
217         || ax_abort -l "Failed to send email to \"$to\"!"
218
219 exit $exit_code