Initial commit: bulk-link mail accounts via occ
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>
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Echte CSV's met credentials nooit committen
|
||||
accounts.csv
|
||||
accounts.*.csv
|
||||
!accounts.example.csv
|
||||
|
||||
# OS / editor
|
||||
.DS_Store
|
||||
*.swp
|
||||
84
README.md
Normal file
84
README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Nextcloud-ConnectMail
|
||||
|
||||
Bulk-link IMAP/SMTP mailaccounts voor Nextcloud-gebruikers via `occ mail:account:create`.
|
||||
|
||||
Bedoeld voor admins die in één keer mail-accounts willen koppelen voor meerdere users —
|
||||
bijvoorbeeld bij onboarding, migratie, of provisioning op basis van een externe lijst.
|
||||
|
||||
## Wanneer dit script gebruiken
|
||||
|
||||
De Nextcloud Mail-app biedt **geen** REST-endpoint om als admin een account voor een
|
||||
andere user te koppelen. Wel bestaat `occ mail:account:create <userId> ...`, maar dat
|
||||
is positioneel en omslachtig voor bulk-werk. Dit script wrapt die call en leest een CSV.
|
||||
|
||||
Alternatieven:
|
||||
- **Provisioning-templates** (admin-UI in Mail): één template per domein, géén
|
||||
per-user credentials. Werkt alleen als alle users hetzelfde mail-cluster delen.
|
||||
- **Eigen REST-controller**: vereist een patch op de Mail-app.
|
||||
|
||||
## Vereisten
|
||||
|
||||
- Shell-toegang tot de Nextcloud-server (rechtstreeks of via SSH/docker exec)
|
||||
- `occ` uitvoerbaar als web-user (meestal `www-data`)
|
||||
- De Nextcloud Mail-app geïnstalleerd en geactiveerd
|
||||
- `python3` op de host waar het script draait (voor RFC-4180 CSV-parsing)
|
||||
|
||||
## Gebruik
|
||||
|
||||
```bash
|
||||
OCC_CMD="docker exec -u www-data nc-dev php occ" \
|
||||
./mail-bulk-link.sh accounts.csv
|
||||
```
|
||||
|
||||
Voor classic installs:
|
||||
|
||||
```bash
|
||||
OCC_CMD="sudo -u www-data php /var/www/nextcloud/occ" \
|
||||
./mail-bulk-link.sh accounts.csv
|
||||
```
|
||||
|
||||
Remote via SSH:
|
||||
|
||||
```bash
|
||||
OCC_CMD="ssh hetzner-ax42 docker exec -u www-data nc-dev php occ" \
|
||||
./mail-bulk-link.sh accounts.csv
|
||||
```
|
||||
|
||||
## CSV-formaat
|
||||
|
||||
Header verplicht, kolomvolgorde vrij. Verplichte kolommen:
|
||||
|
||||
```
|
||||
userId,name,email,imapHost,imapPort,imapSsl,imapUser,imapPass,smtpHost,smtpPort,smtpSsl,smtpUser,smtpPass
|
||||
```
|
||||
|
||||
Optionele kolom: `authMethod` (`password` of `xoauth2`, default `password`).
|
||||
|
||||
- `imapSsl` / `smtpSsl`: `ssl`, `tls`, of `none`
|
||||
- Regels die beginnen met `#` worden overgeslagen
|
||||
- Quote velden met komma's volgens RFC 4180 (`"foo,bar"`)
|
||||
|
||||
Zie [accounts.example.csv](accounts.example.csv) voor een voorbeeld.
|
||||
|
||||
## Beperkingen
|
||||
|
||||
- **Geen connectie-test.** De `occ`-command slaat blind op — foute credentials worden
|
||||
geaccepteerd. De user merkt het pas bij de eerste sync. De REST-route (`POST
|
||||
/api/accounts`) doet wel een live IMAP/SMTP-test, maar werkt alleen voor de
|
||||
ingelogde user zelf.
|
||||
- **Geen initial mailbox-sync.** Wordt door de REST-route wel getriggerd, door
|
||||
`occ` niet. Eerste sync gebeurt bij de eerstvolgende background-job of bij eerste
|
||||
login van de user.
|
||||
- **Wachtwoorden in CSV.** Plaintext op disk tijdens uitvoer. Na de run:
|
||||
`shred -u accounts.csv`. Nooit committen.
|
||||
|
||||
## Security
|
||||
|
||||
- Voeg `accounts.csv` (en varianten) toe aan `.gitignore` van je deploy-repo
|
||||
- Draai dit alleen vanuit een omgeving waar je sowieso admin-rechten hebt
|
||||
- Voor productie: overweeg de credentials uit een secrets-store te halen en de
|
||||
CSV on-the-fly te genereren in tmpfs
|
||||
|
||||
## Licentie
|
||||
|
||||
AGPL-3.0-or-later (consistent met Nextcloud Mail).
|
||||
4
accounts.example.csv
Normal file
4
accounts.example.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
userId,name,email,imapHost,imapPort,imapSsl,imapUser,imapPass,smtpHost,smtpPort,smtpSsl,smtpUser,smtpPass
|
||||
alice,Werk,alice@voorbeeld.nl,imap.voorbeeld.nl,993,ssl,alice@voorbeeld.nl,geheim1,smtp.voorbeeld.nl,587,tls,alice@voorbeeld.nl,geheim1
|
||||
bob,Werk,bob@voorbeeld.nl,imap.voorbeeld.nl,993,ssl,bob@voorbeeld.nl,geheim2,smtp.voorbeeld.nl,587,tls,bob@voorbeeld.nl,geheim2
|
||||
# carol,Vakantie,carol@voorbeeld.nl,imap.voorbeeld.nl,993,ssl,carol@voorbeeld.nl,geheim3,smtp.voorbeeld.nl,587,tls,carol@voorbeeld.nl,geheim3
|
||||
|
121
mail-bulk-link.sh
Executable file
121
mail-bulk-link.sh
Executable file
@@ -0,0 +1,121 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user