dmarc-day, health-sockets: the reporter's day said; the inbound journal dialled
The owner, 2026-10-17: "Показ времени отчёта по UTC принимаем. Это не проблема. Пояснения добавь. Все мелочи закрой." Designs9b33b80,2bf9056. DMARC: a report's day stays the reporter's, in UTC (RFC 7489 § 7.2); the report's Window now says so under its times (windowNote), and Settings' Time zone box and Help (#settings, #dmarc) name it as the one exception to the panel's zone. formatDMARCWindow counts a window that ends at midnight as ending the day before, so a daily report is one day, not "20 Sep - 21 Sep" (TestDMARCWindowIsTheReportersDay, in Moscow's zone). Health: with the inbound relay on, the inbound journal milter's socket is dialled (InboundJournalSocket through web and handlers Config), fail-open as the send log's, and Overview's card counts it; the send log's socket is named journal-milter, as the mockup names it. The confirmation link's sign-in state is drawn (confirm-email-signin.html); the panel already renders it. guide: /license and /server/components, the DMARC pages' paths and day, selfpost-backup -o / -i / -version, the Inbound log's retention field, PANEL_COOKIE_SECURE, the inbound journal's socket, the DMARC exception to the zone. CHANGELOG [Unreleased]. (The e2e case negative_level1_ratelimit, listed as able to pass falsely, was fixed on 2026-10-14 - the note was stale.) go vet + go test (26 packages) and make e2e on the test server for this tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,26 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- **Health dials the inbound journal's socket** with the inbound relay on:
|
||||
the milter that writes Inbound › Log was the one the panel did not check.
|
||||
The send log's socket is named as the mockup names it, `journal-milter`.
|
||||
|
||||
### Changed
|
||||
|
||||
- **A DMARC report says which day it is**: its day stays the reporter's, in
|
||||
UTC (owner, 2026-10-17: accepted as it is), and the report's page now says
|
||||
so under its window; Settings and Help name it as the one exception to
|
||||
the panel's zone. A daily report from midnight to midnight is one day, not
|
||||
two.
|
||||
|
||||
### Documentation
|
||||
|
||||
- The guide's gaps from the docs audit: `/license` and `/server/components`,
|
||||
DMARC pages' paths, `selfpost-backup -o`, `-i` and `-version`, the
|
||||
Inbound log's retention field, `PANEL_COOKIE_SECURE`.
|
||||
|
||||
## [2.14.1] - 2026-10-17
|
||||
|
||||
Overview's head in the mockup's words. No migration; schema 13.
|
||||
|
||||
@@ -118,6 +118,7 @@ func newPanel(cfg config, st *store.Store) (*web.Server, error) {
|
||||
SignSocket: cfg.signSocket,
|
||||
SignLocalSocket: cfg.signLocalSocket,
|
||||
JournalSocket: cfg.journalSocket,
|
||||
InboundJournalSocket: cfg.inboundJournalSocket,
|
||||
SenderSocket: cfg.senderSocket,
|
||||
SessionIdleDays: cfg.sessionIdleDays,
|
||||
DNSResolvers: cfg.dnsResolvers,
|
||||
|
||||
+27
-6
@@ -253,6 +253,11 @@ expected to set; defaults match the code exactly.
|
||||
| `TZ` | Time zone of the container, an IANA name such as `Europe/Moscow`: every time the panel shows, every day it counts (today on Health, the days of the statistics, the days of the log filters) and the stamps Postfix writes in `mail.log`, so the two agree. Stored times stay UTC, so changing it is only a restart. *Server › Settings* shows the zone in use, and says so when the image does not know the name (the times are then UTC). | UTC | `.env` |
|
||||
| `TRUSTED_PROXY_CIDR` | Comma-separated CIDRs (bare IPs allowed) of reverse proxies allowed to supply `X-Forwarded-For` for sign-in and setup rate-limiting (account-change attempts are counted per user, whatever the address). **Leave unset unless you know the exact address of your reverse proxy.** A wrong value lets a client spoof its rate-limit key by sending a forged `X-Forwarded-For` header — the panel trusts the last hop only when the TCP peer matches one of these CIDRs. With Apache on the host (the default), the panel sees the proxy's connections come from the Docker network's gateway, e.g. `172.18.0.1` (`docker network inspect` shows it; measured 2026-10-15: the panel's `127.0.0.1:8080` is published through Docker's proxy, and a connection to such a port from the host arrives from the gateway). | *(empty — XFF ignored)* | `.env` |
|
||||
|
||||
`PANEL_COOKIE_SECURE` is not in the table: the session cookie is `Secure`
|
||||
unless it is `false`, which only a trial over plain HTTP wants, and the
|
||||
compose file does not pass it - the local trial in the README sets it on
|
||||
`docker run` (`-e PANEL_COOKIE_SECURE=false`).
|
||||
|
||||
TLS certificate paths (`TLS_CERT_FILE`, `TLS_KEY_FILE`) are fixed in
|
||||
[deploy/docker-compose.yml](../deploy/docker-compose.yml) to match the `./certs`
|
||||
bind mount — configure the mount, not these variables.
|
||||
@@ -434,6 +439,11 @@ menu: a group with nothing in it is absent. *Inbound* appears only with
|
||||
queue, answer 404 to a user with the *domain* role — the same answer a missing
|
||||
page gets.
|
||||
|
||||
Two pages are outside the menu: `/license`, the AGPL-3.0 text the footer links
|
||||
to, open to anyone; and `/server/components` (global role), the panel's
|
||||
component kit - every building block of its pages on one page, a reference
|
||||
for whoever works on the panel.
|
||||
|
||||
Pages that used to be one are now several. The old Status page is **Overview**
|
||||
(one verdict per check) and **Server › Health** (the tables behind them). The
|
||||
old Settings page is **Account** (your own name, e-mail and password) and
|
||||
@@ -467,8 +477,9 @@ readings, numbered in the order of what stops mail first:
|
||||
|
||||
- **Processes** and **Milter sockets**: the DKIM signer's two - `dkim-signer · applications` for what
|
||||
applications send on 465/587, `dkim-signer · notifications` for the
|
||||
panel's own mail - the sender check, the send log (the journal-milter)
|
||||
and, with one configured, the spam filter. Postfix attaches the signer
|
||||
panel's own mail - the sender check, the send log (the journal-milter),
|
||||
with the inbound relay on the Inbound log's (the inbound-journal-milter,
|
||||
fail-open like the send log's), and, with one configured, the spam filter. Postfix attaches the signer
|
||||
strictly, so either of its rows down means the mail that goes through it
|
||||
waits.
|
||||
- **Sender check** — what the strict check of the `From:` line has cost
|
||||
@@ -613,7 +624,10 @@ the `mail.log` lines on Server › System log and on a message's page read the
|
||||
same clock as the rest of the page. The database keeps its times in UTC, so
|
||||
changing the zone is a restart and nothing more; the days counted before it
|
||||
stay days of the old zone, a shift of a few hours in the statistics. A name
|
||||
the image does not know leaves everything in UTC, and the box says so.
|
||||
the image does not know leaves everything in UTC, and the box says so. One
|
||||
thing keeps UTC on purpose: the day a DMARC report covers is its reporter's,
|
||||
who counts days in UTC, so the lists and a report's title name that day; the
|
||||
report's page gives its window's times in this zone and says which day it is.
|
||||
|
||||
### Notifications
|
||||
|
||||
@@ -1142,7 +1156,10 @@ The CLI also *writes* encrypted backups for scripted/cron use. The password
|
||||
must be at least 12 characters, as on the panel's forms (decrypting takes
|
||||
any), and it comes from `SELFPOST_BACKUP_PASSWORD` or `-password-file <path>`
|
||||
(first line), never from a command-line argument, which would be visible in
|
||||
the process list.
|
||||
the process list. Its other flags: `-o <file>` writes the output to a file
|
||||
instead of standard output, `-i <file>` reads the encrypted archive from a
|
||||
file instead of standard input (with `-decrypt`), and `-version` prints the
|
||||
version.
|
||||
`-e` with the name only takes the value from your shell's environment, so it is
|
||||
not written on the command line:
|
||||
|
||||
@@ -1344,7 +1361,11 @@ sees the *Ingest* box — whether ingest works, the last report received, report
|
||||
kept this week and parse failures. Open a domain's roll-up from the list or
|
||||
from *DMARC reports* in the domain page's menu. A user with the *domain* role
|
||||
sees the reports of the domains assigned to them, without the *Ingest* box.
|
||||
Summaries are pruned (500 kept for each domain, 90 days max).
|
||||
Summaries are pruned (500 kept for each domain, 90 days max). A report's
|
||||
page is `/outbound/dmarc/reports/<id>` and a domain's roll-up
|
||||
`/outbound/dmarc/domains/<id>`. A report's day - in the lists and its title -
|
||||
is the reporter's, counted in UTC as reports count their days; its page shows
|
||||
the window's times in the panel's zone, with a line naming that day.
|
||||
|
||||
A message that is not a report the relay keeps — unreadable as an aggregate
|
||||
report, about a domain not sent from here, or delivered to another domain's
|
||||
@@ -1631,7 +1652,7 @@ the row is deleted by retention, and not while another row carries the same
|
||||
queue id (Postfix reuses ids); a
|
||||
message accepted before `2.2.0` shows them only while they are still in the
|
||||
current file, and the page says so when there are none. Retention is set under **Server › Settings** (global
|
||||
role); `SEND_LOG_RETENTION_DAYS` in `.env` is only the initial default until it
|
||||
role; with the inbound relay on, the Inbound log's has a field of its own there); `SEND_LOG_RETENTION_DAYS` in `.env` is only the initial default until it
|
||||
is changed there.
|
||||
|
||||
### Exporting and importing a single domain
|
||||
|
||||
@@ -35,7 +35,9 @@ type Config struct {
|
||||
SignSocket string
|
||||
SignLocalSocket string
|
||||
JournalSocket string
|
||||
SenderSocket string
|
||||
// InboundJournalSocket is the inbound journal milter's (Inbound › Log).
|
||||
InboundJournalSocket string
|
||||
SenderSocket string
|
||||
// Level-1 Postfix anvil backstop (env RATE_LIMIT_*), shown in the panel
|
||||
// and used to cap domain/app level-2 ceilings (guide § Rate limiting).
|
||||
RateLimitMessagesPerIP int
|
||||
|
||||
@@ -222,12 +222,21 @@ func (h *Handlers) relayIPs() map[string]bool {
|
||||
return ips
|
||||
}
|
||||
|
||||
// formatDMARCWindow is the day, or days, a report covers, as its reporter
|
||||
// counts them: in UTC (RFC 7489 § 7.2, date-range in UTC seconds), which the
|
||||
// panel keeps whatever its own zone (owner, 2026-10-17). A window that ends
|
||||
// at midnight ends the day before: a daily report from 00:00 to 00:00 is one
|
||||
// day, not two.
|
||||
func formatDMARCWindow(begin, end time.Time) string {
|
||||
if begin.IsZero() {
|
||||
return ""
|
||||
}
|
||||
if begin.Year() == end.Year() && begin.YearDay() == end.YearDay() {
|
||||
return begin.UTC().Format("2 Jan")
|
||||
begin, last := begin.UTC(), end.UTC()
|
||||
if last.After(begin) && last.Equal(last.Truncate(24*time.Hour)) {
|
||||
last = last.Add(-time.Second)
|
||||
}
|
||||
return begin.UTC().Format("2 Jan") + " – " + end.UTC().Format("2 Jan")
|
||||
if begin.Year() == last.Year() && begin.YearDay() == last.YearDay() {
|
||||
return begin.Format("2 Jan")
|
||||
}
|
||||
return begin.Format("2 Jan") + " – " + last.Format("2 Jan")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A report's window is its reporter's day or days, in UTC, whatever the
|
||||
// panel's zone; a window that ends at midnight ends the day before.
|
||||
func TestDMARCWindowIsTheReportersDay(t *testing.T) {
|
||||
inZone(t, "Europe/Moscow")
|
||||
day := func(d, h, m, s int) time.Time { return time.Date(2026, 9, d, h, m, s, 0, time.UTC) }
|
||||
for _, c := range []struct {
|
||||
begin, end time.Time
|
||||
want string
|
||||
}{
|
||||
{day(20, 0, 0, 0), day(21, 0, 0, 0), "20 Sep"},
|
||||
{day(20, 0, 0, 0), day(20, 23, 59, 59), "20 Sep"},
|
||||
{day(20, 0, 0, 0), day(22, 0, 0, 0), "20 Sep – 21 Sep"},
|
||||
{day(20, 12, 0, 0), day(21, 12, 0, 0), "20 Sep – 21 Sep"},
|
||||
} {
|
||||
if got := formatDMARCWindow(c.begin, c.end); got != c.want {
|
||||
t.Errorf("%s – %s = %q, want %q", c.begin, c.end, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,8 @@ func socketsServer(t *testing.T) *Handlers {
|
||||
h.cfg.SignLocalSocket = filepath.Join(dir, "sign-local.sock")
|
||||
h.cfg.JournalSocket = filepath.Join(dir, "journal.sock")
|
||||
h.cfg.SenderSocket = filepath.Join(dir, "sender.sock")
|
||||
for _, path := range []string{h.cfg.SignSocket, h.cfg.SignLocalSocket, h.cfg.SenderSocket, h.cfg.JournalSocket} {
|
||||
h.cfg.InboundJournalSocket = filepath.Join(dir, "inbound-journal.sock")
|
||||
for _, path := range []string{h.cfg.SignSocket, h.cfg.SignLocalSocket, h.cfg.SenderSocket, h.cfg.JournalSocket, h.cfg.InboundJournalSocket} {
|
||||
l, err := net.Listen("unix", path)
|
||||
if err != nil {
|
||||
t.Skipf("unix sockets unavailable here: %v", err)
|
||||
@@ -57,8 +58,9 @@ func TestFilterOffAddsNoSocket(t *testing.T) {
|
||||
h := socketsServer(t)
|
||||
h.cfg.InboundEnabled = true
|
||||
h.cfg.InboundAntispamMilter = "" // no filter attached
|
||||
if got := socketsOf(t, h); len(got) != 4 {
|
||||
t.Fatalf("%d sockets with the filter off, want 4: %+v", len(got), got)
|
||||
// The relay's own journal is dialled; no filter is.
|
||||
if got := socketsOf(t, h); len(got) != 5 || got[4].Name != "inbound-journal-milter" {
|
||||
t.Fatalf("%d sockets with the filter off, want 5 (the inbound journal last): %+v", len(got), got)
|
||||
}
|
||||
|
||||
// A milter address with the inbound relay off is not a filter either.
|
||||
@@ -69,7 +71,7 @@ func TestFilterOffAddsNoSocket(t *testing.T) {
|
||||
t.Fatalf("%d sockets with the inbound relay off, want 4: %+v", len(got), got)
|
||||
}
|
||||
card := socketCard(t, h)
|
||||
if card.Value != "4 of 4 answering" || card.Sub != "dkim-signer, sender-check, send-log" || card.Level != "" {
|
||||
if card.Value != "4 of 4 answering" || card.Sub != "dkim-signer, sender-check, journal-milter" || card.Level != "" {
|
||||
t.Errorf("card = %+v", card)
|
||||
}
|
||||
}
|
||||
@@ -85,7 +87,7 @@ func TestFilterOnAnsweringIsTheLastSocket(t *testing.T) {
|
||||
h.cfg.InboundAntispamMilter, h.cfg.InboundAntispamAction = "inet:"+l.Addr().String(), "tempfail"
|
||||
|
||||
got := socketsOf(t, h)
|
||||
if len(got) != 5 || got[4].Name != "Spam filter" || got[4].Note != "inbound" || got[4].Path != "inet:"+l.Addr().String() {
|
||||
if len(got) != 6 || got[5].Name != "Spam filter" || got[5].Note != "inbound" || got[5].Path != "inet:"+l.Addr().String() {
|
||||
t.Fatalf("sockets = %+v", got)
|
||||
}
|
||||
for _, s := range got {
|
||||
@@ -94,7 +96,7 @@ func TestFilterOnAnsweringIsTheLastSocket(t *testing.T) {
|
||||
}
|
||||
}
|
||||
card := socketCard(t, h)
|
||||
if card.Value != "5 of 5 answering" || card.Sub != "incl. the spam filter" || card.Level != "" {
|
||||
if card.Value != "6 of 6 answering" || card.Sub != "incl. the spam filter" || card.Level != "" {
|
||||
t.Errorf("card = %+v", card)
|
||||
}
|
||||
|
||||
@@ -134,10 +136,10 @@ func TestFilterDownIsAMissingSocket(t *testing.T) {
|
||||
} {
|
||||
h.cfg.InboundAntispamMilter, h.cfg.InboundAntispamAction = dead, tt.action
|
||||
got := socketsOf(t, h)
|
||||
if len(got) != 5 {
|
||||
t.Fatalf("action %q: %d sockets, want 5", tt.action, len(got))
|
||||
if len(got) != 6 {
|
||||
t.Fatalf("action %q: %d sockets, want 6", tt.action, len(got))
|
||||
}
|
||||
f := got[4]
|
||||
f := got[5]
|
||||
refused := "Connection to " + l.Addr().String() + " refused. " + tt.detail
|
||||
if f.Present || f.Status != tt.status || f.Detail != refused {
|
||||
t.Errorf("action %q: filter = %+v, want status %q, detail %q", tt.action, f, tt.status, refused)
|
||||
@@ -148,7 +150,7 @@ func TestFilterDownIsAMissingSocket(t *testing.T) {
|
||||
t.Errorf("action %q: SocketStatus = %q, want %q", tt.action, c.SocketStatus, tt.status)
|
||||
}
|
||||
card := socketCard(t, h)
|
||||
if card.Value != "4 of 5 answering" || card.Level != tt.level || !strings.HasSuffix(card.Sub, "Spam filter") {
|
||||
if card.Value != "5 of 6 answering" || card.Level != tt.level || !strings.HasSuffix(card.Sub, "Spam filter") {
|
||||
t.Errorf("action %q: card = %+v", tt.action, card)
|
||||
}
|
||||
|
||||
@@ -162,7 +164,7 @@ func TestFilterDownIsAMissingSocket(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
h.HandleOverviewFragment(rec, reqAs("/overview/fragment", globalPrincipal))
|
||||
page := rec.Body.String()
|
||||
for _, want := range []string{`>` + tt.stamp + `<`, `Milter sockets</a>: 4 of 5 answering`} {
|
||||
for _, want := range []string{`>` + tt.stamp + `<`, `Milter sockets</a>: 5 of 6 answering`} {
|
||||
if !strings.Contains(page, want) {
|
||||
t.Errorf("action %q: Overview is missing %q", tt.action, want)
|
||||
}
|
||||
|
||||
@@ -572,7 +572,12 @@ func (h *Handlers) healthChecks() healthReadings {
|
||||
signerSocket(h.cfg.SignSocket, "applications"),
|
||||
signerSocket(h.cfg.SignLocalSocket, "notifications"),
|
||||
health.CheckSocket("sender-check", h.cfg.SenderSocket, true),
|
||||
health.CheckSocket("send-log", h.cfg.JournalSocket, false),
|
||||
health.CheckSocket("journal-milter", h.cfg.JournalSocket, false),
|
||||
}
|
||||
// The inbound journal writes Inbound › Log, fail-open as the outbound
|
||||
// one: dialled while there is an inbound relay to journal.
|
||||
if h.cfg.InboundEnabled {
|
||||
sockets = append(sockets, health.CheckSocket("inbound-journal-milter", h.cfg.InboundJournalSocket, false))
|
||||
}
|
||||
if on, milter, action := h.inboundFilter(); on {
|
||||
sockets = append(sockets, filterSocket(milter, action))
|
||||
|
||||
@@ -328,8 +328,8 @@ type ReportInput struct {
|
||||
DomainID int64
|
||||
Contact string
|
||||
Pass, Fail int
|
||||
Window string // the window in the title: "20 Sep"
|
||||
Period string // the window with its times: "20 Sep 00:00 – 21 Sep 00:00"
|
||||
Window string // the reporter's day, in the title: "20 Sep"
|
||||
Period string // the window with its times, this zone's: "20 Sep 03:00 – 21 Sep 03:00"
|
||||
Received string // "2026-09-21 04:40"
|
||||
PolicyP string
|
||||
PolicySP string
|
||||
@@ -341,6 +341,16 @@ type ReportInput struct {
|
||||
Hub bool
|
||||
}
|
||||
|
||||
// windowNote says, under a report's window, which day the report is of: the
|
||||
// reporter's, in UTC, which the title and the lists name (owner, 2026-10-17:
|
||||
// "Показ времени отчёта по UTC принимаем ... Пояснения добавь").
|
||||
func windowNote(day string) Text {
|
||||
if day == "" {
|
||||
return nil
|
||||
}
|
||||
return Plain("The reporter's day, " + day + ": reports count their days in UTC, and the title and the lists name that day. The times are this panel's zone.")
|
||||
}
|
||||
|
||||
// NewDMARCReport builds the page of one report.
|
||||
func NewDMARCReport(m Meta, in ReportInput) *DMARCReport {
|
||||
title := in.Reporter
|
||||
@@ -359,7 +369,7 @@ func NewDMARCReport(m Meta, in ReportInput) *DMARCReport {
|
||||
report := []Fact{
|
||||
{Label: "Reporter", Value: Plain(in.Reporter)},
|
||||
{Label: "Report id", Value: Plain(in.ReportID), Mono: true, Small: true},
|
||||
{Label: "Window", Value: Plain(in.Period)},
|
||||
{Label: "Window", Value: Plain(in.Period), Note: windowNote(in.Window)},
|
||||
{Label: "Received", Value: Plain(in.Received)},
|
||||
}
|
||||
if in.Contact != "" {
|
||||
|
||||
@@ -98,7 +98,7 @@ func TestDMARCReportShowsTheReportItsPolicyAndRecords(t *testing.T) {
|
||||
`<h1 class="title is-4">Outlook.com · 20 Sep</h1>`,
|
||||
`<a href="/outbound/dmarc">DMARC reports</a> / <a href="/outbound/dmarc/domains/2">shop.example.org</a> /`,
|
||||
"<span>shop.example.org</span><span class=\"tag is-success is-light\">1 903 pass</span><span class=\"tag is-warning is-light\">7 fail</span>",
|
||||
`<h2>Report</h2>`, `<dd class="sp-mono sp-small">5f1c9a0e7b2d4e61a3</dd>`, `<dd>20 Sep 00:00 – 21 Sep 00:00</dd>`, `<dd>2026-09-21 04:40</dd>`,
|
||||
`<h2>Report</h2>`, `<dd class="sp-mono sp-small">5f1c9a0e7b2d4e61a3</dd>`, `<dd>20 Sep 00:00 – 21 Sep 00:00</dd><dd class="sp-muted">The reporter's day, 20 Sep: reports count their days in UTC, and the title and the lists name that day. The times are this panel's zone.</dd>`, `<dd>2026-09-21 04:40</dd>`,
|
||||
`<h2>Published policy</h2>`, `<dd class="sp-mono">none / none / 100</dd>`, `<dd class="sp-mono">r / r</dd>`, `<dd class="sp-mono sp-small">dmarc@mail.example.org</dd>`,
|
||||
`<h2>Records</h2>`, `Parsed from the aggregate XML`,
|
||||
`203.0.113.25 <span class="tag is-light">this relay</span>`, "<td>1 903</td>",
|
||||
|
||||
@@ -220,7 +220,7 @@ var outboundTopics = []HelpTopic{
|
||||
}},
|
||||
{ID: HelpDMARC, Title: "DMARC reports", Body: []Text{
|
||||
Rich("An aggregate report is the summary a receiving provider sends, usually daily, to the ", Code("rua="),
|
||||
" address of your DMARC record: which servers sent mail as your domain and whether SPF or DKIM passed for it. A domain's report address is one of three choices, set in its Domain settings: no reports (the default for a new domain), the SelfPost hosted address (an address on this server whose reports are parsed and shown under DMARC reports; offered only while hosted reports are enabled on the server), or an address typed for the domain (an ordinary mailbox that receives the raw XML; a button fills in your own profile e-mail). When the typed address is on another domain than the one sending, that domain must publish a report-authorization record, or providers will not send; the domain's page shows it with the other DNS records and checks it. In the tables pass means SPF or DKIM passed and aligned, fail means neither did, and your own server shows as ",
|
||||
" address of your DMARC record - its day is the reporter's, counted in UTC, and that is the day the lists and a report's title name, while the report's page gives its window in this panel's zone: which servers sent mail as your domain and whether SPF or DKIM passed for it. A domain's report address is one of three choices, set in its Domain settings: no reports (the default for a new domain), the SelfPost hosted address (an address on this server whose reports are parsed and shown under DMARC reports; offered only while hosted reports are enabled on the server), or an address typed for the domain (an ordinary mailbox that receives the raw XML; a button fills in your own profile e-mail). When the typed address is on another domain than the one sending, that domain must publish a report-authorization record, or providers will not send; the domain's page shows it with the other DNS records and checks it. In the tables pass means SPF or DKIM passed and aligned, fail means neither did, and your own server shows as ",
|
||||
Em("this relay"), ". A fail from any other source is someone else sending as your domain — a forwarder, an application you forgot, or abuse — so do not tighten ",
|
||||
Code("p="), " until that sender is fixed or removed. A domain administrator reads the reports of their own domains."),
|
||||
}},
|
||||
@@ -289,7 +289,7 @@ var serverTopics = []HelpTopic{
|
||||
{ID: HelpSettings, Title: "Settings", Body: []Text{
|
||||
Rich("Settings holds what is true of the whole instance. The time zone is the container's, ", Code("TZ"), " in ", Code(".env"),
|
||||
" (UTC when it is not set): every time the panel shows and every day it counts is that zone's, as are the stamps Postfix writes in ", Code("mail.log"),
|
||||
", so the two always agree; changing it is a restart of the container. Log retention is how many days the Outbound log keeps its rows, from 7 to 365 and 90 by default; a sweep every six hours deletes older ones, and it is the main driver of how fast ",
|
||||
", so the two always agree; changing it is a restart of the container. The one exception is the day a DMARC report covers, which is the reporter's, counted in UTC. Log retention is how many days the Outbound log keeps its rows, from 7 to 365 and 90 by default; a sweep every six hours deletes older ones, and it is the main driver of how fast ",
|
||||
Code("/data"), " grows. With the inbound relay on there is a second number, for the Inbound log, with the same range. It does not change the rotation of ", Code("mail.log"),
|
||||
", which is daily with about two weeks kept. The sending rate limit shown under it is level 1, the per-client-IP ceiling set in ",
|
||||
Code(".env"), ": changing it needs a restart, and no level-2 limit can exceed it. Notifications are turned on or off here for everyone."),
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
{{template "box_open" .TimeZone}}
|
||||
{{template "body_open"}}
|
||||
{{template "facts" .ZoneFacts}}
|
||||
<p class="mt-4 sp-muted">Every time the panel shows, and every day it counts - today on Health, the days of the statistics - is this zone's, and so are the stamps of <code>mail.log</code>.</p>
|
||||
<p class="mt-4 sp-muted">Every time the panel shows, and every day it counts - today on Health, the days of the statistics - is this zone's, and so are the stamps of <code>mail.log</code>. One exception: the day a DMARC report covers is the reporter's, in UTC.</p>
|
||||
{{template "body_close"}}
|
||||
{{template "box_close"}}
|
||||
</div>
|
||||
|
||||
@@ -81,6 +81,9 @@ type Config struct {
|
||||
SignSocket string
|
||||
SignLocalSocket string
|
||||
JournalSocket string
|
||||
// InboundJournalSocket is the inbound journal milter's, dialled with
|
||||
// the inbound relay on; it fails open like the journal's.
|
||||
InboundJournalSocket string
|
||||
// SenderSocket is the sender check's socket, attached strictly.
|
||||
SenderSocket string
|
||||
// SessionIdleDays is the sliding inactivity window after which a login
|
||||
@@ -160,6 +163,7 @@ func New(st *store.Store, domains *domain.Service, apps *app.Service, inboundSvc
|
||||
SignSocket: cfg.SignSocket,
|
||||
SignLocalSocket: cfg.SignLocalSocket,
|
||||
JournalSocket: cfg.JournalSocket,
|
||||
InboundJournalSocket: cfg.InboundJournalSocket,
|
||||
SenderSocket: cfg.SenderSocket,
|
||||
RateLimitMessagesPerIP: cfg.RateLimitMessagesPerIP,
|
||||
RateLimitWindowSeconds: cfg.RateLimitWindowSeconds,
|
||||
|
||||
Reference in New Issue
Block a user