Commit Graph

218 Commits

Author SHA1 Message Date
mix 403c76d17d docs(guide): restructure into Installation / Instance admin / Domain admin
test / test (push) Has been cancelled
Splits mixed DNS-setup, Operations, Rate-limiting, and Backup sections
along the instance/domain boundary and fixes README anchors that
pointed at the old #dns-setup heading.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 15:45:38 +03:00
mix d9ab7bfa45 docs: retire the completed full-tree review plan
test / test (push) Has been cancelled
Every phase (P0-P7) is closed, so the plan file goes per its own exit criteria; history lives in git and the CHANGELOG. The roadmap's recommended order returns to queue-retries and then inbound-relay, and the order notes in both plans stop pointing at the deleted file, as does the 1.2.5 changelog entry (same treatment as logrotate-mode.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 15:23:52 +03:00
mix 18bfd148ec docs: close P7 — security review of the P0–P1 diff, no findings
The Fable review of the send-log authorization, tryAdmit, session create and app-delete ordering against security.md found no further issues: every send-log query path states its scope and an empty scope matches nothing, rejected rows cannot consume rate-limit budget, the milter's nil-registry fail-open is test-only, and each failure residue of the reordered delete fails safe. Outcome recorded in security.md's header and the plan; nothing added to the accepted risks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 15:02:07 +03:00
mix e70ba9046e fix(panel): resync mail-path maps once after restore
When CheckRestore accepts a backup manifest, the panel re-derives OpenDKIM tables and the Postfix sender map from SQLite on that first boot and reloads both daemons, so archive/database drift is healed before mail flows.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-14 14:58:08 +03:00
mix 02afa0fa80 fix(panel): code-review P6 optional cleanup
test / test (push) Has been cancelled
Close the remaining low-risk items from the full-tree review: rename the settings handler, query assigned domains in SQL, bound the login limiter map, collapse panel.js show/hide helpers, and soften DMARC copy that promised a future in-panel receiver.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-14 14:48:43 +03:00
mix 97a0c7e508 licence,ci: ship OFL with Plex fonts and guard release dispatch (P5)
test / test (push) Has been cancelled
IBM Plex WOFF2 files were shipped without the SIL OFL 1.1 text, NOTICE
told modifiers to edit layout.html for a Source URL that lives in
legal.go, and workflow_dispatch took GITHUB_REF_NAME as the version so a
run from main would publish ghcr.io/...:main.

Closes P5 of docs/plans/code-review.md. CHANGELOG updated.

Co-Authored-By: Cursor Grok 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-14 09:47:33 +03:00
mix efdc2d63fb docs: agree queue-retries (Postfix retry policy in the panel)
test / test (push) Has been cancelled
Roadmap item and plan: show first retry delay, backoff cap and queue
lifetime on Mail queue and the delivery page, reading postconf -h once
at panel start so a manual override is visible. Explanation only; not
implemented yet. CHANGELOG updated.

Co-Authored-By: Cursor Grok 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 23:51:13 +03:00
mix 8dfb483244 test: boot a panel on a data directory restored from its own backup (P4)
test / test (push) Has been cancelled
Restore is not a code path in the panel — the operator extracts the archive
onto /data and starts the image — so it had no test. cmd/panel/restore_test.go
now performs that path in process: download a backup from a running panel
through POST /backup (plain and encrypted), unpack it the way tar -xzf does,
and boot a second panel on the result through run()'s own startup order
(CheckRestore, store.Open, newPanel, Start).

Covered: the restored panel shows the domain and journal the archive carried
and finds the DKIM key, sasldb2 and Postfix sender map where its configuration
says they are; the setup link is not reopened by a restore; a session that
predates the backup still works, as the guide documents; an encrypted download
restores identically; a data directory from another version is refused with
both versions named and the manifest kept for the retry.

serveHTTP is split so the composition it performs (newPanel) can be started
without binding a port. No behaviour change.

Closes the optional P4 item in docs/plans/code-review.md, and with it the
"HandleBackup POST untested" gap from the review's test section. CHANGELOG
updated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 23:46:01 +03:00
mix e6af1a7866 test: cover the auth, sign-in and RBAC surfaces P0 shipped through (P4)
test / test (push) Has been cancelled
- internal/web/auth/ratelimit_test.go: the login/setup limiter's ceiling,
  per-address scope, window reset and the sweep that keeps finished
  buckets out of memory.
- internal/web/auth/handlers_test.go: sign-in (session issued, refusals
  that do not reveal which usernames exist, a lockout a correct password
  cannot bypass, the setup hint before the first administrator) and the
  one-time setup link (creates the first global administrator and then
  closes, rejects a wrong or expired token, refuses credentials the panel
  would not accept later, rate-limited).
