1102 lines
42 KiB
Bash
Executable File
1102 lines
42 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)"
|
|
source "${repo_root}/scripts/lib-compose-project.sh"
|
|
|
|
mode="nossl"
|
|
env_file="${repo_root}/.env.devprod"
|
|
force_register="0"
|
|
skip_register="0"
|
|
force_api_key="0"
|
|
skip_api_key="0"
|
|
skip_occ="0"
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Usage: ./scripts/finish-initial-setup.sh [options]
|
|
|
|
Finalizes first-run setup for bundled services:
|
|
1) registers broker app with bundled external-auth (if needed)
|
|
2) optionally generates node API key and wires external-auth env
|
|
3) recreates impacted services
|
|
4) ensures user_oidc + integration custom apps are enabled in Nextcloud
|
|
|
|
Options:
|
|
--mode ssl|nossl Compose mode (default: nossl)
|
|
--ssl Alias for --mode ssl
|
|
--nossl Alias for --mode nossl
|
|
--env-file <path> Env file to use (default: .env.devprod)
|
|
--force-register Always re-register app in external-auth
|
|
--skip-register Skip external-auth app registration
|
|
--force-api-key Always generate and set node API key
|
|
--skip-api-key Skip node API key generation
|
|
--skip-occ Skip Nextcloud OCC app enable/repair steps
|
|
-h, --help Show this help
|
|
USAGE
|
|
}
|
|
|
|
read_kv() {
|
|
local key="$1"
|
|
local line
|
|
line="$(grep -m1 -E "^${key}=" "${env_file}" || true)"
|
|
if [[ -z "${line}" ]]; then
|
|
return 1
|
|
fi
|
|
echo "${line#*=}"
|
|
}
|
|
|
|
read_trimmed_file_value() {
|
|
local file="$1"
|
|
if [[ -z "${file}" || ! -f "${file}" ]]; then
|
|
return 1
|
|
fi
|
|
tr -d '\r\n' < "${file}" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
|
|
}
|
|
|
|
resolve_path_from_env() {
|
|
local key="$1"
|
|
local default_path="$2"
|
|
local value
|
|
value="$(read_kv "${key}" || true)"
|
|
value="${value:-${default_path}}"
|
|
if [[ "${value}" == /* ]]; then
|
|
echo "${value}"
|
|
else
|
|
echo "${repo_root}/${value}"
|
|
fi
|
|
}
|
|
|
|
set_kv() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local esc
|
|
esc="${value//\\/\\\\}"
|
|
esc="${esc//&/\\&}"
|
|
esc="${esc//|/\\|}"
|
|
if grep -q -E "^${key}=" "${env_file}"; then
|
|
sed -i -E "s|^${key}=.*|${key}=${esc}|" "${env_file}"
|
|
else
|
|
echo "${key}=${value}" >> "${env_file}"
|
|
fi
|
|
}
|
|
|
|
extract_json_value() {
|
|
local json="$1"
|
|
local key="$2"
|
|
if command -v jq >/dev/null 2>&1; then
|
|
echo "${json}" | jq -r --arg key "${key}" '.[$key] // empty' 2>/dev/null || true
|
|
return 0
|
|
fi
|
|
echo "${json}" | sed -n "s/.*\"${key}\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" | head -n1
|
|
}
|
|
|
|
json_escape() {
|
|
local input="$1"
|
|
input="${input//\\/\\\\}"
|
|
input="${input//\"/\\\"}"
|
|
input="${input//$'\n'/ }"
|
|
input="${input//$'\r'/ }"
|
|
echo "${input}"
|
|
}
|
|
|
|
http_retry_count() {
|
|
local raw="${QNI_HTTP_RETRY_ATTEMPTS:-40}"
|
|
if [[ "${raw}" =~ ^[0-9]+$ ]] && (( raw > 0 )); then
|
|
echo "${raw}"
|
|
return 0
|
|
fi
|
|
echo "40"
|
|
}
|
|
|
|
http_retry_sleep() {
|
|
local raw="${QNI_HTTP_RETRY_SLEEP_SECONDS:-15}"
|
|
if [[ "${raw}" =~ ^[0-9]+$ ]] && (( raw > 0 )); then
|
|
echo "${raw}"
|
|
return 0
|
|
fi
|
|
echo "15"
|
|
}
|
|
|
|
wait_for_local_http() {
|
|
local url="$1"
|
|
local label="$2"
|
|
local attempts sleep_seconds attempt
|
|
attempts="$(http_retry_count)"
|
|
sleep_seconds="$(http_retry_sleep)"
|
|
for attempt in $(seq 1 "${attempts}"); do
|
|
if curl -fsS --max-time 10 "${url}" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
if [[ "${attempt}" -lt "${attempts}" ]]; then
|
|
sleep "${sleep_seconds}"
|
|
fi
|
|
done
|
|
echo "Timed out waiting for ${label} at ${url}"
|
|
return 1
|
|
}
|
|
|
|
register_external_auth_app_with_retry() {
|
|
local register_url="$1"
|
|
local payload="$2"
|
|
local attempts sleep_seconds attempt register_resp new_app_id new_app_secret
|
|
attempts="$(http_retry_count)"
|
|
sleep_seconds="$(http_retry_sleep)"
|
|
for attempt in $(seq 1 "${attempts}"); do
|
|
register_resp="$(curl -fsS --max-time 15 -X POST "${register_url}" -H 'content-type: application/json' -d "${payload}" || true)"
|
|
new_app_id="$(extract_json_value "${register_resp}" "appId")"
|
|
new_app_secret="$(extract_json_value "${register_resp}" "appSecret")"
|
|
if [[ -n "${new_app_id}" && -n "${new_app_secret}" ]]; then
|
|
printf '%s\n%s\n' "${new_app_id}" "${new_app_secret}"
|
|
return 0
|
|
fi
|
|
if [[ "${attempt}" -lt "${attempts}" ]]; then
|
|
sleep "${sleep_seconds}"
|
|
fi
|
|
done
|
|
echo "Failed to register app via external-auth after ${attempts} attempts."
|
|
echo "Last response: ${register_resp:-<empty>}"
|
|
return 1
|
|
}
|
|
|
|
test_external_auth_session() {
|
|
local app_id="$1"
|
|
local app_secret="$2"
|
|
local response token
|
|
if [[ -z "${app_id}" || -z "${app_secret}" ]]; then
|
|
return 1
|
|
fi
|
|
response="$(
|
|
curl -sS --max-time 15 -X POST "http://127.0.0.1:${external_auth_host_port}/sessions" \
|
|
-H 'content-type: application/json' \
|
|
-d "{\"appId\":\"$(json_escape "${app_id}")\",\"appSecret\":\"$(json_escape "${app_secret}")\"}" || true
|
|
)"
|
|
token="$(extract_json_value "${response}" "token")"
|
|
[[ -n "${token}" ]]
|
|
}
|
|
|
|
generate_qortal_api_key_with_retry() {
|
|
local generate_url="$1"
|
|
local attempts sleep_seconds attempt key_resp generated_key
|
|
attempts="$(http_retry_count)"
|
|
sleep_seconds="$(http_retry_sleep)"
|
|
for attempt in $(seq 1 "${attempts}"); do
|
|
key_resp="$(curl -fsS --max-time 20 -X POST "${generate_url}" || true)"
|
|
generated_key="$(extract_json_value "${key_resp}" "apiKey")"
|
|
if [[ -z "${generated_key}" ]]; then
|
|
generated_key="$(echo "${key_resp}" | tr -d '\r\n')"
|
|
fi
|
|
if [[ -n "${generated_key}" && "${generated_key}" != "null" ]]; then
|
|
printf '%s\n' "${generated_key}"
|
|
return 0
|
|
fi
|
|
if [[ "${attempt}" -lt "${attempts}" ]]; then
|
|
sleep "${sleep_seconds}"
|
|
fi
|
|
done
|
|
echo "Failed to generate node API key after ${attempts} attempts."
|
|
echo "Last response: ${key_resp:-<empty>}"
|
|
return 1
|
|
}
|
|
|
|
ensure_docker_access() {
|
|
local docker_path=""
|
|
docker_path="$(type -P docker || true)"
|
|
if [[ -z "${docker_path}" ]]; then
|
|
echo "docker is required"
|
|
exit 1
|
|
fi
|
|
|
|
if "${docker_path}" info >/dev/null 2>&1; then
|
|
DOCKER_CMD=("${docker_path}")
|
|
DOCKER_REQUIRES_SUDO="0"
|
|
return 0
|
|
fi
|
|
|
|
if ! command -v sudo >/dev/null 2>&1; then
|
|
echo "docker is installed but this user cannot access it, and sudo is unavailable."
|
|
exit 1
|
|
fi
|
|
|
|
if sudo -n "${docker_path}" info >/dev/null 2>&1; then
|
|
DOCKER_CMD=(sudo "${docker_path}")
|
|
DOCKER_REQUIRES_SUDO="1"
|
|
echo "Using sudo for docker commands."
|
|
return 0
|
|
fi
|
|
|
|
echo "docker requires elevated privileges on this machine."
|
|
echo "You may be prompted for your sudo password."
|
|
if sudo "${docker_path}" info >/dev/null 2>&1; then
|
|
DOCKER_CMD=(sudo "${docker_path}")
|
|
DOCKER_REQUIRES_SUDO="1"
|
|
echo "Using sudo for docker commands."
|
|
return 0
|
|
fi
|
|
|
|
echo "Unable to access docker, even with sudo."
|
|
exit 1
|
|
}
|
|
|
|
docker() {
|
|
"${DOCKER_CMD[@]}" "$@"
|
|
}
|
|
|
|
compose_cmd() {
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" "$@"
|
|
}
|
|
|
|
read_kv_first() {
|
|
local value=""
|
|
local key
|
|
for key in "$@"; do
|
|
value="$(read_kv "${key}" || true)"
|
|
if [[ -n "${value}" ]]; then
|
|
echo "${value}"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
normalize_bool_01() {
|
|
local raw="${1:-}"
|
|
case "$(echo "${raw}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" in
|
|
1|true|yes|y|on)
|
|
echo "1"
|
|
;;
|
|
0|false|no|n|off)
|
|
echo "0"
|
|
;;
|
|
*)
|
|
echo ""
|
|
;;
|
|
esac
|
|
}
|
|
|
|
occ_app_set_qortal() {
|
|
local key="$1"
|
|
local value="$2"
|
|
compose_cmd exec -T app php occ config:app:set qortal_integration "${key}" --value="${value}" >/dev/null 2>&1
|
|
}
|
|
|
|
occ_app_set_custom_pwa() {
|
|
local key="$1"
|
|
local value="$2"
|
|
compose_cmd exec -T app php occ config:app:set custom_pwa "${key}" --value="${value}" >/dev/null 2>&1
|
|
}
|
|
|
|
normalize_vapid_subject() {
|
|
local raw="${1:-}"
|
|
raw="$(echo "${raw}" | tr -d '\r' | xargs)"
|
|
if [[ -z "${raw}" ]]; then
|
|
echo ""
|
|
return 0
|
|
fi
|
|
if [[ "${raw}" == mailto:* || "${raw}" == http://* || "${raw}" == https://* ]]; then
|
|
echo "${raw}"
|
|
return 0
|
|
fi
|
|
if [[ "${raw}" == *"@"* ]]; then
|
|
echo "mailto:${raw}"
|
|
return 0
|
|
fi
|
|
echo "${raw}"
|
|
}
|
|
|
|
derive_email_domain_from_host() {
|
|
local raw_host="${1:-}"
|
|
local host parts_count last_part second_last third_last
|
|
raw_host="$(echo "${raw_host}" | tr -d '\r' | xargs)"
|
|
host="${raw_host#http://}"
|
|
host="${host#https://}"
|
|
host="${host%%/*}"
|
|
host="${host%%:*}"
|
|
host="${host#[}"
|
|
host="${host%]}"
|
|
host="${host,,}"
|
|
|
|
if [[ -z "${host}" ]]; then
|
|
echo ""
|
|
return 0
|
|
fi
|
|
if [[ "${host}" == "localhost" ]]; then
|
|
echo "localhost"
|
|
return 0
|
|
fi
|
|
if [[ "${host}" =~ ^[0-9.]+$ ]]; then
|
|
echo "${host}"
|
|
return 0
|
|
fi
|
|
if [[ "${host}" == *:* ]]; then
|
|
echo "${host}"
|
|
return 0
|
|
fi
|
|
|
|
parts_count="$(awk -F'.' '{print NF}' <<< "${host}")"
|
|
if [[ "${parts_count}" -le 2 ]]; then
|
|
echo "${host}"
|
|
return 0
|
|
fi
|
|
|
|
last_part="${host##*.}"
|
|
second_last="${host%.*}"
|
|
second_last="${second_last##*.}"
|
|
third_last="${host%.*.*}"
|
|
third_last="${third_last##*.}"
|
|
if [[ ${#last_part} -eq 2 && ${#second_last} -le 3 ]]; then
|
|
echo "${third_last}.${second_last}.${last_part}"
|
|
return 0
|
|
fi
|
|
echo "${second_last}.${last_part}"
|
|
}
|
|
|
|
custom_pwa_should_install() {
|
|
local raw normalized
|
|
raw="$(read_kv_first "INSTALL_CUSTOM_PWA" || true)"
|
|
normalized="$(normalize_bool_01 "${raw}")"
|
|
if [[ -z "${normalized}" ]]; then
|
|
normalized="1"
|
|
fi
|
|
[[ "${normalized}" == "1" ]]
|
|
}
|
|
|
|
generate_custom_pwa_vapid_keypair() {
|
|
local output public_key private_key php_code
|
|
php_code='$cfg=["private_key_type"=>OPENSSL_KEYTYPE_EC,"curve_name"=>"prime256v1"];$res=openssl_pkey_new($cfg);if($res===false){fwrite(STDERR,"generate_failed\n");exit(1);}$details=openssl_pkey_get_details($res);if(!is_array($details)||!isset($details["ec"])||!is_array($details["ec"])||!isset($details["ec"]["x"],$details["ec"]["y"],$details["ec"]["d"])){fwrite(STDERR,"details_failed\n");exit(1);}$rawPublic="\x04".$details["ec"]["x"].$details["ec"]["y"];$b64=function($v){return rtrim(strtr(base64_encode($v),"+/","-_"),"=");};echo "PUBLIC=".$b64($rawPublic).PHP_EOL;echo "PRIVATE=".$b64($details["ec"]["d"]).PHP_EOL;'
|
|
|
|
output=""
|
|
if command -v php >/dev/null 2>&1; then
|
|
output="$(php -r "${php_code}" 2>/dev/null || true)"
|
|
fi
|
|
if [[ -z "${output}" ]]; then
|
|
output="$(compose_cmd exec -T app php -r "${php_code}" 2>/dev/null || true)"
|
|
fi
|
|
if [[ -z "${output}" && $(command -v openssl >/dev/null 2>&1; echo $?) -eq 0 ]]; then
|
|
output="$(generate_custom_pwa_vapid_keypair_openssl || true)"
|
|
fi
|
|
public_key="$(echo "${output}" | sed -n 's/^PUBLIC=//p' | head -n1)"
|
|
private_key="$(echo "${output}" | sed -n 's/^PRIVATE=//p' | head -n1)"
|
|
if [[ -z "${public_key}" || -z "${private_key}" ]]; then
|
|
return 1
|
|
fi
|
|
echo "${public_key}|${private_key}"
|
|
return 0
|
|
}
|
|
|
|
generate_custom_pwa_vapid_keypair_openssl() {
|
|
local tmp_key key_text priv_hex pub_hex public_key private_key
|
|
tmp_key="$(mktemp)"
|
|
if ! openssl ecparam -name prime256v1 -genkey -noout -out "${tmp_key}" >/dev/null 2>&1; then
|
|
rm -f "${tmp_key}" >/dev/null 2>&1 || true
|
|
return 1
|
|
fi
|
|
key_text="$(openssl ec -in "${tmp_key}" -text -noout 2>/dev/null || true)"
|
|
rm -f "${tmp_key}" >/dev/null 2>&1 || true
|
|
if [[ -z "${key_text}" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
priv_hex="$(echo "${key_text}" | awk '/priv:/{flag=1;next}/pub:/{flag=0}flag' | tr -d '[:space:]:')"
|
|
pub_hex="$(echo "${key_text}" | awk '/pub:/{flag=1;next}/ASN1 OID:/{flag=0}flag' | tr -d '[:space:]:')"
|
|
if [[ -z "${priv_hex}" || -z "${pub_hex}" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
public_key="$(echo "${pub_hex}" | xxd -r -p | base64 | tr '+/' '-_' | tr -d '=' | tr -d '\n' || true)"
|
|
private_key="$(echo "${priv_hex}" | xxd -r -p | base64 | tr '+/' '-_' | tr -d '=' | tr -d '\n' || true)"
|
|
if [[ -z "${public_key}" || -z "${private_key}" ]]; then
|
|
return 1
|
|
fi
|
|
echo "PUBLIC=${public_key}"
|
|
echo "PRIVATE=${private_key}"
|
|
return 0
|
|
}
|
|
|
|
ensure_custom_pwa_vapid_env() {
|
|
local subject public_key private_key admin_email nextcloud_domain nextcloud_root_domain default_subject generated generated_public generated_private status
|
|
status=0
|
|
subject="$(read_kv_first "CUSTOM_PWA_VAPID_SUBJECT" || true)"
|
|
public_key="$(read_kv_first "CUSTOM_PWA_VAPID_PUBLIC_KEY" || true)"
|
|
private_key="$(read_kv_first "CUSTOM_PWA_VAPID_PRIVATE_KEY" || true)"
|
|
|
|
if [[ -z "${subject}" ]]; then
|
|
admin_email="$(read_kv "NEXTCLOUD_ADMIN_EMAIL" || true)"
|
|
nextcloud_domain="$(read_kv "NEXTCLOUD_DOMAIN" || true)"
|
|
nextcloud_root_domain="$(derive_email_domain_from_host "${nextcloud_domain}")"
|
|
if [[ -n "${admin_email}" ]]; then
|
|
default_subject="mailto:${admin_email}"
|
|
elif [[ -n "${nextcloud_root_domain}" ]]; then
|
|
default_subject="mailto:admin@${nextcloud_root_domain}"
|
|
else
|
|
default_subject="mailto:admin@localhost"
|
|
fi
|
|
subject="${default_subject}"
|
|
set_kv "CUSTOM_PWA_VAPID_SUBJECT" "${subject}"
|
|
echo "Set CUSTOM_PWA_VAPID_SUBJECT in ${env_file}."
|
|
else
|
|
subject="$(normalize_vapid_subject "${subject}")"
|
|
set_kv "CUSTOM_PWA_VAPID_SUBJECT" "${subject}"
|
|
fi
|
|
|
|
if [[ -z "${public_key}" || -z "${private_key}" ]]; then
|
|
generated="$(generate_custom_pwa_vapid_keypair || true)"
|
|
generated_public="${generated%%|*}"
|
|
generated_private="${generated#*|}"
|
|
if [[ -z "${generated_public}" || -z "${generated_private}" || "${generated_public}" == "${generated_private}" ]]; then
|
|
echo "Warning: failed to auto-generate CustomPWA VAPID keypair; set CUSTOM_PWA_VAPID_PUBLIC_KEY and CUSTOM_PWA_VAPID_PRIVATE_KEY manually."
|
|
status=1
|
|
else
|
|
set_kv "CUSTOM_PWA_VAPID_PUBLIC_KEY" "${generated_public}"
|
|
set_kv "CUSTOM_PWA_VAPID_PRIVATE_KEY" "${generated_private}"
|
|
echo "Generated CustomPWA VAPID keypair and saved to ${env_file}."
|
|
fi
|
|
fi
|
|
|
|
return "${status}"
|
|
}
|
|
|
|
sync_custom_pwa_settings() {
|
|
local status subject public_key private_key email_fallback sms_fallback normalized_bool
|
|
status=0
|
|
|
|
subject="$(read_kv_first "CUSTOM_PWA_VAPID_SUBJECT" || true)"
|
|
public_key="$(read_kv_first "CUSTOM_PWA_VAPID_PUBLIC_KEY" || true)"
|
|
private_key="$(read_kv_first "CUSTOM_PWA_VAPID_PRIVATE_KEY" || true)"
|
|
if [[ -n "${subject}" ]]; then
|
|
occ_app_set_custom_pwa "vapid_subject" "${subject}" || status=1
|
|
fi
|
|
if [[ -n "${public_key}" ]]; then
|
|
occ_app_set_custom_pwa "vapid_public_key" "${public_key}" || status=1
|
|
fi
|
|
if [[ -n "${private_key}" ]]; then
|
|
occ_app_set_custom_pwa "vapid_private_key" "${private_key}" || status=1
|
|
fi
|
|
|
|
email_fallback="$(read_kv_first "CUSTOM_PWA_EMAIL_FALLBACK_ENABLED" || true)"
|
|
normalized_bool="$(normalize_bool_01 "${email_fallback}")"
|
|
if [[ -n "${normalized_bool}" ]]; then
|
|
occ_app_set_custom_pwa "email_fallback_enabled" "${normalized_bool}" || status=1
|
|
else
|
|
occ_app_set_custom_pwa "email_fallback_enabled" "1" || status=1
|
|
fi
|
|
|
|
sms_fallback="$(read_kv_first "CUSTOM_PWA_SMS_CRITICAL_FALLBACK_ENABLED" || true)"
|
|
normalized_bool="$(normalize_bool_01 "${sms_fallback}")"
|
|
if [[ -n "${normalized_bool}" ]]; then
|
|
occ_app_set_custom_pwa "sms_critical_fallback_enabled" "${normalized_bool}" || status=1
|
|
else
|
|
occ_app_set_custom_pwa "sms_critical_fallback_enabled" "0" || status=1
|
|
fi
|
|
|
|
return "${status}"
|
|
}
|
|
|
|
sync_qortal_integration_billing_settings() {
|
|
local sc_mode env_mode normalized_bool value nextcloud_public_url nextcloud_domain
|
|
local chd_base_url chd_sync_path chd_register_path chd_checkout_path chd_catalog_path chd_catalog_category_id chd_registration_token
|
|
local billing_provider billing_sync_mode billing_enabled billing_managed
|
|
local billing_contact_name billing_contact_email billing_company_name
|
|
local qortal_node_url qortal_gateway_url qortal_node_api_key
|
|
local msp_mode_enabled msp_admin_group
|
|
local status=0
|
|
|
|
sc_mode="$(compose_cmd exec -T app php occ config:app:get qortal_integration sc_mode 2>/dev/null | tr -d '\r' || true)"
|
|
env_mode="$(read_kv_first "NUQLOUD_MODE" "SOVEREIGN_MODE" "SC_MODE" || true)"
|
|
if [[ -n "${env_mode}" ]]; then
|
|
sc_mode="${env_mode}"
|
|
occ_app_set_qortal "sc_mode" "${env_mode}" || status=1
|
|
fi
|
|
if [[ -z "${sc_mode}" ]]; then
|
|
sc_mode="powered"
|
|
fi
|
|
|
|
if [[ "${sc_mode}" != "full_qortal" ]]; then
|
|
billing_enabled="$(read_kv_first "NUQLOUD_BILLING_ENABLED" "SOVEREIGN_BILLING_ENABLED" "SC_BILLING_ENABLED" || true)"
|
|
normalized_bool="$(normalize_bool_01 "${billing_enabled}")"
|
|
if [[ -n "${normalized_bool}" ]]; then
|
|
occ_app_set_qortal "sc_billing_enabled" "${normalized_bool}" || status=1
|
|
else
|
|
occ_app_set_qortal "sc_billing_enabled" "1" || status=1
|
|
fi
|
|
|
|
billing_managed="$(read_kv_first "NUQLOUD_BILLING_MANAGED_BY_CHD" "SOVEREIGN_BILLING_MANAGED_BY_CHD" "SC_BILLING_MANAGED_BY_CHD" || true)"
|
|
normalized_bool="$(normalize_bool_01 "${billing_managed}")"
|
|
if [[ -n "${normalized_bool}" ]]; then
|
|
occ_app_set_qortal "sc_billing_managed_by_chd" "${normalized_bool}" || status=1
|
|
else
|
|
occ_app_set_qortal "sc_billing_managed_by_chd" "1" || status=1
|
|
fi
|
|
|
|
billing_provider="$(read_kv_first "NUQLOUD_BILLING_PROVIDER" "SOVEREIGN_BILLING_PROVIDER" "SC_BILLING_PROVIDER" || true)"
|
|
if [[ -z "${billing_provider}" ]]; then
|
|
billing_provider="paymenter"
|
|
fi
|
|
occ_app_set_qortal "sc_billing_provider" "${billing_provider}" || status=1
|
|
|
|
billing_sync_mode="$(read_kv_first "NUQLOUD_BILLING_SYNC_MODE" "SOVEREIGN_BILLING_SYNC_MODE" "SC_BILLING_SYNC_MODE" || true)"
|
|
if [[ -n "${billing_sync_mode}" ]]; then
|
|
occ_app_set_qortal "sc_billing_sync_mode" "${billing_sync_mode}" || status=1
|
|
fi
|
|
fi
|
|
|
|
nextcloud_public_url="$(read_kv "NEXTCLOUD_PUBLIC_URL" || true)"
|
|
if [[ -n "${nextcloud_public_url}" ]]; then
|
|
occ_app_set_qortal "nextcloud_public_url" "${nextcloud_public_url}" || status=1
|
|
fi
|
|
|
|
qortal_node_url="$(read_kv_first "QORTAL_NODE_URL" "QORTAL_AUTH_NODE_URL" || true)"
|
|
if [[ -n "${qortal_node_url}" ]]; then
|
|
occ_app_set_qortal "qortal_node_url" "${qortal_node_url}" || status=1
|
|
fi
|
|
qortal_gateway_url="$(read_kv_first "QORTAL_PUBLIC_GATEWAY_URL" "QORTAL_GATEWAY_PUBLIC_URL" "QORTAL_GATEWAY_URL" || true)"
|
|
if [[ -n "${qortal_gateway_url}" ]]; then
|
|
occ_app_set_qortal "qortal_gateway_url" "${qortal_gateway_url}" || status=1
|
|
fi
|
|
qortal_node_api_key="$(read_kv_first "QORTAL_NODE_API_KEY" "QORTAL_AUTH_NODE_API_KEY" || true)"
|
|
if [[ -n "${qortal_node_api_key}" ]]; then
|
|
occ_app_set_qortal "qortal_node_api_key" "${qortal_node_api_key}" || status=1
|
|
fi
|
|
|
|
chd_base_url="$(read_kv_first "NUQLOUD_CHD_BASE_URL" "SOVEREIGN_CHD_BASE_URL" "SC_CHD_BASE_URL" || true)"
|
|
chd_sync_path="$(read_kv_first "NUQLOUD_CHD_SYNC_PATH" "SOVEREIGN_CHD_SYNC_PATH" "SC_CHD_SYNC_PATH" || true)"
|
|
chd_register_path="$(read_kv_first "NUQLOUD_CHD_REGISTER_PATH" "SOVEREIGN_CHD_REGISTER_PATH" "SC_CHD_REGISTER_PATH" || true)"
|
|
chd_checkout_path="$(read_kv_first "NUQLOUD_CHD_CHECKOUT_PATH" "SOVEREIGN_CHD_CHECKOUT_PATH" "SC_CHD_CHECKOUT_PATH" || true)"
|
|
chd_catalog_path="$(read_kv_first "NUQLOUD_CHD_CATALOG_PATH" "SOVEREIGN_CHD_CATALOG_PATH" "SC_CHD_CATALOG_PATH" || true)"
|
|
chd_catalog_category_id="$(read_kv_first "NUQLOUD_CHD_CATALOG_CATEGORY_ID" "SOVEREIGN_CHD_CATALOG_CATEGORY_ID" "SC_CHD_CATALOG_CATEGORY_ID" || true)"
|
|
chd_registration_token="$(read_kv_first "NUQLOUD_CHD_REGISTRATION_TOKEN" "SOVEREIGN_CHD_REGISTRATION_TOKEN" "SC_CHD_REGISTRATION_TOKEN" || true)"
|
|
|
|
if [[ -n "${chd_base_url}" ]]; then
|
|
occ_app_set_qortal "sc_chd_base_url" "${chd_base_url}" || status=1
|
|
fi
|
|
if [[ -n "${chd_sync_path}" ]]; then
|
|
occ_app_set_qortal "sc_chd_sync_path" "${chd_sync_path}" || status=1
|
|
fi
|
|
if [[ -n "${chd_register_path}" ]]; then
|
|
occ_app_set_qortal "sc_chd_register_path" "${chd_register_path}" || status=1
|
|
fi
|
|
if [[ -n "${chd_checkout_path}" ]]; then
|
|
occ_app_set_qortal "sc_chd_checkout_path" "${chd_checkout_path}" || status=1
|
|
fi
|
|
if [[ -n "${chd_catalog_path}" ]]; then
|
|
occ_app_set_qortal "sc_chd_catalog_path" "${chd_catalog_path}" || status=1
|
|
fi
|
|
if [[ -n "${chd_catalog_category_id}" ]]; then
|
|
occ_app_set_qortal "sc_chd_catalog_category_id" "${chd_catalog_category_id}" || status=1
|
|
fi
|
|
if [[ -n "${chd_registration_token}" ]]; then
|
|
occ_app_set_qortal "sc_chd_registration_token" "${chd_registration_token}" || status=1
|
|
fi
|
|
|
|
billing_contact_name="$(read_kv_first "NUQLOUD_BILLING_CONTACT_NAME" "SOVEREIGN_BILLING_CONTACT_NAME" "SC_BILLING_CONTACT_NAME" "NEXTCLOUD_ADMIN_USER" || true)"
|
|
billing_contact_email="$(read_kv_first "NUQLOUD_BILLING_CONTACT_EMAIL" "SOVEREIGN_BILLING_CONTACT_EMAIL" "SC_BILLING_CONTACT_EMAIL" "NEXTCLOUD_ADMIN_EMAIL" || true)"
|
|
nextcloud_domain="$(read_kv "NEXTCLOUD_DOMAIN" || true)"
|
|
billing_company_name="$(read_kv_first "NUQLOUD_BILLING_COMPANY_NAME" "SOVEREIGN_BILLING_COMPANY_NAME" "SC_BILLING_COMPANY_NAME" || true)"
|
|
if [[ -z "${billing_company_name}" && -n "${nextcloud_domain}" ]]; then
|
|
billing_company_name="${nextcloud_domain}"
|
|
fi
|
|
if [[ -n "${billing_contact_name}" ]]; then
|
|
occ_app_set_qortal "sc_billing_contact_name" "${billing_contact_name}" || status=1
|
|
fi
|
|
if [[ -n "${billing_contact_email}" ]]; then
|
|
occ_app_set_qortal "sc_billing_contact_email" "${billing_contact_email}" || status=1
|
|
fi
|
|
if [[ -n "${billing_company_name}" ]]; then
|
|
occ_app_set_qortal "sc_billing_company_name" "${billing_company_name}" || status=1
|
|
fi
|
|
|
|
msp_mode_enabled="$(read_kv_first "NUQLOUD_MSP_MODE_ENABLED" "MSP_MODE_ENABLED" "SC_MSP_MODE_ENABLED" || true)"
|
|
normalized_bool="$(normalize_bool_01 "${msp_mode_enabled}")"
|
|
if [[ -n "${normalized_bool}" ]]; then
|
|
occ_app_set_qortal "msp_mode_enabled" "${normalized_bool}" || status=1
|
|
fi
|
|
msp_admin_group="$(read_kv_first "NUQLOUD_MSP_ADMIN_GROUP" "MSP_ADMIN_GROUP" "SC_MSP_ADMIN_GROUP" || true)"
|
|
if [[ -z "${msp_admin_group}" ]]; then
|
|
msp_admin_group="MSP_Admin"
|
|
fi
|
|
occ_app_set_qortal "msp_admin_group" "${msp_admin_group}" || status=1
|
|
|
|
if [[ "${sc_mode}" != "full_qortal" ]]; then
|
|
if [[ -z "${chd_registration_token}" ]]; then
|
|
echo "Warning: CHD registration token is not set in ${env_file} (expected NUQLOUD_CHD_REGISTRATION_TOKEN, SOVEREIGN_CHD_REGISTRATION_TOKEN, or SC_CHD_REGISTRATION_TOKEN)."
|
|
echo "If your CHD connector requires a registration token, first-time CHD connect/checkout bootstrap will fail until it is configured."
|
|
echo "If your CHD connector allows open registration, this can be left empty."
|
|
fi
|
|
fi
|
|
|
|
return "${status}"
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--mode)
|
|
mode="${2:-}"
|
|
shift 2
|
|
;;
|
|
--mode=*)
|
|
mode="${1#*=}"
|
|
shift
|
|
;;
|
|
--ssl)
|
|
mode="ssl"
|
|
shift
|
|
;;
|
|
--nossl)
|
|
mode="nossl"
|
|
shift
|
|
;;
|
|
--env-file)
|
|
env_file="${2:-}"
|
|
shift 2
|
|
;;
|
|
--env-file=*)
|
|
env_file="${1#*=}"
|
|
shift
|
|
;;
|
|
--force-register)
|
|
force_register="1"
|
|
shift
|
|
;;
|
|
--skip-register)
|
|
skip_register="1"
|
|
shift
|
|
;;
|
|
--force-api-key)
|
|
force_api_key="1"
|
|
shift
|
|
;;
|
|
--skip-api-key)
|
|
skip_api_key="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
|
|
|
|
if [[ "${env_file}" != /* ]]; then
|
|
env_file="${repo_root}/${env_file}"
|
|
fi
|
|
if [[ ! -f "${env_file}" ]]; then
|
|
echo "Missing env file: ${env_file}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "$(type -P docker || true)" ]]; then
|
|
echo "docker is required"
|
|
exit 1
|
|
fi
|
|
ensure_docker_access
|
|
if ! docker compose version >/dev/null 2>&1; then
|
|
echo "docker compose plugin is required"
|
|
exit 1
|
|
fi
|
|
if ! export_compose_project_name_from_env "${env_file}"; then
|
|
warn_missing_compose_project_name "${env_file}"
|
|
fi
|
|
|
|
compose_file="${repo_root}/docker-compose.devprod.nossl.yml"
|
|
if [[ "${mode}" == "ssl" ]]; then
|
|
compose_file="${repo_root}/docker-compose.devprod.yml"
|
|
fi
|
|
|
|
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 -E '^COMPOSE_PROFILES=' "${env_file}" || true)"
|
|
fi
|
|
if [[ -n "${profiles_line}" ]]; then
|
|
current_profiles="${profiles_line#COMPOSE_PROFILES=}"
|
|
fi
|
|
|
|
external_auth_base_url="$(read_kv "QORTAL_EXTERNAL_AUTH_BASE_URL" || true)"
|
|
if [[ "${external_auth_base_url}" == *"external_auth"* ]]; then
|
|
if [[ "${external_auth_base_url}" != "http://external_auth:3191" ]]; then
|
|
echo "Normalizing QORTAL_EXTERNAL_AUTH_BASE_URL to bundled internal URL http://external_auth:3191"
|
|
set_kv "QORTAL_EXTERNAL_AUTH_BASE_URL" "http://external_auth:3191"
|
|
external_auth_base_url="http://external_auth:3191"
|
|
fi
|
|
if [[ -z "${current_profiles}" ]]; then
|
|
current_profiles="external-auth"
|
|
elif [[ ",${current_profiles}," != *",external-auth,"* ]]; then
|
|
current_profiles="${current_profiles},external-auth"
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${current_profiles}" ]]; then
|
|
export COMPOSE_PROFILES="${current_profiles}"
|
|
else
|
|
unset COMPOSE_PROFILES || true
|
|
fi
|
|
|
|
if [[ "${external_auth_base_url}" != *"external_auth"* ]]; then
|
|
echo "QORTAL_EXTERNAL_AUTH_BASE_URL does not reference bundled external_auth."
|
|
echo "Current value: ${external_auth_base_url:-<empty>}"
|
|
echo "This script only handles bundled external-auth finishing tasks."
|
|
exit 1
|
|
fi
|
|
|
|
external_auth_host_port="$(read_kv "EXTERNAL_AUTH_PORT" || true)"
|
|
external_auth_host_port="${external_auth_host_port:-3191}"
|
|
node_api_host_port="$(read_kv "QORTAL_NODE_API_HOST_PORT" || true)"
|
|
node_api_host_port="${node_api_host_port:-12391}"
|
|
|
|
echo "Using compose file: ${compose_file}"
|
|
echo "Using env file: ${env_file}"
|
|
echo "Using profiles: ${COMPOSE_PROFILES:-<none>}"
|
|
|
|
nextcloud_custom_apps_path="$(resolve_path_from_env "NEXTCLOUD_CUSTOM_APPS_PATH" "./nextcloud/custom_apps")"
|
|
mkdir -p "${nextcloud_custom_apps_path}"
|
|
|
|
if [[ -x "${repo_root}/scripts/ensure-user-oidc-app.sh" ]]; then
|
|
NEXTCLOUD_CUSTOM_APPS_PATH="${nextcloud_custom_apps_path}" "${repo_root}/scripts/ensure-user-oidc-app.sh"
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-qortal-integration-app.sh" ]]; then
|
|
NEXTCLOUD_CUSTOM_APPS_PATH="${nextcloud_custom_apps_path}" "${repo_root}/scripts/ensure-qortal-integration-app.sh"
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-oidc-signing-key.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-oidc-signing-key.sh" "${env_file}"
|
|
fi
|
|
|
|
app_id="$(read_kv "QORTAL_EXTERNAL_AUTH_APP_ID" || true)"
|
|
app_secret="$(read_kv "QORTAL_EXTERNAL_AUTH_APP_SECRET" || true)"
|
|
needs_register="0"
|
|
register_name_default="qortal-nextcloud-integration"
|
|
nextcloud_domain="$(read_kv "NEXTCLOUD_DOMAIN" || true)"
|
|
if [[ -n "${nextcloud_domain}" ]]; then
|
|
register_name_default="qortal-nextcloud-${nextcloud_domain}"
|
|
fi
|
|
if [[ "${skip_register}" != "1" ]]; then
|
|
if [[ "${force_register}" == "1" || -z "${app_id}" || -z "${app_secret}" ]]; then
|
|
needs_register="1"
|
|
fi
|
|
fi
|
|
|
|
node_api_key="$(read_kv "QORTAL_AUTH_NODE_API_KEY" || true)"
|
|
needs_api_key="0"
|
|
if [[ "${skip_api_key}" != "1" ]]; then
|
|
if [[ "${force_api_key}" == "1" || -z "${node_api_key}" ]]; then
|
|
needs_api_key="1"
|
|
fi
|
|
fi
|
|
|
|
echo "Ensuring broker + external_auth are up..."
|
|
compose_cmd up -d --build broker
|
|
if [[ "${needs_api_key}" == "1" ]]; then
|
|
echo "Qortal node API key is missing or forced; allowing external_auth to pull qortal_node dependencies."
|
|
compose_cmd up -d --build external_auth
|
|
else
|
|
echo "Keeping qortal_node untouched during bundled external-auth verification."
|
|
compose_cmd up -d --build --no-deps external_auth
|
|
fi
|
|
|
|
if [[ "${skip_register}" != "1" && "${needs_register}" != "1" ]]; then
|
|
if ! wait_for_local_http "http://127.0.0.1:${external_auth_host_port}/health" "bundled external-auth"; then
|
|
exit 1
|
|
fi
|
|
if ! test_external_auth_session "${app_id}" "${app_secret}"; then
|
|
echo "Existing external-auth app credentials were rejected by /sessions; re-registering."
|
|
needs_register="1"
|
|
fi
|
|
fi
|
|
|
|
if [[ -x "${repo_root}/scripts/ensure-nextcloud-apps-writable.sh" ]]; then
|
|
if ! "${repo_root}/scripts/ensure-nextcloud-apps-writable.sh" --mode "${mode}" --env-file "${env_file}"; then
|
|
echo "Warning: could not guarantee Nextcloud apps directory writeability."
|
|
echo "Apps page and app install/update operations may fail until fixed."
|
|
fi
|
|
fi
|
|
|
|
if [[ "${needs_register}" == "1" ]]; then
|
|
echo "Registering app with external-auth..."
|
|
if ! wait_for_local_http "http://127.0.0.1:${external_auth_host_port}/health" "bundled external-auth"; then
|
|
exit 1
|
|
fi
|
|
register_name_escaped="$(json_escape "${register_name_default}")"
|
|
register_payload="{\"name\":\"${register_name_escaped}\"}"
|
|
register_output="$(
|
|
register_external_auth_app_with_retry \
|
|
"http://127.0.0.1:${external_auth_host_port}/apps/register" \
|
|
"${register_payload}"
|
|
)" || exit 1
|
|
new_app_id="$(echo "${register_output}" | sed -n '1p')"
|
|
new_app_secret="$(echo "${register_output}" | sed -n '2p')"
|
|
|
|
if [[ -z "${new_app_id}" || -z "${new_app_secret}" ]]; then
|
|
echo "Failed to register app via external-auth."
|
|
exit 1
|
|
fi
|
|
|
|
set_kv "QORTAL_EXTERNAL_AUTH_APP_ID" "${new_app_id}"
|
|
set_kv "QORTAL_EXTERNAL_AUTH_APP_SECRET" "${new_app_secret}"
|
|
echo "Saved external-auth app credentials into ${env_file}."
|
|
compose_cmd up -d --build --force-recreate broker
|
|
else
|
|
echo "Skipping app registration (existing credentials present or --skip-register used)."
|
|
fi
|
|
|
|
if [[ "${needs_api_key}" == "1" ]]; then
|
|
qortal_api_key_file="${repo_root}/qortal/data/apikey.txt"
|
|
generated_key="$(read_trimmed_file_value "${qortal_api_key_file}" || true)"
|
|
if [[ -n "${generated_key}" ]]; then
|
|
echo "Using existing Qortal node API key from ${qortal_api_key_file}."
|
|
fi
|
|
if [[ "${generated_key}" == "null" ]]; then
|
|
generated_key=""
|
|
fi
|
|
if [[ -z "${generated_key}" ]]; then
|
|
echo "Generating Qortal node API key..."
|
|
generated_key="$(
|
|
generate_qortal_api_key_with_retry \
|
|
"http://127.0.0.1:${node_api_host_port}/admin/apikey/generate"
|
|
)" || exit 1
|
|
fi
|
|
if [[ -z "${generated_key}" || "${generated_key}" == "null" ]]; then
|
|
echo "Failed to resolve node API key."
|
|
exit 1
|
|
fi
|
|
|
|
set_kv "QORTAL_AUTH_NODE_API_KEY" "${generated_key}"
|
|
set_kv "QORTAL_AUTH_NODE_API_KEY_MODE" "paths"
|
|
set_kv "QORTAL_AUTH_NODE_API_KEY_PATHS" "/"
|
|
mkdir -p "$(dirname "${qortal_api_key_file}")"
|
|
printf '%s\n' "${generated_key}" > "${qortal_api_key_file}"
|
|
echo "Saved QORTAL_AUTH_NODE_API_KEY into ${env_file}."
|
|
compose_cmd up -d --build --force-recreate external_auth
|
|
else
|
|
echo "Skipping node API key generation (existing key present or --skip-api-key used)."
|
|
fi
|
|
|
|
custom_pwa_vapid_env_status=0
|
|
if custom_pwa_should_install; then
|
|
ensure_custom_pwa_vapid_env || custom_pwa_vapid_env_status=$?
|
|
if [[ "${custom_pwa_vapid_env_status}" -ne 0 ]]; then
|
|
echo "Warning: CustomPWA VAPID env values are incomplete. Configure CUSTOM_PWA_VAPID_SUBJECT/CUSTOM_PWA_VAPID_PUBLIC_KEY/CUSTOM_PWA_VAPID_PRIVATE_KEY in ${env_file}."
|
|
fi
|
|
fi
|
|
|
|
if [[ "${skip_occ}" != "1" ]]; then
|
|
echo "Running Nextcloud OCC app enable/repair..."
|
|
oidc_issuer="$(read_kv "OIDC_ISSUER" || true)"
|
|
oidc_client_id="$(read_kv "OIDC_CLIENT_ID" || true)"
|
|
oidc_client_secret="$(read_kv "OIDC_CLIENT_SECRET" || true)"
|
|
oidc_client_id="${oidc_client_id:-nextcloud-local}"
|
|
oidc_provider_config_status=0
|
|
oidc_provider_skipped=0
|
|
oidc_discovery_url=""
|
|
if [[ -n "${oidc_issuer}" ]]; then
|
|
oidc_discovery_url="${oidc_issuer%/}/.well-known/openid-configuration"
|
|
fi
|
|
|
|
occ_ready="0"
|
|
occ_ready_retries="${OCC_READY_RETRIES:-36}"
|
|
occ_ready_sleep_seconds="${OCC_READY_SLEEP_SECONDS:-5}"
|
|
for _ in $(seq 1 "${occ_ready_retries}"); do
|
|
if compose_cmd exec -T app php occ status >/dev/null 2>&1; then
|
|
occ_ready="1"
|
|
break
|
|
fi
|
|
sleep "${occ_ready_sleep_seconds}"
|
|
done
|
|
|
|
if [[ "${occ_ready}" == "1" ]]; then
|
|
set +e
|
|
msp_mode_enabled="$(read_kv_first "NUQLOUD_MSP_MODE_ENABLED" "MSP_MODE_ENABLED" "SC_MSP_MODE_ENABLED" || true)"
|
|
msp_admin_group="$(read_kv_first "NUQLOUD_MSP_ADMIN_GROUP" "MSP_ADMIN_GROUP" "SC_MSP_ADMIN_GROUP" || true)"
|
|
if [[ -z "${msp_admin_group}" ]]; then
|
|
msp_admin_group="MSP_Admin"
|
|
fi
|
|
msp_mode_enabled="$(normalize_bool_01 "${msp_mode_enabled}")"
|
|
nextcloud_url_sync_status=0
|
|
if [[ -x "${repo_root}/scripts/ensure-nextcloud-url-config.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-nextcloud-url-config.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" >/dev/null 2>&1
|
|
nextcloud_url_sync_status=$?
|
|
fi
|
|
nextcloud_service_auth_status=0
|
|
if [[ -x "${repo_root}/scripts/ensure-nextcloud-service-auth.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-nextcloud-service-auth.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" \
|
|
--repair >/dev/null 2>&1
|
|
nextcloud_service_auth_status=$?
|
|
fi
|
|
compose_cmd exec -T app php occ app:enable --force user_oidc >/dev/null 2>&1
|
|
enable_oidc_status=$?
|
|
install_oidc_status=0
|
|
if [[ "${enable_oidc_status}" -ne 0 ]]; then
|
|
compose_cmd exec -T app php occ app:install user_oidc >/dev/null 2>&1
|
|
install_oidc_status=$?
|
|
compose_cmd exec -T app php occ app:enable --force user_oidc >/dev/null 2>&1
|
|
enable_oidc_status=$?
|
|
fi
|
|
notify_push_enable_status=0
|
|
notify_push_install_status=0
|
|
notify_push_verify_status=0
|
|
compose_cmd exec -T app php occ app:enable --force notify_push >/dev/null 2>&1
|
|
notify_push_enable_status=$?
|
|
if [[ "${notify_push_enable_status}" -ne 0 ]]; then
|
|
compose_cmd exec -T app php occ app:install notify_push >/dev/null 2>&1
|
|
notify_push_install_status=$?
|
|
compose_cmd exec -T app php occ app:enable --force notify_push >/dev/null 2>&1
|
|
notify_push_enable_status=$?
|
|
fi
|
|
compose_cmd exec -T app php occ app:list >/tmp/finish_occ_app_list_notify_push.txt 2>/dev/null
|
|
if grep -qE '^[[:space:]]+- notify_push([[:space:]:]|$)' /tmp/finish_occ_app_list_notify_push.txt 2>/dev/null; then
|
|
notify_push_verify_status=0
|
|
else
|
|
notify_push_verify_status=1
|
|
fi
|
|
rm -f /tmp/finish_occ_app_list_notify_push.txt >/dev/null 2>&1 || true
|
|
compose_cmd exec -T app php occ user_oidc:provider --help >/dev/null 2>&1
|
|
verify_oidc_status=$?
|
|
if [[ -n "${oidc_discovery_url}" && -n "${oidc_client_secret}" ]]; then
|
|
compose_cmd exec -T app php occ user_oidc:provider qortal \
|
|
-c "${oidc_client_id}" \
|
|
-s "${oidc_client_secret}" \
|
|
-d "${oidc_discovery_url}" \
|
|
--scope="openid profile email" \
|
|
--mapping-uid=sub \
|
|
--mapping-display-name=name \
|
|
--mapping-email=email >/dev/null 2>&1
|
|
oidc_provider_config_status=$?
|
|
else
|
|
oidc_provider_skipped=1
|
|
fi
|
|
oidc_login_label_status=0
|
|
sovereign_mode="$(compose_cmd exec -T app php occ config:app:get qortal_integration sc_mode 2>/dev/null | tr -d '\r' || true)"
|
|
if [[ "${sovereign_mode}" == "full_qortal" ]]; then
|
|
compose_cmd exec -T app php occ config:system:delete user_oidc login_label >/dev/null 2>&1
|
|
oidc_login_label_status=$?
|
|
else
|
|
compose_cmd exec -T app php occ config:system:set user_oidc login_label --value="Create Account / Login" >/dev/null 2>&1
|
|
oidc_login_label_status=$?
|
|
fi
|
|
compose_cmd exec -T app php occ app:enable qortal_integration >/dev/null 2>&1
|
|
enable_qortal_status=$?
|
|
msp_group_status=0
|
|
if [[ "${msp_mode_enabled}" == "1" ]]; then
|
|
compose_cmd exec -T app php occ group:add "${msp_admin_group}" >/dev/null 2>&1
|
|
msp_group_status=$?
|
|
fi
|
|
custom_apps_to_enable=(chd_admin qortal_files_bridge qortal_talk_bridge nuqloud_scrum)
|
|
if custom_pwa_should_install; then
|
|
custom_apps_to_enable=(custom_pwa "${custom_apps_to_enable[@]}")
|
|
fi
|
|
for custom_app in "${custom_apps_to_enable[@]}"; do
|
|
compose_cmd exec -T app php occ app:enable "${custom_app}" >/dev/null 2>&1 || true
|
|
done
|
|
custom_pwa_settings_sync_status=0
|
|
if custom_pwa_should_install; then
|
|
sync_custom_pwa_settings || custom_pwa_settings_sync_status=$?
|
|
fi
|
|
qortal_settings_sync_status=0
|
|
if [[ "${enable_qortal_status}" -eq 0 ]]; then
|
|
sync_qortal_integration_billing_settings || qortal_settings_sync_status=$?
|
|
fi
|
|
qortal_runtime_sync_status=0
|
|
if [[ -x "${repo_root}/scripts/ensure-qortal-integration-runtime-config.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-qortal-integration-runtime-config.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" >/dev/null 2>&1
|
|
qortal_runtime_sync_status=$?
|
|
fi
|
|
default_app_bundle_status=0
|
|
if [[ -x "${repo_root}/scripts/ensure-nextcloud-default-app-bundle.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-nextcloud-default-app-bundle.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" >/dev/null 2>&1
|
|
default_app_bundle_status=$?
|
|
fi
|
|
compose_cmd exec -T app php occ maintenance:repair >/dev/null 2>&1
|
|
repair_status=$?
|
|
compose_cmd exec -T app php occ maintenance:repair --include-expensive >/dev/null 2>&1
|
|
repair_expensive_status=$?
|
|
set -e
|
|
if [[ "${nextcloud_url_sync_status}" -ne 0 || "${nextcloud_service_auth_status}" -ne 0 || "${enable_oidc_status}" -ne 0 || "${notify_push_enable_status}" -ne 0 || "${notify_push_verify_status}" -ne 0 || "${verify_oidc_status}" -ne 0 || "${oidc_provider_config_status}" -ne 0 || "${oidc_login_label_status}" -ne 0 || "${enable_qortal_status}" -ne 0 || "${msp_group_status}" -ne 0 || "${custom_pwa_settings_sync_status}" -ne 0 || "${qortal_settings_sync_status}" -ne 0 || "${qortal_runtime_sync_status}" -ne 0 || "${default_app_bundle_status}" -ne 0 || "${repair_status}" -ne 0 || "${repair_expensive_status}" -ne 0 ]]; then
|
|
echo "Warning: OCC commands did not fully complete. Re-run manually:"
|
|
echo " ./scripts/ensure-nextcloud-url-config.sh --compose-file ${compose_file} --env-file ${env_file}"
|
|
echo " ./scripts/ensure-nextcloud-service-auth.sh --compose-file ${compose_file} --env-file ${env_file} --repair"
|
|
echo " ./scripts/ensure-qortal-integration-runtime-config.sh --compose-file ${compose_file} --env-file ${env_file}"
|
|
echo " ./scripts/ensure-nextcloud-default-app-bundle.sh --compose-file ${compose_file} --env-file ${env_file}"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:install user_oidc"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:enable --force user_oidc"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:install notify_push"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:enable --force notify_push"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ user_oidc:provider --help"
|
|
if [[ -n "${oidc_discovery_url}" ]]; then
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ user_oidc:provider qortal -c ${oidc_client_id} -s '<client-secret>' -d ${oidc_discovery_url} --scope='openid profile email' --mapping-uid=sub --mapping-display-name=name --mapping-email=email"
|
|
fi
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ config:system:set user_oidc login_label --value='Create Account / Login'"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:enable qortal_integration"
|
|
if [[ "${msp_mode_enabled}" == "1" ]]; then
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ group:add '${msp_admin_group}'"
|
|
fi
|
|
if custom_pwa_should_install; then
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ config:app:set custom_pwa vapid_subject --value='$(read_kv_first "CUSTOM_PWA_VAPID_SUBJECT" || true)'"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ config:app:set custom_pwa vapid_public_key --value='$(read_kv_first "CUSTOM_PWA_VAPID_PUBLIC_KEY" || true)'"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ config:app:set custom_pwa vapid_private_key --value='<private-key-from-env>'"
|
|
fi
|
|
echo " # Re-run finish script to sync qortal_integration billing settings from env"
|
|
echo " ./scripts/finish-initial-setup.sh --mode ${mode} --env-file ${env_file}"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ db:add-missing-indices"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ maintenance:repair"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ maintenance:repair --include-expensive"
|
|
if [[ "${install_oidc_status}" -ne 0 && "${enable_oidc_status}" -ne 0 ]]; then
|
|
echo " Note: app:install user_oidc fallback failed during setup; check network/appstore access or custom_apps mount."
|
|
fi
|
|
if [[ "${oidc_provider_skipped}" -eq 1 ]]; then
|
|
echo " Note: provider auto-config skipped because OIDC_ISSUER or OIDC_CLIENT_SECRET is empty in ${env_file}."
|
|
fi
|
|
if [[ "${notify_push_verify_status}" -ne 0 ]]; then
|
|
echo " Note: notify_push is not enabled yet; install/enable it after confirming appstore connectivity."
|
|
fi
|
|
fi
|
|
else
|
|
echo "Warning: Nextcloud OCC did not become ready within timeout ($((occ_ready_retries * occ_ready_sleep_seconds))s)."
|
|
if [[ -x "${repo_root}/scripts/diagnose-nextcloud-occ-readiness.sh" ]]; then
|
|
echo "Collecting OCC readiness diagnostics..."
|
|
"${repo_root}/scripts/diagnose-nextcloud-occ-readiness.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" || true
|
|
fi
|
|
echo "Run later:"
|
|
echo " ./scripts/diagnose-nextcloud-occ-readiness.sh --compose-file ${compose_file} --env-file ${env_file}"
|
|
echo " ./scripts/ensure-nextcloud-default-app-bundle.sh --compose-file ${compose_file} --env-file ${env_file}"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:install user_oidc"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:enable --force user_oidc"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:install notify_push"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:enable --force notify_push"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:enable qortal_integration"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ db:add-missing-indices"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ maintenance:repair"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ maintenance:repair --include-expensive"
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
echo "Initial setup finishing complete."
|
|
echo "Verify:"
|
|
echo " curl -sS http://127.0.0.1:${external_auth_host_port}/health"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} ps"
|