324 lines
9.5 KiB
Bash
Executable File
324 lines
9.5 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)"
|
|
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 <<USAGE
|
|
Usage: ./scripts/install-production-docker.sh [options]
|
|
|
|
Options:
|
|
--mode ssl|nossl Compose mode (default: nossl)
|
|
--ssl Alias for --mode ssl
|
|
--nossl Alias for --mode nossl
|
|
--with-external-auth Ensure external-auth profile is enabled
|
|
--without-external-auth Disable external-auth profile
|
|
--with-qortal Include qortal_node service (default)
|
|
--without-qortal Exclude qortal_node service
|
|
--env-file <path> 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 <<MSG
|
|
Edit ${env_file} with production values, then rerun.
|
|
Use --accept-defaults only for initial smoke testing.
|
|
MSG
|
|
exit 1
|
|
fi
|
|
|
|
qortal_context_raw="$(read_env_value "QORTAL_NODE_CONTEXT" "../qortal")"
|
|
qortal_context_path="$(resolve_context_path "${qortal_context_raw}")"
|
|
qortal_repo_url="${QORTAL_GIT_URL:-${default_qortal_repo_url}}"
|
|
ensure_context_repo "Qortal Core" "${qortal_context_path}" "${qortal_repo_url}"
|
|
|
|
compose_file="${repo_root}/docker-compose.devprod.nossl.yml"
|
|
if [[ "${mode}" == "ssl" ]]; then
|
|
compose_file="${repo_root}/docker-compose.devprod.yml"
|
|
fi
|
|
|
|
mkdir -p "${repo_root}/qortal/data"
|
|
if [[ -x "${repo_root}/scripts/ensure-qortal-settings.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-qortal-settings.sh"
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-qortal-start-args.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-qortal-start-args.sh" "${env_file}"
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/select-qortal-p2p-port.sh" ]]; then
|
|
"${repo_root}/scripts/select-qortal-p2p-port.sh" "${env_file}"
|
|
fi
|
|
if [[ -f "${repo_root}/scripts/ensure-broker-internal-token.sh" ]]; then
|
|
bash "${repo_root}/scripts/ensure-broker-internal-token.sh" "${env_file}"
|
|
fi
|
|
|
|
broker_internal_api_token="$(read_env_value "BROKER_INTERNAL_API_TOKEN" "")"
|
|
if [[ -z "${broker_internal_api_token}" ]]; then
|
|
echo "BROKER_INTERNAL_API_TOKEN is missing in ${env_file}"
|
|
echo "Run: bash scripts/ensure-broker-internal-token.sh ${env_file}"
|
|
exit 1
|
|
fi
|
|
export BROKER_INTERNAL_API_TOKEN="${broker_internal_api_token}"
|
|
|
|
broker_cors_allowed_origins="$(read_env_value "BROKER_CORS_ALLOWED_ORIGINS" "")"
|
|
if [[ -n "${broker_cors_allowed_origins}" ]]; then
|
|
export BROKER_CORS_ALLOWED_ORIGINS="${broker_cors_allowed_origins}"
|
|
fi
|
|
echo "Broker auth env loaded from ${env_file}: token_set=yes cors_origins=${broker_cors_allowed_origins:-<empty>}"
|
|
|
|
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:-<none>}"
|
|
|
|
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 <<DONE
|
|
Installation bootstrap complete.
|
|
|
|
Next steps:
|
|
1. Verify running services:
|
|
docker compose -f ${compose_file} --env-file ${env_file} ps
|
|
2. If using external-auth profile and node API key is required, verify env contains:
|
|
QORTAL_AUTH_NODE_API_KEY=...
|
|
QORTAL_AUTH_NODE_API_KEY_MODE=paths
|
|
QORTAL_AUTH_NODE_API_KEY_PATHS=/
|
|
3. Broker internal API security:
|
|
BROKER_INTERNAL_API_TOKEN is generated/required in ${env_file}
|
|
and must be set in Nextcloud admin as "Broker Internal API Token"
|
|
if your Nextcloud app container does not receive QORTAL_BROKER_INTERNAL_API_TOKEN env.
|
|
4. Open Nextcloud Admin > Qortal Integration and run "Test Broker Connection".
|
|
DONE
|