#!/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" usage() { cat < --env-file [options] Sync qortal_integration app config from env-backed runtime values so recreate/setup flows do not leave stale Qortal node or gateway URLs behind. Synced keys: - external_auth_base_url - external_auth_app_id - external_auth_app_secret - external_auth_node_url - external_auth_node_api_key - external_auth_node_api_key_mode - external_auth_node_api_key_paths - qortal_node_url - qortal_node_api_key - qortal_gateway_url - oidc_issuer_url - oidc_client_id - oidc_client_secret - oidc_require_email_for_new_account Options: --compose-file Docker compose file to use (required) --env-file Env file to use (required) --app-service Nextcloud app service name (default: app) -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#*=}" } read_kv_first() { local value="" local key for key in "$@"; do value="$(read_kv "${key}" || true)" if [[ -n "${value}" ]]; then echo "${value}" return 0 fi done return 1 } trim() { local value="${1:-}" value="$(echo "${value}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" echo "${value}" } normalize_oidc_require_email() { local value value="$(trim "${1:-}")" value="$(echo "${value}" | tr '[:upper:]' '[:lower:]')" case "${value}" in ""|default|env|required|on|true|1|yes) echo "required" ;; off|false|0|no|optional) echo "off" ;; *) echo "required" ;; esac } occ_app_get() { local key="$1" docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data "${app_service}" \ php occ config:app:get qortal_integration "${key}" 2>/dev/null | tr -d '\r' | xargs || true } occ_app_set() { local key="$1" local value="$2" docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data "${app_service}" \ php occ config:app:set qortal_integration "${key}" --value="${value}" >/dev/null 2>&1 } set_and_verify() { local key="$1" local value="$2" local stored="" occ_app_set "${key}" "${value}" stored="$(occ_app_get "${key}")" if [[ "${stored}" != "${value}" ]]; then echo "Failed verifying qortal_integration ${key}: expected '${value}', got '${stored}'" return 1 fi return 0 } sync_oidc_require_email_with_broker() { local required_value="$1" local broker_internal_api_token="$2" local expected_bool="false" if [[ "${required_value}" == "required" ]]; then expected_bool="true" fi docker compose -f "${compose_file}" --env-file "${env_file}" exec -T \ -e "BROKER_BASE_URL=http://broker:3000" \ -e "BROKER_INTERNAL_API_TOKEN=${broker_internal_api_token}" \ -e "OIDC_REQUIRE_EMAIL_FOR_NEW_ACCOUNT=${expected_bool}" \ --user www-data "${app_service}" \ php -r ' $baseUrl = rtrim((string)getenv("BROKER_BASE_URL"), "/"); $token = trim((string)getenv("BROKER_INTERNAL_API_TOKEN")); $requireEmailRaw = strtolower(trim((string)getenv("OIDC_REQUIRE_EMAIL_FOR_NEW_ACCOUNT"))); if ($baseUrl === "" || $token === "") { fwrite(STDERR, "Missing broker sync inputs\n"); exit(1); } $expected = in_array($requireEmailRaw, ["1", "true", "yes", "on", "required"], true); $payload = json_encode(["requireEmailForNewAccount" => $expected], JSON_UNESCAPED_SLASHES); if ($payload === false) { fwrite(STDERR, "Failed to encode broker OIDC payload\n"); exit(1); } $context = stream_context_create([ "http" => [ "method" => "POST", "header" => "Content-Type: application/json\r\nX-Broker-Internal-Token: " . $token . "\r\n", "content" => $payload, "timeout" => 30, "ignore_errors" => true, ], ]); $response = @file_get_contents($baseUrl . "/api/oidc/config", false, $context); $statusLine = $http_response_header[0] ?? ""; if ($response === false || !preg_match("/^HTTP\/\S+\s+2\d\d\b/", $statusLine)) { fwrite(STDERR, "Broker OIDC sync failed\n"); exit(1); } $decoded = json_decode($response, true); if (!is_array($decoded) || !array_key_exists("requireEmailForNewAccount", $decoded) || (bool)$decoded["requireEmailForNewAccount"] !== $expected) { fwrite(STDERR, "Broker OIDC sync did not persist the expected email requirement\n"); exit(1); } ' >/dev/null 2>&1 } 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 ;; -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 if ! docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data "${app_service}" php occ status >/dev/null 2>&1; then echo "Nextcloud OCC is not ready in service '${app_service}'." exit 1 fi echo "Ensuring qortal_integration runtime config..." echo " compose_file=${compose_file}" echo " env_file=${env_file}" desired_external_auth_base_url="$(trim "$(read_kv "QORTAL_EXTERNAL_AUTH_BASE_URL" || true)")" desired_external_auth_app_id="$(trim "$(read_kv "QORTAL_EXTERNAL_AUTH_APP_ID" || true)")" desired_external_auth_app_secret="$(read_kv "QORTAL_EXTERNAL_AUTH_APP_SECRET" || true)" desired_external_auth_node_url="$(trim "$(read_kv_first "QORTAL_AUTH_NODE_URL" "QORTAL_NODE_URL" || true)")" desired_qortal_node_url="$(trim "$(read_kv_first "QORTAL_NODE_URL" "QORTAL_AUTH_NODE_URL" || true)")" desired_qortal_gateway_url="$(trim "$(read_kv_first "QORTAL_PUBLIC_GATEWAY_URL" "QORTAL_GATEWAY_PUBLIC_URL" "QORTAL_GATEWAY_URL" || true)")" desired_oidc_issuer_url="$(trim "$(read_kv "OIDC_ISSUER" || true)")" desired_oidc_client_id="$(trim "$(read_kv "OIDC_CLIENT_ID" || true)")" desired_oidc_client_secret="$(read_kv "OIDC_CLIENT_SECRET" || true)" desired_node_api_key="$(read_kv_first "QORTAL_NODE_API_KEY" "QORTAL_AUTH_NODE_API_KEY" || true)" desired_node_api_key_mode="$(trim "$(read_kv "QORTAL_AUTH_NODE_API_KEY_MODE" || true)")" desired_node_api_key_paths="$(trim "$(read_kv "QORTAL_AUTH_NODE_API_KEY_PATHS" || true)")" desired_oidc_require_email_for_new_account="$(normalize_oidc_require_email "$(read_kv "OIDC_REQUIRE_EMAIL_FOR_NEW_ACCOUNT" || true)")" desired_broker_internal_api_token="$(read_kv "BROKER_INTERNAL_API_TOKEN" || true)" if [[ -z "${desired_external_auth_node_url}" ]]; then desired_external_auth_node_url="http://qortal_node:12391" fi if [[ -z "${desired_qortal_node_url}" ]]; then desired_qortal_node_url="${desired_external_auth_node_url}" fi if [[ -z "${desired_node_api_key_mode}" ]]; then desired_node_api_key_mode="paths" fi if [[ -z "${desired_node_api_key_paths}" ]]; then desired_node_api_key_paths="/" fi if [[ -z "${desired_oidc_client_id}" ]]; then desired_oidc_client_id="nextcloud-local" fi status=0 set_and_verify "external_auth_base_url" "${desired_external_auth_base_url}" || status=1 set_and_verify "external_auth_app_id" "${desired_external_auth_app_id}" || status=1 set_and_verify "external_auth_app_secret" "${desired_external_auth_app_secret}" || status=1 set_and_verify "external_auth_node_url" "${desired_external_auth_node_url}" || status=1 set_and_verify "external_auth_node_api_key" "${desired_node_api_key}" || status=1 set_and_verify "external_auth_node_api_key_mode" "${desired_node_api_key_mode}" || status=1 set_and_verify "external_auth_node_api_key_paths" "${desired_node_api_key_paths}" || status=1 set_and_verify "qortal_node_url" "${desired_qortal_node_url}" || status=1 set_and_verify "qortal_node_api_key" "${desired_node_api_key}" || status=1 set_and_verify "qortal_gateway_url" "${desired_qortal_gateway_url}" || status=1 set_and_verify "oidc_issuer_url" "${desired_oidc_issuer_url}" || status=1 set_and_verify "oidc_client_id" "${desired_oidc_client_id}" || status=1 set_and_verify "oidc_client_secret" "${desired_oidc_client_secret}" || status=1 set_and_verify "oidc_require_email_for_new_account" "${desired_oidc_require_email_for_new_account}" || status=1 if ! sync_oidc_require_email_with_broker "${desired_oidc_require_email_for_new_account}" "${desired_broker_internal_api_token}"; then status=1 fi if [[ "${status}" -ne 0 ]]; then echo "Qortal integration runtime config verification failed." exit 1 fi echo "Qortal integration runtime config verified." echo " external_auth_node_url=${desired_external_auth_node_url}" echo " qortal_node_url=${desired_qortal_node_url}" echo " qortal_gateway_url=${desired_qortal_gateway_url:-}" echo " oidc_issuer_url=${desired_oidc_issuer_url:-}" echo " oidc_client_id=${desired_oidc_client_id}" echo " oidc_require_email_for_new_account=${desired_oidc_require_email_for_new_account}"