Commit Graph
10 Commits
Author SHA1 Message Date
mixandClaude Opus 5.5 f51f5db245 app: logins folded to lower case, unique without regard to case (audit H4, 0008)
Postfix's reject_sender_login_mismatch compares the SASL login with the
sender map without regard to case - seen on the e2e stand this time (a
one-off probe, not committed: map rewritten to "APP1", client authenticated
as "app1", RCPT accepted). The panel stored logins as typed under a BINARY
UNIQUE, so "Ops" and "ops" were two applications to the panel and one sender
to Postfix: a tenant could send with another tenant's envelope sender.

- app.foldLogin: Service.Create lower-cases the login (owner: fold, do not
  refuse); CreateWithSettings rolls back the stored (folded) login.
- ImportApplication keeps the login as written: folding it would rename the
  SASL account and break the imported application's working credentials;
  the index below refuses a case clash there too.
- Migration 0008 (DDL): CREATE UNIQUE INDEX idx_applications_login_nocase ON
  applications (login COLLATE NOCASE). The column's own BINARY UNIQUE stays
  (SQLite cannot change a collation without a table rebuild, which a
  migration transaction cannot do with foreign keys on). Checked on the live
  installation first: 4 logins, no case clash; "Quieter3C" keeps its case.
  0008 is not run anywhere yet; send_log.domain_id joins it later in stage 2.

Tests: "Ops" created as ops, SASL account and map binding in lower case,
OPS in another domain and an imported oPs refused; the store refuses ops
beside Ops; schema at user_version 8 with the index.

CHANGELOG [Unreleased], audit plan line ticked, roadmap 15/61.
Evidence on the test server for this tree (with the probe): go vet clean,
go test ./... 23 packages ok, make e2e exit 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:55:24 +03:00
mixandClaude Opus 5.5 b6feac2200 panel-redesign: the application form is one POST, under its domain
Stage 1, step 4 (plan § Routes).

Service (internal/app)
  - Settings{Mode, Addresses, AuthIPRestrict, AuthAllowedIPs, Limit} is what
    the form holds besides the login. CreateWithSettings creates the
    application with all of it and removes it again if a later write fails;
    SaveSettings validates the sender rule first and, if a later write
    fails, puts back what it had already changed. The form produces a whole
    application or leaves things as they were.
  - Limit.Mode "" is "use the domain limit"; auto takes its ceiling from a
    recalculation the caller supplies.

