#!/usr/bin/env bash set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd "${script_dir}/.." && pwd)" default_qortal_repo_url="https://github.com/Qortal/qortal.git" default_external_auth_repo_url="https://gitea.qortal.link/crowetic/Qortal-External-Auth.git" mode="nossl" with_external_auth="auto" with_qortal="1" env_file="${repo_root}/.env.devprod" accept_defaults="0" skip_occ="0" usage() { cat < Env file to use (default: .env.devprod) --accept-defaults If env file is auto-created from template, continue without stopping --skip-occ Skip post-start Nextcloud app enable/repair commands -h, --help Show this help USAGE } while [[ $# -gt 0 ]]; do case "$1" in --mode) mode="${2:-}" shift 2 ;; --mode=*) mode="${1#*=}" shift ;; --ssl) mode="ssl" shift ;; --nossl) mode="nossl" shift ;; --with-external-auth) with_external_auth="1" shift ;; --without-external-auth) with_external_auth="0" shift ;; --with-qortal) with_qortal="1" shift ;; --without-qortal) with_qortal="0" shift ;; --env-file) env_file="${2:-}" shift 2 ;; --env-file=*) env_file="${1#*=}" shift ;; --accept-defaults) accept_defaults="1" shift ;; --skip-occ) skip_occ="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}" usage exit 1 fi read_env_value() { local key="$1" local default_value="${2:-}" local value if command -v rg >/dev/null 2>&1; then value="$(rg -m1 "^${key}=" "${env_file}" | cut -d= -f2- || true)" else value="$(grep -m1 -E "^${key}=" "${env_file}" | cut -d= -f2- || true)" fi if [[ -z "${value}" ]]; then echo "${default_value}" return fi if [[ "${value}" =~ ^\".*\"$ ]]; then value="${value:1:${#value}-2}" elif [[ "${value}" =~ ^\'.*\'$ ]]; then value="${value:1:${#value}-2}" fi echo "${value}" } resolve_context_path() { local raw_path="$1" if [[ "${raw_path}" == /* ]]; then echo "${raw_path}" else echo "${repo_root}/${raw_path}" fi } ensure_context_repo() { local label="$1" local target_path="$2" local repo_url="$3" if [[ -d "${target_path}/.git" ]]; then echo "Using existing ${label} repo at ${target_path}" return 0 fi if [[ -e "${target_path}" && ! -d "${target_path}/.git" ]]; then echo "${label} context exists but is not a git repo: ${target_path}" echo "Set a valid path in ${env_file} or remove this path." exit 1 fi echo "Cloning ${label} repo into ${target_path}" mkdir -p "$(dirname "${target_path}")" if ! git clone --depth 1 "${repo_url}" "${target_path}"; then echo "Failed to clone ${label} from ${repo_url}" echo "Set the context manually in ${env_file} and rerun." exit 1 fi } if ! command -v docker >/dev/null 2>&1; then echo "docker is required" exit 1 fi if ! docker compose version >/dev/null 2>&1; then echo "docker compose plugin is required" exit 1 fi env_template="${repo_root}/.env.devprod.example" if [[ ! -f "${env_template}" ]]; then echo "Missing env template: ${env_template}" exit 1 fi if [[ "${env_file}" != /* ]]; then env_file="${repo_root}/${env_file}" fi created_env="0" if [[ ! -f "${env_file}" ]]; then cp "${env_template}" "${env_file}" created_env="1" echo "Created ${env_file} from template." fi if [[ "${created_env}" == "1" && "${accept_defaults}" != "1" ]]; then cat <}" current_profiles="" if command -v rg >/dev/null 2>&1; then profiles_line="$(rg -m1 '^COMPOSE_PROFILES=' "${env_file}" || true)" else profiles_line="$(grep -m1 '^COMPOSE_PROFILES=' "${env_file}" || true)" fi if [[ -n "${profiles_line}" ]]; then current_profiles="${profiles_line#COMPOSE_PROFILES=}" fi if [[ "${with_external_auth}" == "1" ]]; then if [[ -z "${current_profiles}" ]]; then current_profiles="external-auth" elif [[ ",${current_profiles}," != *",external-auth,"* ]]; then current_profiles="${current_profiles},external-auth" fi elif [[ "${with_external_auth}" == "0" ]]; then filtered=() IFS=',' read -r -a profile_parts <<< "${current_profiles}" for p in "${profile_parts[@]}"; do p_trimmed="$(echo "${p}" | tr -d '[:space:]')" [[ -z "${p_trimmed}" ]] && continue [[ "${p_trimmed}" == "external-auth" ]] && continue filtered+=("${p_trimmed}") done current_profiles="$(IFS=','; echo "${filtered[*]}")" fi if [[ -n "${current_profiles}" ]]; then export COMPOSE_PROFILES="${current_profiles}" else unset COMPOSE_PROFILES || true fi if [[ ",${COMPOSE_PROFILES:-}," == *",external-auth," ]]; then external_auth_context_raw="$(read_env_value "EXTERNAL_AUTH_CONTEXT" "../Qortal-External-Auth")" external_auth_context_path="$(resolve_context_path "${external_auth_context_raw}")" external_auth_repo_url="${EXTERNAL_AUTH_GIT_URL:-${default_external_auth_repo_url}}" ensure_context_repo "Qortal External Auth" "${external_auth_context_path}" "${external_auth_repo_url}" fi if [[ "${with_qortal}" == "0" && ",${COMPOSE_PROFILES:-}," == *",external-auth,"* ]]; then echo "Cannot run --without-qortal while external-auth profile is enabled." exit 1 fi echo "Using compose file: ${compose_file}" echo "Using env file: ${env_file}" echo "Using profiles: ${COMPOSE_PROFILES:-}" if [[ "${with_qortal}" == "1" ]]; then docker compose -f "${compose_file}" --env-file "${env_file}" up -d --build else mapfile -t services < <(docker compose -f "${compose_file}" --env-file "${env_file}" config --services) target_services=() for svc in "${services[@]}"; do if [[ "${svc}" == "qortal_node" ]]; then continue fi target_services+=("${svc}") done if [[ "${#target_services[@]}" -eq 0 ]]; then echo "No services selected to start." exit 1 fi docker compose -f "${compose_file}" --env-file "${env_file}" up -d --build "${target_services[@]}" fi if [[ "${skip_occ}" != "1" ]]; then set +e docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:enable qortal_integration >/dev/null 2>&1 docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ maintenance:repair >/dev/null 2>&1 occ_status=$? set -e if [[ "${occ_status}" -ne 0 ]]; then echo "Warning: Nextcloud OCC post-setup commands did not complete (app may still be initializing)." fi fi cat < Qortal Integration and run "Test Broker Connection". DONE