Wrapper script around `occ mail:account:create` to bulk-link IMAP/SMTP accounts for multiple Nextcloud users from a CSV file. Includes README with usage, limitations (no connection test, no initial sync), and .gitignore that blocks real credential CSVs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
122 lines
3.7 KiB
Bash
Executable File
122 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Bulk-link IMAP/SMTP accounts in Nextcloud Mail via `occ mail:account:create`.
|
|
#
|
|
# Usage:
|
|
# OCC_CMD="docker exec -u www-data nc-dev php occ" ./mail-bulk-link.sh accounts.csv
|
|
# OCC_CMD="sudo -u www-data php /var/www/nextcloud/occ" ./mail-bulk-link.sh accounts.csv
|
|
#
|
|
# CSV format (header required, exact column names):
|
|
# userId,name,email,imapHost,imapPort,imapSsl,imapUser,imapPass,smtpHost,smtpPort,smtpSsl,smtpUser,smtpPass[,authMethod]
|
|
#
|
|
# - imapSsl/smtpSsl: ssl | tls | none
|
|
# - authMethod: optional, defaults to "password" (use "xoauth2" for OAuth)
|
|
# - Lines starting with # and empty lines are skipped.
|
|
# - Quote fields containing commas with double quotes per RFC 4180.
|
|
# - Passwords with shell-special chars are safe — they're passed as argv, not eval'd.
|
|
|
|
set -euo pipefail
|
|
|
|
OCC_CMD="${OCC_CMD:-}"
|
|
if [[ -z "$OCC_CMD" ]]; then
|
|
echo "ERROR: Set OCC_CMD env var, e.g.:" >&2
|
|
echo " OCC_CMD=\"docker exec -u www-data nc-dev php occ\" $0 accounts.csv" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ $# -ne 1 ]]; then
|
|
echo "Usage: OCC_CMD=\"...\" $0 <accounts.csv>" >&2
|
|
exit 2
|
|
fi
|
|
|
|
CSV="$1"
|
|
if [[ ! -r "$CSV" ]]; then
|
|
echo "ERROR: cannot read $CSV" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# Read header and build column index map
|
|
IFS=, read -r -a HEADER < "$CSV"
|
|
declare -A COL
|
|
for i in "${!HEADER[@]}"; do
|
|
name="${HEADER[$i]//$'\r'/}" # strip CR if file is CRLF
|
|
name="${name// /}"
|
|
COL["$name"]=$i
|
|
done
|
|
|
|
required=(userId name email imapHost imapPort imapSsl imapUser imapPass smtpHost smtpPort smtpSsl smtpUser smtpPass)
|
|
for col in "${required[@]}"; do
|
|
if [[ -z "${COL[$col]:-}" && "${COL[$col]:-x}" != "0" ]]; then
|
|
echo "ERROR: missing required CSV column '$col'" >&2
|
|
exit 2
|
|
fi
|
|
done
|
|
|
|
# Minimal RFC-4180 parser: handles quoted fields and embedded commas.
|
|
# Outputs one field per line on FD 3; caller reads with mapfile.
|
|
parse_csv_line() {
|
|
local line="$1"
|
|
python3 -c '
|
|
import csv, sys
|
|
for row in csv.reader([sys.argv[1]]):
|
|
for f in row:
|
|
print(f)
|
|
' "$line"
|
|
}
|
|
|
|
ok=0
|
|
fail=0
|
|
skipped=0
|
|
lineno=1 # header already consumed
|
|
|
|
# Read remaining lines
|
|
tail -n +2 "$CSV" | while IFS= read -r line || [[ -n "$line" ]]; do
|
|
lineno=$((lineno + 1))
|
|
# Skip empty / comment lines
|
|
[[ -z "${line//[[:space:]]/}" ]] && continue
|
|
[[ "${line:0:1}" == "#" ]] && continue
|
|
|
|
mapfile -t fields < <(parse_csv_line "$line")
|
|
|
|
get() { echo "${fields[${COL[$1]}]:-}"; }
|
|
|
|
userId=$(get userId)
|
|
name=$(get name)
|
|
email=$(get email)
|
|
imapHost=$(get imapHost); imapPort=$(get imapPort); imapSsl=$(get imapSsl)
|
|
imapUser=$(get imapUser); imapPass=$(get imapPass)
|
|
smtpHost=$(get smtpHost); smtpPort=$(get smtpPort); smtpSsl=$(get smtpSsl)
|
|
smtpUser=$(get smtpUser); smtpPass=$(get smtpPass)
|
|
authMethod=""
|
|
if [[ -n "${COL[authMethod]:-}" ]]; then
|
|
authMethod=$(get authMethod)
|
|
fi
|
|
authMethod="${authMethod:-password}"
|
|
|
|
# Basic sanity check
|
|
for v in userId name email imapHost imapPort imapSsl imapUser imapPass smtpHost smtpPort smtpSsl smtpUser smtpPass; do
|
|
if [[ -z "${!v}" ]]; then
|
|
echo "[line $lineno] SKIP: empty field '$v'" >&2
|
|
skipped=$((skipped + 1))
|
|
continue 2
|
|
fi
|
|
done
|
|
|
|
echo "[line $lineno] linking $email -> user '$userId' ..."
|
|
|
|
# shellcheck disable=SC2086
|
|
if $OCC_CMD mail:account:create \
|
|
"$userId" "$name" "$email" \
|
|
"$imapHost" "$imapPort" "$imapSsl" "$imapUser" "$imapPass" \
|
|
"$smtpHost" "$smtpPort" "$smtpSsl" "$smtpUser" "$smtpPass" \
|
|
"$authMethod"; then
|
|
ok=$((ok + 1))
|
|
else
|
|
echo "[line $lineno] FAILED for user '$userId' ($email)" >&2
|
|
fail=$((fail + 1))
|
|
fi
|
|
done
|
|
|
|
echo "---"
|
|
echo "Done. ok=$ok fail=$fail skipped=$skipped"
|