satisfy shellcheck
[log-quiet] / sysd-mail-once
index 2f7f4d5d9efd035d7803d623bb946090f4597be7..2c9d0263748e9b715acddb5bf3c0d57d28bb1dd9 100755 (executable)
@@ -1,5 +1,12 @@
 #!/bin/bash
 #!/bin/bash
-# Copyright (C) 2016 Ian Kelling
+# I, Ian Kelling, follow the GNU license recommendations at
+# https://www.gnu.org/licenses/license-recommendations.en.html. They
+# recommend that small programs, < 300 lines, be licensed under the
+# Apache License 2.0. This file contains or is part of one or more small
+# programs. If a small program grows beyond 300 lines, I plan to switch
+# its license to GPL.
+
+# Copyright 2024 Ian Kelling
 
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # limitations under the License.
 
 
 # limitations under the License.
 
 
+
 set -eE -o pipefail
 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
 
 errors=3
 set -eE -o pipefail
 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
 
 errors=3
-cbase=$HOME/sysd-mail-once-state
+tmp=(~)
+cbase="${tmp[0]}/sysd-mail-once-state"
 to=root
 to=root
-case "$1" in
-  -h|--help)
-    cat <<EOF
-Usage: sysd-mail-once [-t TO_ADDRESS] [-ERRORS] SERVICE COMMAND [ARGS...]
-For systemd timers, email on repeated failure & success after failure.
+dryrun=false
+print_all=false
+while [[ $1 == -* ]]; do
+  case "$1" in
+    -h|--help)
+      cat <<EOF
+Usage: sysd-mail-once [-t TO_ADDRESS] [-ERRORS] SERVICE COMMAND [COMMAND_ARGS...]
+
+For use with systemd timers, to email (with exim) on repeated failure &
+success after failure.
 
 In the service triggered by the timer, prepend this script to the ExecStart.
 The email will contain the service's logs for the last ERRORS runs.
 
 In the service triggered by the timer, prepend this script to the ExecStart.
 The email will contain the service's logs for the last ERRORS runs.
@@ -32,63 +46,142 @@ The email will contain the service's logs for the last ERRORS runs.
 
 Stores error counts in $cbase
 
 
 Stores error counts in $cbase
 
--ERRORS:  ERRORS is the number of failurs to accumulate before mailing the error.
-          Default is 3.
+-ERRORS         ERRORS is the number of failurs to accumulate before mailing the error.
+                Default is 3.
+
+-a              Email the logs of all errors intead of just the last one.
+-t TO_ADDRESS   Address to email about errors
+-n              Dry run. Execute command but only print out what we would do in response.
 EOF
 EOF
-    exit 0
-    ;;
-  -[0-9]*)
-    errors=${1#-}
-    shift
-    ;;
-  -t)
-    to="$2"
-    shift 2
-    ;;
-esac
-service=$1
+      exit 0
+      ;;
+    -[0-9]*)
+      errors=${1#-}
+      ;;
+    -a)
+      print_all=true
+      ;;
+    -t)
+      to="$2"
+      shift
+      ;;
+    -n)
+      dryrun=true
+      ;;
+    *)
+      echo "error: unexpected arg: $1"
+      exit 1
+      ;;
+  esac
+  shift
+done
+
+if (( $# < 2 )); then
+  echo "error: expected at least 2 args after options. args:"
+  exit 1
+fi
+
+service="$1"
 shift
 
 shift
 
+
+# maybe run, depending on $dryrun
+m() {
+  if $dryrun; then
+    printf "%s\n" "$*"
+  else
+    "$@"
+  fi
+}
+# maybe run, with stdin
+mi() {
+  if $dryrun; then
+    printf "%s <<'EOF'\n" "$*"
+    cat
+    echo EOF
+  else
+    "$@"
+  fi
+}
+e() {
+  if $dryrun; then
+    printf "dryrun: %s\n" "$*"
+  fi
+}
+
 c=$cbase/$service # c for command file path base
 
 c=$cbase/$service # c for command file path base
 
-glob="$c[0-9]*"
+e "c=$c"
+
+glob="${c}[0-9]*"
 arr=($glob); file="${arr[0]}"; [[ $glob != "$file" ]] || file=
 arr=($glob); file="${arr[0]}"; [[ $glob != "$file" ]] || file=
-[[ -d $cbase ]] || mkdir -p $cbase
+if [[ $file ]]; then
+  e "file=$file"
+fi
 
 
-if [[ ! $file ]]; then
-  cursor=$(journalctl --show-cursor -qn0|sed 's/^\s*--\scursor:\s*//')
+if ! [[ -d $cbase ]]; then
+  mkdir -p $cbase
 fi
 
 fi
 
+
 code=0
 "$@" || code=$?
 if (( code )); then
   send_mail=false
 code=0
 "$@" || code=$?
 if (( code )); then
   send_mail=false
+  cursor=$(journalctl --show-cursor -qn0|sed 's/^\s*--\scursor:\s*//')
   if [[ $file ]]; then
   if [[ $file ]]; then
-    i=${file#$c}
+    i=${file#"$c"}
     if (( i < errors )); then
       new_file=$c$((i+1))
     if (( i < errors )); then
       new_file=$c$((i+1))
-      mv $file $new_file
+      m mv $file $new_file
       file=$new_file
       if [[ $file == $c$errors ]]; then
         send_mail=true
       file=$new_file
       if [[ $file == $c$errors ]]; then
         send_mail=true
+      else
+        if $dryrun; then
+          printf "dryrun: appending to file $file: %s\n" "$cursor"
+        else
+          printf "%s\n" "$cursor" >>$file
+        fi
       fi
     fi
   else
     file=${c}1
       fi
     fi
   else
     file=${c}1
-    printf "%s\n" "$cursor" >$file
+    if $dryrun; then
+      printf "dryrun: creating $file, contents: %s\n" "$cursor"
+    else
+      printf "%s\n" "$cursor" >$file
+    fi
     if (( errors == 1 )); then
       send_mail=true
     fi
   fi
   if $send_mail; then
     if (( errors == 1 )); then
       send_mail=true
     fi
   fi
   if $send_mail; then
-    journalctl -u $service.service --after-cursor=$(<$file) | \
-      mail -s "$HOSTNAME: $service exit code: $code" "$to"
+    if $print_all; then
+      cursor=$(head -n1 $file)
+    else
+      cursor=$(tail -n1 $file)
+    fi
+    echo "sysd-mail-once: emailing on $errors errors. exit code: $code"
+    mi exim -odf -t <<EOF
+To: $to
+From: $(id -u -n)@$(hostname -f)
+Subject: $HOSTNAME: $service exit code: $code
+
+$(journalctl -u $service.service --after-cursor="$cursor")
+EOF
   fi
 else
   if [[ $file ]]; then
   fi
 else
   if [[ $file ]]; then
-    rm -f $file
+    rm -f $file
     if [[ $file == $c$errors ]]; then
     if [[ $file == $c$errors ]]; then
-      echo | mail -s "$HOSTNAME: $service success" "$to"
+      echo "sysd-mail-once: emailing success after >= $errors errors."
+      mi exim -odf -t <<EOF
+To: $to
+From: $(id -u -n)@$(hostname -f)
+Subject: $HOSTNAME: $service success
+
+EOF
     fi
   fi
 fi
     fi
   fi
 fi