#!/usr/bin/env bash set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd "${script_dir}/.." && pwd)" source "${script_dir}/lib-compose-project.sh" compose_file="" env_file="" app_service="app" broker_service="broker" repair_mode="0" usage() { cat < --env-file [options] Verify broker -> Nextcloud OCS service authentication works and optionally repair it. Uses NEXTCLOUD_SERVICE_USER from env as-is (legacy sovereign_oidc_service or nuqloud_oidc_service are both supported). Options: --compose-file docker compose file to use (required) --env-file env file to use (required) --app-service Nextcloud app service name (default: app) --broker-service Broker service name (default: broker) --repair Attempt automatic repair on auth failure -h, --help Show this help USAGE } read_kv() { local key="$1" local line line="$(grep -m1 -E "^${key}=" "${env_file}" || true)" if [[ -z "${line}" ]]; then return 1 fi echo "${line#*=}" } trim() { local value="$1" value="$(echo "${value}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" echo "${value}" } while [[ $# -gt 0 ]]; do case "$1" in --compose-file) compose_file="${2:-}" shift 2 ;; --compose-file=*) compose_file="${1#*=}" shift ;; --env-file) env_file="${2:-}" shift 2 ;; --env-file=*) env_file="${1#*=}" shift ;; --app-service) app_service="${2:-}" shift 2 ;; --app-service=*) app_service="${1#*=}" shift ;; --broker-service) broker_service="${2:-}" shift 2 ;; --broker-service=*) broker_service="${1#*=}" shift ;; --repair) repair_mode="1" shift ;; -h|--help) usage exit 0 ;; *) echo "Unknown option: $1" usage exit 1 ;; esac done if [[ -z "${compose_file}" || -z "${env_file}" ]]; then usage exit 1 fi if [[ "${compose_file}" != /* ]]; then compose_file="${repo_root}/${compose_file}" fi if [[ "${env_file}" != /* ]]; then env_file="${repo_root}/${env_file}" fi if [[ ! -f "${compose_file}" ]]; then echo "Missing compose file: ${compose_file}" exit 1 fi if [[ ! -f "${env_file}" ]]; then echo "Missing env file: ${env_file}" exit 1 fi export_compose_project_name_from_env "${env_file}" || true nextcloud_base_url="$(trim "$(read_kv "NEXTCLOUD_BASE_URL" || true)")" service_user="$(trim "$(read_kv "NEXTCLOUD_SERVICE_USER" || true)")" service_password="$(read_kv "NEXTCLOUD_SERVICE_PASSWORD" || true)" admin_user="$(trim "$(read_kv "NEXTCLOUD_ADMIN_USER" || true)")" if [[ -z "${nextcloud_base_url}" ]]; then echo "NEXTCLOUD_BASE_URL is missing in ${env_file}" exit 1 fi if [[ -z "${service_user}" || -z "${service_password}" ]]; then echo "NEXTCLOUD_SERVICE_USER/NEXTCLOUD_SERVICE_PASSWORD must be set in ${env_file}" exit 1 fi if ! docker compose -f "${compose_file}" --env-file "${env_file}" exec -T "${app_service}" php occ status >/dev/null 2>&1; then echo "Nextcloud OCC is not ready in service '${app_service}'." exit 1 fi test_service_auth() { local status status="$(docker compose -f "${compose_file}" --env-file "${env_file}" exec -T "${broker_service}" node -e ' const base = (process.env.NEXTCLOUD_BASE_URL || "").replace(/\/+$/, ""); const user = process.env.NEXTCLOUD_SERVICE_USER || ""; const pass = process.env.NEXTCLOUD_SERVICE_PASSWORD || ""; const endpoint = `${base}/ocs/v1.php/cloud/users?search=${encodeURIComponent(user)}&format=json`; const auth = Buffer.from(`${user}:${pass}`).toString("base64"); fetch(endpoint, { method: "GET", headers: { Authorization: `Basic ${auth}`, "OCS-APIRequest": "true", Accept: "application/json" } }).then(async (res) => { process.stdout.write(String(res.status)); }).catch(() => { process.stdout.write("000"); });' 2>/dev/null || true)" echo "${status}" } test_service_auth_with_retry() { local attempts="${1:-18}" local delay_seconds="${2:-2}" local status="" local i for i in $(seq 1 "${attempts}"); do status="$(test_service_auth)" if [[ "${status}" == "200" ]]; then echo "${status}" return 0 fi # A direct 401 usually means credentials mismatch, so fail fast. if [[ "${status}" == "401" ]]; then echo "${status}" return 0 fi if [[ "${i}" -lt "${attempts}" ]]; then sleep "${delay_seconds}" fi done echo "${status}" } auth_status="$(test_service_auth_with_retry 18 2)" if [[ "${auth_status}" == "200" ]]; then echo "Nextcloud broker service auth is healthy (HTTP 200)." exit 0 fi if [[ "${repair_mode}" != "1" ]]; then echo "Nextcloud broker service auth check failed (HTTP ${auth_status:-unknown})." echo "Run with --repair or reset NEXTCLOUD_SERVICE_* credentials." exit 1 fi echo "Attempting broker service auth repair (current HTTP ${auth_status:-unknown})..." if [[ -n "${admin_user}" && "${service_user}" == "${admin_user}" ]]; then echo "Refusing automatic password reset for admin account '${service_user}'." echo "Set dedicated NEXTCLOUD_SERVICE_USER/NEXTCLOUD_SERVICE_PASSWORD in ${env_file}, then rerun." exit 1 fi if ! docker compose -f "${compose_file}" --env-file "${env_file}" exec -T "${app_service}" php occ user:info "${service_user}" >/dev/null 2>&1; then echo "Creating missing service user '${service_user}'..." docker compose -f "${compose_file}" --env-file "${env_file}" exec -T \ -e OC_PASS="${service_password}" \ "${app_service}" php occ user:add --password-from-env --display-name="${service_user}" "${service_user}" >/dev/null fi # Required for OCS user provisioning operations. docker compose -f "${compose_file}" --env-file "${env_file}" exec -T "${app_service}" php occ group:adduser admin "${service_user}" >/dev/null 2>&1 || true docker compose -f "${compose_file}" --env-file "${env_file}" exec -T \ -e OC_PASS="${service_password}" \ "${app_service}" php occ user:resetpassword --password-from-env "${service_user}" >/dev/null auth_status_after="$(test_service_auth_with_retry 18 2)" if [[ "${auth_status_after}" != "200" ]]; then echo "Repair attempted but broker service auth still failing (HTTP ${auth_status_after:-unknown})." exit 1 fi echo "Repaired broker service auth (HTTP 200)."