#!/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' 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/" 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'