AkurAI Build
Menu

AkurAI-Build

public

Latest change e9497c4d8313c394e907ae962fdfe3bb5025013e - Add mail-server backup, audit, and update operations by Ólafur Búi Ólafsson

set -euo pipefail
action="$1"
case "$action" in
check | apply | postverify) ;;
*)
  echo 'action must be check, apply, or postverify' >&2
  exit 2
  ;;
esac

patch_state() {
  local upgradable security reboot packages lists_age
  upgradable="$(apt list --upgradable 2>/dev/null | grep -c '/')"
  security="$(apt list --upgradable 2>/dev/null | grep -c -- '-security')"
  reboot=no
  [ -f /var/run/reboot-required ] && reboot=yes
  packages="$(tr '\n' ',' </var/run/reboot-required.pkgs 2>/dev/null | sed 's/,$//')"
  lists_age=$((($(date -u +%s) - $(stat -c %Y /var/lib/apt/lists 2>/dev/null || echo 0)) / 3600))
  printf 'upgradable=%s security=%s reboot_required=%s reboot_packages=%s apt_lists_age_h=%s\n' \
    "$upgradable" "$security" "$reboot" "${packages:-none}" "$lists_age"
}

postverify() {
  local failures=0 unit port state banner
  for unit in postfix@- dovecot opendkim opendmarc spamass-milter fail2ban; do
    state="$(systemctl is-active "$unit" 2>/dev/null || true)"
    printf 'unit %s=%s\n' "$unit" "$state"
    [ "$state" = active ] || failures=$((failures + 1))
  done
  for port in 25 587 993 143; do
    if ss -ltn "sport = :$port" 2>/dev/null | grep -q LISTEN; then
      printf 'port %s=listening\n' "$port"
    else
      printf 'port %s=closed\n' "$port"
      failures=$((failures + 1))
    fi
  done
  banner="$(exec 3<>/dev/tcp/127.0.0.1/25 && head -1 <&3 | tr -d '\r' && exec 3<&-)" || banner=''
  printf 'smtp_banner=%s\n' "${banner:-none}"
  case "$banner" in
  220*) ;;
  *) failures=$((failures + 1)) ;;
  esac
  banner="$(timeout 15 openssl s_client -quiet -verify_quiet -connect 127.0.0.1:993 \
    </dev/null 2>/dev/null | head -1 | tr -d '\r')" || banner=''
  printf 'imaps_banner=%s\n' "${banner:-none}"
  case "$banner" in
  '* OK'*) ;;
  *) failures=$((failures + 1)) ;;
  esac
  printf 'mail_services=%s failures=%s\n' \
    "$([ "$failures" -eq 0 ] && echo healthy || echo degraded)" "$failures"
  [ "$failures" -eq 0 ]
}

if [ "$action" = check ]; then
  sudo apt-get update -qq
  patch_state
  apt list --upgradable 2>/dev/null | grep '/' || true
  postverify
  exit 0
fi

if [ "$action" = postverify ]; then
  postverify
  exit 0
fi

# A patch run must never be the only copy of this mailbox store.
latest="$(sudo find /var/backups/akurai-mail -maxdepth 1 -type f -name 'akurai-mail-*.tar.gz.gpg' \
  -printf '%T@ %p\n' 2>/dev/null | sort -nr | head -1 | cut -d' ' -f2-)"
if [ -z "$latest" ]; then
  echo 'refusing to patch: no encrypted mail backup exists (run `akurai-ec2 mail-backup run`)' >&2
  exit 1
fi
age=$(($(date -u +%s) - $(sudo stat -c %Y "$latest")))
if [ "$age" -ge 93600 ]; then
  printf 'refusing to patch: newest mail backup is %ss old (limit 93600)\n' "$age" >&2
  exit 1
fi
printf 'backup_gate=ok latest=%s age_secs=%s\n' "$(basename "$latest")" "$age"

sudo apt-get update -qq
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
  -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold dist-upgrade
sudo DEBIAN_FRONTEND=noninteractive apt-get -y --purge autoremove
sudo apt-get clean
patch_state
postverify