- internal/web/handlers/authz_test.go: every global-only route answers a
  domain administrator — and a request with no principal — with 404, with
  a positive control so the table cannot pass on a handler that always
  404s. This is the check that would have caught the send-log leak.
- test/e2e: CoreDNS pinned to 1.14.6 instead of latest; the level-1
  failure message quoted RATE_LIMIT_MESSAGES_PER_IP=5 while the stand
  sets 50.

docs/plans/code-review.md P4 checked off except the optional backup-boot
test; CHANGELOG updated. No production code changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 23:35:46 +03:00
mix aab35f0679 fix(panel): P3 optional items — urlquery, aria-live, confirm-without-JS note
- Deliveries fragment/pagination links urlquery-encode the domain/app
  filters instead of splicing them into the query string raw.
- The four polled regions (deliveries rows, status, mail queue, system
  log) carry aria-live="polite" so a screen reader announces refreshes.
- security.md accepted risks note that data-confirm prompts are
  JavaScript-only and why that is acceptable.

docs/plans/code-review.md P3 fully checked off; CHANGELOG updated.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 22:54:31 +03:00
mix 0570608738 fix(panel): GUI defects from the 1.2.x layout pass (P3)
- .flash.error now renders on the danger surface instead of the success
  one; RateLimitErr previously showed as green with red text.
- User delete goes through a confirmation page (GET/POST
  /users/{uid}/delete), matching the domain-delete pattern, instead of a
  plain submit button next to Save with no confirmation.
- Extracted the repeated DNS Host/Type/Value markup on a domain's page and
  the duplicated Settings credentials form into shared partials. No
  behaviour change.

