The upgrade was performed three times, on amber, opal and ash (current web server). This article conflates the issues encounted.

Python

Needed to setup /usr/bin/python as a link for the newly installed Python 3.13.

sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.13 5

Dovecot

The Dovecot configuration files have changed quite a bit for 2.4 This is a summary of the changes I had to make, starting from the default Debian dovecot package configuration files.

10-auth.conf

disable_plaintext_auth has been replaced by auth_allow_cleartext which defaults to "no". To be explicit, I have uncommented this directive.

Don’t forget to uncomment the line which reads in auth-passwdfile.conf.ext.

10-mail.conf

Debian default for the mail_driver is mbox; changed to maildir:

mail_driver = maildir

The specification of the Maildir location in 2.3 was this:

mail_location = maildir:~/Maildir:INDEX=~/Maildir/index:CONTROL=~/Maildir/control

In 2.4, it is replaced by:

mail_path = ~/Maildir
mail_control_path = ~/Maildir/control
mail_index_path = ~/Maildir/index

10-ssl.conf

To specify the location of the certificate files, the 2.3 syntax is:

ssl_cert = </etc/letsencrypt/live/hydrus.org.uk/fullchain.pem
ssl_key = </etc/letsencrypt/live/hydrus.org.uk/privkey.pem

In 2.4, these lines are replaced by:

ssl_server_cert_file = /etc/letsencrypt/live/hydrus.org.uk/fullchain.pem
ssl_server_key_file = /etc/letsencrypt/live/hydrus.org.uk/privkey.pem

auth-passwdfile.conf.ext

This now looks like:

passdb passwd-file {
  default_password_scheme = crypt
  auth_username_format = %{user}
  passwd_file_path = /etc/mail-auth
}

userdb passwd-file {
  auth_username_format=%{user}
  passwd_file_path = /etc/mail-auth

Grub

During the install on opal, Grub refused to install itself on /dev/sda, due to detection of a ufs2 filesystem:

usr/sbin/grub-setup: error: /dev/sda appears to contain a ufs2
filesystem which isn't known to reserve space for DOS-style
boot. Installing GRUB there could result in FILESYSTEM DESTRUCTION
if valuable data is overwritten by grub-setup (--skip-fs-probe
disables this check, use at your own risk).

So, specifying --skip-fs-probe did not work, just generated an even more bizarre error message:

grub-install: warning: Embedding is not possible.  GRUB can only be
installed in this setup by using blocklists.  However, blocklists are
UNRELIABLE and their use is discouraged..
grub-install: error: will not proceed with blocklists.

However, since the first partition started at sector 2048, I could fix the problem by clobbering the preceeding sectors:

[mark@opal]$ sudo dd if=/dev/zero of=/dev/sda bs=512 seek=1 count=2047
2047+0 records in
2047+0 records out
1048064 bytes (1.0 MB, 1.0 MiB) copied, 0.0666945 s, 15.7 MB/s
[mark@opal:~]$ sudo grub-install /dev/sda
Installing for i386-pc platform.
Installation finished. No error reported.

The solution was found in this post, from someone with a similar issue:

Logwatch

The logwatch report in trixie considered legal IMAP logins as unmatched entries. It also was way more chatty regarding aborted logins.

To fix, needed to patch /usr/share/logwatch/scripts/services/dovecot:

--- dovecot.orig        2025-08-13 11:12:31.942391344 +0100
+++ dovecot     2025-08-16 07:34:52.203302308 +0100
@@ -164,8 +164,8 @@
          $ConnectionPOP3{$Host}++;
          $Connection{$Host}++;
       }
-   } elsif ( (($User, $Host) = ( $ThisLine =~ /^(?:$dovecottag )?imap-login: Login: (.*?) \[(.*)\]/ ) ) or
-             (($User, $Host) = ( $ThisLine =~ /^(?:$dovecottag )?imap-login: Login: user=\<(.*?)\>.*rip=(.*), lip=.*/ ) )  or
+   } elsif ( (($User, $Host) = ( $ThisLine =~ /^(?:$dovecottag )?imap-login: Logged in: (.*?) \[(.*)\]/ ) ) or
+             (($User, $Host) = ( $ThisLine =~ /^(?:$dovecottag )?imap-login: Logged in: user=\<(.*?)\>.*rip=(.*), lip=.*/ ) )  or
              (($User, $Host, $Session) = ( $ThisLine =~ /^(?:$dovecottag )?imap-login: (?:Info: )?Login: user=\<(.*?)\>.*rip=(.*), lip=.*, session=<([^>]+)>/ ) ) ) {
       if ($Host !~ /$IgnoreHost/) {
          $Host = hostName($Host);
@@ -290,13 +290,13 @@
       $ConnectionClosed{"Server shutting down"}++;
    } elsif ( ($Reason, $Host) = ($ThisLine =~ /TLS initialization failed/) ) {
       $TLSInitFail++;
-   } elsif ( ($Host) = ($ThisLine =~ /Aborted login:.* rip=(.*),/) ) {
+   } elsif ( ($Host) = ($ThisLine =~ /Login aborted:.* rip=(.*),/) ) {
       $Host = hostName($Host);
       $Aborted{$Host}++;
-   } elsif ( ($Host) = ($ThisLine =~ /Aborted login \[(.*)\]/) ) {
+   } elsif ( ($Host) = ($ThisLine =~ /Login aborted \[(.*)\]/) ) {
       $Host = hostName($Host);
       $Aborted{$Host}++;
-   } elsif ( ($Reason) = ($ThisLine =~ /Aborted login \((.*)\):/)) {
+   } elsif ( ($Reason) = ($ThisLine =~ /Login aborted \((.*)\):/)) {
       $Aborted{$Reason}++;
    } elsif ( ($User,$IP) = ($ThisLine =~ /auth: (?:LOGIN|login)\((.*),(\d+\.\d+\.\d+\.\d+)\): Request timed out waiting for client to continue authentication/) ) {
       $AuthTimedOut{$User}{$IP}++;
@@ -331,7 +331,7 @@
        $ConnectionClosed{$Reason}++;
    } elsif ($ThisLine =~ /(IMAP|POP3).+: (Connection closed.*)/) {
       $Disconnected{$2}++;
-   } elsif ( ($Host) = ($ThisLine =~ /(?:imap\-login|pop3\-login): Aborted login: .*rip=(?:::ffff:)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) ) {
+   } elsif ( ($Host) = ($ThisLine =~ /(?:imap\-login|pop3\-login): Login aborted: .*rip=(?:::ffff:)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) ) {
       $Aborted{$Host}++;
    } elsif ( ($Error) = ($ThisLine =~ /child \d* (?:\(login\) )?returned error (.*)/)) {
    # dovecot: child 23747 (login) returned error 89

Looks like Dovecot 2.4 has some modified logging messages.