various minor fixes and improvements
[distro-setup] / mail-setup
index 3c1ad8160784494b96b3ab4f8582370864e584dd..54c8b6597cb16b9fe1e3fab4fc46860b9ef0085e 100755 (executable)
@@ -1,7 +1,23 @@
 #!/bin/bash
 # * intro
-# Copyright (C) 2019 Ian Kelling
-# SPDX-License-Identifier: AGPL-3.0-or-later
+
+# Program to install and configure Ian's email related programs
+# Copyright (C) 2024  Ian Kelling
+
+# 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 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# SPDX-License-Identifier: GPL-3.0-or-later
 
 # todo:
 # on bk (and fsf servers that run multiple exim4 daemons, eg eximfsf2 and eximfsf3),
 #   /usr/sbin/invoke-rc.d-diverted "$@"
 # fi
 
-# Things I tend to forget. on MAIL_HOST, daemon runs with /etc/exim4/my.conf,
+# Things I tend to forget. on MAIL_HOST, daemon runs with /etc/exim4/nn-mainlog.conf,
 # due to /etc/default/exim4 containing:
-# COMMONOPTIONS='-C /etc/exim4/my.conf'
-# UPEX4OPTS='-o /etc/exim4/my.conf'
+# COMMONOPTIONS='-C /etc/exim4/nn-mainlog.conf'
+# UPEX4OPTS='-o /etc/exim4/nn-mainlog.conf'
 #
 # The non-daemon config
-# gets generated from this script calling update-exim4.conf -d /etc/myexim4
+# gets generated from this script calling update-exim4.conf -d /etc/nond-exim4
 # which has log path
-# log_file_path = /var/log/exim4/my%s
+# log_file_path = /var/log/exim4/nond%s
 #
 # On non bk|MAIL_HOST, the config and log file are all standard.
 #
 # todo: run mailping test after running, or otherwise
 # clear out terminal alert
 
-# todo: disable postgrey
+# todo: disable postgrey. (why did we have it?)
 
 # todo: in testforward-check, we should also look
 
@@ -183,10 +199,10 @@ if [ -z "$BASH_VERSION" ]; then echo "error: shell is not bash" >&2; exit 1; fi
 
 shopt -s nullglob
 
-if [[ -s /usr/local/lib/err ]]; then
-  source /usr/local/lib/err
-elif [[ -s /a/bin/errhandle/err ]]; then
-  source /a/bin/errhandle/err
+if [[ -s /usr/local/lib/bash-bear ]]; then
+  source /usr/local/lib/bash-bear
+elif [[ -s /a/bin/bash-bear-trap/bash-bear ]]; then
+  source /a/bin/bash-bear-trap/bash-bear
 else
   echo "no err tracing script found"
   exit 1
@@ -345,28 +361,8 @@ reload=false
 if [[ -e /var/local/mail-setup-reload ]]; then
   reload=true
 fi
-u() { # update file. note: duplicated in brc
-  local tmp tmpdir dest="$1"
-  local base="${dest##*/}"
-  local dir="${dest%/*}"
-  if [[ $dir != "$base" ]]; then
-    # dest has a directory component
-    mkdir -p "$dir"
-  fi
-  ur=false # u result
-  tmpdir=$(mktemp -d)
-  cat >$tmpdir/"$base"
-  tmp=$(rsync -ic $tmpdir/"$base" "$dest")
-  if [[ $tmp ]]; then
-    printf "%s\n" "$tmp"
-    ur=true
-    if [[ $dest == /etc/systemd/system/* ]]; then
-      touch /var/local/mail-setup-reload
-      reload=true
-    fi
-  fi
-  rm -rf $tmpdir
-}
+
+source /a/bin/fai/fai/config/distro-install-common/bash-misc-funcs
 setini() {
   key="$1" value="$2" section="$3"
   file="/etc/radicale/config"
@@ -399,7 +395,6 @@ sre() {
 mailhost() {
   [[ $HOSTNAME == "$MAIL_HOST" ]]
 }
-e() { printf "%s\n" "$*"; }
 reifactive() {
   for service; do
     if systemctl is-active $service >/dev/null; then
@@ -438,12 +433,33 @@ fi
 bhost_t=false
 case $HOSTNAME in
   $MAIL_HOST) : ;;
-  kd|frodo|x2|x3|kw|sy|bo)
+  kd|x2|x3|kw|sy|bo|so)
     bhost_t=true
     ;;
 esac
 
 
+# rspamd background. I kept seeing spamassassin at the top of top, and
+# seeming to cause my cpu fans to speed up, and remembered that rspamd
+# was supposed to be more efficient. But the last benchmark I could find
+# was at least 8 years old. so, I did a test of scanning 1 message.  It
+# looks like spamassassin is about 3-4x more cpu used according to time,
+# but spamassassin seems to be doing more tests. But, there are other
+# ways to avoid cpu cycles. Most notably, I could do the spam scanning
+# as a network call to a non-laptop machine.
+#
+# rspamd has a lot less documentation than spamassassin. I was trying to
+# figure out something and I had to go to the source code, and then I
+# only figured it out because I knew how spamassassin works. I could
+# imagine that if I had time to be an expert in both, maybe I'd find
+# rspamd to be better, but I don't have time and I'm probably better off
+# learning spamassassin.
+#
+# rspam could still be useful to compare results with spamassassin, and
+# it seems to generally function fine as a spam scanner, so I'm going to
+# leave it installed.
+use_rspamd=false
+
 # * Install universal packages
 
 
@@ -458,13 +474,49 @@ Pin-Priority: 500
 EOF
 fi
 
+
+unit-exists() {
+  systemctl cat $1 &>/dev/null
+}
+spamd-timer-exists() {
+  unit-exists spamassassin-maintenance.timer
+}
+
+# name change in t12, and now timer instead of cron option in /etc/default
+first_spamd_run=false
+if ! spamd-timer-exists; then
+  first_spamd_run=true
+fi
+
+
 # light version of exim does not have sasl auth support.
 # note: for bitfolk hosts, unbound has important config with conflink.
-pi-nostart exim4 exim4-daemon-heavy spamassassin unbound clamav-daemon wireguard
+pi-nostart exim4 exim4-daemon-heavy spamassassin unbound clamav-daemon wireguard rspamd
+
+spamd_remove=spamassassin
+spamd_ser=spamd
+if systemctl cat spamassassin &>/dev/null; then
+  spamd_remove=spamd
+  spamd_ser=spamassassin
+elif $first_spamd_run; then
+  if spamd-timer-exists; then
+    systemctl start spamassassin-maintenance
+  fi
+fi
+if $use_rspamd; then
+  myspam_ser=rspamd
+else
+  myspam_ser=$spamd_ser
+fi
+
+if spamd-timer-exists; then
+  systemctl enable --now spamassassin-maintenance.timer
+fi
 
 # note: pyzor debian readme says you need to run some initialization command
 # but its outdated.
-pi spf-tools-perl p0f postgrey pyzor razor jq moreutils certbot fail2ban
+pi spf-tools-perl p0f pyzor razor jq moreutils certbot fail2ban
+pu postgrey
 case $HOSTNAME in
   je) : ;;
   # not included due to using wireguard: openvpn
@@ -493,11 +545,82 @@ fi
 # our nostart pi fails to avoid enabling
 
 
+
+# * initial dns config & daemon setup
+#
+# use systemd-resolved for glibc resolutions, setup symlinks
+
+pi libnss-resolve
+
+# if this link gets replaced with a normal file we will get exim log
+# errors on MAIL_HOST like so:
+#
+# R=fsfsmarthost defer (-36) DT=0s: host lookup for mail.fsf.org did not complete (DNS timeout?)
+
+if [[ ! -L /etc/nsswitch.conf ]]; then
+  sudo mkdir -p /etc/resolved-nsswitch
+  sudo mv /etc/nsswitch.conf /etc/resolved-nsswitch
+  sudo ln -sf /etc/resolved-nsswitch/nsswitch.conf /etc
+fi
+
+f=/etc/basic-nsswitch/nsswitch.conf
+if [[ ! -e $f ]]; then
+  sudo mkdir -p ${f%/*}
+  sudo cp /etc/nsswitch.conf $f
+  sudo sed -i --follow-symlinks 's/^ *hosts:.*/hosts: files dns myhostname/' $f
+fi
+case $HOSTNAME in
+  bk|je)
+    # je should be able to get along systemd-resolved, but ive had some odd
+    # very intermittent dns failures with spamassassin, it seems it might only
+    # be happening with systemd-resolved, so just use unbound
+    # to make it consistent with the other hosts.
+    sudo sed -i --follow-symlinks 's/^ *hosts:.*/hosts: files dns myhostname/' /etc/nsswitch.conf
+    soff systemd-resolved
+    sudo ln -sf 127.0.0.1-resolv/stub-resolv.conf /etc/resolv.conf
+    sgo unbound
+    # cautious measure to make sure resolution is working
+    sleep 1
+    ;;
+  *)
+    # default is
+    # files mdns4_minimal [NOTFOUND=return] dns myhostname
+    # mdns4 is needed for my printer and for bbb webrtc, not sure exactly why.
+    # https://www.freedesktop.org/software/systemd/man/nss-resolve.html#
+    # seems more important than some potential use case.
+    # Interestingly, t9/t10 man page says use files before resolve, debian 10 says the opposite.
+    # removing files makes hostname -f not actually give the fully qualified domain name.
+    sudo sed -i --follow-symlinks 's/^ *hosts:.*/hosts: files resolve [!UNAVAIL=return] mdns4_minimal [NOTFOUND=return] myhostname/' /etc/resolved-nsswitch/nsswitch.conf
+    ;;
+esac
+
+case $HOSTNAME in
+  bk)
+    sgo named
+    ;;
+esac
+
+
+u /etc/apparmor.d/abstractions/nameservice.d/iank <<'EOF'
+/etc/resolved-nsswitch/nsswitch.conf r,
+/etc/basic-nsswitch/nsswitch.conf r,
+# Aug 06 23:09:11 kd audit[3995]: AVC apparmor="DENIED" operation="connect" profile="/usr/bin/freshclam" name="/run/systemd/resolve/io.systemd.Resolve" pid=3995 comm="freshclam" requested_mask="wr" denied_mask="wr" fsuid=109 ouid=101
+# I dont know if this is quite the right fix, but I saw other sockets
+# in the nameservice files that were rw, so figured it was ok to add this and it worked.
+/run/systemd/resolve/io.systemd.Resolve rw,
+EOF
+
+if $ur && systemctl is-active apparmor; then
+  m systemctl reload apparmor
+fi
+
+
+
 # * Mail clean cronjob
 
 u /etc/systemd/system/mailclean.timer <<'EOF'
 [Unit]
