satisfy shellcheck
[log-quiet] / sysd-mail-once
index 6e6807686a43c3e7f78a6c93b556533da9e8c02a..2c9d0263748e9b715acddb5bf3c0d57d28bb1dd9 100755 (executable)
@@ -1,5 +1,12 @@
 #!/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.
 # limitations under the License.
 
 
+
 set -eE -o pipefail
 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
 
 errors=3
-cbase=$HOME/sysd-mail-once-state
-case "$1" in
+tmp=(~)
+cbase="${tmp[0]}/sysd-mail-once-state"
+to=root
+dryrun=false
+print_all=false
+while [[ $1 == -* ]]; do
+  case "$1" in
     -h|--help)
-        cat <<EOF
-Usage: sysd-log-once [-ERRORS] SERVICE COMMAND [ARGS...]
-For systemd timers, email on repeated failure & success after failure.
+      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.
@@ -31,50 +46,142 @@ The email will contain the service's logs for the last ERRORS runs.
 
 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
-        exit 0
-        ;;
+      exit 0
+      ;;
     -[0-9]*)
-        errors=${1#-}
-        shift
-        ;;
-esac
-service=$1
+      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
 
-c=$cbase/$service
 
-glob="$c[0-9]*"
+# 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
+
+e "c=$c"
+
+glob="${c}[0-9]*"
 arr=($glob); file="${arr[0]}"; [[ $glob != "$file" ]] || file=
-u=${USER:-root}
-[[ -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
-if "$@"; then
-    if [[ $file ]]; then
-        rm -f $file
-        if [[ $file == $c$errors ]]; then
-            echo | mail -s "$HOSTNAME: $service success" $u@localhost
+
+
+code=0
+"$@" || code=$?
+if (( code )); then
+  send_mail=false
+  cursor=$(journalctl --show-cursor -qn0|sed 's/^\s*--\scursor:\s*//')
+  if [[ $file ]]; then
+    i=${file#"$c"}
+    if (( i < errors )); then
+      new_file=$c$((i+1))
+      m mv $file $new_file
+      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 # $@ failed
-    if [[ $file ]]; then
-        i=${file#$c}
-        if (( i < errors )); then
-            new_file=$c$((i+1))
-            mv $file $new_file
-            file=$new_file
-            if [[ $file == $c$errors ]]; then
-                journalctl -u $service.service --after-cursor=$(<$file) | \
-                    mail -s "$HOSTNAME: $service failure" $u@localhost
-            fi
-        fi
+  else
+    file=${c}1
+    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 $print_all; then
+      cursor=$(head -n1 $file)
     else
-        file=${c}1
-        printf "%s\n" "$cursor" >$file
+      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
+    m rm -f $file
+    if [[ $file == $c$errors ]]; then
+      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