release: 1.2.0: Panel 1.2.0-1.2.5: layout, RBAC, tests, GUI and CI fixes

[1.2.0]
The second MINOR after 1.0.0: domain administrators with per-domain scope, a panel visual refresh on the SelfPost palette, and refinements to navigation and the send log. Upgrading runs one SQLite migration (`0005_panel_users`); the single administrator becomes a global user.
Added:
  - panel: **domain-admin role** — global administrators manage panel users and
Fixed:
  - Docker build: `LICENSE` is no longer excluded by `.dockerignore`, so the
  - panel: on signed-in pages with a narrow card (**Settings**, the user form)
Changed:
  - panel: navigation session icons are distinct — **Settings** uses a gear,
  - panel: `/` redirects domain administrators to `/domains`; global users still
  - panel: **Sign out** in the navigation column uses the same type size and
  - panel: **visual style** brought in line with the SelfPost mark — brick accent
  - panel: the send log's **status is a badge**, in the same ok/warn/error/unknown
  … (1 more in CHANGELOG)
[1.2.1]
Panel refinements after 1.2.0: navigation icons, status-badge centreing, drill-down back-link placement, and safeguards for the sole global administrator. Upgrading is a tag bump; no migration.
Fixed:
  - panel: status badge text sat low in the box (and below the heading or label
  - panel: the **Users** navigation icon was two full silhouettes with staggered
  - panel: the **Settings** navigation icon was a sun-with-rays (circle plus
  - panel: the user create/edit form placed «Back to users» at the bottom of the
Changed:
  - panel: the user edit form disables role change and delete for the only global
  - panel: the user create/edit form hides **Assigned domains** when the role is
  - panel: **Settings** shows panel credentials and DMARC aggregate reports side
[1.2.2]
Status page layout after 1.2.1: paired cards in a wide column, denser machine and check copy, and a user-form checkbox fix. Upgrading is a tag bump; no migration.
Changed:
  - panel: **Status** is wide again so paired cards fill the column. Layout:
Fixed:
  - panel: cards inside `.split` used `margin: 0 auto`, which in a CSS grid
  - panel: on the user create/edit form, **Assigned domains** checkboxes stacked
[1.2.3]
Domain detail layout and panel polish after 1.2.2. Upgrading is a tag bump; no migration.
Changed:
  - panel: the domain detail page is wide with paired cards (DKIM+SPF ‖ DMARC;
  - panel: Domains list — **Add domain** sits beside the domain field; the
  - panel: page URLs, browser titles, and headings are aligned — **Settings** is
Fixed:
  - image: `mail.log` rotation no longer silently stops when the build context
[1.2.4]
Level-2 rate-limit semantics inverted after 1.2.3, plus a small DNS field height fix. Upgrading is a tag bump; no migration.
Changed:
  - rate limiting (level 2): domain ceilings apply to every client IP (no IP
Fixed:
  - panel: on the domain DNS status grid, the Type (TXT) field height matches
[1.2.5]
Rate-limit form polish after 1.2.4. Upgrading is a tag bump; no migration.
Changed:
  - panel: rate-limit forms refined — level-1 backstop as muted copy on the

Squashed CHANGELOG sections: [1.2.0], [1.2.1], [1.2.2], [1.2.3], [1.2.4], [1.2.5].

Full detail: CHANGELOG.md (1.2.0, 1.2.1, 1.2.2, 1.2.3, 1.2.4, 1.2.5).
This commit is contained in:
mix
2026-09-22 15:24:52 +03:00
parent f62fe23826
commit 60e08e802e
73 changed files with 3614 additions and 1517 deletions
-72
View File
@@ -1,72 +0,0 @@
# Plan: domain-admin (domain administrator role)
**Status:** agreed
**Version:** target bump **1.x** MINOR, given a compatible migration of the
current administrator into a global one.
**Order:** recommended after [web-split](web-split.md), before
[inbound-relay](inbound-relay.md).
---
## What this is
Today the panel has exactly one subject: `requireAuth` is a boolean gate, not a
role ([web.go](../../internal/web/web.go) — the
`mux.Handle("/", s.requireAuth(authed))` wrapper), and the session carries
nothing beyond the fact of being signed in.
The role grants access to **explicitly assigned domains** (one or several); the
list of domains is set by the **global administrator**. For each domain on that
list:
- that domain's applications (creation, sender mode, password regeneration,
deletion, its own L2 limit);
- the domain's DKIM/DNS status;
- the send log filtered to the domain — the filter already exists in the log
([sendLogData](../../internal/web/handlers/handlers_monitor.go)).
What stays outside the role is what is global by nature:
- adding and removing domains;
- creating domain-admin users and assigning domains to them;
- `/reload`;
- the full backup (that is all of `/data` including `sasldb2`, i.e. every
domain at once);
- the queue and the `mail.log` tail — those are server-wide and not tied to a
domain.
## Why this extends v1.0
[product.md](../product.md) puts "multiple panel users, roles" out of scope
(one administrator). A second subject is a deliberate widening of the project's
boundary, as inbound-relay is.
The cost is phase-sized, not patch-sized:
- a users table and their binding to domains;
- the role in the session;
- authorisation in every handler (not only on the route — today `{id}`/`{aid}`
are checked for nothing beyond existence);
- reworking first-run setup and password change for several users;
- accounting for the new subject in backup and domain export.
*(The earlier wording of this item — "2FA and multiple administrators" — has
been replaced: 2FA is off the table, and "multiple administrators" is narrowed
to one specific role, because what is needed is not a second all-powerful admin
but limited access for the owner of one or several domains, with the list set
by the global administrator.)*
## Done when
- A global administrator and a domain-admin with different rights both work
through the panel; the domain-admin cannot reach past the **assigned**
domains;
- the current single admin migrates into a global one without losing access;
- backup/restore accounts for users and their bindings;
- `build`/`vet`/`test`/image green.
## Risks
- An incomplete `{id}`/`{aid}` check in a handler — access leaking to someone
else's domain;
- breaking setup or backup — that would be a semver major, not 1.x.
+3 -2
View File
@@ -3,8 +3,9 @@
**Status:** agreed
**Version:** target bump **1.x** MINOR; **`2.x` possible** — to be settled once
the implementation lands (do not fix a major in advance).
**Order:** recommended after [web-split](web-split.md) and
[domain-admin](domain-admin.md).
**Order:** recommended next among agreed 1.x+ items (after `internal/web` split
and the domain-admin role, both shipped — see [CHANGELOG](../CHANGELOG.md)
`[1.2.0]`).
---
+28
View File
@@ -0,0 +1,28 @@
# Plan: logrotate-mode (mail.log stops rotating in some images)
**Status:** done
**Version:** patch; no schema, no configuration surface.
---
## Summary
`mail.log` stopped rotating when `/etc/logrotate.d/mail` landed in the image
with group/other write permission (e.g. build context from a Windows tar sync).
logrotate ignores such configs but exits 0, so the loop looked healthy while
the log grew without bound.
## What shipped
- [`build/Dockerfile`](../../build/Dockerfile): `COPY --chmod` pins config
(`0644`) and script (`0755`) modes so the image no longer depends on checkout
file modes.
- [`build/logrotate-loop.sh`](../../build/logrotate-loop.sh): preflight and
per-iteration checks refuse group/other-writable configs; logrotate stderr
mentioning `Ignoring` is fatal.
- [`test/e2e/logrotate_check.go`](../../test/e2e/logrotate_check.go): asserts
mode `644`, forced `logrotate -f`, and that a group-writable context file
still produces `644` in the image.
`create 0640 postfix selfpost` in [`build/logrotate-mail.conf`](../../build/logrotate-mail.conf)
is unchanged — required for panel readability after rotation.
-70
View File
@@ -1,70 +0,0 @@
# Plan: web-split (splitting `internal/web`)
**Status:** done (see [CHANGELOG](../CHANGELOG.md) `[1.1.0]`)
**Version:** `1.x`; an internal refactor, it does not force a break on its own.
---
## What this is
`internal/web` is the project's largest package: ~50 files (templates and
static assets included), ~25 `.go` files and ~4000 lines of Go, with the
handlers for every panel section, sessions, security headers, origin checking,
form validation and template rendering all sitting in one flat namespace.
The candidates to split out are `web/handlers` and `web/auth`, or a cut along
the panel's own domains.
## Why now
At its current size the flat package reads fine: the file names
(`handlers_domains.go`, `handlers_apps.go`, `handlers_monitor.go`) do the work
directories would, and splitting would force exporting what is package-private
today — widening the internal API for cosmetics.
It starts to pay off once the package grows: **domain-admin** and
**inbound-relay** both add code to it — the role brings authorisation into
every handler, the inbound relay brings its own pages and handlers for inbound
domains. The refactor is cheaper before that growth than after it.
## Recommended order
**web-split → domain-admin → inbound-relay** (see the
[roadmap](../roadmap.md)).
1. **web-split** — lay down the package structure (including a place for
`web/auth`) while there are no cross-cutting edits from the role and no new
inbound handlers.
2. **domain-admin** — authorisation in every handler builds on a package layout
already chosen.
3. **inbound-relay** — a new vertical slice; easier to add to an already split
package than to refactor alongside the two features before it.
The order is a recommendation, not a blocker.
## Chosen scheme
**Horizontal split into four packages** (decided at implementation):
```
internal/web/ # Config, Server, New, Handler — composition root; security.go
internal/web/view/ # embed templates/static, render/renderFragment, staticHandler
internal/web/auth/ # session, login/logout/setup, requireAuth, currentUser
internal/web/validate/ # shared form validation (avoids auth ↔ handlers import cycle)
internal/web/handlers/ # all authenticated page handlers (handlers_*.go)
```
`cmd/panel` keeps importing only `internal/web`. Subpackages are not exported
beyond what the composition root needs.
## Done when
The package is split along the scheme above. After the split: `build`/`vet`/`test`
green, the panel's behaviour unchanged.
## Risks
- Splitting too early — a superfluous internal API and churn with nothing to
show for it;
- leaving it until after the growth — a harder refactor, tangled up with the
features.