-Description=Run mailclean daily
+Description=Run mailclean
 
 [Timer]
 OnCalendar=monthly
@@ -514,7 +637,7 @@ After=multi-user.target
 [Service]
 User=$u
 Type=oneshot
-ExecStart=/usr/local/bin/sysd-mail-once mailclean /a/bin/distro-setup/mailclean
+ExecStart=/usr/local/bin/sysd-mail-once mailclean /usr/local/bin/mailclean
 EOF
 
 # * postgrey
@@ -759,25 +882,26 @@ EOF
 
 # this is just a bug fix for trisquel.
 f=/etc/apparmor.d/usr.sbin.unbound
-line="/usr/sbin/unbound flags=(attach_disconnected) {"
-if ! grep -qFx "$line" $f; then
-  badline="/usr/sbin/unbound {"
-  if ! grep -qFx "$badline" $f; then
+good_string="/usr/sbin/unbound flags=(attach_disconnected) {"
+if ! grep -qF "$good_string" $f; then
+  bad_string="/usr/sbin/unbound {"
+  if ! grep -qF "$bad_string" $f; then
     err expected line in $f not found
   fi
-  sed -i "s,^$badline$,$line," $f
+  sed -i "s,$bad_string$,$good_string," $f
   if systemctl is-active apparmor &>/dev/null; then
     m systemctl reload apparmor
   fi
 fi
 
+
 # note: anything added to nn_progs needs corresponding rm
 # down below in the host switch
 nn_progs=(exim4)
 if mailhost; then
   # Note dovecots lmtp doesnt need to be in the same nn to accept delivery.
   # Its in the nn so remote clients can connect to it.
-  nn_progs+=(spamassassin dovecot)
+  nn_progs+=($spamd_ser rspamd dovecot)
 fi
 
 case $HOSTNAME in
@@ -857,7 +981,7 @@ EOF
     done
     ;;
   *)
-    for unit in exim4 spamassassin dovecot unbound; do
+    for unit in exim4 $spamd_ser rspamd $spamd_remove dovecot unbound; do
       f=/etc/systemd/system/$unit.service.d/nn.conf
       if [[ -s $f ]]; then
         rm -fv $f
@@ -880,6 +1004,30 @@ RestartSec=20
 EOF
 fi
 
+# * rspamd config
+
+m usermod -a -G _rspamd $u
+
+## if we wanted to, we could run redis outside the mail nn by adding to
+## its bind config option like this, and then tell rspamd to connect to
+## this address. But it is slightly simpler to not do that.
+
+#/a/exe/cedit /etc/redis/redis.conf <<'EOF'
+# bind 127.0.0.1 -::1 10.173.8.1
+# Note: redis config is only readable by redis. if we wanted to not do
+# that for our modifications, we could add this.
+# include /etc/redis-local.conf
+#EOF
+
+# to use the web interface without launching a firefox in the network namespace, we would need
+# /etc/rspamd/local.d/worker-controller.inc:
+# bind_socket = "*:11334";
+# secure_ip = "10.173.8.1";
+# secure_ip = "127.0.0.1";
+# secure_ip = "::1";
+
+# but the web interface seems generally  not needed.
+
 # * spamassassin config
 u /etc/sysctl.d/80-iank-mail.conf <<'EOF'
 # see exim spec
@@ -909,12 +1057,15 @@ EOF
 # 2020-10-19 remove old file. remove this when all hosts updated
 rm -fv /etc/systemd/system/spamddnsfix.{timer,service}
 
-u /etc/default/spamassassin <<'EOF'
+rm -f /etc/default/$spamd_remove
+u /etc/default/$spamd_ser <<'EOF'
 # defaults plus debugging flags for an issue im having
 OPTIONS="--create-prefs --max-children 5 --helper-home-dir"
-PIDFILE="/var/run/spamd.pid"
+PIDFILE="/run/spamd.pid"
 # my additions
 NICE="--nicelevel 15"
+# not used in t12+, that uses
+# /usr/lib/systemd/system/spamassassin-maintenance.timer
 CRON=1
 EOF
 
@@ -1127,6 +1278,10 @@ banaction = iptables-exim
 ignoreip = 209.51.188.13 2001:470:142::13 209.51.188.92 2001:470:142:3::10 72.14.176.105 2600:3c00:e000:280::2 10.173.8.1
 EOF
 if $ur; then