docs/plans/code-review.md P3 checked off; CHANGELOG updated.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 21:50:42 +03:00
mix 1cf8bfcbe2 docs: rewrite security/operator docs for the multi-user panel (P2)
test / test (push) Has been cancelled
The CSRF ADR, guide.md, architecture.md route table, the internal/backup
package comment, roadmap/dmarc-reports admin.dmarc_report_email references,
the e2e coverage summary, and the setupManager/store package comments still
described the pre-1.2.0 single-administrator panel or stale implementation
details. Closes P2 of docs/plans/code-review.md; no behaviour change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 16:15:14 +03:00
mix 4761991dd5 panel,mail: fail closed on the rate-limit race, session create and app delete
test / test (push) Has been cancelled
The level-2 limiter counted stored plus in-flight messages and reserved its own slot in two critical sections, so SMTP sessions that overlapped could each take the last free slot; tryAdmit now does both under one lock. A session that cannot be written no longer yields a cookie the browser would carry while every request bounced to /login. Deleting an application clears its SASL account before its registry row, matching domain delete, so a saslpasswd2 failure leaves a retryable application rather than an account that still authenticates.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 14:50:04 +03:00
mix 8355479e03 docs: note e2e send-log badge scraper fix in Unreleased
test / test (push) Has been cancelled
The release gate timed out waiting for status=sent because its HTML scrapers had drifted from the panel's badge markup; record the fix and the handler regression test.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 14:36:11 +03:00
mix 6216e430e9 test(e2e): follow send-log status badge markup in scrapers
test / test (push) Has been cancelled
The deliveries_rows template wraps status in badge spans, but the e2e gate still looked for bare td cells, so send_verify_dkim_and_status timed out even when mail was delivered and logged. Add a handler regression test so go test ./... catches this drift without Docker.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 14:19:31 +03:00
mix a1e7e05d26 ci: gofmt files that failed the formatting workflow check
test / test (push) Has been cancelled
Whitespace-only alignment from gofmt; removes a stray BOM in web.go.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 12:24:34 +03:00
mix fae0068c4a panel: scope the send log to a domain admin's own domains
test / test (push) Has been cancelled
The Deliveries list narrowed the journal only when exactly one domain was assigned, so an administrator with none or with two or more read every domain's rows. The domain scope is now an IN constraint the store query carries, a filter that states no scope matches nothing, and the domain/app query parameters are validated against the principal before the query runs.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 12:20:51 +03:00
mix b829625066 docs: record full-tree review and 2.x schema squash
test / test (push) Has been cancelled
Capture the 2026-08-13 review as the active plan, queue it ahead of inbound-relay, and defer SQLite migration squash to 2.x with a user_version gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 01:36:08 +03:00
mix 71048922b9 release: 1.2.5
test / test (push) Has been cancelled
Close Unreleased for the rate-limit form polish; pin compose and docs to 1.2.5.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 01:04:19 +03:00
mix 5caca024c3 panel: restore natural Export and Danger card layout
test / test (push) Has been cancelled
Drop the shared-baseline flex pin on .split cards; mismatched body
lengths made Export / Danger look worse than the plain stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 01:02:15 +03:00
mix 8289ed3916 panel: align paired domain settings and export actions
test / test (push) Has been cancelled
Subgrid Domain settings rows, match Address mode and trusted-IP controls,
and pin Export/Danger buttons to a shared baseline in split cards.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 00:39:55 +03:00
mix 958d07fcdd panel: polish rate-limit form copy and field alignment
test / test (push) Has been cancelled
Drop the app L1 backstop line, move trusted-IP help under the IP field,
shorten the domain rate-limit title, and align field-pair labels with DMARC.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 00:26:17 +03:00
mix f0cd804cb3 panel: tighten rate-limit and address-mode copy
Restore muted L1 backstop line, move domain limit help above Save, and drop
the duplicate Address mode field label.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 00:13:34 +03:00
mix 3950bd333b docs: put rate-limit UI polish back under Unreleased
test / test (push) Has been cancelled
1.2.4 stays as cut at the release commit; the L1 display and badge work
belongs in Unreleased until the next version cut (development.md).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 23:40:33 +03:00
mix 584f8cbc1c panel: clearer L1 rate-limit display and status badges
test / test (push) Has been cancelled
release / prepare (push) Has been cancelled
release / build (amd64, ubuntu-latest) (push) Has been cancelled
release / build (arm64, ubuntu-24.04-arm) (push) Has been cancelled
release / merge (push) Has been cancelled
Show the level-1 backstop as its own line and code-row, use st badges for
active/inactive domain and app limits, and fold the polish into 1.2.4.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 23:37:12 +03:00
mix 6671bd3393 release: 1.2.4
test / test (push) Has been cancelled
release / prepare (push) Has been cancelled
release / build (amd64, ubuntu-latest) (push) Has been cancelled
release / build (arm64, ubuntu-24.04-arm) (push) Has been cancelled
release / merge (push) Has been cancelled
Close Unreleased for the level-2 rate-limit semantics change and DNS Type
field height fix; pin compose and docs to 1.2.4.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 23:21:28 +03:00
mix 00e36df553 rate limit: domain ceiling for all IPs, trusted app override
Invert level-2 semantics so domain limits apply to every client IP and
application limits with trusted IPs raise the ceiling above the domain
(still capped by level 1). Panel shows L1, validates maxima, and documents
the model on Settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 23:19:51 +03:00
mix b0ebe061b5 panel: match DNS Type TXT height to Host fields
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 23:06:09 +03:00
mix 601e183e0c release: 1.2.3
Close CHANGELOG [Unreleased] as 1.2.3; bump the compose pin and image references. Includes domain detail layout polish and Domains add-row.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 22:52:19 +03:00
mix f9e259a66d panel: refine domain detail layout and drop section index
test / test (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 22:16:03 +03:00
mix 44e79c4120 panel: pair domain detail cards like Status (SPF | DMARC)
test / test (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 15:33:41 +03:00
mix 1d30605533 panel: align page URLs, titles, and headings
test / test (push) Has been cancelled
Settings moves to /settings with a 308 redirect from /account; domains, Status, Users, and user forms get matching browser titles and h1 text; backup page title reflects domain import.

Co-Authored-By: Composer <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 15:24:14 +03:00
mix b87baa6dd6 fix: pin logrotate config mode in image and fail loud on bad permissions
test / test (push) Has been cancelled
COPY --chmod makes /etc/logrotate.d/mail 0644 regardless of build context
file modes (Windows tar sync). logrotate-loop preflight exits non-zero when
logrotate would ignore the config. E2e covers mode, forced rotation, and a
group-writable context build.

Co-Authored-By: Composer 2.5 <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 00:51:37 +03:00
mix 9420e30d6f release: 1.2.2
test / test (push) Has been cancelled
Close CHANGELOG [Unreleased] as 1.2.2; bump the compose pin and image references.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 00:34:09 +03:00
mix 93cf1de3b7 panel: denser Status layout with true two-column cards
test / test (push) Has been cancelled
Pair Machine|Processes, queue|cert, and sockets|hostname; fix .split
auto-margins so cards fill half the row; trim Status prose and machine
details; drop the page section index; note panel-docs on the roadmap.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 00:29:34 +03:00
mix 6b8658602d panel: place mail queue and TLS cert side by side on Status
test / test (push) Has been cancelled
Widen the status page and wrap the short queue/certificate cards in
.split so the stack is shorter; other checks stay full width.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 23:24:24 +03:00
mix 3a520d9ec7 panel: fix Assigned domains checkbox layout on user form
test / test (push) Has been cancelled
Form-wide block labels and full-width inputs stacked the box above the
domain name; checkbox rows now share label.check and fieldset spacing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 23:15:37 +03:00
mix 4015ecd70d release: 1.2.1
test / test (push) Has been cancelled
Close CHANGELOG [Unreleased] as 1.2.1; bump the compose pin and image references.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 23:03:54 +03:00
mix ad014ecdc1 panel: centre status badge text optically
test / test (push) Has been cancelled
IBM Plex Mono sits low in its em square; top-heavy badge padding left
ok/warn sunk below the adjacent heading. Bottom padding is now heavier.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 22:58:45 +03:00
mix de6f7a88da panel: disable demote and delete for the only global admin
test / test (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 22:42:01 +03:00
mix 5985977f21 panel: hide domain pick for global administrators on user form
test / test (push) Has been cancelled
Global administrators manage every domain; the assignment checkboxes are
now hidden when that role is selected.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 22:37:04 +03:00
mix 9ff7514621 panel: tidy the Users nav icon
test / test (push) Has been cancelled
Two full silhouettes with staggered baselines read lopsided at 16 px; the
rear person is now a right-side crescent behind an aligned front silhouette.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 22:31:07 +03:00
mix ae7f5258a6 panel: use a gear for the Settings nav icon
The icon was a sun-with-rays (circle plus spokes), not a cog, despite the
1.2.0 note that Settings used a gear.

Co-Authored-By: Cursor <cursoragent@cursor.com>
2026-08-11 22:13:29 +03:00
mix 5bd632c8e0 panel: place drill-down back links under the heading
test / test (push) Has been cancelled
The user form had Back to users at the bottom of the card; all drill-down
pages now use a shared back_link template with a structural test.

Co-Authored-By: Claude <Composer 2.5> <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 21:58:08 +03:00
mix f1074eb526 panel: show Settings credentials and DMARC side by side
test / test (push) Has been cancelled
Global administrators get two cards in the .split layout; domain-scoped
users keep the single narrow form.

Co-Authored-By: Claude <Composer 2.5> <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 18:40:59 +03:00
mix bc005dea85 release: 1.2.0
test / test (push) Has been cancelled
Close CHANGELOG [Unreleased] as 1.2.0; correct missing entries; bump the
compose pin and image references.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 17:39:11 +03:00
mix 4b2f3880ef panel: align heading with narrow cards on signed-in pages
Settings and the user form centred the title on the 48rem measure while
.card.narrow centred on 24rem, so their left edges drifted by 12rem. Cap every
direct child of main when a narrow card is present, without shrinking the
column, so navigation stays put and login/setup stay unchanged.

Co-Authored-By: Cursor <cursoragent@cursor.com>
2026-08-11 17:34:04 +03:00
mix fae930a37a panel: give Settings, Users, and the session user distinct nav icons
test / test (push) Has been cancelled
Settings had been sharing the account silhouette with Users and the signed-in user label, so three different concepts read as the same icon. Settings now uses a gear, the user line the single-user icon, and Users a two-person group mark.

Co-Authored-By: Cursor <cursoragent@cursor.com>
2026-08-11 16:06:38 +03:00
mix c443149623 docs: plan the narrow pages' heading alignment
test / test (push) Has been cancelled
Settings and the user form leave their heading at the left of the reading
measure while the card floats 12rem to the right of it. main > * caps every
child at 48rem and centres it; .card.narrow overrides the max-width down to
24rem but not the auto margins it never mentions, so the two centre on
different measures and half the difference is the offset.

Records what the question was actually about: not what is wrong with Settings
but what it shares with exactly one other page. Four templates use the narrow
card and eleven use a full-width one, which sorts every page into three groups
— full-width card, narrow card without navigation, narrow card with it — and
only the last has the problem. Settings and the user form are the only pages
that are both signed-in and built from a single narrow card; every other page
has one of those properties, never both.

Also records why the obvious fix is unavailable, so it is not tried a fourth
time: the shell centres the navigation and the page as a pair, so anything that
changes the column width is visible from the navigation (296px, measured). The
fix has to work inside the column.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 10:19:11 +03:00
mix 5eaf665c01 docs: remove completed plans and trim the open-work tracker
test / test (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 01:34:04 +03:00