From 51f94194be0a2f7aa090f9a8ac01834d8458979d Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Sat, 3 Jan 2026 06:52:53 -0300 Subject: [PATCH] fix(nextcloud): dedupe + update mail accounts --- scripts/nextcloud-mail-sync.sh | 89 ++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 14 deletions(-) diff --git a/scripts/nextcloud-mail-sync.sh b/scripts/nextcloud-mail-sync.sh index e31da48..9865176 100755 --- a/scripts/nextcloud-mail-sync.sh +++ b/scripts/nextcloud-mail-sync.sh @@ -11,19 +11,33 @@ if ! command -v jq >/dev/null 2>&1; then apt-get update && apt-get install -y jq curl >/dev/null fi -account_exists() { +list_mail_accounts() { local user_id="${1}" - local email="${2}" + local export_out # Nextcloud Mail does not provide a list command; export is safe (does not print passwords). - local export - if ! export=$(/usr/sbin/runuser -u www-data -- php occ mail:account:export "${user_id}" 2>/dev/null); then + if ! export_out=$(/usr/sbin/runuser -u www-data -- php occ mail:account:export "${user_id}" 2>/dev/null); then echo "WARN: unable to export mail accounts for ${user_id}; skipping sync for safety" >&2 - return 0 + return 1 fi - # Output formatting varies by Nextcloud/Mail versions and locale; match by email address. - grep -Fq -- "${email}" <<<"${export}" + # The export output is human-readable and includes blocks like: + # Account 10: + # - E-Mail: user@example.com + # Extract "account-id email" pairs. + awk ' + /^Account[[:space:]]+[0-9]+:/ { + id=$2; + sub(/:$/, "", id); + next; + } + id != "" && /@/ { + if (match($0, /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/, m)) { + printf("%s\t%s\n", id, m[0]); + id=""; + } + } + ' <<<"${export_out}" | sort -u } token=$( @@ -59,13 +73,60 @@ echo "${users}" | jq -c '.[]' | while read -r user; do fi [[ -z "${mailu_email}" || -z "${app_pw}" ]] && continue - if account_exists "${username}" "${mailu_email}"; then - echo "Skipping ${mailu_email}, already exists" + + if ! accounts=$(list_mail_accounts "${username}"); then continue fi - echo "Syncing ${mailu_email}" - /usr/sbin/runuser -u www-data -- php occ mail:account:create \ - "${username}" "${username}" "${mailu_email}" \ - mail.bstein.dev 993 ssl "${mailu_email}" "${app_pw}" \ - mail.bstein.dev 587 tls "${mailu_email}" "${app_pw}" || true + + # Manage only internal Mailu-domain accounts; leave any external accounts untouched. + mailu_accounts=$(awk -v d="${MAILU_DOMAIN,,}" 'tolower($2) ~ ("@" d "$") {print}' <<<"${accounts}" || true) + + desired_email="${mailu_email}" + primary_id="" + primary_email="" + + if [[ -n "${mailu_accounts}" ]]; then + while IFS=$'\t' read -r account_id account_email; do + if [[ -z "${primary_id}" ]]; then + primary_id="${account_id}" + primary_email="${account_email}" + fi + if [[ "${account_email,,}" == "${desired_email,,}" ]]; then + primary_id="${account_id}" + primary_email="${account_email}" + break + fi + done <<<"${mailu_accounts}" + + echo "Updating ${username} mail account ${primary_id} (${primary_email})" + /usr/sbin/runuser -u www-data -- php occ mail:account:update -q "${primary_id}" \ + --name "${username}" \ + --email "${desired_email}" \ + --imap-host mail.bstein.dev \ + --imap-port 993 \ + --imap-ssl-mode ssl \ + --imap-user "${desired_email}" \ + --imap-password "${app_pw}" \ + --smtp-host mail.bstein.dev \ + --smtp-port 587 \ + --smtp-ssl-mode tls \ + --smtp-user "${desired_email}" \ + --smtp-password "${app_pw}" \ + --auth-method password >/dev/null 2>&1 || true + + # Remove any extra Mailu-domain accounts for this user to prevent duplicates. + while IFS=$'\t' read -r account_id account_email; do + if [[ "${account_id}" == "${primary_id}" ]]; then + continue + fi + echo "Deleting extra mail account ${account_id} (${account_email})" + /usr/sbin/runuser -u www-data -- php occ mail:account:delete -q "${account_id}" >/dev/null 2>&1 || true + done <<<"${mailu_accounts}" + else + echo "Creating mail account for ${username} (${desired_email})" + /usr/sbin/runuser -u www-data -- php occ mail:account:create -q \ + "${username}" "${username}" "${desired_email}" \ + mail.bstein.dev 993 ssl "${desired_email}" "${app_pw}" \ + mail.bstein.dev 587 tls "${desired_email}" "${app_pw}" password >/dev/null 2>&1 || true + fi done