#!/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="${repo_root}/docker-compose.devprod.nossl.yml" env_file="${repo_root}/.env.devprod" repair=0 usage() { cat <<'EOF' Usage: scripts/verify-qortal-integration-sync.sh [options] Options: --compose-file Compose file path (default: docker-compose.devprod.nossl.yml) --env-file Env file path (default: .env.devprod) --repair Copy source app into mounted custom_apps path and refresh OCC if mismatched --help Show this help EOF } while [[ $# -gt 0 ]]; do case "$1" in --compose-file) compose_file="$2" shift 2 ;; --env-file) env_file="$2" shift 2 ;; --repair) repair=1 shift ;; --help) usage exit 0 ;; *) echo "Unknown argument: $1" usage exit 1 ;; esac done if [[ ! -f "${env_file}" ]]; then echo "Missing env file: ${env_file}" exit 1 fi export_compose_project_name_from_env "${env_file}" || true read_kv() { local key="$1" local file="$2" local line line="$(grep -m1 -E "^${key}=" "${file}" || true)" if [[ -z "${line}" ]]; then return 1 fi echo "${line#*=}" } resolve_path_from_env() { local key="$1" local default_path="$2" local value value="$(read_kv "${key}" "${env_file}" || true)" value="${value:-${default_path}}" if [[ "${value}" == /* ]]; then echo "${value}" else echo "${repo_root}/${value}" fi } source_dir="${repo_root}/nextcloud/custom_apps/qortal_integration" custom_apps_root="$(resolve_path_from_env "NEXTCLOUD_CUSTOM_APPS_PATH" "./nextcloud/custom_apps")" target_dir="${custom_apps_root}/qortal_integration" if [[ ! -f "${source_dir}/appinfo/info.xml" ]]; then echo "Missing source app at ${source_dir}" exit 1 fi if [[ ! -d "${target_dir}" ]]; then echo "Missing mounted app path ${target_dir}" if [[ "${repair}" -eq 1 ]]; then mkdir -p "${custom_apps_root}" if command -v rsync >/dev/null 2>&1; then rsync -a --delete "${source_dir}/" "${target_dir}/" else rm -rf "${target_dir}" mkdir -p "${target_dir}" cp -R "${source_dir}/." "${target_dir}/" fi echo "Repaired mounted app path by copying source." else exit 1 fi fi files=( "appinfo/info.xml" "js/account.js" "js/admin.js" "templates/account.php" "templates/admin.php" "lib/Controller/ApiController.php" ) hash_file() { local path="$1" sha256sum "${path}" | awk '{print $1}' } container_exec() { local cmd="$1" docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app sh -lc "${cmd}" 2>/dev/null } container_app_path="$(docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:getpath qortal_integration 2>/dev/null | tr -d '\r' || true)" if [[ -z "${container_app_path}" ]]; then container_app_path="/var/www/html/custom_apps/qortal_integration" fi container_accessible=1 if ! container_exec "test -d '${container_app_path}'"; then container_accessible=0 fi container_hash() { local rel="$1" container_exec "if [ -f '${container_app_path}/${rel}' ]; then sha256sum '${container_app_path}/${rel}' | awk '{print \$1}'; fi" | tr -d '\r' } host_mismatch=0 container_mismatch=0 if [[ "${container_accessible}" -ne 1 ]]; then container_mismatch=1 echo "Container app path is not accessible: ${container_app_path}" fi for rel in "${files[@]}"; do src="${source_dir}/${rel}" tgt="${target_dir}/${rel}" if [[ ! -f "${src}" ]]; then continue fi src_hash="$(hash_file "${src}")" if [[ -f "${tgt}" ]]; then tgt_hash="$(hash_file "${tgt}")" if [[ "${src_hash}" != "${tgt_hash}" ]]; then host_mismatch=1 echo "Mismatch host source vs mounted: ${rel}" fi else host_mismatch=1 echo "Missing in mounted app path: ${rel}" fi if [[ "${container_accessible}" -ne 1 ]]; then continue fi live_hash="$(container_hash "${rel}" || true)" if [[ -n "${live_hash}" && "${src_hash}" != "${live_hash}" ]]; then container_mismatch=1 echo "Mismatch source vs container live app: ${rel}" fi done if [[ "${repair}" -eq 1 && "${host_mismatch}" -eq 1 ]]; then echo "Repair: syncing source -> mounted custom apps path..." if command -v rsync >/dev/null 2>&1; then rsync -a --delete "${source_dir}/" "${target_dir}/" else rm -rf "${target_dir}" mkdir -p "${target_dir}" cp -R "${source_dir}/." "${target_dir}/" fi fi if [[ "${repair}" -eq 1 && ( "${host_mismatch}" -eq 1 || "${container_mismatch}" -eq 1 ) ]]; then echo "Repair: refreshing app registration + caches..." docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:disable qortal_integration >/dev/null 2>&1 || true docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:enable qortal_integration >/dev/null 2>&1 || true if [[ -x "${repo_root}/scripts/clear-nextcloud-appdata-assets.sh" ]]; then "${repo_root}/scripts/clear-nextcloud-appdata-assets.sh" \ --compose-file "${compose_file}" \ --env-file "${env_file}" >/dev/null 2>&1 || true fi docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ maintenance:repair >/dev/null 2>&1 || true host_mismatch=0 container_mismatch=0 container_accessible=1 if ! container_exec "test -d '${container_app_path}'"; then container_accessible=0 container_mismatch=1 fi for rel in "${files[@]}"; do src="${source_dir}/${rel}" tgt="${target_dir}/${rel}" [[ -f "${src}" ]] || continue src_hash="$(hash_file "${src}")" if [[ ! -f "${tgt}" || "${src_hash}" != "$(hash_file "${tgt}")" ]]; then host_mismatch=1 fi if [[ "${container_accessible}" -ne 1 ]]; then continue fi live_hash="$(container_hash "${rel}" || true)" if [[ -n "${live_hash}" && "${src_hash}" != "${live_hash}" ]]; then container_mismatch=1 fi done fi if [[ "${host_mismatch}" -eq 0 && "${container_mismatch}" -eq 0 ]]; then echo "qortal_integration app sync verified:" echo " source=${source_dir}" echo " mounted=${target_dir}" echo " container=${container_app_path}" exit 0 fi echo "qortal_integration app sync verification failed." echo " source=${source_dir}" echo " mounted=${target_dir}" echo " container=${container_app_path}" if [[ "${repair}" -eq 0 ]]; then echo "Re-run with --repair to auto-fix copy + OCC refresh." fi exit 1