feat: implement B.2 — rotate mail.log by rename + postfix reload

Replaces copytruncate with rename + `postfix reload` (the same mechanism
`postfix logrotate` itself uses), closing the up-to-one-second window where
copytruncate could drop in-flight delivery lines and leave a send-log row
stuck at "queued" forever.

logrotate-mail.conf keeps `create 0644 root root` rather than `nocreate` as
originally planned: verified on a live container that Postfix recreates the
file itself only lazily, on the next write after reload, and at mode 0600 —
unreadable by the unprivileged panel process. `create` hands the file back at
0644 immediately after rename, before Postfix ever touches it.

logtail.follow() re-drains the old file descriptor once more right before
switching to the rotated file, closing the residual gap between the last
poll's drain and the rotation check. readLogTail() treats a momentarily
missing mail.log as an empty screen rather than a logged error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 23:45:15 +03:00
parent 538a4b6603
commit db1572d7ad
8 changed files with 64 additions and 13 deletions
+9 -6
View File
@@ -1,10 +1,13 @@
#!/bin/sh
# Periodic logrotate for /var/log/mail.log (spec 9, 10). Postfix's maillog_file
# is written by postlogd, which keeps the file open for the life of the
# process — there is no daemon to signal on rotation, so the logrotate.d config
# uses copytruncate (a brief truncation race can drop the last few in-flight
# lines, which is an acceptable trade for not having to reload Postfix on every
# rotation).
# Periodic logrotate for /var/log/mail.log (spec 9, 10). Rotation renames the
# file, recreates it (`create 0644 root root`, matching a cold container
# start), then runs `postfix reload` (the same mechanism `postfix logrotate`
# uses): postlogd keeps writing to the renamed inode until reload, and the
# panel's log-tailer holds its own descriptor on that inode, so nothing
# written before the reload is lost. `create` (rather than `nocreate`) matters
# here beyond timing: a reload-triggered recreate lands the file at 0600,
# which the unprivileged panel process cannot read — confirmed on a live
# container — so logrotate must be the one to create it at 0644.
#
# logrotate itself only rotates once the configured "daily" period has elapsed
# (tracked in /var/lib/logrotate/status), so it is safe to invoke this more
+4 -1
View File
@@ -5,5 +5,8 @@
notifempty
compress
delaycompress
copytruncate
create 0644 root root
postrotate
/usr/sbin/postfix reload
endscript
}