Routes and handlers
  - GET, POST /outbound/domains/{id}/applications/new
    GET, POST /outbound/domains/{id}/applications/{aid}
    POST .../{aid}/password, .../{aid}/ratelimit/recalc, .../{aid}/delete
    replace POST /domains/{id}/applications and the six /applications/{aid}/*
    routes. lookupDomainApplication answers 404 when the application is not
    in the domain named by the path - also when the caller may open that
    domain.
  - A generated password is the response to its POST only (no GET path) and
    is sent with Cache-Control: no-store.
  - The login is not renamed by the form; a changed login is a 400.

Old domain page: the three per-application forms (mode, IP restriction,
limit) are one form with one Save; "Remove limit" became the "Use the domain
limit" choice. The e2e client follows (edited, not run here).

Tests: service - create and save in every limit mode, and all-or-nothing on
a refused sender rule, a failed recalculation and a failed sender-map
rebuild; handlers - the form's parsing, one POST creating sender + IPs +
limit, four kinds of refused form creating nothing, save, the no-store
header on both password pages, and five application routes against another
tenant's application addressed through its own domain and through the
caller's.

With @routes removed by hand TestRoutesFollowNavigation now reports only
GET /server/health (step 5).

go test ./...  green except the two Windows-only failures in internal/domain
and internal/logtail.

Roadmap 14/27.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:02:26 +03:00
mixandClaude Opus 5.5 8d1af89e1a panel-redesign: the 2.0 schema from zero - one baseline, no 1.x chain
Stage 1, step 1. BREAKING: a 1.x database is not migrated.

Schema
  - internal/store/migrations/ holds one file, 0001_init.sql, equal to the
    2.0 schema; 0002..0010 are deleted (they stay in history at 1.9.5). No
    admin table created and dropped, no ALTERs.
  - users.email replaces users.dmarc_report_email: the address is the
    user's own. The default DMARC report address is a user's setting
    (dmarc_default_mode hosted/account/custom/none, dmarc_default_address);
    the instance setting that mirrored the global user's field is gone.
  - domains.dmarc_rua_user_id: a domain follows a named user's default, or
    has an address of its own in dmarc_rua ('' = none). ON DELETE SET NULL
    leaves it at "no reports", never at someone else's default.
  - user_inbound_domains, users.all_domains, users.all_inbound_domains: in
    the schema for the inbound delegation of the next step.
  - users.role is 'global' / 'domain' (was 'domain_admin');
    rate_limits.allowed_ips, unused since 1.9.0, is dropped.

Opening a database
  - The baseline sets PRAGMA application_id ("SP20"). store.Open refuses a
    file that has a schema version without it (ErrForeignSchema) and one
    newer than the build. A 1.x file at user_version 10 would otherwise read
    as already migrated. The refused file is left untouched.

Code
  - Store.DomainDMARCRua is the one place a domain's report address is
    resolved; dnscheck.ResolveDMARCRua and Store.GlobalDMARCReportEmail are
    gone. dmarc.AllowedRecipients, the domain page, the dashboard and the
    DNS re-check go through it.
  - The old templates keep working on the new model until their pages are
    restyled: the Settings form's address field is the account e-mail and
    switches the user's default between it and none; a domain's "Same as
    Settings" makes it follow the user who chose it; a new or imported
    domain follows its creator.
  - Domain export/import: a domain that follows a user exports no address.

Tests: the baseline pinned table by table (TestBaselineSchema), refusal of a
1.x and of a newer database, constraints and cascades of the new columns and
table, resolution in every default mode, the deleted-user case, and the three
bridged handlers.

docs/schema-migrations.md is rewritten for the baseline; roadmap 11/27,
schema-squash marked built.

go test ./...   (Windows)
  ok for every package except two that fail the same way before this commit:
  internal/domain  TestWriteLoadPrivateKeyRoundtrip (file mode on NTFS)
  internal/logtail TestFollowTailsAndRotates (rename of an open file)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:31:43 +03:00
mix 7b8685d038 release: 1.9.0: Per-application client IP allow-list and rate-limit overrides
Application sending controls: client IP allow-list for authorization, and level-2 rate limits that override the domain ceiling per application.
Added:
  - Per-application **client IP allow-list** on the domain page: when enabled,
  - SQLite migration `0009_application_auth_ips.sql` (moves legacy trusted IPs
Changed:
  - Application level-2 rate limit **overrides** the domain limit for that login
  - Domain export/import carries `auth_ip_restrict` / `auth_allowed_ips`; legacy
  - Panel help, Settings, and [guide.md](docs/guide.md) describe override semantics
  - [guide.md](docs/guide.md): full-backup section documents all four capture

Full detail: CHANGELOG.md (1.9.0).
2026-09-22 15:24:53 +03:00
mix 60b743e6c2 release: 1.6.0: 30-day send stats and optional auto level-2 rate limits
30-day sending statistics per domain and application, plus optional auto level-2 rate limits derived from average send rate.
Added:
  - Domain page **Sending statistics** card: total messages, peak and average
  - Level-2 rate limit **Manual** / **Auto** mode on domain and application
  - Domain export JSON now includes rate limits (`mode`, `auto_multiplier`,
Changed:
  - Auto mode uses the level-1 window (`RATE_LIMIT_WINDOW_SECONDS`); manual

Full detail: CHANGELOG.md (1.6.0).
2026-09-22 15:24:52 +03:00
mix 0730b2090e release: 1.3.0: 1.3.0-1.3.1: security pass, queue persist, full backup layout
[1.3.0]
Security and quality after 1.2.5: domain-admin send-log authorization, fail-closed sign-in and application delete, level-2 rate-limit race fix, restore Resync, expanded tests, operator docs, release CI, and OFL for IBM Plex. Upgrading from 1.2.x is a tag bump; no migration.
Added:
  - licence: the SIL Open Font License 1.1 text now travels with the IBM Plex
  - docs: agreed roadmap item **queue-retries** — show this Postfix's retry
Security:
  - The independent security review of the send-log authorization and
Fixed:
  - test (e2e): send-log status scrapers follow the badge markup in
  - security (panel): the Deliveries list is scoped to a domain administrator's
  - mail (level-2 rate limit): the ceiling is no longer overshot by messages that
  - panel (sign-in): a session that cannot be written to the database no longer
  - panel (applications): deleting an application removes its SASL credentials
  … (2 more in CHANGELOG)
Changed:
  - docs: operator and as-built docs aligned with the code after a full
  - docs: [guide.md](docs/guide.md) reorganised into **Installation**, **Instance
  - docs: the 2026-08-13 full-tree review plan is complete — every phase (P0–P7)
  - licence: [NOTICE](NOTICE) tells modifiers to update `SourceURL` in
  - ci: the release image is published only for a **published** GitHub Release
  … (9 more in CHANGELOG)
[1.3.1]
Retry policy in the panel, persistent Postfix queue, and self-contained full backups. Mail queue and delivery history show this Postfix's first retry, backoff cap, and queue lifetime. Deferred mail survives container recreate. Full backups now archive `data/`, compose, `.env`, and `certs/`; the archive layout breaks 1.3.0 flat backups (unpack those with `tar xzf backup.tar.gz -C ./data` as before). Upgrading from 1.3.0 is a tag bump; no schema migration.
Added:
  - Mail queue and a delivery's history show this Postfix's retry policy
  - docs: **Plan checklists** in [development.md](docs/development.md) — format,
  - docs: **Implementation checklists** with model routing in
  - docs: roadmap candidates **send-log-retention** (panel Settings for delivery
  - Postfix queue under `/data/postfix/queue` — deferred and active mail survive
  … (1 more in CHANGELOG)
Changed:
  - **Breaking:** full-backup archive layout — paths are prefixed with `data/`;
  - docs: operator guide, architecture, security, and backup UI updated for
  - docs: HTML mockups for a full panel UI refresh
Changed:
  - docs: panel UI mockups after review — Status restored to live readings
  - docs: panel UI design system and mockups rebuilt as separate pages
  - docs: panel UI mockups — add-domain sits in the list card (Domains and
Fixed:
  - ci (GHCR): per-arch package tags (`X.Y.Z-amd64`, `X.Y.Z-arm64`) are dropped

Squashed CHANGELOG sections: [1.3.0], [1.3.1].

Full detail: CHANGELOG.md (1.3.0, 1.3.1).
2026-09-22 15:24:52 +03:00
mix 60e08e802e 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).
2026-09-22 15:24:52 +03:00
mix 75207f0d1e release: 1.0.0: Public 1.0 image; delivery log; docs and SECURITY.md (incl. 1.0.1)
[1.0.0]
Added:
  - A **Delivery log** on each delivery's page (`/deliveries/{id}`): the
  - A **History** block on the same page: the journal's two timestamps stated as
Fixed:
  - Postfix config: copy TLS cert/key from the (often `:ro`, host-owned) mount
  - Postfix config: allow `maillog_file` under `/data` via
  - E2e: read `/data/setup-token` via `docker compose exec` (file is `0600`
  - Entrypoint: check `SELFPOST_HOSTNAME` before `/data` setup (so a bad identity
  - E2e gate: wait for host-published `/healthz` before panel setup, and stop
  … (3 more in CHANGELOG)
Changed:
  - Document what `.spbk` and `.spde` stand for (SelfPost backup / SelfPost domain
  - Docs aligned with the code: setup URL is `/setup/<token>` (README and
  - Full backups no longer carry `/data/log`. It is Postfix's raw log plus its
  - Monitoring screens (status, mail queue, system log, deliveries) use adaptive
  - Documentation package consolidated into `docs/development.md`: Documentation
  … (4 more in CHANGELOG)
[1.0.1]
A documentation and packaging release: no change to the mail path, the database, or the on-disk layout. Upgrading is a tag bump.
Added:
  - `SECURITY.md` — how to report a vulnerability privately (GitHub private
  - [docs/plans/](docs/plans/) — one document per agreed extension: the optional
Changed:
  - The panel's **Account** entry is now called **Settings** — nav link, page
  - The signed-in name in the panel's nav is now labelled `User:`, so it reads as
  - [docs/product.md](docs/product.md) reframes the future line: the inbound
  - [docs/security.md](docs/security.md) is now in English, matching the rest of
  - The README documentation table now points at `SECURITY.md` for reporting a
  … (2 more in CHANGELOG)
Fixed:
  - The panel's static assets are served with a content ETag and

Squashed CHANGELOG sections: [1.0.0], [1.0.1].

Full detail: CHANGELOG.md (1.0.0, 1.0.1).
2026-09-22 15:24:51 +03:00
mix 5e5d157496 release: 0.5.0: Pre-1.0 security review, domain-admin role, doc consolidation
A bounce could be recorded as a successful delivery. The log-tailer's
Fixed:
  - A bounce could be recorded as a successful delivery. The log-tailer's
  - Log-tailer resumes where it stopped instead of jumping to end-of-file on
  - Level-2 rate limit no longer overshoots under concurrency: messages that
Changed:
  - The project has a single public home: `github.com/mixeme/selfpost`. Codeberg
  - Code comments no longer cite the archived specification. References like
  - `docs/code-review.md` is gone. Its plan is finished — phases 0 (bar the
  - `docs/architecture.md` gained a *Code layers* section: a diagram of
  - Phase 1 doc/code hygiene (`docs/code-review.md`): removed ~30 stale
  … (1 more in CHANGELOG)
Security:
  - Pre-release security review (plan § D, model Fable, 2026-08-06): full pass
  - `saslpasswd2` argv: the application login is now passed after a `--`
Added:
  - Optional password encryption for the two secret-bearing downloads (plan
  - Domain import accepts an encrypted export: the envelope is detected by its
  - `selfpost-backup` writes encrypted archives and reads them back:
  - `TestParseDelivery` covers the exotic mail.log shapes the review asked for
  - docs: README *Encrypting a backup or export*; `docs/security.md` §
  … (14 more in CHANGELOG)
Removed:
  - docs: `security.md` — accepted risk «restore old backup revives session rows»
Changed:
  - docs: `implementation-plan.md` trimmed to the sole open v1.x gate — pre-release
  - docs: `architecture.md` — sessions (SQLite, idle renew, password change),
  - docs: `development.md` — expanded e2e stack and `release.yml` matrix workflow.
  - docs: `security.md` — accepted risk for send-log rows stuck at `queued` after
  - docs: `roadmap.md` — optional send-log / `mail.log` follow-ups under v1.x tail.
  … (10 more in CHANGELOG)

Full detail: CHANGELOG.md (0.5.0).
2026-09-22 15:24:51 +03:00
mix 8ce050ef7f release: 0.1.0: Phases 0-11: panel, Postfix/DKIM, milter, backup, Docker image
Initial feature-complete implementation of the v1.0 specification (phases 0-11 of `docs/implementation-plan.md`).
Added:
  - Panel (Go, single static binary) with SQLite persistence, one-time
  - Domain management with per-domain DKIM (RSA-2048, generated in pure Go) and
  - Application (sender identity) management: SASL credentials via `sasldb2`,
  - Full Postfix relay config generated from env at container start: SMTPS 465,
  - Journal milter (pure Go, `go-milter`) recording every send to `send_log`;
  … (6 more in CHANGELOG)

Full detail: CHANGELOG.md (0.1.0).
2026-09-22 15:24:51 +03:00