+  # Ensure the log file monitored by fail2ban exists, or else fail2ban can't start.
+  if [[ ! -e /var/log/exim4/mainlog ]]; then
+    install -m 640 -o Debian-exim -g adm /dev/null /var/log/exim4/mainlog
+  fi
   m systemctl restart fail2ban
 fi
 
@@ -1369,8 +1524,13 @@ acl_not_smtp = acl_check_not_smtp
 
 
 DEBBUGS_DOMAIN = b.b8.nz
+EOF
 
+if $use_rspamd; then
+  cat >>/etc/exim4/conf.d/main/000_local <<'EOF'
+spamd_address = 127.0.0.1 11333 variant=rspamd
 EOF
+fi
 
 if dpkg --compare-versions "$(dpkg-query -f='${Version}\n' --show exim4)" ge 4.94; then
   cat >>/etc/exim4/conf.d/main/000_local <<'EOF'
@@ -1415,6 +1575,14 @@ EOF
 rm -fv /etc/exim4/data_local_acl # old path
 
 u /etc/exim4/conf.d/data_local_acl <<'EOF'
+
+warn
+  remove_header = X-Spam_score: X-Spam_score_int : X-Spam_bar : X-Spam_report
+
+warn
+  !hosts = +iank_trusted
+  # Smarthosts connect with residential ips and thus get flagged as spam if we do a spam check.
+  !authenticated = plain_server:login_server
 # Except for the "condition =", this was
 # a comment in the check_data acl. The comment about this not
 # being suitable has been changed in newer exim versions. The only thing
@@ -1425,14 +1593,19 @@ u /etc/exim4/conf.d/data_local_acl <<'EOF'
 # suggested in official docs, and 100k in the wiki example because
 # those docs are rather old and I see a 110k spam message
 # pretty quickly looking through my spam folder.
+  condition = ${if < {$message_size}{5000K}}
+  spam = Debian-exim:true
+  add_header = X-Spam_score_int: $spam_score_int
+  add_header = X-Spam_score: $spam_score
+  add_header = X-Spam_bar: $spam_bar
+  add_header = X-Spam_report: $spam_report
+  add_header = X-Spam_action: $spam_action
 
+# i don't want mail to myself getting wastefully scanned or
+# mistakenly flagged as spam, but I do want to scan my spam test emails.
 warn
-  !hosts = +iank_trusted
-  remove_header = X-Spam_score: X-Spam_score_int : X-Spam_bar : X-Spam_report
-
-warn
-  !hosts = +iank_trusted
-  # Smarthosts connect with residential ips and thus get flagged as spam if we do a spam check.
+  condition = ${if forany{<, $recipients}{match{$item}{\N^testignore@\N}}}
+  hosts = +iank_trusted
   !authenticated = plain_server:login_server
   condition = ${if < {$message_size}{5000K}}
   spam = Debian-exim:true
@@ -1516,7 +1689,8 @@ EOF
 # TODO: copy the defaults into their own file, and setup a cronjob so
 # that if file.dpkg-dist shows up, and it is different, we get an alert.
 
