Files
mail-server/config/user-patches.sh
T
eSlider e1208be24e chore(mail): pinned-version update check off, Gmail postscreen whitelist, docs
- compose: ENABLE_UPDATE_CHECK=0 (image intentionally pinned to DMS v15.1.0).

- postscreen: permit Google SMTP outbound ranges (Gmail retries from rotating IPs,

  '450 PASS NEW' never completes) — keeps LinkedIn/Gmail mail out of the tarpit.

- README: DNS/ops notes; user-patches: whitelist copy path.
2026-09-16 14:59:45 +01:00

84 lines
4.5 KiB
Bash
Executable File

#!/bin/bash
# Dovecot shared mailboxes. info@produktor.io gets access to the mailboxes of
# two owner classes (issue #79, issue #251 / epic #250):
# - production owners (ano@, andriy.oblivantsev@, postmaster@): read-only
# (`lookup read`) — one login in Roundcube covers the whole account list;
# - incubator owners: read + delete (`lookup read delete expunge
# write-deleted`) — the mailbox owner never logs in, mail is imported via
# doveadm; deleting a message in Roundcube = filter/exclusion from the
# corpus (autosync, epic B).
# Incubator model (corrected 2026-09-02, issue #252): the incubator mailbox IS
# the owner's HISTORICAL ADDRESS per period, not an abstract "source" mailbox.
# The wheregroup period = andriy.oblivantsev@wheregroup.com; later periods get
# their own account (eslider@gmail.com, ...@viscreation.de, ...). The A1 pilot
# box wheregroup@produktor.io (abstract "source" model) was deleted after its
# 1000 messages were migrated to the historical account — grant_share skips
# owners that are not (yet) in postfix-accounts.cf, so a not-yet-created
# account is a silent no-op.
# DMS runs this only on the FIRST start of each container instance (plain
# `docker compose restart` skips the setup step by design — /CONTAINER_START
# marker), so it must stay idempotent. ACLs, the shared dict and subscriptions
# persist in mail-state / maildirs across restarts.
set -euo pipefail
# 1. acl_shared_dict directory: must exist and be writable by the mail user.
SHARED_DB_DIR=/var/lib/dovecot/db
mkdir -p "${SHARED_DB_DIR}"
chown docker:docker "${SHARED_DB_DIR}"
chmod 0770 "${SHARED_DB_DIR}"
# 2. Grant info@ rights on every current mailbox of the shared owners.
# doveadm acl set is the only way Dovecot records the share in acl_shared_dict
# (manual dovecot-acl files do NOT populate the dictionary — Dovecot docs).
# NOTE: this Dovecot build accepts full right NAMES ("lookup read"), single
# letters ("lr") are rejected with "Invalid right". The incubator set below
# was verified on live (issue #251): `write-deleted` is enough for the
# \Deleted flag that Roundcube sets on Delete — the extra `write` right is
# NOT required; `expunge` is also what Dovecot MOVE needs on the source side
# when Roundcube moves a deleted message to Trash.
READER='info@produktor.io'
PRODUCTION_OWNERS='ano@produktor.io andriy.oblivantsev@produktor.io postmaster@produktor.io'
INCUBATOR_OWNERS='andriy.oblivantsev@wheregroup.com eslider@gmail.com viscreation@gmail.com viscreation@gmx.de andriy.oblivantsev@gridfactor.de ao@rpf.de andriy.oblivantsev@gmail.com viscreation@viscreation.de'
grant_share() { # $1=owner, remaining=right names
local owner=$1
shift
# Skip owners not (yet) in postfix-accounts.cf — e.g. right after a fresh
# clone, before `setup email add` was run for the incubator source.
if ! doveadm mailbox list -u "${owner}" >/dev/null 2>&1; then
echo "user-patches: skip ${owner}: account does not exist yet (run 'setup email add ${owner}')"
return 0
fi
# The shared mailbox "shared/<owner>" maps to the owner's INBOX (Dovecot
# shared-storage semantics) — subscribe it explicitly so Roundcube's
# subscribed folder list shows it.
doveadm mailbox subscribe -u "${READER}" "shared/${owner}"
# A brand-new mailbox owner has no INBOX yet and `doveadm mailbox list`
# above would be empty, so no share would be recorded. Ensure INBOX exists
# first (issue #251); "Mailbox already exists" is fine.
doveadm mailbox create -u "${owner}" INBOX >/dev/null 2>&1 || true
for mb in $(doveadm mailbox list -u "${owner}"); do
doveadm acl set -u "${owner}" "${mb}" "user=${READER}" "$@"
if [ "${mb}" != "INBOX" ]; then
doveadm mailbox subscribe -u "${READER}" "shared/${owner}/${mb}"
fi
done
}
# Production owners stay read-only for info@ (regression guard for #79).
for owner in ${PRODUCTION_OWNERS}; do
grant_share "${owner}" lookup read
done
# Incubator owners: info@ can read AND delete (filter semantics, epic #250).
for owner in ${INCUBATOR_OWNERS}; do
grant_share "${owner}" lookup read delete expunge write-deleted
done
# LinkedIn sender whitelist for postscreen (2026-09-04): LinkedIn mail to
# produktor.io was rejected with 450 by postscreen (new sender IPs). Keep the
# cidr file in sync with config/linkedin_whitelist.cidr.
cp /tmp/docker-mailserver/linkedin_whitelist.cidr /etc/postfix/linkedin_whitelist.cidr 2>/dev/null
chown postfix:postfix /etc/postfix/linkedin_whitelist.cidr 2>/dev/null
postconf -e 'postscreen_access_list = permit_mynetworks, cidr:/etc/postfix/linkedin_whitelist.cidr'