Files
Qortal-Nextcloud-Integration/scripts/sync-bundled-runtime-env-from-nextcloud.sh
2026-07-11 10:59:18 -07:00

299 lines
8.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "${script_dir}/.." && pwd)"
mode="nossl"
env_file="${repo_root}/.env.devprod"
restart_services="0"
env_changed="0"
app_config_changed="0"
usage() {
cat <<USAGE
Usage: ./scripts/sync-bundled-runtime-env-from-nextcloud.sh [options]
Reads bundled runtime credentials from qortal_integration app config and, when
they differ, writes them back into the env file used by docker recreate flows.
Options:
--mode ssl|nossl Compose mode (default: nossl)
--env-file <path> Env file to update (default: .env.devprod)
--restart-services Recreate broker + external_auth if env values change
-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#*=}"
}
set_kv() {
local key="$1"
local value="$2"
local esc
esc="${value//\\/\\\\}"
esc="${esc//&/\\&}"
esc="${esc//|/\\|}"
if grep -q -E "^${key}=" "${env_file}"; then
sed -i -E "s|^${key}=.*|${key}=${esc}|" "${env_file}"
else
echo "${key}=${value}" >> "${env_file}"
fi
}
json_escape() {
local input="$1"
input="${input//\\/\\\\}"
input="${input//\"/\\\"}"
input="${input//$'\n'/ }"
input="${input//$'\r'/ }"
echo "${input}"
}
extract_json_value() {
local json="$1"
local key="$2"
if command -v jq >/dev/null 2>&1; then
echo "${json}" | jq -r --arg key "${key}" '.[$key] // .data[$key] // empty'
return 0
fi
echo "${json}" | sed -n "s/.*\"${key}\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" | head -n1
}
ensure_docker_access() {
local docker_path=""
docker_path="$(type -P docker || true)"
if [[ -z "${docker_path}" ]]; then
echo "docker is required"
exit 1
fi
if "${docker_path}" info >/dev/null 2>&1; then
DOCKER_CMD=("${docker_path}")
return 0
fi
if ! command -v sudo >/dev/null 2>&1; then
echo "docker is installed but this user cannot access it, and sudo is unavailable."
exit 1
fi
if sudo -n "${docker_path}" info >/dev/null 2>&1; then
DOCKER_CMD=(sudo "${docker_path}")
return 0
fi
echo "docker requires elevated privileges on this machine."
echo "You may be prompted for your sudo password."
if sudo "${docker_path}" info >/dev/null 2>&1; then
DOCKER_CMD=(sudo "${docker_path}")
return 0
fi
echo "Unable to access docker, even with sudo."
exit 1
}
docker() {
"${DOCKER_CMD[@]}" "$@"
}
occ_app_get() {
local key="$1"
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app \
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 app \
php occ config:app:set qortal_integration "${key}" --value="${value}" >/dev/null 2>&1
}
test_external_auth_session() {
local app_id="$1"
local app_secret="$2"
local response token
if [[ -z "${app_id}" || -z "${app_secret}" ]]; then
return 1
fi
response="$(
curl -sS --max-time 15 -X POST "http://127.0.0.1:${external_auth_host_port}/sessions" \
-H 'content-type: application/json' \
-d "{\"appId\":\"$(json_escape "${app_id}")\",\"appSecret\":\"$(json_escape "${app_secret}")\"}" || true
)"
token="$(extract_json_value "${response}" "token")"
[[ -n "${token}" ]]
}
mark_env_external_auth_credentials() {
local app_id="$1"
local app_secret="$2"
set_kv "QORTAL_EXTERNAL_AUTH_APP_ID" "${app_id}"
set_kv "QORTAL_EXTERNAL_AUTH_APP_SECRET" "${app_secret}"
env_changed="1"
}
mark_app_config_external_auth_credentials() {
local app_id="$1"
local app_secret="$2"
occ_app_set "external_auth_app_id" "${app_id}"
occ_app_set "external_auth_app_secret" "${app_secret}"
app_config_changed="1"
}
mark_env_node_api_key() {
local node_api_key="$1"
set_kv "QORTAL_AUTH_NODE_API_KEY" "${node_api_key}"
set_kv "QORTAL_AUTH_NODE_API_KEY_MODE" "paths"
set_kv "QORTAL_AUTH_NODE_API_KEY_PATHS" "/"
env_changed="1"
}
mark_app_config_node_api_key() {
local node_api_key="$1"
occ_app_set "external_auth_node_api_key" "${node_api_key}"
occ_app_set "qortal_node_api_key" "${node_api_key}"
app_config_changed="1"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--mode)
mode="${2:-}"
shift 2
;;
--mode=*)
mode="${1#*=}"
shift
;;
--env-file)
env_file="${2:-}"
shift 2
;;
--env-file=*)
env_file="${1#*=}"
shift
;;
--restart-services)
restart_services="1"
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1"
usage
exit 1
;;
esac
done
if [[ "${mode}" != "ssl" && "${mode}" != "nossl" ]]; then
echo "Invalid mode: ${mode}"
exit 1
fi
if [[ "${env_file}" != /* ]]; then
env_file="${repo_root}/${env_file}"
fi
if [[ ! -f "${env_file}" ]]; then
echo "Missing env file: ${env_file}"
exit 1
fi
compose_file="${repo_root}/docker-compose.devprod.nossl.yml"
if [[ "${mode}" == "ssl" ]]; then
compose_file="${repo_root}/docker-compose.devprod.yml"
fi
external_auth_host_port="$(read_kv "EXTERNAL_AUTH_PORT" || true)"
external_auth_host_port="${external_auth_host_port:-3191}"
ensure_docker_access
if ! docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ status >/dev/null 2>&1; then
echo "Nextcloud OCC is not ready; skipping bundled runtime env sync."
exit 0
fi
stored_app_id="$(occ_app_get "external_auth_app_id")"
stored_app_secret="$(occ_app_get "external_auth_app_secret")"
stored_node_api_key="$(occ_app_get "external_auth_node_api_key")"
if [[ -z "${stored_node_api_key}" ]]; then
stored_node_api_key="$(occ_app_get "qortal_node_api_key")"
fi
current_app_id="$(read_kv "QORTAL_EXTERNAL_AUTH_APP_ID" || true)"
current_app_secret="$(read_kv "QORTAL_EXTERNAL_AUTH_APP_SECRET" || true)"
current_node_api_key="$(read_kv "QORTAL_AUTH_NODE_API_KEY" || true)"
stored_creds_valid="0"
env_creds_valid="0"
if test_external_auth_session "${stored_app_id}" "${stored_app_secret}"; then
stored_creds_valid="1"
fi
if test_external_auth_session "${current_app_id}" "${current_app_secret}"; then
env_creds_valid="1"
fi
if [[ -n "${stored_app_id}" && -n "${stored_app_secret}" ]]; then
if [[ "${stored_app_id}" != "${current_app_id}" || "${stored_app_secret}" != "${current_app_secret}" ]]; then
if [[ "${stored_creds_valid}" == "1" && "${env_creds_valid}" != "1" ]]; then
mark_env_external_auth_credentials "${stored_app_id}" "${stored_app_secret}"
elif [[ "${env_creds_valid}" == "1" && "${stored_creds_valid}" != "1" ]]; then
mark_app_config_external_auth_credentials "${current_app_id}" "${current_app_secret}"
elif [[ "${stored_creds_valid}" == "1" && "${env_creds_valid}" == "1" ]]; then
mark_env_external_auth_credentials "${stored_app_id}" "${stored_app_secret}"
fi
fi
fi
if [[ -n "${stored_node_api_key}" && "${stored_node_api_key}" != "${current_node_api_key}" ]]; then
if [[ -z "${current_node_api_key}" ]]; then
mark_env_node_api_key "${stored_node_api_key}"
else
mark_app_config_node_api_key "${current_node_api_key}"
fi
elif [[ -n "${current_node_api_key}" && -z "${stored_node_api_key}" ]]; then
mark_app_config_node_api_key "${current_node_api_key}"
fi
if [[ "${stored_creds_valid}" != "1" && "${env_creds_valid}" == "1" ]]; then
echo "Bundled runtime note: Nextcloud app config external-auth credentials were invalid; synchronized from env."
elif [[ "${env_creds_valid}" != "1" && "${stored_creds_valid}" == "1" ]]; then
echo "Bundled runtime note: env external-auth credentials were invalid; synchronized from Nextcloud app config."
elif [[ "${stored_creds_valid}" != "1" && "${env_creds_valid}" != "1" ]]; then
echo "Bundled runtime credentials match their sources, but external_auth rejected both credential sets."
echo "Current App ID source values are not valid for /sessions."
if [[ -z "${current_node_api_key}" && -z "${stored_node_api_key}" ]]; then
echo "QORTAL_AUTH_NODE_API_KEY is also missing in both env and Nextcloud app config."
fi
exit 1
fi
if [[ -z "${current_node_api_key}" && -z "${stored_node_api_key}" ]]; then
echo "Warning: QORTAL_AUTH_NODE_API_KEY is missing in both env and Nextcloud app config."
fi
if [[ "${env_changed}" == "1" || "${app_config_changed}" == "1" ]]; then
echo "Synchronized bundled runtime credentials from Nextcloud app config into ${env_file}."
if [[ "${restart_services}" == "1" ]]; then
echo "Recreating broker + external_auth to apply synchronized env values..."
docker compose -f "${compose_file}" --env-file "${env_file}" up -d --force-recreate --no-deps broker external_auth
fi
else
echo "Bundled runtime credentials are aligned and valid against external_auth."
fi