-u /etc/exim4/conf.d/transport/30_exim4-config_remote_smtp_smarthost <<'EOF'
+{
+  cat <<'EOF'
 ### transport/30_exim4-config_remote_smtp_smarthost
 #################################
 
@@ -1527,7 +1701,6 @@ u /etc/exim4/conf.d/transport/30_exim4-config_remote_smtp_smarthost <<'EOF'
 remote_smtp_smarthost:
   debug_print = "T: remote_smtp_smarthost for $local_part@$domain"
   driver = smtp
-  message_linelength_limit = 2097152
   multi_domain
   hosts_try_auth = <; ${if exists{CONFDIR/passwd.client} \
         {\
@@ -1572,8 +1745,16 @@ tls_privatekey = REMOTE_SMTP_SMARTHOST_PRIVATEKEY
   protocol = REMOTE_SMTP_SMARTHOST_PROTOCOL
 .endif
 EOF
+  # os needs an update, doesn't have this setting in exim yet.
+  if [[ $HOSTNAME != li ]]; then
+    cat <<'EOF'
+  message_linelength_limit = 2097152
+EOF
+  fi
+} | u /etc/exim4/conf.d/transport/30_exim4-config_remote_smtp_smarthost
 
-u /etc/exim4/conf.d/transport/30_exim4-config_remote_smtp <<'EOF'
+{
+  cat <<'EOF'
 ### transport/30_exim4-config_remote_smtp
 #################################
 # This transport is used for delivering messages over SMTP connections.
@@ -1581,7 +1762,6 @@ u /etc/exim4/conf.d/transport/30_exim4-config_remote_smtp <<'EOF'
 remote_smtp:
   debug_print = "T: remote_smtp for $local_part@$domain"
   driver = smtp
-  message_linelength_limit = 2097152
 .ifdef REMOTE_SMTP_HOSTS_AVOID_TLS
   hosts_avoid_tls = REMOTE_SMTP_HOSTS_AVOID_TLS
 .endif
@@ -1638,12 +1818,19 @@ tls_privatekey = REMOTE_SMTP_PRIVATEKEY
 .endif
 
 EOF
+# os needs an update, doesn't have this setting in exim yet.
+if [[ $HOSTNAME != li ]]; then
+  cat <<'EOF'
+  message_linelength_limit = 2097152
+EOF
+fi
+} | u /etc/exim4/conf.d/transport/30_exim4-config_remote_smtp
 
-u /etc/exim4/conf.d/transport/30_backup_remote <<'EOF'
+{
+  cat <<'EOF'
 backup_remote:
   driver = smtp
   multi_domain
-  message_linelength_limit = 2097152
   hosts_require_auth = *
   hosts_try_auth = *
   envelope_to_add
@@ -1681,6 +1868,13 @@ tls_privatekey = REMOTE_SMTP_SMARTHOST_PRIVATEKEY
   headers_remove = REMOTE_SMTP_TRANSPORTS_HEADERS_REMOVE
 .endif
 EOF
+# os needs an update, doesn't have this setting in exim yet.
+if [[ $HOSTNAME != li ]]; then
+  cat <<'EOF'
+  message_linelength_limit = 2097152
+EOF
+fi
+} | u /etc/exim4/conf.d/transport/30_backup_remote
 
 u /etc/exim4/conf.d/router/900_exim4-config_local_user <<'EOF'
 ### router/900_exim4-config_local_user
@@ -1712,14 +1906,14 @@ dovecot_lmtp:
   envelope_to_add
 EOF
 
-u /etc/exim4/conf.d/transport/30_remote_smtp_vpn <<'EOF'
+{
+  cat <<'EOF'
 # same as debians 30_exim4-config_remote_smtp, but
 # with interface added at the end.
 
 remote_smtp_vpn:
   debug_print = "T: remote_smtp_vpn for $local_part@$domain"
   driver = smtp
-  message_linelength_limit = 2097152
 .ifdef REMOTE_SMTP_HOSTS_AVOID_TLS
   hosts_avoid_tls = REMOTE_SMTP_HOSTS_AVOID_TLS
 .endif
@@ -1767,14 +1961,21 @@ tls_privatekey = REMOTE_SMTP_PRIVATEKEY
 .endif
   interface = <; 10.8.0.4 ; 2600:3c00:e002:3800::4
 EOF
+# os needs an update, doesn't have this setting in exim yet.
+if [[ $HOSTNAME != li ]]; then
+  cat <<'EOF'
+  message_linelength_limit = 2097152
+EOF
+fi
+} | u /etc/exim4/conf.d/transport/30_remote_smtp_vpn
 
-u /etc/exim4/conf.d/transport/30_smarthost_dkim <<'EOF'
+{
+  cat <<'EOF'
 # ian: this is remote_smtp_smarthost plus the dkim parts from remote_smtp
 
 smarthost_dkim:
   debug_print = "T: remote_smtp_smarthost for $local_part@$domain"
   driver = smtp
-  message_linelength_limit = 2097152
   multi_domain
   hosts_try_auth = <; ${if exists{CONFDIR/passwd.client} \
         {\
@@ -1834,7 +2035,13 @@ dkim_strict = DKIM_STRICT
 dkim_sign_headers = DKIM_SIGN_HEADERS
 .endif
 EOF
-
+# os needs an update, doesn't have this setting in exim yet.
+if [[ $HOSTNAME != li ]]; then
+  cat <<'EOF'
+  message_linelength_limit = 2097152
+EOF
+fi
+} | u /etc/exim4/conf.d/transport/30_smarthost_dkim
 
 cat >/etc/exim4/update-exim4.conf.conf  <<'EOF'
 # default stuff, i havent checked if its needed
@@ -1864,8 +2071,8 @@ if mailhost; then
   # plus debug does not help.
   # sudo -u radicale radicale -D
 
-  # created password file with:
-  # htpasswd -c /p/c/machine_specific/li/filesystem/etc/caldav-htpasswd
+  # created radicale password file with:
+  # htpasswd -c /p/c/machine_specific/li/filesystem/etc/caldav-htpasswd ian
   # chmod 640 /p/c/machine_specific/li/filesystem/etc/caldav-htpasswd
   # # setup chgrp www-data in ./conflink
 
@@ -2037,7 +2244,7 @@ EOF
 ssl = required
 # this is the same as the certbot list, i check changes in /a/bin/ds/filesystem/usr/local/bin/check-lets-encrypt-ssl-settings
 ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
-ssl_protocols = TLSv1.2
+ssl_min_protocol = TLSv1.2
 ssl_prefer_server_ciphers = no
 
 protocol lmtp {
@@ -2045,6 +2252,23 @@ protocol lmtp {
 # default is just $mail_plugins
   mail_plugins = $mail_plugins sieve
 }
+
+# /etc/dovecot/conf.d/10-master.conf says the default is 256M.
+# but I started getting oom errors in the syslog
+# Mar 27 15:10:04 sy dovecot[330088]: lmtp(iank)<3839880><gO/BDwtvBGaIlzoA7AdaJQ>: Fatal: master: service(lmtp): child 3839880 returned error 83 (Out of memory (service lmtp { vsz_limit=256 MB }, you may need to increase it) - set CORE_OUTOFMEM=1 environment to get core dump)
+# exim would just queue mail until it eventually succeeded.
+# Deciding what to increase it to, I found this
+# https://dovecot.org/list/dovecot/2011-December/080056.html
+# which suggests 3x the largest dovecot.index.cache file
+# and then I found that
+# md/l/testignore/dovecot.index.cache is 429M, my largest cache file,
+# but that folder only has 2k messages.
+# next biggest is md/l/qemu-devel/dovecot.index.cache 236M
+# which lead to me a search https://doc.dovecot.org/admin_manual/known_issues/large_cache/
+# which suggests 1.5x the maximum cache file size 1G, and
+# that I can safely rm the index.
+default_vsz_limit = 1500M
+
 EOF
       if dpkg --compare-versions "$(dpkg-query -f='${Version}\n' --show dovecot-core)" ge 1:2.3; then
         cat <<EOF
@@ -2068,14 +2292,28 @@ EOF
     fi
 
     rm -fv /etc/dovecot/conf.d/20-lmtp.conf # file from prev version
+
+    # Having backups of indexes is a waste of space. This also means we
+    # don't send them around with btrbk, I think it is probably
+    # preferable use a bit more cpu to recalculate indexes.
+    install -d -m 700 -o iank -g iank /var/dovecot-indexes
     cat >>/etc/dovecot/local.conf <<EOF
+
+
+# This will decrease memory use, and seems likely to decrease cpu & disk
+# use since I rarely use dovecot for most folders.
+mail_cache_max_size = 50M
+
+
 # simple password file based login
 !include conf.d/auth-passwdfile.conf.ext
 
 # ian: %u is used for alerts user vs iank
-mail_location = maildir:/m/%u:LAYOUT=fs:INBOX=/m/%u/INBOX
-mail_uid = $u
-mail_gid = $u
+# https://doc.dovecot.org/configuration_manual/mail_location/Maildir/
+mail_location = maildir:/m/%u:LAYOUT=fs:INBOX=/m/%u/INBOX:INDEX=/var/dovecot-indexes/%u
+# note: i don't know if these need to be set, but this seems fine.
+mail_uid = iank
+mail_gid = iank
 
 protocol lmtp {
 # For a normal setup with exim, we need something like this, which
@@ -2486,8 +2724,8 @@ EOF
     m mkdir -p $rctmpdir /m/rc
     m chown -R www-data.www-data $rctmpdir /m/rc
     m chmod 750 $rctmpdir
-    # Ensure the log file monitored by fail2ban exists, or else fail2ban can't start.
     # todo: check for other mailinabox things
+    # Ensure the log file monitored by fail2ban exists, or else fail2ban can't start.
     m sudo -u www-data touch $rclogdir/errors.log
 
     #### begin carddav install
@@ -2748,7 +2986,7 @@ EOF
     u /usr/local/bin/ncup <<'EOFOUTER'
 #!/bin/bash
 
-source /usr/local/lib/err
+source /usr/local/lib/bash-bear
 
 m() { printf "%s\n" "$*";  "$@"; }
 err-cleanup() {
@@ -2802,61 +3040,66 @@ fi
 
 # * debbugs
 
-pi debbugs
-# missing dependency. apache error log:
-# Can't locate List/AllUtils.pm in @INC (you may need to install the List::AllUtils module) (@INC contains: /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.34.0 /usr/local/share/perl/5.34.0 /usr/lib/x86_64-linux-gnu/perl5/5.34 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl-base /usr/lib/x86_64-linux-gnu/perl/5.34 /usr/share/perl/5.34 /usr/local/lib/site_perl) at /var/lib/debbugs/www/cgi/pkgreport.cgi line 23.
-pi liblist-allutils-perl lynx
-# workarounds for broken debbugsconfig which is
-# itself deprecated. this is temporary before I
-# figure out how to install from git
-gunzip /usr/share/doc/debbugs/examples/text.gz
-mkdir -p /etc/debbugs/sources
-debbugsconfig
-
-
-# ld for local debbugs
-/a/exe/web-conf -t -a 127.0.1.1 -p 80 -r /var/lib/debbugs/www - apache2 ld <<'EOF'
-# copied from debbugs upstream example
-<Directory /var/lib/debbugs/www>
-   Options Indexes SymLinksIfOwnerMatch MultiViews
-   DirectoryIndex index.html
-   Require all granted
-</Directory>
-
-ScriptAlias /cgi/ /var/lib/debbugs/www/cgi/
-<Directory "/var/lib/debbugs/www/cgi/">
-  AllowOverride None
-  Options ExecCGI SymLinksIfOwnerMatch
-  Require all granted
-</Directory>
-
-RewriteEngine on
-RewriteCond %{HTTP_USER_AGENT} .*apt-listbugs.*
-RewriteRule .*                 /apt-listbugs.html [R,L]
-
-#  RewriteLog /org/bugs.debian.org/apache-rewrite.log
-#  RewriteLogLevel 0
-
-#RewriteRule ^/$ http://www.debian.org/Bugs/
-RewriteRule ^/(robots\.txt|release-critical|apt-listbugs\.html)$ - [L]
-# The following two redirect to up-to-date pages
-RewriteRule ^/[[:space:]]*#?([[:digit:]][[:digit:]][[:digit:]]+)([;&].+)?$ /cgi-bin/bugreport.cgi?bug=$1$2 [L,R,NE]
-RewriteRule ^/([^/+]*)([+])([^/]*)$ "/$1%%{%}2B$3" [N]
-RewriteRule ^/[Ff][Rr][Oo][Mm]:([^/]+\@.+)$ /cgi-bin/pkgreport.cgi?submitter=$1 [PT,NE]
-# Commented out, 'cuz aj says it will crash master. (old master)
-# RewriteRule ^/[Ss][Ee][Vv][Ee][Rr][Ii][Tt][Yy]:([^/]+\@.+)$ /cgi-bin/pkgreport.cgi?severity=$1 [L,R]
-RewriteRule ^/([^/]+\@.+)$ /cgi-bin/pkgreport.cgi?maint=$1 [PT,NE]
-RewriteRule ^/mbox:([[:digit:]][[:digit:]][[:digit:]]+)([;&].+)?$ /cgi-bin/bugreport.cgi?mbox=yes&bug=$1$2 [PT,NE]
-RewriteRule ^/src:([^/]+)$ /cgi-bin/pkgreport.cgi?src=$1 [PT,NE]
-RewriteRule ^/severity:([^/]+)$ /cgi-bin/pkgreport.cgi?severity=$1 [PT,NE]
-RewriteRule ^/tag:([^/]+)$ /cgi-bin/pkgreport.cgi?tag=$1 [PT,NE]
-# RewriteMap fix-chars int:noescape
-RewriteCond %{REQUEST_URI} ^/(Access\.html|Developer\.html|Reporting\.html|server-request\.html|server-control\.html|server-refcard\.html).* [NC]
-RewriteRule .* - [L]
-# PT|passthrough to bugreport.cgi and pkgreport.cgi
-RewriteRule ^/([0-9]+)$ /cgi-bin/bugreport.cgi?bug=$1 [PT,NE]
-RewriteRule ^/([^/]+)$ /cgi-bin/pkgreport.cgi?pkg=$1 [PT,NE]
-EOF
+# disabled for now. the debbugs package is gone in the latest debian,
+# need to figure out an install from source.
+
+# pi debbugs
+# # missing dependency. apache error log:
+# # Can't locate List/AllUtils.pm in @INC (you may need to install the List::AllUtils module) (@INC contains: /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.34.0 /usr/local/share/perl/5.34.0 /usr/lib/x86_64-linux-gnu/perl5/5.34 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl-base /usr/lib/x86_64-linux-gnu/perl/5.34 /usr/share/perl/5.34 /usr/local/lib/site_perl) at /var/lib/debbugs/www/cgi/pkgreport.cgi line 23.
+# pi liblist-allutils-perl lynx
+# # workarounds for broken debbugsconfig which is
+# # itself deprecated. this is temporary before I
+# # figure out how to install from git
+# if [[ -e /usr/share/doc/debbugs/examples/text.gz ]]; then
+#   gunzip /usr/share/doc/debbugs/examples/text.gz
+# fi
+# mkdir -p /etc/debbugs/indices
+# debbugsconfig
+
+
+# # ld for local debbugs
+# /a/exe/web-conf -l -t -a 127.0.1.1 -p 80 -r /var/lib/debbugs/www - apache2 ld <<'EOF'
+# # copied from debbugs upstream example
+# <Directory /var/lib/debbugs/www>
+#    Options Indexes SymLinksIfOwnerMatch MultiViews
+#    DirectoryIndex index.html
+#    Require all granted
+# </Directory>
+
+# ScriptAlias /cgi/ /var/lib/debbugs/www/cgi/
+# <Directory "/var/lib/debbugs/www/cgi/">
+#   AllowOverride None
+#   Options ExecCGI SymLinksIfOwnerMatch
+#   Require all granted
+# </Directory>
+
+# RewriteEngine on
+# RewriteCond %{HTTP_USER_AGENT}       .*apt-listbugs.*
+# RewriteRule .*                       /apt-listbugs.html [R,L]
+
+# #  RewriteLog /org/bugs.debian.org/apache-rewrite.log
+# #  RewriteLogLevel 0
+
+# #RewriteRule ^/$ http://www.debian.org/Bugs/
+# RewriteRule ^/(robots\.txt|release-critical|apt-listbugs\.html)$ - [L]
+# # The following two redirect to up-to-date pages
+# RewriteRule ^/[[:space:]]*#?([[:digit:]][[:digit:]][[:digit:]]+)([;&].+)?$ /cgi-bin/bugreport.cgi?bug=$1$2 [L,R,NE]
+# RewriteRule ^/([^/+]*)([+])([^/]*)$ "/$1%%{%}2B$3" [N]
+# RewriteRule ^/[Ff][Rr][Oo][Mm]:([^/]+\@.+)$ /cgi-bin/pkgreport.cgi?submitter=$1 [PT,NE]
+# # Commented out, 'cuz aj says it will crash master. (old master)
+# # RewriteRule ^/[Ss][Ee][Vv][Ee][Rr][Ii][Tt][Yy]:([^/]+\@.+)$ /cgi-bin/pkgreport.cgi?severity=$1 [L,R]
+# RewriteRule ^/([^/]+\@.+)$ /cgi-bin/pkgreport.cgi?maint=$1 [PT,NE]
+# RewriteRule ^/mbox:([[:digit:]][[:digit:]][[:digit:]]+)([;&].+)?$ /cgi-bin/bugreport.cgi?mbox=yes&bug=$1$2 [PT,NE]
+# RewriteRule ^/src:([^/]+)$ /cgi-bin/pkgreport.cgi?src=$1 [PT,NE]
+# RewriteRule ^/severity:([^/]+)$ /cgi-bin/pkgreport.cgi?severity=$1 [PT,NE]
+# RewriteRule ^/tag:([^/]+)$ /cgi-bin/pkgreport.cgi?tag=$1 [PT,NE]
+# # RewriteMap fix-chars       int:noescape
+# RewriteCond %{REQUEST_URI} ^/(Access\.html|Developer\.html|Reporting\.html|server-request\.html|server-control\.html|server-refcard\.html).* [NC]
+# RewriteRule .* - [L]
+# # PT|passthrough to bugreport.cgi and pkgreport.cgi
+# RewriteRule ^/([0-9]+)$ /cgi-bin/bugreport.cgi?bug=$1 [PT,NE]
+# RewriteRule ^/([^/]+)$ /cgi-bin/pkgreport.cgi?pkg=$1 [PT,NE]
+# EOF
 
 
 # * exim host conditional config
@@ -2928,10 +3171,11 @@ case $HOSTNAME in
     # which will overwrite any existing file
     u /etc/default/exim4 <<'EOF'
 QUEUERUNNER='combined'
-# note: this is duplicated in brc2, 10m here is -q10m there.
 QUEUEINTERVAL='10m'
-COMMONOPTIONS='-C /etc/exim4/my.conf'
-UPEX4OPTS='-o /etc/exim4/my.conf'
+COMMONOPTIONS='-C /etc/exim4/nn-mainlog.conf'
+UPEX4OPTS='-o /etc/exim4/nn-mainlog.conf'
+# in t12 exim, this replaces all the above options
+EXIMSERVICE='-bdf -q10m -C /etc/exim4/nn-mainlog.conf'
 # i use epanic-clean for alerting if there are bad paniclog entries
 E4BCD_WATCH_PANICLOG='no'
 EOF
@@ -2941,13 +3185,13 @@ EOF
     chmod g+s,u+s /usr/sbin/exim4
     # need this to avoid error on service reload:
     # 2022-08-07 18:44:34.005 [892491] pid 892491: SIGHUP received: re-exec daemon
-    # 2022-08-07 18:44:34.036 [892491] cwd=/var/spool/exim4 5 args: /usr/sbin/exim4 -bd -q30m -C /etc/exim4/my.conf
+    # 2022-08-07 18:44:34.036 [892491] cwd=/var/spool/exim4 5 args: /usr/sbin/exim4 -bd -q30m -C /etc/exim4/nn-mainlog.conf
     # 2022-08-07 18:44:34.043 [892491] socket bind() to port 25 for address (any IPv6) failed: Permission denied: waiting 30s before trying again (9 more tries)
     # note: the daemon gives up and dies after retrying those 9 times.
     # I came upon this by guessing and trial and error.
     setcap CAP_NET_BIND_SERVICE+ei /usr/sbin/exim4
     u /etc/exim4/trusted_configs <<'EOF'
-/etc/exim4/my.conf
+/etc/exim4/nn-mainlog.conf
 EOF
     ;;
   *)
@@ -3107,27 +3351,34 @@ debbugs_pipe:
   return_output
 EOF
 
+    # disable for now
+    echo|u /etc/exim4/conf.d/router/153_debbugs
 
-    # We dont want delays or backups for mail being stored locally.
-    # We could put domain exclusion on other routes, but going for
-    # higher priority instead.
-    u /etc/exim4/conf.d/router/153_debbugs <<'EOF'
-debbugs:
-  debug_print = "R: debbugs for $local_part@$domain"
-  driver = accept
-  transport = debbugs_pipe
-  local_parts = submit : bugs : maintonly : quiet : forwarded : \
-                done : close : request : submitter : control : ^\\d+
-  domains = DEBBUGS_DOMAIN
-
-bounce_debbugs:
-  debug_print = "R: bounce_debbugs for $local_part@$domain"
-  driver = redirect
-  allow_fail
-  data = :fail: Unknown user
-  domains = DEBBUGS_DOMAIN
-EOF
+    #     # We dont want delays or backups for mail being stored locally.
+    #     # We could put domain exclusion on other routes, but going for
+    #     # higher priority instead.
+    #     u /etc/exim4/conf.d/router/153_debbugs <<'EOF'
+    # debbugs:
+    #   debug_print = "R: debbugs for $local_part@$domain"
+    #   driver = accept
+    #   transport = debbugs_pipe
+    #   local_parts = submit : bugs : maintonly : quiet : forwarded : \
+      #                 done : close : request : submitter : control : ^\\d+
+    #   domains = DEBBUGS_DOMAIN
+
+    # bounce_debbugs:
+    #   debug_print = "R: bounce_debbugs for $local_part@$domain"
+    #   driver = redirect
+    #   allow_fail
+    #   data = :fail: Unknown user
+    #   domains = DEBBUGS_DOMAIN
+    # EOF
 
+    install -m=0775 -d -g Debian-exim -o iank /var/spool/exim4/gw
+    f=/var/spool/exim4/gw/.no-delay-eximids
+    if [[ ! -e $f ]]; then
+      install -g Debian-exim -o iank /dev/null $f
+    fi
     u /etc/exim4/conf.d/router/155_delay <<'EOF'
 # By default, delay sending email by 30-40 minutes in case I
 # change my mind.
@@ -3147,14 +3398,14 @@ delay_iank:
   condition = ${if and { \
 {< {$tod_epoch} {${eval10:$received_time + 60*30}}} \
 {!def:h_i:} \
-{!bool{${lookup{$message_exim_id}lsearch{/etc/exim4/no-delay-eximids}{true}}}} \
-{!bool{${lookup{all}lsearch{/etc/exim4/no-delay-eximids}{true}}}} \
+{!bool{${lookup{$message_exim_id}lsearch{/var/spool/exim4/gw/.no-delay-eximids}{true}}}} \
+{!bool{${lookup{all}lsearch{/var/spool/exim4/gw/.no-delay-eximids}{true}}}} \
 } {true}{false}}
   headers_remove = <; i:
   domains = ! +local_domains
   # uncomment for testing delays to jtuttle
   # local_parts = ! root : ! testignore : ! alerts : ! ian-pager : ! daylert
-  local_parts = ! root : ! testignore : ! alerts : ! jtuttle : ! ian-pager : ! daylert
+  local_parts = ! root : ! testignore : ! alerts : ! jtuttle : ! ian-pager : ! daylert : ! r2e
   ignore_target_hosts = ROUTER_DNSLOOKUP_IGNORE_TARGET_HOSTS
 EOF
 
@@ -3227,16 +3478,27 @@ MAILDIR_HOME_MAILDIR_LOCATION = /m/md/Sent
 EOF
 
 
+    # ian: save a copy of sent mail. i thought of other ways to do this,
+    # for example, to only save sent mail that is not sent from my mail
+    # client which saves a copy by default, but in the end, it seems
+    # simplest to turn that off. We want to save external mail sent by
+    # smarthosts. However, there is one complication: encrypted
+    # mail. Saving it here just gets us an encrypted copy that we can't
+    # read. Soo, we could bcc ourselves: then we still have the
+    # annoyance that it is encrypted so we can't grep it. Or, we could
+    # hack emacs so that it sends us an unencrypted copy. Turns out that
+    # the emacs function which saves sent email can also send us a
+    # copy. But, then we have 3 copies: the encrypted copy exim saves,
+    # the unencrypted copy exim saves, and the copy emacs saves.  Soo,
+    # we can emacs send a copy directly to the sent alias but only when
+    # it is not mail_host, and have the exim condition for redirecting a
+    # copy to the sent alias avoid doing it if it has an emacs user
+    # agent header.
     u /etc/exim4/conf.d/router/186_sentarchive_nn <<'EOF'
-# ian: save a copy of sent mail. i thought of other ways to
-# do this, for example, to only save sent mail that is not sent
-# from my mail client which saves a copy by default, but in the
-# end, it seems simplest to turn that off. We want to save
-# external mail sent by smarthosts.
 sentarchive_nn:
   driver = redirect
   domains = ! +local_domains
-  condition = ${if !bool{${lookup{$local_part@$domain}lsearch{/etc/exim4/ignore-sent}{true}}}}
+  condition = ${if and {{!bool{${lookup{$local_part@$domain}lsearch{/etc/exim4/ignore-sent}{true}}}} {!match {$h_user-agent:}{emacs}}}}
   data    = vojdedIdNejyebni@b8.nz
   unseen
 EOF
@@ -3284,6 +3546,12 @@ EOF
     # This name won\'t appear on From: lines of outgoing messages if rewriting is enabled.
     echo iankelling.org > /etc/mailname
 
+    # mail default domain.
+    u /etc/mailutils.conf <<'EOF'
+address {
+  email-domain iankelling.org;
+};
+EOF
 
     # mail.iankelling.org so local imap clients can connect with tls and
     # when they happen to not be local.
@@ -3392,7 +3660,7 @@ EOF
 # defaults but no queue runner and alternate config dir
 QUEUERUNNER='no'
 COMMONOPTIONS='-oP /run/exim4/eximin.pid'
-UPEX4OPTS='-d /etc/myexim4'
+UPEX4OPTS='-d /etc/nond-exim4'
 EOF
 
     echo bk.b8.nz > /etc/mailname
@@ -3498,11 +3766,13 @@ backup_local:
 EOF
 
       # Bind to wghole to receive mailbackup.
-      wgholeip=$(sed -rn 's/^ *Address *= *([^/]+).*/\1/p' /etc/wireguard/wghole.conf)
-      cat >>/etc/exim4/update-exim4.conf.conf <<EOF
+      if [[ -e /etc/wireguard/wghole.conf ]]; then
+        wgholeip=$(sed -rn 's/^ *Address *= *([^/]+).*/\1/p' /etc/wireguard/wghole.conf)
+        cat >>/etc/exim4/update-exim4.conf.conf <<EOF
 dc_other_hostnames='eximbackup.b8.nz'
 dc_local_interfaces='127.0.0.1;::1;$wgholeip'
 EOF
+      fi
 
       # wghole & thus exim will fail to start without internet connectivity.
       u /etc/systemd/system/exim4.service.d/backup.conf <<'EOF'
@@ -3550,16 +3820,16 @@ case $HOSTNAME in
     m rsync -ra --delete --delete-excluded \
       --exclude=/conf.d/router/161_backup_redir_nn \
       --exclude=/conf.d/router/186_sentarchive_nn \
-      --exclude=/conf.d/main/000_local-nn /etc/exim4/ /etc/myexim4
-    cat >>/etc/myexim4/conf.d/main/000_local <<'EOF'
+      --exclude=/conf.d/main/000_local-nn /etc/exim4/ /etc/nond-exim4
+    cat >>/etc/nond-exim4/conf.d/main/000_local <<'EOF'
 # this makes it easier to see which exim is doing what
-log_file_path = /var/log/exim4/my%s
+log_file_path = /var/log/exim4/nond%s
 EOF
 
 
 
     cat >/etc/logrotate.d/myexim <<'EOF'
-/var/log/exim4/mymain /var/log/exim4/myreject {
+/var/log/exim4/nondmain /var/log/exim4/nondreject {
        daily
        missingok
        rotate 1000
@@ -3567,7 +3837,7 @@ EOF
        notifempty
        nocreate
 }
-/var/log/exim4/mypanic {
+/var/log/exim4/nondpanic {
        size 10M
        missingok
        rotate 10
@@ -3581,9 +3851,9 @@ EOF
     # If we ever wanted to have a separate spool,
     # we could do it like this.
     #     cat >>/etc/exim4/conf.d/main/000_local-nn <<'EOF'
-    # spool_directory = /var/spool/myexim4
+    # spool_directory = /var/spool/nond-exim4
     # EOF
-    cat >>/etc/myexim4/update-exim4.conf.conf <<'EOF'
+    cat >>/etc/nond-exim4/update-exim4.conf.conf <<'EOF'
 dc_eximconfig_configtype='smarthost'
 dc_smarthost='nn.b8.nz'
 EOF
@@ -3591,41 +3861,41 @@ EOF
   bk)
 
     # config for the non-nn exim
-    cat >>/etc/myexim4/conf.d/main/000_local <<'EOF'
+    cat >>/etc/nond-exim4/conf.d/main/000_local <<'EOF'
 MAIN_HARDCODE_PRIMARY_HOSTNAME = mail2.iankelling.org
 EOF
     ;;
   $MAIL_HOST)
 
 
-    u /etc/myexim4/conf.d/router/185_sentarchive <<'EOF'
+    u /etc/nond-exim4/conf.d/router/185_sentarchive <<'EOF'
 sentarchive:
   driver = redirect
   domains = ! +local_domains
   senders = <; *@fsf.org ; *@posteo.net
-  condition = ${if !bool{${lookup{$local_part@$domain}lsearch{/etc/exim4/ignore-sent}{true}}}}
+  condition = ${if and {{!bool{${lookup{$local_part@$domain}lsearch{/etc/exim4/ignore-sent}{true}}}} {!match {$h_user-agent:}{emacs}}}}
   data    = vojdedIdNejyebni@b8.nz
   unseen
 EOF
 
-    u /etc/myexim4/conf.d/router/160_backup_redir <<'EOF'
+    u /etc/nond-exim4/conf.d/router/160_backup_redir <<'EOF'
 backup_redir:
-driver = redirect
-# i dont email myself from my own machine much, so lets ignore that.
-domains = ! +local_domains
-senders = <; *@fsf.org ; *@posteo.net
-condition = ${if !bool{${lookup{$local_part@$domain}lsearch{/etc/exim4/ignore-sent}{true}}}}
-# b is just an arbirary short string
-data = b@eximbackup.b8.nz
-# note, to test this, i could temporarily allow testignore.
-# alerts avoids potential mail loop.
-local_parts = ! root : ! testignore : ! alerts : ! daylert
-unseen = true
-errors_to = alerts@iankelling.org
+  driver = redirect
+  # i dont email myself from my own machine much, so lets ignore that.
+  domains = ! +local_domains
+  senders = <; *@fsf.org ; *@posteo.net
+  condition = ${if and {{!bool{${lookup{$local_part@$domain}lsearch{/etc/exim4/ignore-sent}{true}}}} {!match {$h_user-agent:}{emacs}}}}
+  # b is just an arbirary short string
+  data = b@eximbackup.b8.nz
+  # note, to test this, i could temporarily allow testignore.
+  # alerts avoids potential mail loop.
+  local_parts = ! root : ! testignore : ! alerts : ! daylert
+  unseen = true
+  errors_to = alerts@iankelling.org
 EOF
 
     # for bk, we have a exim4in.service that will do this for us.
-    m update-exim4.conf -d /etc/myexim4
+    m update-exim4.conf -d /etc/nond-exim4
     ;;
 esac
 
 # if [[ ! $uid ]]; then
 #   # /a/opt/debbugs/debian/README.mail
 #   adduser --uid 610 --system --group --home /o/debbugs \
-#           --no-create-home --disabled-login --force-badname debbugs
+  #           --no-create-home --disabled-login --force-badname debbugs
 # m find /o/debbugs -xdev -path ./var/tmp -prune -o -uid $uid -execdir chown -h 610 {} +
 # m find /o/debbugs -xdev -path ./var/tmp -prune -o -gid $gid -execdir chgrp -h 610 {} +
 # elif [[ $uid != 610 ]]; then
 #   err debbugs exist but is not uid 610: investigate
 # fi
 
-# * start / stop services
-
-reifactive dnsmasq nscd
-
-if $reload; then
-  m systemctl daemon-reload
-fi
-
-# optimization, this only needs to run once.
-if [[ ! -e /sys/class/net/wghole ]]; then
-  # checking bhost_t is redundant, but could help us catch errors.
-  if $bhost_t || [[ -e /etc/wireguard/wghole.conf ]]; then
-    # todo: in mail-setup, we have a static list of backup hosts, not *y
-    m systemctl --now enable wg-quick@wghole
-  fi
-fi
-
-# optimization, this only needs to be run once
-if [[ ! -e /var/lib/prometheus/node-exporter/exim_paniclog.prom ]]; then
-  sysd-prom-fail-install epanicclean
-  m systemctl --now enable epanicclean
-fi
-
-case $HOSTNAME in
-  je)
-    /a/exe/web-conf apache2 je.b8.nz
-    ;;
-  bk)
-    /a/exe/web-conf apache2 mail2.iankelling.org
-    ;;
-esac
-
-# optimization, this only needs to run once.
-if [[ ! -e /etc/exim4/fullchain.pem ]]; then
-  m /a/bin/ds/mail-cert-cron -1
-  m systemctl --now enable mailcert.timer
-fi
-
-case $HOSTNAME in
-  $MAIL_HOST|bk)
-    m systemctl --now enable mailnn mailnnroute
-    ;;&
-  $MAIL_HOST)
-    # we use dns to start wg
-    if $reload; then
-      sre unbound
-    else
-      m systemctl --now enable unbound
-    fi
-    ;;&
-  $MAIL_HOST|bk)
-    # If these have changes, id rather manually restart it, id rather
-    # not restart and cause temporary errors
-    if $reload; then
-      sre $vpnser
-    else
-      m systemctl --now enable $vpnser
-    fi
-    ;;&
-  bk)
-    if ! systemctl is-active clamav-daemon >/dev/null; then
-      m systemctl --now enable clamav-daemon
-      out=$(rsync -aiSAX --chown=root:root --chmod=g-s /a/bin/ds/filesystem/etc/systemd/system/epanicclean.service /etc/systemd/system)
-      if [[ $out ]]; then
-        reload=true
-      fi
-
-      # note, this will cause paniclog entries because it takes like 45
-      # seconds for clamav to start, i use ./epanic-clean to remove
-      # them.
-    fi
-    ;;&
-  $MAIL_HOST|bk|je)
-    # start spamassassin/dovecot before exim.
-    sre dovecot spamassassin
-    # Wait a bit before restarting exim, else I get a paniclog entry
-    # like: spam acl condition: all spamd servers failed. But I'm tired
-    # of waiting. I'll deal with this some other way.
-    #
-    # sleep 3
-    m systemctl --now enable mailclean.timer
-    ;;&
-  $MAIL_HOST)
-    # < 2.1 (eg: in t9), uses a different data format which required manual
-    # migration. dont start if we are running an old version.
-    if dpkg --compare-versions "$(dpkg -s radicale | awk '$1 == "Version:" { print $2 }')" ge 2.1; then
-      m systemctl --now enable radicale
-    fi
-    ;;&
-esac
-
-# for debugging dns issues
-case $HOSTNAME in
-  je|bk)
-    systemctl enable --now logrotate-fast.timer
-    ;;
-esac
-
-# last use of $reload happens in previous block
-rm -f /var/local/mail-setup-reload
-
-
-case $HOSTNAME in
-  $MAIL_HOST|bk|je|li)
-    # on li, these are never started, except $vpnser
-    :
-    ;;
-  *)
-    soff radicale mailclean.timer dovecot spamassassin $vpnser mailnn clamav-daemon
-    ;;
-esac
-
-sre exim4
-
-case $HOSTNAME in
-  $MAIL_HOST)
-    m systemctl --now enable mailbindwatchdog
-    ;;
-  *)
-    soff mailbindwatchdog
-    ;;
-esac
-
-
-case $HOSTNAME in
-  bk) sre exim4in ;;
-esac
-
 # * mail monitoring / testing
 
 # note, to test clamav, send an email with body that only contains
