Skip to content

Add outbound relay (smarthost) admin UI#4

Open
cyberb wants to merge 5 commits into
masterfrom
relay-outbound-admin
Open

Add outbound relay (smarthost) admin UI#4
cyberb wants to merge 5 commits into
masterfrom
relay-outbound-admin

Conversation

@cyberb

@cyberb cyberb commented Jul 15, 2026

Copy link
Copy Markdown
Member

Why

Self-hosted mail sent directly from home/residential IPs is widely rejected by Gmail/Outlook — IP reputation, Spamhaus PBL, and (Nov 2025) hard 5.7.26 rejections — and many ISPs block port 25 outright (platform issue #567). SPF/DKIM/DMARC/PTR are necessary but not sufficient; a clean/new/residential IP still gets throttled or blocked.

The industry-standard fix — shipped by mailcow, mailu, docker-mailserver — is to route outbound mail through an authenticated relay (smarthost). This productizes the manual relayhost + sasl_passwd workaround from platform issue #553 and makes it survive upgrades.

What

1. Outbound relay (smarthost) + admin UI

  • Persistence — relay settings (enabled/host/port/user/password) in a [relay] section of user_mail.cfg.
  • Postfixmain.cf renders a relayhost + SASL block when enabled; RegenerateConfigs writes and postmaps sasl_passwd, re-applied on every install/refresh.
  • Backendwebui daemon (cmd/webui, rest.Server) serving GET/POST /api/relay over a unix socket, reusing the installer and restarting Postfix via the platform. Runs as mail; blank password = keep current.
  • FrontendVue 3 + Element Plus admin page at /admin/; nginx proxies /api/ to the webui socket, alongside Roundcube. Mirrors the mailcow/mailu split of webmail vs server admin (Roundcube is a webmail client and has no concept of MTA relay).
  • Tests — Playwright spec drives the relay form (data-testid).
  • Roundcube bumped 1.6.1 → 1.6.15 (LTS security; stays on PHP 8.0).

2. Migrate app state $SNAP_COMMON$SNAP_DATA

The app kept all mutable state (Postgres DB, DKIM keys, maildirs, spool, relay creds, generated configs) in $SNAP_COMMON, which is shared across revisions and never rolls back — so a rolled-back binary could hit newer-format data. Moved it all to $SNAP_DATA (snapshotted + transactional rollback), keeping only web.socket in $SNAP_COMMON (platform contract).

  • Data root common → current; new AppCommonDir var so nginx still binds web.socket in $SNAP_COMMON.
  • MigrateCommonToData() renames every $SNAP_COMMON entry (except sockets) into $SNAP_DATA once, guarded by a marker; runs at the top of InitConfig, so existing devices migrate on post-refresh (while services are stopped, os.Rename on the same filesystem).
  • Service scripts switched to $SNAP_DATA (except the web.socket line); php-fpm.conf pid/listen paths templated.

⚠️ One-way migration. After upgrading, state lives in $SNAP_DATA. Reverting to a pre-migration revision would leave the old binary looking in $SNAP_COMMON (now empty of app data) — a snap revert across this boundary is lossy, as expected when relocating data.

Verification

  • go build ./... && go vet ./... && go test ./... clean; gofmt clean.
  • Vite build OK (base /admin/); .drone.jsonnet regenerates; adds a www build step (node 20) → build/snap/www.
  • No hardcoded common paths remain except the web.socket line.

Not yet exercised on a real device — CI test/e2e/test-upgrade will validate the full snap + migration path.

Closes syncloud/platform#553 · relates to syncloud/platform#567

cyberb added 5 commits July 15, 2026 15:31
Self-hosted mail sent directly from home/residential IPs is widely
rejected by Gmail/Outlook (IP reputation, Spamhaus PBL) and many ISPs
block port 25, so direct delivery fails regardless of SPF/DKIM/DMARC.
The industry-standard fix, as shipped by mailcow/mailu/docker-mailserver,
is to route outbound mail through an authenticated relay (smarthost).

Productizes the manual workaround from platform issue #553 so it also
survives upgrades:

- Persist relay settings (enabled/host/port/user/password) in the
  [relay] section of user_mail.cfg under $SNAP_COMMON.
- Postfix main.cf template renders a relayhost + SASL block when relay
  is enabled; RegenerateConfigs writes and postmaps sasl_passwd, so the
  relay is reapplied on every install/refresh.
- New webui daemon (cmd/webui, rest.Server) serves GET/POST /api/relay
  over a unix socket, reusing the installer to apply changes and restart
  postfix via the platform.
- nginx serves a Vue 3 + Element Plus admin page at /admin/ and proxies
  /api/ to the webui socket, alongside Roundcube webmail (mailcow-style
  split of webmail vs server admin).
- Playwright spec covers the relay form end to end.

Also bump Roundcube 1.6.1 -> 1.6.15 (LTS security releases).
$SNAP_DATA is snapshotted and rolled back transactionally on a failed
refresh; $SNAP_COMMON is shared across revisions and never rolls back.
The app kept all mutable state (Postgres DB, DKIM keys, maildirs, spool,
relay credentials, generated configs) in $SNAP_COMMON, so a rolled-back
binary could face newer-format data. Move it all to $SNAP_DATA, keeping
only web.socket in $SNAP_COMMON (the Syncloud platform routes app traffic
to that hardcoded path).

- installer data root common -> current; add AppCommonDir so nginx still
  binds web.socket in $SNAP_COMMON.
- MigrateCommonToData() renames every $SNAP_COMMON entry (except sockets)
  into $SNAP_DATA once, guarded by a marker; runs at the top of InitConfig
  so both install and post-refresh migrate existing devices.
- Service scripts use $SNAP_DATA (except the web.socket line); php-fpm.conf
  pid/listen paths templated via AppDataDir.
After moving app state to $SNAP_DATA, InitConfig only chowned the data
dir, leaving $SNAP_COMMON root-owned. The nginx service runs as mail and
must create web.socket under $SNAP_COMMON (platform contract), so bind
failed with EACCES and nginx crash-looped (platform served 503). Ensure
$SNAP_COMMON exists and is owned by mail, as it was before the migration.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[mail] implement ISP relay (indirect) delivery feature

1 participant