#!/usr/bin/env bash set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd "${script_dir}/../.." && pwd)" profile="" env_file="${repo_root}/.env.devprod" compose_file="" mode="auto" occ_path="" dry_run="0" skip_finish="0" bootstrap_now="0" declare -a bootstrap_args=() usage() { cat < [options] Apply an installation/repair profile selected by topology detection. Profiles: repo-devprod-bootstrap Uses repo Docker scripts (recreate + finish) host-nextcloud-hybrid Host-Nextcloud adoption installer Options: --profile Profile to apply (required) --repo-root Repository root (default: auto) --env-file Env file path (default: .env.devprod) --compose-file Compose file (auto-detected when omitted) --mode auto|ssl|nossl Recreate mode (default: auto) --occ-path Host Nextcloud occ path (for host profile) --dry-run Print commands without executing --skip-finish Skip finish-initial-setup.sh after recreate --bootstrap-now For host-nextcloud-hybrid, run install-host-nextcloud-hybrid.sh --bootstrap-arg Extra argument passed through to the install script (repeatable) -h, --help Show this help USAGE } read_kv() { local key="$1" local file="$2" local line line="$(grep -m1 -E "^${key}=" "${file}" 2>/dev/null || true)" if [[ -z "${line}" ]]; then return 1 fi printf '%s' "${line#*=}" } host_occ_cmd() { if [[ -z "${occ_path:-}" || ! -f "${occ_path:-}" ]]; then return 1 fi if [[ -z "$(command -v php || true)" ]]; then return 1 fi if [[ "${EUID:-$(id -u)}" -eq 0 ]]; then if command -v sudo >/dev/null 2>&1; then sudo -u www-data php "${occ_path}" "$@" return $? fi if command -v runuser >/dev/null 2>&1; then runuser -u www-data -- php "${occ_path}" "$@" return $? fi fi php "${occ_path}" "$@" } while [[ $# -gt 0 ]]; do case "$1" in --profile) profile="${2:-}" shift 2 ;; --profile=*) profile="${1#*=}" shift ;; --repo-root) repo_root="${2:-}" shift 2 ;; --repo-root=*) repo_root="${1#*=}" shift ;; --env-file) env_file="${2:-}" shift 2 ;; --env-file=*) env_file="${1#*=}" shift ;; --compose-file) compose_file="${2:-}" shift 2 ;; --compose-file=*) compose_file="${1#*=}" shift ;; --mode) mode="${2:-}" shift 2 ;; --mode=*) mode="${1#*=}" shift ;; --occ-path) occ_path="${2:-}" shift 2 ;; --occ-path=*) occ_path="${1#*=}" shift ;; --dry-run) dry_run="1" shift ;; --skip-finish) skip_finish="1" shift ;; --bootstrap-now) bootstrap_now="1" shift ;; --bootstrap-arg) if [[ -z "${2:-}" ]]; then echo "Missing value for --bootstrap-arg" exit 1 fi bootstrap_args+=("${2}") shift 2 ;; --bootstrap-arg=*) bootstrap_args+=("${1#*=}") shift ;; -h|--help) usage exit 0 ;; *) echo "Unknown option: $1" usage exit 1 ;; esac done if [[ -z "${profile}" ]]; then echo "--profile is required." usage exit 1 fi if [[ "${repo_root}" != /* ]]; then repo_root="$(cd "${repo_root}" 2>/dev/null && pwd)" fi if [[ "${env_file}" != /* ]]; then env_file="${repo_root}/${env_file}" fi if [[ -n "${compose_file}" && "${compose_file}" != /* ]]; then compose_file="${repo_root}/${compose_file}" fi if [[ "${mode}" != "auto" && "${mode}" != "ssl" && "${mode}" != "nossl" ]]; then echo "Invalid --mode value: ${mode}" exit 1 fi if [[ -z "${compose_file}" ]]; then if [[ -f "${repo_root}/docker-compose.devprod.nossl.yml" ]]; then compose_file="${repo_root}/docker-compose.devprod.nossl.yml" elif [[ -f "${repo_root}/docker-compose.devprod.yml" ]]; then compose_file="${repo_root}/docker-compose.devprod.yml" else compose_file="${repo_root}/docker-compose.devprod.nossl.yml" fi fi run_cmd() { if [[ "${dry_run}" == "1" ]]; then printf '[dry-run] ' printf '%q ' "$@" printf '\n' return 0 fi "$@" } detect_mode_from_compose_path() { local input="${1:-}" if [[ "${mode}" != "auto" ]]; then printf '%s' "${mode}" return 0 fi if [[ "${input}" == *".nossl.yml" ]]; then printf 'nossl' else printf 'ssl' fi } apply_repo_devprod_bootstrap() { if [[ ! -f "${env_file}" ]]; then echo "Missing env file: ${env_file}" exit 1 fi if [[ ! -f "${compose_file}" ]]; then echo "Missing compose file: ${compose_file}" exit 1 fi local effective_mode effective_mode="$(detect_mode_from_compose_path "${compose_file}")" local recreate_cmd=("${repo_root}/recreate-devprod.sh") if [[ "${effective_mode}" == "nossl" ]]; then recreate_cmd+=("--nossl") else recreate_cmd+=("--ssl") fi local external_auth_base_url external_auth_base_url="$(read_kv "QORTAL_EXTERNAL_AUTH_BASE_URL" "${env_file}" || true)" if [[ "${external_auth_base_url}" == *"external_auth"* ]]; then recreate_cmd+=("--extauth") fi echo "Applying profile: repo-devprod-bootstrap" echo "Repo root: ${repo_root}" echo "Env file: ${env_file}" echo "Compose file: ${compose_file}" if [[ -x "${repo_root}/scripts/ensure-env-keys.sh" ]]; then run_cmd "${repo_root}/scripts/ensure-env-keys.sh" --env-file "${env_file}" --template-file "${repo_root}/.env.example" fi run_cmd "${recreate_cmd[@]}" if [[ "${skip_finish}" == "1" ]]; then echo "Skipping finish-initial-setup by request (--skip-finish)." return 0 fi local finish_cmd=("${repo_root}/scripts/finish-initial-setup.sh") if [[ "${effective_mode}" == "nossl" ]]; then finish_cmd+=("--nossl") else finish_cmd+=("--ssl") fi finish_cmd+=("--env-file" "${env_file}") run_cmd "${finish_cmd[@]}" } apply_host_nextcloud_hybrid() { local nc_url="" local dbtype="" local docker_reachable="0" local apps_list="" local has_spreed="0" local has_richdocuments="0" local has_notify_push="0" local install_script="${repo_root}/scripts/install-host-nextcloud-hybrid.sh" echo "Applying profile: host-nextcloud-hybrid" if [[ -z "${occ_path}" ]]; then occ_path="/var/www/nextcloud/occ" fi echo "Detected/selected OCC path: ${occ_path}" echo if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then docker_reachable="1" fi nc_url="$(host_occ_cmd config:system:get overwrite.cli.url 2>/dev/null | tr -d '\r' || true)" dbtype="$(host_occ_cmd config:system:get dbtype 2>/dev/null | tr -d '\r' || true)" apps_list="$(host_occ_cmd app:list 2>/dev/null || true)" if grep -qE '^[[:space:]]+- spreed([[:space:]:]|$)' <<< "${apps_list}"; then has_spreed="1" fi if grep -qE '^[[:space:]]+- richdocuments([[:space:]:]|$)' <<< "${apps_list}"; then has_richdocuments="1" fi if grep -qE '^[[:space:]]+- notify_push([[:space:]:]|$)' <<< "${apps_list}"; then has_notify_push="1" fi echo "Observed host state:" echo " nextcloud_url: ${nc_url:-}" echo " dbtype: ${dbtype:-}" echo " docker_daemon_reachable: ${docker_reachable}" echo " spreed_enabled: ${has_spreed}" echo " richdocuments_enabled: ${has_richdocuments}" echo " notify_push_enabled: ${has_notify_push}" echo echo "Recommended adoption path for this topology:" echo "1) Keep host Nextcloud, Apache, DB, and Redis in place." if [[ "${has_spreed}" == "1" || "${has_richdocuments}" == "1" || "${has_notify_push}" == "1" ]]; then echo "2) Reuse existing host-integrated Nextcloud apps and sidecars where present:" [[ "${has_notify_push}" == "1" ]] && echo " - notify_push already enabled" [[ "${has_richdocuments}" == "1" ]] && echo " - richdocuments already enabled" [[ "${has_spreed}" == "1" ]] && echo " - spreed already enabled" echo " Prefer adding NuQloud auth/broker pieces around them before replacing any Talk/Office setup." else echo "2) No existing Talk/Office app enablement detected; this host may be a cleaner candidate for fuller NuQloud sidecar adoption." fi echo "3) Provision Docker sidecars for the missing NuQloud services:" echo " - broker" echo " - broker_db" echo " - external_auth" echo " - optional qortal_node" echo "4) Apply the host-hybrid installer:" echo " ./scripts/install-host-nextcloud-hybrid.sh --occ-path ${occ_path}" echo "5) Use the lifecycle helpers for later operations:" echo " ./scripts/update-host-nextcloud-hybrid.sh" echo " ./scripts/restart-host-nextcloud-hybrid.sh" echo " ./scripts/stop-host-nextcloud-hybrid.sh" echo "6) Re-run topology detection after changes:" echo " ./scripts/topology/discover-cloud-topology.sh --text --repo-root ${repo_root}" if [[ "${bootstrap_now}" != "1" ]]; then return 0 fi if [[ ! -x "${install_script}" ]]; then echo echo "Cannot run install: missing executable ${install_script}" exit 1 fi echo echo "Running host installer now (--bootstrap-now)..." local bootstrap_cmd=("${install_script}" "--repo-root" "${repo_root}" "--occ-path" "${occ_path}") if [[ "${#bootstrap_args[@]}" -gt 0 ]]; then bootstrap_cmd+=("${bootstrap_args[@]}") fi run_cmd "${bootstrap_cmd[@]}" } case "${profile}" in repo-devprod-bootstrap) apply_repo_devprod_bootstrap ;; host-nextcloud-hybrid) apply_host_nextcloud_hybrid ;; *) echo "Unknown profile: ${profile}" usage exit 1 ;; esac