@@ -3870,6 +4012,8 @@ After=local-fs.target
 StartLimitIntervalSec=0
 
 [Service]
+# avoid fans spinning up
+CPUQuota=22%
 Type=simple
 ExecStart=/usr/local/bin/mailtest-check slow
 Restart=always
@@ -3879,7 +4023,6 @@ RestartSec=60
 WantedBy=graphical.target
 EOF
     sysd-prom-fail-install mailtest-check
-    sre mailtest-check
     ;;&
   $MAIL_HOST)
     test_froms=(ian@iankelling.org z@zroe.org iank@gnu.org)
@@ -3964,6 +4107,135 @@ EOFOUTER
 esac
 
 
+# * start / stop services
+
+reifactive dnsmasq nscd
+
+if $reload; then
+  m systemctl daemon-reload
+fi
+
+# optimization, this only needs to run once.
+if [[ ! -e /sys/class/net/wghole ]]; then
+  # checking bhost_t is redundant, but could help us catch errors.
+  if $bhost_t || [[ -e /etc/wireguard/wghole.conf ]]; then
+    # todo: in mail-setup, we have a static list of backup hosts, not *y
+    m systemctl --now enable wg-quick@wghole
+  fi
+fi
+
+# optimization, this only needs to be run once
+if [[ ! -e /var/lib/prometheus/node-exporter/exim_paniclog.prom ]]; then
+  sysd-prom-fail-install epanicclean
+  m systemctl --now enable epanicclean
+fi
+
+case $HOSTNAME in
+  je)
+    /a/exe/web-conf apache2 je.b8.nz
+    ;;
+  bk)
+    /a/exe/web-conf apache2 mail2.iankelling.org
+    ;;
+esac
+
+# optimization, this only needs to run once.  But, if we move to a
+# computer we haven't used much, we need to fetch a fresh cert.
+# Existence check is just to avoid ugly error message from openssl.
+if [[ ! -e /etc/exim4/fullchain.pem ]] ||  ! openssl x509 -checkend $(( 60 * 60 * 24 * 3 )) -noout -in /etc/exim4/fullchain.pem; then
+  m /a/bin/ds/mail-cert-cron -1 -i
+  m systemctl --now enable mailcert.timer
+fi
+
+case $HOSTNAME in
+  $MAIL_HOST|bk)
+    m systemctl --now enable mailnn mailnnroute
+    ;;&
+  $MAIL_HOST)
+    # we use dns to start wg
+    if $reload; then
+      sre unbound
+    else
+      m systemctl --now enable unbound
+    fi
+    ;;&
+  $MAIL_HOST|bk)
+    # If these have changes, id rather manually restart it, id rather
+    # not restart and cause temporary errors
+    if $reload; then
+      sre $vpnser
+    else
+      m systemctl --now enable $vpnser
+    fi
+    ;;&
+  bk)
+    if ! systemctl is-active clamav-daemon >/dev/null; then
+      m systemctl --now enable clamav-daemon
+      out=$(rsync -aiSAX --chown=root:root --chmod=g-s /a/bin/ds/filesystem/etc/systemd/system/epanicclean.service /etc/systemd/system)
+      if [[ $out ]]; then
+        reload=true
+      fi
+
+      # note, this will cause paniclog entries because it takes like 45
+      # seconds for clamav to start, i use ./epanic-clean to remove
+      # them.
+    fi
+    ;;&
+  $MAIL_HOST|bk|je)
+    # start spamassassin/dovecot before exim.
+    sre dovecot $myspam_ser mailtest-check
+    # Wait a bit before restarting exim, else I get a paniclog entry
+    # like: spam acl condition: all spamd servers failed. But I'm tired
+    # of waiting. I'll deal with this some other way.
+    #
+    # sleep 3
+    m systemctl --now enable mailclean.timer
+    ;;&
+  $MAIL_HOST)
+    # < 2.1 (eg: in t9), uses a different data format which required manual
+    # migration. dont start if we are running an old version.
+    if dpkg --compare-versions "$(dpkg -s radicale | awk '$1 == "Version:" { print $2 }')" ge 2.1; then
+      m systemctl --now enable radicale
+    fi
+    ;;&
+esac
+
+# for debugging dns issues
+case $HOSTNAME in
+  je|bk)
+    systemctl enable --now logrotate-fast.timer
+    ;;
+esac
+
+# last use of $reload happens in previous block
+rm -f /var/local/mail-setup-reload
+
+
+case $HOSTNAME in
+  $MAIL_HOST|bk|je|li)
+    # on li, these are never started, except $vpnser
+    :
+    ;;
+  *)
+    soff radicale mailclean.timer dovecot $spamd_ser rspamd $vpnser mailnn clamav-daemon
+    ;;
+esac
+
+sre exim4
+
+case $HOSTNAME in
+  $MAIL_HOST)
+    m systemctl --now enable mailbindwatchdog
+    ;;
+  *)
+    soff mailbindwatchdog
+    ;;
+esac
+
+
+case $HOSTNAME in
+  bk) sre exim4in ;;
+esac
 
 # * misc
 m sudo -u $u mkdir -p /home/$u/.cache