diff --git a/scripts/nextcloud-mail-sync.sh b/scripts/nextcloud-mail-sync.sh index dd606d9..a3ca3b6 100755 --- a/scripts/nextcloud-mail-sync.sh +++ b/scripts/nextcloud-mail-sync.sh @@ -6,6 +6,7 @@ KC_REALM="${KC_REALM:?}" KC_ADMIN_USER="${KC_ADMIN_USER:?}" KC_ADMIN_PASS="${KC_ADMIN_PASS:?}" MAILU_DOMAIN="${MAILU_DOMAIN:?}" +ONLY_USERNAME="${ONLY_USERNAME:-}" if ! command -v jq >/dev/null 2>&1; then apt-get update && apt-get install -y jq curl >/dev/null @@ -57,11 +58,52 @@ fi cd /var/www/html -users=$(curl -s -H "Authorization: Bearer ${token}" \ - "${KC_BASE}/admin/realms/${KC_REALM}/users?max=2000") +kc_users_url="${KC_BASE}/admin/realms/${KC_REALM}/users?max=2000" +if [[ -n "${ONLY_USERNAME}" ]]; then + username_q=$(jq -nr --arg v "${ONLY_USERNAME}" '$v|@uri') + kc_users_url="${KC_BASE}/admin/realms/${KC_REALM}/users?username=${username_q}&exact=true&max=1" +fi -echo "${users}" | jq -c '.[]' | while read -r user; do - username=$(echo "${user}" | jq -r '.username') +users=$(curl -s -H "Authorization: Bearer ${token}" "${kc_users_url}") + +kc_set_user_mail_meta() { + local user_id="${1}" + local primary_email="${2}" + local mailu_account_count="${3}" + local synced_at="${4}" + + # Fetch the full user representation so we don't accidentally clobber attributes. + local user_json updated_json + if ! user_json=$(curl -fsS -H "Authorization: Bearer ${token}" \ + "${KC_BASE}/admin/realms/${KC_REALM}/users/${user_id}"); then + echo "WARN: unable to fetch Keycloak user ${user_id} for metadata writeback" >&2 + return 1 + fi + + updated_json=$( + jq -c \ + --arg primary_email "${primary_email}" \ + --arg mailu_account_count "${mailu_account_count}" \ + --arg synced_at "${synced_at}" \ + ' + .attributes = (.attributes // {}) | + .attributes.nextcloud_mail_primary_email = [$primary_email] | + .attributes.nextcloud_mail_account_count = [$mailu_account_count] | + .attributes.nextcloud_mail_synced_at = [$synced_at] | + del(.access) + ' <<<"${user_json}" + ) + + curl -fsS -X PUT \ + -H "Authorization: Bearer ${token}" \ + -H "Content-Type: application/json" \ + -d "${updated_json}" \ + "${KC_BASE}/admin/realms/${KC_REALM}/users/${user_id}" >/dev/null +} + +while read -r user; do + user_id=$(jq -r '.id' <<<"${user}") + username=$(jq -r '.username' <<<"${user}") keycloak_email=$(echo "${user}" | jq -r '.email // empty') mailu_email=$(echo "${user}" | jq -r '(.attributes.mailu_email[0] // .attributes.mailu_email // empty)') app_pw=$(echo "${user}" | jq -r '(.attributes.mailu_app_password[0] // .attributes.mailu_app_password // empty)') @@ -131,4 +173,32 @@ echo "${users}" | jq -c '.[]' | while read -r user; do 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 + + # Write non-secret metadata back to Keycloak for UI introspection and onboarding gating. + synced_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + if accounts_after=$(list_mail_accounts "${username}"); then + mailu_accounts_after=$(awk -v d="${MAILU_DOMAIN,,}" 'tolower($2) ~ ("@" d "$") {print}' <<<"${accounts_after}" || true) + if [[ -n "${mailu_accounts_after}" ]]; then + mailu_account_count=$(printf '%s\n' "${mailu_accounts_after}" | wc -l | tr -d ' ') + else + mailu_account_count="0" + fi + primary_email_after="" + if [[ -n "${mailu_accounts_after}" ]]; then + while IFS=$'\t' read -r _account_id account_email; do + if [[ "${account_email,,}" == "${desired_email,,}" ]]; then + primary_email_after="${account_email}" + break + fi + if [[ -z "${primary_email_after}" ]]; then + primary_email_after="${account_email}" + fi + done <<<"${mailu_accounts_after}" + fi + else + mailu_account_count="0" + primary_email_after="" + fi + + kc_set_user_mail_meta "${user_id}" "${primary_email_after}" "${mailu_account_count}" "${synced_at}" || true +done < <(jq -c '.[]' <<<"${users}") diff --git a/services/bstein-dev-home/rbac.yaml b/services/bstein-dev-home/rbac.yaml index cbc8050..f97ed24 100644 --- a/services/bstein-dev-home/rbac.yaml +++ b/services/bstein-dev-home/rbac.yaml @@ -75,3 +75,34 @@ subjects: - kind: ServiceAccount name: bstein-dev-home namespace: bstein-dev-home +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: bstein-dev-home-nextcloud-mail-sync + namespace: nextcloud +rules: + - apiGroups: ["batch"] + resources: ["cronjobs"] + verbs: ["get"] + resourceNames: ["nextcloud-mail-sync"] + - apiGroups: ["batch"] + resources: ["jobs"] + verbs: ["create", "get", "list", "watch"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: bstein-dev-home-nextcloud-mail-sync + namespace: nextcloud +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: bstein-dev-home-nextcloud-mail-sync +subjects: + - kind: ServiceAccount + name: bstein-dev-home + namespace: bstein-dev-home