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:
@@ -0,0 +1,21 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// After log rotation renames mail.log away, Postfix takes about a second to
|
||||
// recreate it on reload (spec B.2); a missing file in that window is a normal,
|
||||
// transient gap, not an operator-facing failure.
|
||||
func TestReadLogTailMissingFileIsNotAnError(t *testing.T) {
|
||||
s := &Server{cfg: Config{MailLogPath: filepath.Join(t.TempDir(), "mail.log")}}
|
||||
|
||||
lines, errText := s.readLogTail()
|
||||
if lines != nil {
|
||||
t.Errorf("lines = %v, want nil", lines)
|
||||
}
|
||||
if errText != "" {
|
||||
t.Errorf("errText = %q, want empty (missing file is not an error)", errText)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user