release: 1.9.2: Review pass: port-25 milters, DMARC validation, rate-limit fix
Review pass over `1.4.0`…`1.9.1`: port 25 no longer shares the submission milter chain, DMARC reports are validated before they are stored, and the auto rate limit is correct for a non-default level-1 window. Schema migration `0010` (index only). Upgrading is a tag bump. Added: - SQLite migration `0010_send_log_app_login_index.sql` — - DMARC aggregate reports are accepted as **zip** as well as gzip or plain XML - e2e: `checkPort25ConfigCombinations` regenerates the Postfix configuration Changed: - README hero line and a **Positioning** block in `docs/product.md` record the Fixed: - The setup account's password is hashed at bcrypt cost `12` like every other - Login/setup rate limiter: protecting blocked buckets from eviction is now - `postqueue -p` output is bounded to 4 MiB (the timeout alone left the size of - [schema-migrations.md](docs/schema-migrations.md): the schema section was - postqueue reader is now time-bounded: `postqueue -p` runs under a 5-second … (11 more in CHANGELOG) Changed: - [roadmap.md](docs/roadmap.md) now carries everything the 2026-08-19 code - Extracted `internal/configsafe` — the empty-value and forbidden-character - Build, deploy and migration comments no longer cite the archived - Dropped the `parseDomainRateLimitForm` / `parseAppRateLimitForm` pass-through - Extracted shared helpers: `internal/atomicfile` (atomic temp-write+rename) and … (7 more in CHANGELOG) Removed: - Unused legacy asset `docs/assets/icon.png` (superseded by the four brand SVGs - Plan documents for already-shipped features: `domain-stats-auto-ratelimit`, Full detail: CHANGELOG.md (1.9.2).
This commit is contained in:
+326
-20
@@ -5,6 +5,303 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Panel redesign, account e-mail and notifications: a user's e-mail is its own
|
||||
field (2.0 baseline `users.email`, today `dmarc_report_email`) — edited under
|
||||
**Account**, seen and set by the global administrator on the user form and in
|
||||
the users list; DMARC may *use* it, as one choice beside the hosted address,
|
||||
another address or none (Settings and domain settings). **Account** gains a
|
||||
Notifications box and **Settings** a switch for the new roadmap candidate
|
||||
`panel-notifications`; `password-reset` is added as a candidate too. The
|
||||
inbound spam filter becomes visible: a *Spam filter* box on every inbound
|
||||
domain, a tag on the inbound domains list, its socket among the milter
|
||||
sockets. Preflight loses a box that only filled space — the test form now
|
||||
shares its box with its result — and the plan's layout rule and contract say
|
||||
so: what stands beside a form must be real. The role is labelled by its reach,
|
||||
`global` or `domain` — everyone in the panel is an administrator — and each
|
||||
assignment list on the user form gains **All**, a per-user flag that covers
|
||||
domains added later. The DMARC default report address is a **user's**
|
||||
setting, not the server's: it moves from Server › Settings to **Account**
|
||||
with its report-authorization record, and a domain follows the default of a
|
||||
*named* user, so two people sharing a domain never pull it two ways.
|
||||
- Panel redesign, contrast: a sweep of every text node against its real ground
|
||||
found the solid red button at 3:1 (Bulma derives a solid button's text from
|
||||
the fill's lightness; brick had been patched, red had not), the muted grey
|
||||
at 4.1:1, the amber postmark at 4.0 and Bulma's menu labels at 4.2. Solid
|
||||
buttons are white on colour, muted is `#5c6774`, the postmark uses the warn
|
||||
token; all 33 screens now measure 4.5:1 or better. The sweep is kept as
|
||||
`panel/audit.js` — text size, contrast, overflow, clipped values, missing
|
||||
icons — and its clean output joins the evidence a restyle step must show.
|
||||
- Panel redesign, header (owner's choice: variant A, with the perforation on a
|
||||
light edge as on the mark): the navbar carries the **wordmark** instead of
|
||||
the stamp — `docs/assets/selfpost-wordmark.svg` and `-ink`, the stamp's own
|
||||
outlines, recorded in `docs/product.md` — and under the brick bar runs the
|
||||
stamp's paper margin with the perforation along its outer edge: round holes,
|
||||
flat teeth, the ink hairline (`panel/perf-edge.svg`). All 33 screens rebuilt;
|
||||
the components page documents the shell; the comparison sheet is removed.
|
||||
The six health cards now split into six, three or two columns, so one card
|
||||
is never left alone on a row. Three corrections after the owner's review:
|
||||
the paper edge is 8px under the 60px bar — it had been scaled from the hole
|
||||
size to 16px, where the mark's margin is a tenth of the brick field; the
|
||||
health row is compact again (71px: smaller icons, each line a block with its
|
||||
own line height, a verdict never wraps and stretches the row); and a
|
||||
read-only value is as tall as the Copy button joined to it. `check.py` no
|
||||
longer counts `min-width: 0` as setting a width. Text sizes: the filter
|
||||
controls in a box head and the navbar's dropdown entries are 15px like the
|
||||
bar itself (they had Bulma's `is-small` and its .875rem dropdown); `check.py`
|
||||
now rejects `is-small` on anything but a progress bar.
|
||||
- Panel redesign, type scale: about 40 % of the panel's text measured under
|
||||
13px (labels 11.7, table heads 10.5, fact labels 10.2, tags and help 11.3).
|
||||
Body is now 16px with two tokens under it — `--sp-fs-s` 14px for supporting
|
||||
text and `--sp-fs-xs` 13px for upper-case mono labels and tags; Bulma's
|
||||
small sizes are overridden or dropped (`sp-small` replaces `is-size-7`), and
|
||||
`check.py` fails any font size under the floor. Every table now sits in a
|
||||
`table-container` (enforced), addresses in tables break after the `@`, the
|
||||
inbound log folds the client IP under the sender; all screens fit without
|
||||
scrolling at 1280px and up. Documented on the components page and in the
|
||||
plan (§ Type and tables).
|
||||
- Panel redesign, header comparison sheet
|
||||
([docs/assets/panel-redesign/header/](docs/assets/panel-redesign/header/index.html)):
|
||||
at nav size the stamp dissolves into the brick bar and the perforated edge
|
||||
was drawn inverted (round bumps instead of round holes). Three replacements
|
||||
beside the current header — **A** wordmark with the stamp's real hole-to-tooth
|
||||
perforation bitten into the bar, **B** wordmark on a plain bar, **C** a
|
||||
stamp-paper header with an ink hairline and brick only behind the name. The
|
||||
wordmark is cut from the stamp's own outlines. Pending the owner's choice;
|
||||
the mockups are unchanged. `.claude/launch.json` serves the mockups locally
|
||||
(`python -m http.server 8765` from the repository root).
|
||||
- Panel redesign, the roadmap's screens: **Inbound › Filter lists** and the
|
||||
adjusted **Inbound › Log** (client, decision, engine and reason; *Allow /
|
||||
Deny sender*) for inbound-antispam-panel, **Inbound › Quarantine** (list and
|
||||
held message with Release / Discard) for inbound-quarantine, and **Server ›
|
||||
Preflight** for preflight — all from existing components; 33 screens. Their
|
||||
paths join § Routes; the quarantine and preflight plans link their mockups.
|
||||
A mockup is the target look, not a decision to build: each item keeps its
|
||||
status, and quarantine's open questions stay open.
|
||||
- Panel redesign, two owner decisions recorded in
|
||||
`docs/plans/panel-redesign.md`: **no compatibility** — the redesign is
|
||||
`2.0.0`, old paths are removed without redirects, the SQLite migration chain
|
||||
is replaced by one baseline and 2.0 starts from an empty data directory
|
||||
(schema-squash ships as a step of this plan, without its upgrade gate; the
|
||||
only live installation is the owner's); and
|
||||
**inbound delegation** — a domain administrator manages assigned inbound
|
||||
domains, granted separately from outbound (`user_inbound_domains`, a second
|
||||
list on the user form, per-domain authorization under `/inbound/`). Mockups `users`, `user`,
|
||||
`user-delete` redrawn for the two lists. Checklist `3/21`.
|
||||
- Panel redesign, routes: the owner decided that paths follow the new names
|
||||
and structure. `docs/plans/panel-redesign.md` § Routes maps every screen to
|
||||
its new path (`/overview`, `/outbound/…`, `/inbound/…`, `/server/…`,
|
||||
`/account`) with the rules that come along — one `<page>/fragment` name, the
|
||||
application form as a single POST, 301s from old GET paths for one MINOR,
|
||||
subtree `requireGlobal()` under security review, and
|
||||
`TestRoutesFollowNavigation`. Mockups gained what the first pass missed: the
|
||||
*Reload configuration* box (now on Server › Health), the `flash` component
|
||||
and the `sp-help` link that replaces the help drawer. Checklist `1/17`.
|
||||
- Panel redesign, design contract:
|
||||
[docs/plans/panel-redesign.md](docs/plans/panel-redesign.md) rewritten for
|
||||
the accepted direction — three layers (Bulma as shipped, adapters, own `sp-`
|
||||
components), three page layouts, what an implementer must not do, the guard
|
||||
tests that land before any template, a `design-first` CI step and
|
||||
evidence-based acceptance. A
|
||||
[components page](docs/assets/panel-redesign/panel/components.html) shows
|
||||
every building block in every state; `panel/check.py` lints the mockups
|
||||
against the contract and writes `panel/outlines.json`, the per-screen
|
||||
component skeleton an implementation is compared with. Own modifier classes
|
||||
renamed to carry the `sp-` prefix so the vocabulary is machine-checkable.
|
||||
Agent rule `.cursor/rules/panel-design.mdc`; roadmap row updated (`0/15`).
|
||||
- Panel redesign, stage 2: the accepted direction drawn on every screen —
|
||||
29 pages under [docs/assets/panel-redesign/panel/](docs/assets/panel-redesign/index.html).
|
||||
Direction C (Bulma) is the base, with A's postal signature (postmark verdict,
|
||||
numbered mono section heads, the stamp's perforated edge) and B's icons.
|
||||
Overview keeps verdicts only and the tables move to a new **Server › Health**
|
||||
page; the own password becomes **Account**; queues are drawn as tables
|
||||
(needs `postqueue -j`) and **Inbound** gets its own Log and Queue. Pages are
|
||||
built from fragments by `panel/build.py`; the built HTML is committed.
|
||||
Mockups only; nothing in `internal/web` changes.
|
||||
- Panel redesign, stage 1: three directions drawn on the three key screens
|
||||
(Overview, an outbound domain, sign-in) under
|
||||
[docs/assets/panel-redesign/](docs/assets/panel-redesign/index.html) —
|
||||
**A** Pico CSS ("Blank"), **B** Tabler ("Console"), **C** Bulma ("Counter"),
|
||||
with a cover sheet comparing size, JavaScript, CSP fit and own CSS. All three
|
||||
share a navigation grouped by mail direction (Overview · Outbound · Inbound ·
|
||||
Server, with a symmetric Log and Queue on both sides), one block per DNS
|
||||
record instead of separate "status" and "records to publish" cards, and
|
||||
applications as a table. Mockups only; nothing in `internal/web` changes.
|
||||
The brief behind them supersedes the "no component library" decision in
|
||||
`docs/plans/panel-redesign.md`; that file is rewritten once a direction is
|
||||
accepted.
|
||||
- Roadmap item and givens for a panel visual redesign
|
||||
([docs/plans/panel-redesign.md](docs/plans/panel-redesign.md)) — layout
|
||||
grammar (`measure` / `fill` plus Web Awesome layout CSS), no JS component
|
||||
library, new mockups rather than `docs/assets/panel-ui/`. Status: candidate.
|
||||
- Roadmap item and plan for `/preflight` installation check page
|
||||
([docs/plans/preflight.md](docs/plans/preflight.md)) — instance-level
|
||||
infrastructure verification (rDNS, TLS, ports, HELO banner, proxy headers,
|
||||
DKIM socket, test email). Status: candidate.
|
||||
|
||||
## [1.9.2] - 2026-09-22
|
||||
|
||||
Review pass over `1.4.0`…`1.9.1`: port 25 no longer shares the submission
|
||||
milter chain, DMARC reports are validated before they are stored, and the auto
|
||||
rate limit is correct for a non-default level-1 window. Schema migration
|
||||
`0010` (index only). Upgrading is a tag bump.
|
||||
|
||||
A full-tree code review on 2026-08-19 (architecture, code quality, security,
|
||||
GUI, tests, documentation, AGPL compliance; 38 findings) produced most of what
|
||||
follows. Its four open items live in [roadmap.md](docs/roadmap.md); its working
|
||||
document is not part of the release.
|
||||
|
||||
### Added
|
||||
|
||||
- SQLite migration `0010_send_log_app_login_index.sql` —
|
||||
`idx_send_log_app_login_created_at`. The journal-milter counts an
|
||||
application's messages on every `MAIL FROM`, and the domain page computes
|
||||
per-application statistics on every render; both were scanning the
|
||||
`created_at` range because `app_login` was unindexed.
|
||||
- DMARC aggregate reports are accepted as **zip** as well as gzip or plain XML
|
||||
(several reporters ship a single-entry zip; those reports previously counted
|
||||
as parse failures).
|
||||
- e2e: `checkPort25ConfigCombinations` regenerates the Postfix configuration
|
||||
inside the container for all four combinations of `INBOUND_RELAY_ENABLE` /
|
||||
`DMARC_REPORTS_ENABLE` / `INBOUND_ANTISPAM_MILTER` and requires
|
||||
`postfix check` to pass. The `1.9.1` crash-loop shipped in `1.4.0` and
|
||||
survived three releases because the suite only ever exercised the flags-off
|
||||
path.
|
||||
|
||||
### Changed
|
||||
|
||||
- README hero line and a **Positioning** block in `docs/product.md` record the
|
||||
tagline (`YOUR SMTP. YOUR RULES.`) and hero copy; the stamp subtitle stays
|
||||
`SELF-HOSTED SMTP RELAY`. `docs/assets/selfpost-proof.html` updated to match.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The setup account's password is hashed at bcrypt cost `12` like every other
|
||||
panel password; it was still being written at `bcrypt.DefaultCost` (10). The
|
||||
work factor now has one definition, `auth.BcryptCost`. bcrypt records the
|
||||
cost in the hash, so existing passwords keep verifying unchanged.
|
||||
- Login/setup rate limiter: protecting blocked buckets from eviction is now
|
||||
bounded. Blocked buckets are still never evicted to make room, but past
|
||||
`maxBuckets × 4` they are evicted too (nearest expiry first) — otherwise an
|
||||
attacker with many source addresses could grow the bucket map without limit
|
||||
by making each address fail its way to blocked. The overflow is logged at
|
||||
most once per window.
|
||||
- `postqueue -p` output is bounded to 4 MiB (the timeout alone left the size of
|
||||
the listing, and therefore the panel's allocation, up to the queue).
|
||||
- [schema-migrations.md](docs/schema-migrations.md): the schema section was
|
||||
still headed `v9` while the chain head is `v10`, and three links pointed at
|
||||
plan files deleted when those features shipped.
|
||||
- postqueue reader is now time-bounded: `postqueue -p` runs under a 5-second
|
||||
context timeout so a stuck queue command cannot hang the queue panel path
|
||||
indefinitely.
|
||||
- DMARC panel layout classes now exist in
|
||||
`internal/web/view/static/panel.css` (`.pair`, `.attn`, `.desk-only`,
|
||||
`.phone-only`, `.phone-list`, `.check-row`), so desktop/mobile DMARC views and
|
||||
attention highlighting render as intended.
|
||||
- `dmarc-ingest` now bounds stdin with `io.LimitReader` (10 MiB) before parsing.
|
||||
- User-role update no longer contains a duplicate "last global admin" demotion
|
||||
guard; one check now returns a context-specific message for self-demotion.
|
||||
- Settings flash text now reflects what actually changed and only mentions
|
||||
session sign-out when the password changed.
|
||||
- Help page/drawer links now use `SourceURL` (`{{.SourceURL}}/blob/main/docs/...`)
|
||||
instead of a hardcoded repository URL.
|
||||
- Help drawer close controls include accessibility attributes (`role="button"`,
|
||||
`aria-label`) for the scrim and close action.
|
||||
- Test coverage expanded for DMARC map token injection rejection and email
|
||||
validation edge cases (ASCII/local-part/domain/freemail cases).
|
||||
- **Port 25 no longer runs the submission milter chain.** `smtpd_milters` is
|
||||
now always overridden on `smtp/inet` — to the optional antispam milter, or
|
||||
to nothing. Previously, with `INBOUND_ANTISPAM_MILTER` unset, inbound mail
|
||||
inherited main.cf's chain: OpenDKIM (strict, `tempfail`) could defer relayed
|
||||
mail during a signing outage, and the journal-milter filed inbound mail into
|
||||
the outbound send log under the **sender's** domain — so Deliveries showed
|
||||
other people's mail, and a forged `From:` on an inbound message consumed that
|
||||
sending domain's level-2 rate-limit budget. [architecture.md](docs/architecture.md)
|
||||
already described the intended behaviour; the code now matches it.
|
||||
- **DMARC reports are validated before they are stored.** A report is accepted
|
||||
only when the domain in `policy_published` is a sending domain configured
|
||||
here and, for a per-domain hosted address, matches that address's `+tag`.
|
||||
The rua address form is predictable, so anyone able to reach it could
|
||||
previously file reports under any domain, skew the panel's alignment figures
|
||||
and the tighten-`p=` hint, and evict genuine reports through the 500-report
|
||||
retention cap.
|
||||
- Auto level-2 rate limit: the ceiling is now scaled to the level-1 window
|
||||
before the multiplier is applied. `ceil(avg msg/h × multiplier)` was stored
|
||||
as the ceiling for a window of `RATE_LIMIT_WINDOW_SECONDS`, so any window
|
||||
other than the default `3600` shifted the effective allowance by
|
||||
`window/3600` (a 1800s window allowed twice the intended rate). A domain
|
||||
that has sent anything now also gets a floor of 1.
|
||||
- Single-domain export no longer drops rate limits silently when the store
|
||||
errors — the error is returned instead of being swallowed by `err == nil && ok`.
|
||||
|
||||
### Changed
|
||||
|
||||
- [roadmap.md](docs/roadmap.md) now carries everything the 2026-08-19 code
|
||||
review left unimplemented, so no finding lives only in a plan file:
|
||||
`csrf-tokens` (the recorded §4.4 decision that never became a task),
|
||||
`template-data-typing`, `structured-logging`, and
|
||||
`review-2026-08-followups` for the small remainder (backup re-auth, the
|
||||
missing service/handler/error-page tests, and nine minor findings that were
|
||||
never carried into the review's own phase tables). Each roadmap row stands on
|
||||
its own — file, symptom, and what "done" means — and the declined finding
|
||||
(`//go:build linux` for the `/proc` reader) is recorded as declined so it is
|
||||
not raised again. The review's working document is gone from `docs/plans/`
|
||||
now that nothing depends on it; git history keeps it.
|
||||
- Extracted `internal/configsafe` — the empty-value and forbidden-character
|
||||
check every generated config file shares. Each writer keeps its own forbidden
|
||||
set next to the file it protects (an OpenDKIM table and a Postfix map break on
|
||||
different characters); only the check itself is shared.
|
||||
- Build, deploy and migration comments no longer cite the archived
|
||||
specification. The `spec N` references CHANGELOG `[0.5.0]` said were gone
|
||||
survived in `build/`, `deploy/`, the first two migrations and the release
|
||||
workflow; they are removed and the affected comment paragraphs re-wrapped.
|
||||
Comments only — no build or configuration behaviour changes.
|
||||
- Dropped the `parseDomainRateLimitForm` / `parseAppRateLimitForm` pass-through
|
||||
wrappers left behind when the two parsers were merged: nothing but their own
|
||||
tests still called them.
|
||||
- Extracted shared helpers: `internal/atomicfile` (atomic temp-write+rename) and
|
||||
`internal/supervisor` (supervisorctl start/signal wrappers), and reused them in
|
||||
domain/postfix code paths.
|
||||
- `docs/roadmap.md`: shipped `panel-docs` now references CHANGELOG history
|
||||
instead of a removed plan file.
|
||||
- [schema-migrations.md](docs/schema-migrations.md) notes that migration `0009`
|
||||
changed what the IP list *means* — permissive (those IPs got the application
|
||||
ceiling) before 1.9.0, restrictive (every other IP refused) after. Its
|
||||
conversion branch only fires for a database that carried application-scope
|
||||
`allowed_ips` from before 1.9.0.
|
||||
- Panel help, [guide.md](docs/guide.md) and [architecture.md](docs/architecture.md):
|
||||
the client IP allow-list is a **sending** control, not an authentication
|
||||
boundary — the SASL login still succeeds, the check runs in the
|
||||
journal-milter at `MAIL FROM`, and it is fail-open like the rate limits.
|
||||
architecture.md's rate-limit description still documented the pre-1.9.0
|
||||
trusted-IP semantics.
|
||||
- Removed unused duplicates of the inbound validators (`Host`, `Port`,
|
||||
`TLSMode`, `RecipientMode`, `MailboxInDomain`, `NormalizeHost`) from
|
||||
`internal/web/validate` — the live path uses `internal/inbound/validate.go`;
|
||||
the copies were reachable only from their own tests.
|
||||
- `gofmt` across `cmd/` and `internal/` (10 files had drifted); dead branch
|
||||
removed from `postfix.inboundNexthop`.
|
||||
- The planned inbound-antispam migration is renumbered `0011_inbound_spam_log.sql`.
|
||||
- Operator and as-built docs aligned with the code after a full pass —
|
||||
[architecture.md](docs/architecture.md) (port 25 opens for inbound relay
|
||||
and/or DMARC ingest; antispam requires inbound relay; DMARC pipe transport
|
||||
and map paths; `/help` and `/dmarc*` routes; four panel roles including
|
||||
rate-limit recalc; restore Resync vs manual reload); [guide.md](docs/guide.md)
|
||||
and [README.md](README.md) (image pin `1.9.1`, port 25 conditions, combined
|
||||
listener limits, reload vs Resync); [roadmap.md](docs/roadmap.md) (`panel-docs`
|
||||
closed at `[1.8.0]`, next item inbound-antispam-panel); [development.md](docs/development.md)
|
||||
version-cut table through `1.9.1`; [schema-migrations.md](docs/schema-migrations.md)
|
||||
last-updated note. No behaviour change.
|
||||
|
||||
### Removed
|
||||
|
||||
- Unused legacy asset `docs/assets/icon.png` (superseded by the four brand SVGs
|
||||
and `internal/web/static/favicon.png`; nothing referenced it).
|
||||
- Plan documents for already-shipped features: `domain-stats-auto-ratelimit`,
|
||||
`dmarc-reports`, `inbound-relay`, `panel-docs`, `queue-retries`,
|
||||
`send-log-retention`. Only unimplemented plans remain in `docs/plans/`.
|
||||
|
||||
## [1.9.1] - 2026-08-19
|
||||
|
||||
Port-25 Postfix startup when inbound relay, DMARC ingest, or the inbound antispam
|
||||
@@ -239,6 +536,14 @@ tag bump; no schema migration.
|
||||
parsed aggregate report (aligned vs third-party fail), not a hub-only
|
||||
summary.
|
||||
|
||||
### Fixed
|
||||
|
||||
- ci (GHCR): per-arch package tags (`X.Y.Z-amd64`, `X.Y.Z-arm64`) are dropped
|
||||
after the manifest merge via the GitHub Packages API. The `[1.3.0]` merge job
|
||||
called `docker buildx imagetools rm`, which is not a valid subcommand — cleanup
|
||||
failed with a warning and the side-effect tags stayed in the registry until
|
||||
this fix.
|
||||
|
||||
## [1.3.0] - 2026-08-14
|
||||
|
||||
Security and quality after 1.2.5: domain-admin send-log authorization,
|
||||
@@ -256,7 +561,7 @@ Plex. Upgrading from 1.2.x is a tag bump; no migration.
|
||||
- docs: agreed roadmap item **queue-retries** — show this Postfix's retry
|
||||
policy (first delay, backoff cap, queue lifetime) on Mail queue and on a
|
||||
delivery's history, reading `postconf -h` once at panel start so a manual
|
||||
override is visible. Plan: [docs/plans/queue-retries.md](docs/plans/queue-retries.md).
|
||||
override is visible. Plan: `docs/plans/queue-retries.md` (removed once shipped).
|
||||
Explanation only; no attempt counter and no panel knobs. Not yet
|
||||
implemented.
|
||||
|
||||
@@ -323,11 +628,6 @@ Plex. Upgrading from 1.2.x is a tag bump; no migration.
|
||||
the same Resync on demand. The `internal/backup` package comment now matches
|
||||
this behaviour.
|
||||
|
||||
- ci (GHCR): per-arch package tags (`X.Y.Z-amd64`, `X.Y.Z-arm64`) are dropped
|
||||
after the manifest merge via the GitHub Packages API. The merge job had called
|
||||
`docker buildx imagetools rm`, which is not a valid subcommand — cleanup failed
|
||||
with a warning and the side-effect tags stayed in the registry.
|
||||
|
||||
### Changed
|
||||
|
||||
- docs: operator and as-built docs aligned with the code after a full
|
||||
@@ -377,15 +677,13 @@ Plex. Upgrading from 1.2.x is a tag bump; no migration.
|
||||
(`vX.Y.Z`) or a manual `workflow_dispatch` with an explicit SemVer version — a
|
||||
bare git tag push no longer starts the build. `release.yml` listens for
|
||||
`release: published`, checks out that tag (not `main` HEAD), e2e-gates each
|
||||
native arch build, merges `X.Y.Z-amd64` and `X.Y.Z-arm64` into one manifest,
|
||||
then removes the per-arch tags from GHCR via the GitHub Packages API so
|
||||
operators see only `ghcr.io/mixeme/selfpost:X.Y.Z` (what
|
||||
`deploy/docker-compose.yml` pins). A dispatch whose version input is missing
|
||||
or not `X.Y.Z` fails in `prepare`. [development.md](docs/development.md)
|
||||
documents draft vs published releases, why deleting a release tag converts
|
||||
it back to draft, and Gitea → GitHub tag-mirror pitfalls (do not prune release
|
||||
tags on GitHub; a mirrored `v1.0.0` still runs that tag's `on: push: tags`
|
||||
workflow).
|
||||
native arch build, and merges `X.Y.Z-amd64` and `X.Y.Z-arm64` into one
|
||||
manifest. Reliable removal of the per-arch side-effect tags from GHCR landed
|
||||
in `[1.3.1]`. A dispatch whose version input is missing or not `X.Y.Z` fails
|
||||
in `prepare`. [development.md](docs/development.md) documents draft vs
|
||||
published releases, why deleting a release tag converts it back to draft, and
|
||||
Gitea → GitHub tag-mirror pitfalls (do not prune release tags on GitHub; a
|
||||
mirrored `v1.0.0` still runs that tag's `on: push: tags` workflow).
|
||||
|
||||
- test: the authorization and sign-in surfaces that had no tests now have them.
|
||||
The login limiter is covered for its ceiling, its per-address scope, the reset
|
||||
@@ -455,7 +753,7 @@ Plex. Upgrading from 1.2.x is a tag bump; no migration.
|
||||
RBAC in the doc-alignment pass above). Corrected stale
|
||||
`admin.dmarc_report_email` references in
|
||||
[roadmap.md](docs/roadmap.md) and
|
||||
[docs/plans/dmarc-reports.md](docs/plans/dmarc-reports.md) to the setting's
|
||||
`docs/plans/dmarc-reports.md` (removed once shipped) to the setting's
|
||||
actual home after migration `0005`. No behaviour change.
|
||||
|
||||
- panel: code-review P6 cleanup — the unused `auth.RequireGlobal` middleware is
|
||||
@@ -482,7 +780,9 @@ Rate-limit form polish after 1.2.4. Upgrading is a tag bump; no migration.
|
||||
carry muted leads and matched control height; trusted-IP help sits under the
|
||||
IP field. Domain settings pairs DMARC reports with the level-2 rate limit
|
||||
using CSS subgrid so titles, fields, and Save / Remove buttons line up across
|
||||
columns.
|
||||
columns. Domain page Export and Danger cards stack naturally again — a
|
||||
shared-baseline flex pin on `.split` cards made mismatched body lengths look
|
||||
worse than a plain stack.
|
||||
|
||||
## [1.2.4] - 2026-08-12
|
||||
|
||||
@@ -1289,9 +1589,10 @@ database, or the on-disk layout. Upgrading is a tag bump.
|
||||
Bookmarks to the old paths stop working.
|
||||
|
||||
- panel: each entry in the navigation bar now carries an icon beside its label,
|
||||
so the bar is scannable at a glance instead of a row of similar-length words.
|
||||
The icons are inline SVG drawn in the entry's own colour — no extra request,
|
||||
no exemption from the panel's Content-Security-Policy — and are hidden from
|
||||
so the bar is scannable at a glance instead of a row of similar-length words —
|
||||
Backup uses a filing-cabinet mark; Sign out has its own icon too. The icons
|
||||
are inline SVG drawn in the entry's own colour — no extra request, no
|
||||
exemption from the panel's Content-Security-Policy — and are hidden from
|
||||
screen readers, which still announce the label alone.
|
||||
|
||||
- panel: the navigation bar is laid out as two rows on purpose — the signed-in
|
||||
@@ -1431,6 +1732,11 @@ database, or the on-disk layout. Upgrading is a tag bump.
|
||||
login/password and on the sending server name.
|
||||
- panel: the *Addresses* field is hidden while an application's address mode is
|
||||
*Any address of the domain*, where the server ignores it.
|
||||
- deploy: `cap_add` gains `CAP_FOWNER` and `CAP_FSETID` so the entrypoint can
|
||||
chmod and set the setgid bit on `/data` directories it has just chowned under
|
||||
`cap_drop: ALL`, and `CAP_KILL` so supervisord can signal OpenDKIM across uids
|
||||
when domains are added or removed. Production boot under the hardened compose
|
||||
file exposed both gaps after Phase 10.
|
||||
- ci: disable provenance attestation on release image push, so the ghcr.io
|
||||
manifest list shows only `linux/amd64`/`linux/arm64` (no `unknown/unknown`).
|
||||
- ci: run `go vet` and `go test ./...` on every push to `main` and every pull
|
||||
|
||||
Reference in New Issue
Block a user