fix(logtail): keep mail.log in /data and reconcile stuck rows (v1.x closure phase 2)
test / test (push) Has been cancelled
test / test (push) Has been cancelled
Move the delivery log from the ephemeral /var/log to /data/log/mail.log so the lines that resolve a queued send-log row survive a container recreate. postlogd writes it as postfix, the panel reads it through the selfpost group (dir 2750, file 0640, normalised every start); backups exclude log/. Close the residual gap with a queue sweep: rows queued for over two minutes whose id postqueue -p no longer lists are marked bounced. The sweep waits until the tailer has read the log to its end and does nothing when the queue cannot be listed, so a message in flight is never touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+31
-14
@@ -94,10 +94,20 @@ Milter chain in Postfix: OpenDKIM (tempfail) then journal (accept on failure).
|
||||
|
||||
### Log tailer and `mail.log` rotation
|
||||
|
||||
`mail.log` lives under `/var/log` (not in `/data`). Rotation uses rename +
|
||||
`postfix reload` ([build/logrotate-mail.conf](../build/logrotate-mail.conf)), not
|
||||
`copytruncate` — the latter can drop `status=sent` lines and leave send-log rows
|
||||
stuck at `queued`. After rename, logrotate runs `create 0644 root root` (Postfix
|
||||
`mail.log` lives at `/data/log/mail.log` — inside the persistent bind mount, so
|
||||
the delivery lines that resolve a `queued` send-log row are not lost when the
|
||||
container is recreated. `postlogd` writes it as user `postfix`; the panel reads
|
||||
it through the shared `selfpost` group (directory `2750 postfix:selfpost`, file
|
||||
`0640`, both normalised on every start by
|
||||
[build/entrypoint.sh](../build/entrypoint.sh)). The path is one default in two
|
||||
places, `maillog_file` in [build/postfix-config.sh](../build/postfix-config.sh)
|
||||
and `MAIL_LOG` in [cmd/panel/main.go](../cmd/panel/main.go). Backups exclude
|
||||
`log/`: it is diagnostic output, not state to restore.
|
||||
|
||||
Rotation uses rename + `postfix reload`
|
||||
([build/logrotate-mail.conf](../build/logrotate-mail.conf)), not `copytruncate` —
|
||||
the latter can drop `status=sent` lines and leave send-log rows stuck at
|
||||
`queued`. After rename, logrotate runs `create 0640 postfix selfpost` (postlogd
|
||||
recreates the file lazily on first write as mode `0600`, which the unprivileged
|
||||
panel user cannot read). `follow()` drains the old inode once more before
|
||||
switching descriptors; the panel treats a missing log file as an empty tail, not
|
||||
@@ -112,14 +122,20 @@ the current file from the start; re-parsing lines is harmless because
|
||||
with nothing stored, begins at end-of-file, so installing the panel does not
|
||||
replay a pre-existing log.
|
||||
|
||||
**Remaining gap:**
|
||||
|
||||
- **Container recreate** — `/var/log` is ephemeral; the log is lost with the
|
||||
container, so the delivery lines for rows still `queued` are gone with it and
|
||||
those rows stay `queued` forever.
|
||||
|
||||
Possible follow-ups if this becomes painful: mount the mail log under `/data`, or
|
||||
reconcile stuck rows via `postqueue`.
|
||||
**Queue reconcile** is the backstop for what the log cannot explain at all: a
|
||||
row still `queued` more than two minutes after it was accepted, whose queue id
|
||||
`postqueue -p` no longer lists, is marked `bounced` (swept every five minutes,
|
||||
[internal/logtail](../internal/logtail/logtail.go),
|
||||
[postfix.QueueIDs](../internal/postfix/queue.go)). Postfix having dropped the
|
||||
message means nothing more will ever be reported about it, so the row can only
|
||||
be closed on an assumption, and it is closed as a failure because a delivery the
|
||||
panel cannot evidence must not be shown as one. Three things keep the sweep from
|
||||
guessing where it need not: it starts only after the tailer has read to
|
||||
end-of-file once (on a restart the log itself holds the answer), the two-minute
|
||||
grace covers messages merely in flight, and a `postqueue` that cannot be read
|
||||
leaves every row untouched rather than closing them all. Now that the log
|
||||
survives the container, reaching this path means the lines are gone for good —
|
||||
rotated past fourteen files while the panel was down, or deleted.
|
||||
|
||||
**Two one-shot reads** sit beside the follow loop and are unrelated to it, both
|
||||
serving panel pages on request: `TailLines` (the last *n* lines, for
|
||||
@@ -250,6 +266,7 @@ single-connection trade-off that follows from it.
|
||||
| `opendkim/` | DKIM keys + tables |
|
||||
| `sasl/sasldb2` | Application SASL credentials |
|
||||
| `postfix/sender_login_maps` | Login → From binding |
|
||||
| `log/mail.log` | Postfix delivery log + rotated copies (excluded from backups) |
|
||||
| `manifest.json` | Backup version stamp (consumed on restore) |
|
||||
|
||||
Not in `/data`: TLS certificates (reverse-proxy mount), Postfix queue
|
||||
@@ -260,8 +277,8 @@ Not in `/data`: TLS certificates (reverse-proxy mount), Postfix queue
|
||||
`postfix reload` in `postrotate` — see § Log tailer above).
|
||||
|
||||
**Backup:** panel button or `selfpost-backup` CLI — SQLite snapshot + tar of
|
||||
`/data` tree; version check on restore. Stopped-container `tar` of `./data` is
|
||||
safe (see guide).
|
||||
`/data` tree, minus `log/`, the setup token and any `tls/`; version check on
|
||||
restore. Stopped-container `tar` of `./data` is safe (see guide).
|
||||
|
||||
**Optional encryption** of the two secret-bearing downloads
|
||||
([internal/secretfile](../internal/secretfile/secretfile.go)): password →
|
||||
|
||||
+14
-6
@@ -109,7 +109,9 @@ supported configuration:
|
||||
(`/data/selfpost.db`), `SELFPOST_SETUP_TOKEN_FILE`
|
||||
(`/data/setup-token`), `PANEL_HTTP_ADDR` (`:8080`),
|
||||
`JOURNAL_MILTER_SOCKET` (`/run/selfpost/journal.sock`), `MAIL_LOG`
|
||||
(`/var/log/mail.log`), `PANEL_COOKIE_SECURE` (`true`), `OPENDKIM_SOCKET`
|
||||
(`/data/log/mail.log` — read by the panel and written by Postfix, so a change
|
||||
here has to be matched in `build/postfix-config.sh`),
|
||||
`PANEL_COOKIE_SECURE` (`true`), `OPENDKIM_SOCKET`
|
||||
(`/run/opendkim/opendkim.sock`), `OPENDKIM_DIR` (`/data/opendkim`),
|
||||
`DKIM_SELECTOR_DEFAULT` (`selfpost`), `SASL_DB_PATH`
|
||||
(`/data/sasl/sasldb2`), `SASL_REALM` (defaults to `SELFPOST_HOSTNAME`),
|
||||
@@ -191,8 +193,9 @@ service healthy and will mail be accepted?"
|
||||
- **Deliveries** (`/deliveries`) — searchable send log with server-side filters
|
||||
by domain and application. A row identifies its message and nothing more —
|
||||
time, sender, recipient, subject and status `queued` (accepted, not yet
|
||||
delivered), `sent` (handed off successfully), or `rejected` (refused — for
|
||||
example by a level-2 rate limit); *Details* opens that row's own page
|
||||
delivered), `sent` (handed off successfully), `deferred` (Postfix is retrying),
|
||||
`bounced` (final failure), or `rejected` (refused — for example by a level-2
|
||||
rate limit); *Details* opens that row's own page
|
||||
(`/deliveries/{id}`). That page carries the sending domain, the application it
|
||||
was submitted under, the Postfix queue id and the journal id, beside the
|
||||
message's history — when it was accepted and what Postfix later reported for
|
||||
@@ -203,10 +206,12 @@ service healthy and will mail be accepted?"
|
||||
`SEND_LOG_RETENTION_DAYS`.
|
||||
- **Mail queue** (`/mail-queue`) — live view of messages Postfix is still
|
||||
trying to deliver or deferring.
|
||||
- **System log** (`/system-log`) — tail of `/var/log/mail.log` (Postfix and
|
||||
- **System log** (`/system-log`) — tail of `/data/log/mail.log` (Postfix and
|
||||
related daemon lines). The log rotates daily (14 files kept) with a
|
||||
`postfix reload` after each rotation; a background loop checks every six
|
||||
hours.
|
||||
hours. It lives in the data volume, so it survives a container recreate along
|
||||
with the rest of the state — `./data/log/` on the host — but it is *not*
|
||||
included in backups: it is diagnostics, not state.
|
||||
- **Backup** (`/backup`) — download a full-server backup or import a
|
||||
single-domain export. See [Backup, restore, and moving a single domain](#backup-restore-and-moving-a-single-domain).
|
||||
- **Account** (`/account`) — change the administrator username and/or password.
|
||||
@@ -288,7 +293,10 @@ Two related but distinct operations — spec 7.5:
|
||||
|
||||
**Alternative: archive `./data` while stopped.** If the service can be taken
|
||||
offline, `docker compose down` then `tar czf selfpost-data.tar.gz ./data` on
|
||||
the host is safe — nothing is writing to SQLite. Do **not** tar `./data` while
|
||||
the host is safe — nothing is writing to SQLite. Unlike the panel/CLI backup
|
||||
this sweeps in `./data/log/` too, which is Postfix's raw log and usually the
|
||||
bulk of the archive; add `--exclude=./data/log` if you only want the state.
|
||||
Do **not** tar `./data` while
|
||||
the container is running: the database uses WAL mode and a naive copy can
|
||||
capture an inconsistent snapshot. The panel/CLI backup remains preferable when
|
||||
you cannot afford downtime because it takes a consistent SQLite snapshot via
|
||||
|
||||
+2
-1
@@ -55,7 +55,8 @@
|
||||
- **Фаза 3 плана ревизии выполнена (2026-08-06)** (operational improvements, P2–P3): (1) log-tailer сохраняет позицию чтения — таблица `logtail_state` (миграция `0003`, `internal/store/logtail.go`) хранит offset + отпечаток первых 512 байт лога, `internal/logtail/offset.go` решает откуда стартовать: отпечаток совпал → продолжаем с offset (дочитывается хвост, написанный пока панель лежала); не совпал (лог сменился/пересоздан) → читаем файл с начала (повторный разбор безвреден, `UpdateStatus` идемпотентен); записи нет вовсе (первый запуск) → с конца, как раньше. Запись offset — не чаще раза в 5 с, плюс форс при ротации и на выключении; сохраняется позиция *потреблённых* байт (минус недочитанная частичная строка). (2) L2-лимит перестал промахиваться при параллельных сессиях: между проверкой на MAIL FROM и вставкой строки на end-of-message сообщение не видно в БД, поэтому N одновременных сессий пропускали друг друга — теперь к счёту из БД добавляются «в полёте» (`internal/milter/inflight.go`, общий на процесс реестр резерваций); резервация освобождается после записи в send-log, на ABORT и по TTL 10 минут (у go-milter нет колбэка на закрытие соединения, а вечная резервация — это fail-closed-дрейф, которого у лимитера быть не должно). Транзакция «count+insert», как предлагал review, невозможна буквально: эти два шага разнесены по разным стадиям SMTP-транзакции. Тесты: restart/rotation-resume для tailer'а, четыре сценария резерваций для лимита. `gofmt`/`go vet` чистые; `go test ./...` — падения только известные Windows-специфичные (`internal/domain`, `TestFollowTailsAndRotates`). Не проверено на стенде (нет Docker локально) — кандидат на следующий прогон на стенде.
|
||||
- **Добор по плану ревизии выполнен (2026-08-06):** (1) проект переехал на единственную площадку — GitHub (Codeberg уходит): вместе с URL, лицензионными шапками SVG/HTML и docs переехал путь Go-модуля на `github.com/mixeme/selfpost` (`go.mod`, `test/e2e/go.mod`, все импорты, `MODULE` в Makefile, `-ldflags` в Dockerfile и development.md) — оставлять импорты на исчезающем хосте нельзя, `go get`/`go install` сломались бы; (2) ссылки на архивную спецификацию убраны из кода целиком — не только «spec 7.x», как просило ревью, но и «spec 4/5/6/8/9», страдавшие тем же, каждая заменена на живой документ с секцией там, где документ большой; (3) [architecture.md](architecture.md) § Code layers — диаграмма слоёв (A2); (4) `TestParseDelivery` расширен экзотикой mail.log — и **вскрыл реальный баг**: шаблон брал `status=` жадно, то есть последнее вхождение в строке, а Postfix дописывает ответ удалённого сервера дословно, поэтому отказ с `status=sent` в тексте ответа попадал в журнал как доставленный (исправлено на ленивый разбор); (5) `CONTRIBUTING.md` перенесён в 2.x, бамп тега образа и git-тег оставлены в [roadmap.md](roadmap.md) § v1.x. `gofmt`/`go vet` чистые в обоих модулях, `go test ./...` — падения только известные Windows-специфичные (`internal/domain`, `TestFollowTailsAndRotates`). На стенде не проверялось (нет Docker локально).
|
||||
- **v1.x-closure Фаза 1 выполнена (2026-08-08)** (адаптивный опрос мониторинга): четыре HTMX-фрагмента (`status_body`, `mail_queue_body`, `system_log_body`, `deliveries_rows`) несут `data-poll` и `hx-trigger="load"` только для первого запроса; `panel.js` планирует следующий опрос после `htmx:afterSwap` / `htmx:responseError` — 5 s при активности оператора на странице, 30 s при видимой, но простаивающей вкладке, 0 при скрытой (`beforeRequest` + сброс таймеров на `visibilitychange`). Без `hx-trigger="every … [expr]"` (CSP / `unsafe-eval`). Docs: `architecture.md`, `roadmap.md`, CHANGELOG. На стенде не проверялось.
|
||||
- **Дальше:** v1.x-closure [v1.x-closure-plan.md](v1.x-closure-plan.md) — **Фаза 1 закрыта** (адаптивный опрос мониторинга: 5 s / 30 s / 0 при скрытой вкладке, `data-poll` в `panel.js`). Следующий шаг — **Фаза 2** (mail.log в `/data` + postqueue reconcile). Релизный гейт по коду закрыт; бамп тега образа и git tag — по явной команде (Фазы 4–5).
|
||||
- **v1.x-closure Фаза 2 выполнена (2026-08-08)** (send-log vs `mail.log`): (1) лог переехал из эфемерного `/var/log` в `/data/log/mail.log` — `maillog_file` в `postfix-config.sh` и `MAIL_LOG` в `cmd/panel/main.go` берут один и тот же дефолт, `entrypoint.sh` создаёт каталог `2750 postfix:selfpost` и нормализует файлы в `0640` на каждом старте (пишет `postlogd` от `postfix`, читает панель по общей группе; postlogd сам создал бы файл в `0600`, поэтому создаём его мы, а logrotate — `create 0640 postfix selfpost`), каталог исключён из общего `chown … panel` в начале entrypoint и из архива бэкапа (`internal/backup`, диагностика, а не состояние). (2) Строки, чьи delivery-строки потеряны безвозвратно, больше не висят `queued` вечно: `postfix.QueueIDs` разбирает `postqueue -p`, `store.ListQueuedOlderThan` отдаёт кандидатов, `internal/logtail` раз в 5 минут закрывает как `bounced` те, чьего queue-id в очереди уже нет (grace 2 мин). Три предохранителя: sweep стартует только после того, как tailer впервые дочитал лог до конца (на рестарте ответ лежит в самом логе), grace покрывает письмо «в полёте», нечитаемый `postqueue` не трогает ничего. Ложно-отрицательный `bounced` — новый принятый риск в [security.md](security.md). Тесты: парсер очереди (включая строку-причину deferred и «Mail queue is empty»), `ListQueuedOlderThan`, три сценария sweep, исключение `log/` из бэкапа. `gofmt`/`go vet` чистые в обоих модулях, `go test ./...` — падения только известные Windows-специфичные (`internal/domain`, `TestFollowTailsAndRotates`). **На стенде не проверялось (нет Docker локально): образ не собирался, контейнер не стартовал** — права на `/data/log`, чтение лога панелью, прокрутка logrotate и `postqueue -p` из-под `panel` подлежат проверке при выкате.
|
||||
- **Дальше:** v1.x-closure [v1.x-closure-plan.md](v1.x-closure-plan.md) — **Фазы 1–2 закрыты**. Следующий шаг — **Фаза 3** (docs: development.md, README, удаление планов и `docs/archive/`). Релизный гейт по коду закрыт; бамп тега образа и git tag — по явной команде (Фазы 4–5).
|
||||
- **Принятые риски** — [security.md](security.md). **Опционально v1.x / 2.x** — [roadmap.md](roadmap.md) (хвост документации, send-log gaps, Фаза O1+, роль администратора домена).
|
||||
- **Прод:** инстанс с реальным Let's Encrypt сертификатом и живым deliverability (DKIM/SPF pass). Контейнер там всё ещё на образе v1.0 — Фаза 14 в него не выкатывалась. При апгрейде: админа один раз разлогинит (сменилось имя cookie), а от reverse-proxy требуется передача исходного `Host` (Apache-фрагмент из `deploy/` это делает).
|
||||
|
||||
|
||||
+10
-7
@@ -81,13 +81,16 @@ git-тег `vX.Y.Z`; `implementation-plan.md` в `docs/archive/`, ссылок
|
||||
осознанно не делать.~~ **Закрыто (v1.x-closure Фаза 1):** адаптивный интервал
|
||||
5 с / 30 с / 0 (скрытая вкладка) в `panel.js` через `data-poll`.
|
||||
|
||||
**Send-log vs `mail.log` (частично закрыто).** Persist позиции чтения сделан
|
||||
(таблица `logtail_state`, миграция `0003`): после
|
||||
рестарта панели log-tailer дочитывает пропущенный хвост. Остаётся пересоздание
|
||||
контейнера — `mail.log` не в `/data` и теряется вместе с ним, такие строки
|
||||
навсегда останутся `queued`. Кандидаты, если станет больно: volume для лога,
|
||||
сверка зависших строк через `postqueue`. As-built и принятый риск:
|
||||
[architecture.md](architecture.md) § Log tailer, [security.md](security.md).
|
||||
**Send-log vs `mail.log`.** ~~Persist позиции чтения сделан (таблица
|
||||
`logtail_state`, миграция `0003`): после рестарта панели log-tailer дочитывает
|
||||
пропущенный хвост. Остаётся пересоздание контейнера — `mail.log` не в `/data` и
|
||||
теряется вместе с ним, такие строки навсегда останутся `queued`. Кандидаты, если
|
||||
станет больно: volume для лога, сверка зависших строк через `postqueue`.~~
|
||||
**Закрыто (v1.x-closure Фаза 2):** сделаны оба кандидата — `mail.log` переехал в
|
||||
`/data/log/`, а строки, чьи delivery-строки потеряны безвозвратно, закрываются
|
||||
сверкой с `postqueue -p` (grace 2 мин → `bounced`). As-built и оставшийся риск
|
||||
(ложный `bounced`): [architecture.md](architecture.md) § Log tailer,
|
||||
[security.md](security.md).
|
||||
|
||||
---
|
||||
|
||||
|
||||
+19
-6
@@ -118,12 +118,25 @@ Hardening сверх обязательного (security-заголовки, п
|
||||
безвозвратно нерасшифровываемый архив хуже незашифрованного: пароль SelfPost
|
||||
не хранит. Триггером сделать шифрование обязательным считать появление
|
||||
второго администратора (тогда «кто скачал» перестаёт быть одним человеком).
|
||||
- **Send-log может навсегда остаться `queued` после пересоздания контейнера.**
|
||||
Рестарт панели это больше не вызывает: log-tailer сохраняет позицию чтения
|
||||
(`logtail_state`, миграция `0003`) и после старта дочитывает хвост, написанный
|
||||
пока панель лежала. Но `mail.log` живёт не в `/data` и теряется вместе с
|
||||
контейнером — строки, чьи delivery-строки были только в нём, останутся
|
||||
`queued`. См. [architecture.md](architecture.md) § Log tailer.
|
||||
- **Строка журнала, оставшаяся без delivery-строк, закрывается как `bounced`, а
|
||||
не как есть.** Риск «вечный `queued`» снят: `mail.log` переехал в
|
||||
`/data/log/` и переживает пересоздание контейнера, а log-tailer сохраняет
|
||||
позицию чтения (`logtail_state`, миграция `0003`), так что после старта хвост
|
||||
дочитывается. Остаток — строки, delivery-строки которых потеряны
|
||||
безвозвратно (лог провернулся дальше 14 файлов, пока панель лежала, либо был
|
||||
удалён): сверка с `postqueue -p` видит, что письма в очереди нет, и через
|
||||
2 минуты grace ставит `bounced`. Если письмо на самом деле ушло, статус
|
||||
окажется ложно-отрицательным. Принято сознательно: доставка, которую панель
|
||||
не может подтвердить, не должна показываться как подтверждённая, а вечный
|
||||
`queued` не отличим от «висит прямо сейчас». Сверка не срабатывает, пока
|
||||
tailer не дочитал лог до конца, и не трогает ничего, если `postqueue` не
|
||||
читается. См. [architecture.md](architecture.md) § Log tailer.
|
||||
- **Доступ к `mail.log` из-под непривилегированной панели.** Каталог
|
||||
`/data/log` — `2750 postfix:selfpost`, файл — `0640`: пишет `postlogd`
|
||||
(пользователь `postfix`), читает панель по общей группе `selfpost`, миру файл
|
||||
недоступен. Лог содержит envelope-адреса и IP клиентов, но не тела и не
|
||||
заголовки писем; в бэкап он не попадает (`log/` исключён), чтобы выгрузка
|
||||
оставалась состоянием, а не диагностикой.
|
||||
|
||||
## ADR: CSRF через проверку Origin, без токенов
|
||||
|
||||
|
||||
+24
-15
@@ -36,7 +36,7 @@
|
||||
## Общий чек-лист прогресса
|
||||
|
||||
- [x] **Фаза 1** — адаптивный опрос мониторинга
|
||||
- [ ] **Фаза 2** — mail.log в `/data` + postqueue reconcile
|
||||
- [x] **Фаза 2** — mail.log в `/data` + postqueue reconcile
|
||||
- [ ] **Фаза 3** — docs: development.md, README, удаление планов и `docs/archive/`
|
||||
- [ ] **Фаза 4** — релизный коммит `1.0.0` (по явной команде)
|
||||
- [ ] **Фаза 5** — tag `v1.0.0` + push (по явной команде)
|
||||
@@ -76,26 +76,35 @@
|
||||
|
||||
### 2.1 `mail.log` → `/data/log/mail.log`
|
||||
|
||||
- [ ] `build/postfix-config.sh` — `maillog_file`
|
||||
- [ ] `build/logrotate-mail.conf` — путь
|
||||
- [ ] `build/entrypoint.sh` — `mkdir`, права (postfix write, panel read)
|
||||
- [ ] `cmd/panel/main.go` — default `MAIL_LOG`
|
||||
- [ ] `test/e2e/mail_helpers.go` — путь в контейнере
|
||||
- [ ] `guide.md` — `MAIL_LOG`, рост диска `/data/log`
|
||||
- [ ] `envdoc_test` — при изменении env
|
||||
- [x] `build/postfix-config.sh` — `maillog_file` из `MAIL_LOG`
|
||||
- [x] `build/logrotate-mail.conf` — путь + `create 0640 postfix selfpost`
|
||||
- [x] `build/entrypoint.sh` — `mkdir`, права (`2750 postfix:selfpost`, файл `0640`),
|
||||
исключение `/data/log` из общего `chown` на `panel`
|
||||
- [x] `cmd/panel/main.go` — default `MAIL_LOG`
|
||||
- [x] `internal/backup` — исключить `log/` из архива (+ тест)
|
||||
- [x] `test/e2e/mail_helpers.go` — путь в контейнере
|
||||
- [x] `guide.md` — `MAIL_LOG`, System log, бэкап `./data`; README — рост диска
|
||||
- [x] `envdoc_test` — `MAIL_LOG` в `buildScriptKeys`
|
||||
|
||||
### 2.2 Postqueue reconcile
|
||||
|
||||
- [ ] `internal/postfix` — парсер queue-id из `postqueue -p` + тест
|
||||
- [ ] `internal/store` — `ListQueuedOlderThan(grace)`
|
||||
- [ ] `internal/logtail` — periodic sweep, grace ~2 min → `bounced`
|
||||
- [ ] Тесты reconcile
|
||||
- [x] `internal/postfix` — `QueueIDs` / парсер queue-id из `postqueue -p` + тест
|
||||
- [x] `internal/store` — `ListQueuedOlderThan(cutoff)`
|
||||
- [x] `internal/logtail` — periodic sweep (5 min), grace 2 min → `bounced`,
|
||||
старт только после того, как tailer дочитал лог до конца
|
||||
- [x] Тесты reconcile
|
||||
|
||||
### 2.3 Документация и риски
|
||||
|
||||
- [ ] `architecture.md` — `/data/log`, reconcile; убрать gap «container recreate»
|
||||
- [ ] `security.md` — снять принятый риск «вечный queued»
|
||||
- [ ] CHANGELOG `[Unreleased]`
|
||||
- [x] `architecture.md` — `/data/log`, reconcile; gap «container recreate» убран
|
||||
- [x] `security.md` — риск «вечный queued» снят; на его месте — ложный `bounced`
|
||||
и права на лог
|
||||
- [x] CHANGELOG `[Unreleased]`
|
||||
|
||||
**Стенд (остаток, для оператора):** Docker на машине разработки недоступен —
|
||||
сборка образа и старт контейнера не проверены. Проверить при выкате: `/data/log`
|
||||
создаётся с нужными правами, панель читает `mail.log`, logrotate проворачивает
|
||||
файл, `postqueue -p` читается из-под `panel`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user