#!/usr/bin/env bash set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd "${script_dir}/.." && pwd)" action="" env_file=".env.devprod" compose_file="" mode="auto" topology="auto" broker_dir="/opt/qortal-broker" broker_dir_explicit="0" env_file_explicit="0" compose_file_explicit="0" dry_run="0" force_refresh_contexts="0" finish_host_nextcloud_upgrade="0" rebuild_qortal="0" full_rebuild="0" extauth="auto" qortal_branch="" qortal_branch_next="0" usage() { cat < [options] Smart lifecycle wrapper for NuQloud installs. Detects repo Docker devprod vs host Nextcloud hybrid installs and delegates to the existing lifecycle scripts. Actions: detect Print detected topology start Start/recreate services for the detected topology update Refresh code/config and recreate managed services rebuild Rebuild containers where supported, then recreate services stop Stop managed services without deleting volumes Options: --env-file Repo devprod env or host broker env hint --compose-file Compose file hint for detection/host start/stop --mode auto|ssl|nossl Repo devprod mode (default: auto) --topology auto|repo-devprod|host-hybrid Override topology detection --broker-dir Host-hybrid broker dir (default: /opt/qortal-broker) --extauth|--no-extauth Repo devprod external-auth profile hint --qortal Include/rebuild qortal_node where supported --qortal-branch Rebuild Qortal Core from a specific branch --full Full repo devprod rebuild where supported --force-refresh-contexts Allow dirty git context refreshes in host update --finish-host-nextcloud-upgrade Permit host Nextcloud occ upgrade repair flow --dry-run Print the delegated command only -h, --help Show this help USAGE } resolve_path() { local raw="${1:-}" if [[ -z "${raw}" ]]; then printf '%s' "" elif [[ "${raw}" == /* ]]; then printf '%s' "${raw}" else printf '%s' "${repo_root}/${raw}" fi } print_cmd() { printf 'Would run:' printf ' %q' "$@" printf '\n' } run_or_print() { if [[ "${dry_run}" == "1" ]]; then print_cmd "$@" return 0 fi "$@" } json_string_value() { local key="$1" sed -nE "s/.*\"${key}\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\\1/p" | head -n1 } infer_mode_from_compose() { local file="${1:-}" if [[ "${mode}" != "auto" ]]; then printf '%s' "${mode}" elif [[ "${file}" == *".nossl."* || "${file}" == *"nossl"* ]]; then printf '%s' "nossl" else printf '%s' "ssl" fi } detect_topology_json() { local args=(--repo-root "${repo_root}" --env-file "${resolved_env_file}" --json) if [[ -n "${resolved_compose_file}" ]]; then args+=(--compose-file "${resolved_compose_file}") fi "${script_dir}/discover-cloud-topology.sh" "${args[@]}" } detect_topology_id() { local json id json="$(detect_topology_json 2>/dev/null || true)" id="$(printf '%s\n' "${json}" | json_string_value "id")" if [[ -z "${id}" && -f "${resolved_broker_dir}/.env" && -f "${resolved_broker_dir}/docker-compose.yml" ]]; then id="host_nextcloud_plus_docker" fi if [[ -z "${id}" && -f "${resolved_env_file}" ]]; then id="docker_compose_generic" fi printf '%s' "${id:-unknown}" } repo_devprod_cmd() { local delegated_mode="$1" local cmd=() case "${action}" in start) cmd=("${repo_root}/start-devprod.sh" "--${delegated_mode}") if [[ "${extauth}" == "1" ]]; then cmd+=(--extauth) elif [[ "${extauth}" == "0" ]]; then cmd+=(--no-extauth) fi ;; update|rebuild) cmd=("${repo_root}/recreate-devprod.sh" "--${delegated_mode}") [[ "${extauth}" == "1" ]] && cmd+=(--extauth) [[ "${rebuild_qortal}" == "1" ]] && cmd+=(--qortal) [[ "${full_rebuild}" == "1" ]] && cmd+=(--full) [[ -n "${qortal_branch}" ]] && cmd+=(--qortal-branch "${qortal_branch}") ;; stop) cmd=("${repo_root}/stop-devprod.sh" "--mode" "${delegated_mode}") ;; *) echo "Unsupported repo devprod action: ${action}" >&2 exit 1 ;; esac run_or_print "${cmd[@]}" } host_hybrid_cmd() { local host_env_file="${resolved_broker_dir}/.env" local host_compose_file="${resolved_broker_dir}/docker-compose.yml" local cmd=() if [[ -n "${compose_file}" ]]; then host_compose_file="${resolved_compose_file}" fi if [[ "${env_file}" != ".env.devprod" || -f "${resolved_env_file}" && "${resolved_env_file}" == "${resolved_broker_dir}/.env" ]]; then host_env_file="${resolved_env_file}" fi case "${action}" in start) cmd=("${script_dir}/restart-host-nextcloud-hybrid.sh" --broker-dir "${resolved_broker_dir}" --env-file "${host_env_file}" --compose-file "${host_compose_file}") [[ "${rebuild_qortal}" == "1" ]] && cmd+=(--qortal) ;; update|rebuild) cmd=("${script_dir}/update-host-nextcloud-hybrid.sh" --broker-dir "${resolved_broker_dir}") [[ "${force_refresh_contexts}" == "1" ]] && cmd+=(--force-refresh-contexts) [[ "${finish_host_nextcloud_upgrade}" == "1" ]] && cmd+=(--finish-host-nextcloud-upgrade) ;; stop) cmd=("${script_dir}/stop-host-nextcloud-hybrid.sh" --broker-dir "${resolved_broker_dir}" --env-file "${host_env_file}" --compose-file "${host_compose_file}") ;; *) echo "Unsupported host-hybrid action: ${action}" >&2 exit 1 ;; esac run_or_print "${cmd[@]}" } while [[ $# -gt 0 ]]; do if [[ "${qortal_branch_next}" == "1" ]]; then qortal_branch="${1:-}" qortal_branch_next="0" shift continue fi case "$1" in detect|start|update|rebuild|stop) action="$1" shift ;; --env-file) env_file="${2:-}" env_file_explicit="1" shift 2 ;; --env-file=*) env_file="${1#*=}" env_file_explicit="1" shift ;; --compose-file) compose_file="${2:-}" compose_file_explicit="1" shift 2 ;; --compose-file=*) compose_file="${1#*=}" compose_file_explicit="1" shift ;; --mode) mode="${2:-}" shift 2 ;; --mode=*) mode="${1#*=}" shift ;; --topology) topology="${2:-}" shift 2 ;; --topology=*) topology="${1#*=}" shift ;; --ssl) mode="ssl" shift ;; --nossl) mode="nossl" shift ;; --broker-dir) broker_dir="${2:-}" broker_dir_explicit="1" shift 2 ;; --broker-dir=*) broker_dir="${1#*=}" broker_dir_explicit="1" shift ;; --extauth) extauth="1" shift ;; --no-extauth) extauth="0" shift ;; --qortal) rebuild_qortal="1" shift ;; --qortal=*) rebuild_qortal="1" qortal_branch="${1#*=}" shift ;; --qortal-branch) rebuild_qortal="1" qortal_branch_next="1" shift ;; --qortal-branch=*) rebuild_qortal="1" qortal_branch="${1#*=}" shift ;; --full) full_rebuild="1" rebuild_qortal="1" shift ;; --force-refresh-contexts) force_refresh_contexts="1" shift ;; --finish-host-nextcloud-upgrade) finish_host_nextcloud_upgrade="1" shift ;; --dry-run) dry_run="1" shift ;; -h|--help) usage exit 0 ;; *) echo "Unknown option: $1" >&2 usage exit 1 ;; esac done if [[ -z "${action}" ]]; then usage exit 1 fi if [[ "${qortal_branch_next}" == "1" ]]; then echo "--qortal-branch requires a value" >&2 exit 1 fi if [[ "${mode}" != "auto" && "${mode}" != "ssl" && "${mode}" != "nossl" ]]; then echo "Invalid mode: ${mode}" >&2 exit 1 fi if [[ "${topology}" != "auto" && "${topology}" != "repo-devprod" && "${topology}" != "host-hybrid" ]]; then echo "Invalid topology: ${topology}" >&2 exit 1 fi if [[ -n "${qortal_branch}" ]] && ! git check-ref-format --branch "${qortal_branch}" >/dev/null 2>&1; then echo "Invalid Qortal branch name: ${qortal_branch}" >&2 exit 1 fi resolved_env_file="$(resolve_path "${env_file}")" resolved_compose_file="" if [[ -n "${compose_file}" ]]; then resolved_compose_file="$(resolve_path "${compose_file}")" elif [[ -f "${repo_root}/docker-compose.devprod.nossl.yml" ]]; then resolved_compose_file="${repo_root}/docker-compose.devprod.nossl.yml" elif [[ -f "${repo_root}/docker-compose.devprod.yml" ]]; then resolved_compose_file="${repo_root}/docker-compose.devprod.yml" fi resolved_broker_dir="$(resolve_path "${broker_dir}")" if [[ "${broker_dir_explicit}" == "0" && "${env_file_explicit}" == "1" && "$(basename "${resolved_env_file}")" == ".env" ]]; then resolved_broker_dir="$(dirname "${resolved_env_file}")" elif [[ "${broker_dir_explicit}" == "0" && "${compose_file_explicit}" == "1" && "$(basename "${resolved_compose_file}")" == "docker-compose.yml" ]]; then resolved_broker_dir="$(dirname "${resolved_compose_file}")" fi if [[ "${action}" == "detect" ]]; then args=(--repo-root "${repo_root}" --env-file "${resolved_env_file}" --text) [[ -n "${resolved_compose_file}" ]] && args+=(--compose-file "${resolved_compose_file}") run_or_print "${script_dir}/discover-cloud-topology.sh" "${args[@]}" exit 0 fi delegated_mode="$(infer_mode_from_compose "${resolved_compose_file}")" case "${topology}" in repo-devprod) repo_devprod_cmd "${delegated_mode}" exit 0 ;; host-hybrid) host_hybrid_cmd exit 0 ;; esac if [[ "${broker_dir_explicit}" == "1" && -f "${resolved_broker_dir}/.env" && -f "${resolved_broker_dir}/docker-compose.yml" ]]; then topology_id="host_nextcloud_plus_docker" elif [[ "${env_file_explicit}" == "1" && "${resolved_env_file}" == "${resolved_broker_dir}/.env" ]]; then topology_id="host_nextcloud_plus_docker" elif [[ "${compose_file_explicit}" == "1" && "${resolved_compose_file}" == "${resolved_broker_dir}/docker-compose.yml" ]]; then topology_id="host_nextcloud_plus_docker" else topology_id="$(detect_topology_id)" fi case "${topology_id}" in host_nextcloud|host_nextcloud_plus_docker) host_hybrid_cmd ;; repo_docker_devprod|docker_compose_generic) repo_devprod_cmd "${delegated_mode}" ;; *) if [[ -f "${resolved_broker_dir}/.env" && -f "${resolved_broker_dir}/docker-compose.yml" ]]; then host_hybrid_cmd elif [[ -f "${resolved_env_file}" ]]; then repo_devprod_cmd "${delegated_mode}" else echo "Could not detect a supported NuQloud install." >&2 echo "Provide --env-file/--compose-file for repo devprod or --broker-dir for host-hybrid." >&2 exit 1 fi ;; esac