Files
Qortal-Nextcloud-Integration/scripts/bootstrap-host-nextcloud-hybrid.sh

784 lines
24 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 "${script_dir}/lib-nextcloud-apps-path.sh"
default_qortal_repo_url="https://github.com/Qortal/qortal.git"
default_external_auth_repo_url="https://gitea.qortal.link/crowetic/Qortal-External-Auth.git"
occ_path="/var/www/nextcloud/occ"
broker_dir="/opt/qortal-broker"
apache_site_dir="/etc/apache2/sites-available"
apache_site_name="qortalbroker.conf"
dry_run="0"
external_auth_mode="bundled"
qortal_mode="bundled"
nextcloud_url=""
nextcloud_root=""
nextcloud_custom_apps_path=""
broker_host=""
broker_url=""
external_auth_url=""
external_auth_app_id=""
external_auth_app_secret=""
qortal_node_url=""
qortal_node_api_key=""
service_user="nuqloud_oidc_service"
service_password=""
broker_db_password=""
oidc_client_id="nextcloud-local"
oidc_client_secret=""
broker_internal_api_token=""
external_auth_context=""
qortal_node_context=""
external_auth_port="3191"
broker_port="3000"
qortal_node_gateway_host_port="12390"
qortal_node_api_host_port="12391"
qortal_node_p2p_host_port="12392"
qortal_node_qdn_host_port="12394"
external_auth_context_explicit="0"
qortal_node_context_explicit="0"
usage() {
cat <<USAGE
Usage: ./scripts/bootstrap-host-nextcloud-hybrid.sh [options]
Prepare sidecar files and rollout notes for an existing host-installed
Nextcloud instance with Docker sidecars.
Options:
--occ-path <path> Host Nextcloud occ path (default: /var/www/nextcloud/occ)
--broker-dir <path> Output/runtime dir for broker sidecars (default: /opt/qortal-broker)
--nextcloud-url <url> Explicit Nextcloud public URL (auto-detected from occ when omitted)
--broker-host <host> Broker public hostname (default: qortalbroker.<base-domain>)
--broker-url <url> Broker public URL (default: https://<broker-host>)
--external-auth bundled|existing External Auth source (default: bundled)
--external-auth-url <url> External Auth base URL (required for existing mode)
--external-auth-app-id <id> External Auth app ID (required for existing mode)
--external-auth-app-secret <sec> External Auth app secret (required for existing mode)
--with-qortal bundled|skip Qortal node source (default: bundled)
--qortal-node-url <url> Qortal node base URL (default: bundled node URL)
--qortal-node-api-key <key> Qortal node API key (optional; auto-generated later when bundled)
--service-user <user> Nextcloud service auth user (default: nuqloud_oidc_service)
--service-password <pass> Nextcloud service auth password (auto-generated when omitted)
--broker-db-password <pass> Broker DB password (auto-generated when omitted)
--oidc-client-id <id> OIDC client ID (default: nextcloud-local)
--oidc-client-secret <sec> OIDC client secret (auto-generated when omitted)
--broker-internal-api-token <tok> Broker internal API token (auto-generated when omitted)
--external-auth-context <path> Qortal External Auth repo path (default: ../Qortal-External-Auth)
--qortal-node-context <path> Qortal node repo path (default: ../qortal)
--nextcloud-custom-apps-path <path> Host Nextcloud app path (default: detected existing apps path, else <occ parent>/apps)
--broker-port <port> Broker bind port (default: 3000)
--external-auth-port <port> External Auth bind port (default: 3191)
--apache-site-dir <path> Apache vhost dir (default: /etc/apache2/sites-available)
--apache-site-name <file> Apache vhost filename (default: qortalbroker.conf)
--repo-root <path> Repository root (default: auto)
--dry-run Print intended actions without writing files
-h, --help Show this help
USAGE
}
gen_secret() {
local generated=""
while [[ "${#generated}" -lt 32 ]]; do
generated+="$(
LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom 2>/dev/null | dd bs=64 count=1 status=none 2>/dev/null || true
)"
done
printf '%.32s' "${generated}"
}
trim() {
printf '%s' "${1:-}" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
}
run_cmd() {
if [[ "${dry_run}" == "1" ]]; then
printf '[dry-run] '
printf '%q ' "$@"
printf '\n'
return 0
fi
"$@"
}
occ_cmd() {
if [[ ! -f "${occ_path}" ]]; then
echo "OCC path not found: ${occ_path}" >&2
return 1
fi
if [[ -z "$(command -v php || true)" ]]; then
echo "php is required to query occ." >&2
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}" "$@"
}
derive_nextcloud_url() {
local detected=""
detected="$(occ_cmd config:system:get overwrite.cli.url 2>/dev/null | tr -d '\r' || true)"
if [[ -z "${detected}" ]]; then
detected="$(occ_cmd config:system:get trusted_domains 0 2>/dev/null | tr -d '\r' || true)"
if [[ -n "${detected}" && "${detected}" != http* ]]; then
detected="https://${detected}"
fi
fi
printf '%s' "${detected}"
}
infer_base_domain() {
local host="${1:-}"
if [[ "${host}" == *.* ]]; then
printf '%s' "${host#*.}"
else
printf '%s' "${host}"
fi
}
resolve_context_path() {
local value="${1:-}"
if [[ -z "${value}" ]]; then
printf '%s' ""
return 0
fi
if [[ "${value}" == /* ]]; then
printf '%s' "${value}"
else
printf '%s' "$(cd "${repo_root}" 2>/dev/null && pwd)/${value}"
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}" ]]; then
echo "${label} context exists but is not a git repo: ${target_path}" >&2
echo "Remove it or pass a valid repo path explicitly." >&2
return 1
fi
if [[ "${dry_run}" != "1" ]] && ! command -v git >/dev/null 2>&1; then
echo "git is required to clone ${label} into ${target_path}" >&2
return 1
fi
echo "Cloning ${label} repo into ${target_path}"
run_cmd mkdir -p "$(dirname "${target_path}")"
run_cmd git clone --depth 1 "${repo_url}" "${target_path}"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--occ-path)
occ_path="${2:-}"
shift 2
;;
--occ-path=*)
occ_path="${1#*=}"
shift
;;
--broker-dir)
broker_dir="${2:-}"
shift 2
;;
--broker-dir=*)
broker_dir="${1#*=}"
shift
;;
--nextcloud-url)
nextcloud_url="${2:-}"
shift 2
;;
--nextcloud-url=*)
nextcloud_url="${1#*=}"
shift
;;
--broker-host)
broker_host="${2:-}"
shift 2
;;
--broker-host=*)
broker_host="${1#*=}"
shift
;;
--broker-url)
broker_url="${2:-}"
shift 2
;;
--broker-url=*)
broker_url="${1#*=}"
shift
;;
--external-auth-url)
external_auth_url="${2:-}"
shift 2
;;
--external-auth-url=*)
external_auth_url="${1#*=}"
shift
;;
--external-auth)
external_auth_mode="${2:-}"
shift 2
;;
--external-auth=*)
external_auth_mode="${1#*=}"
shift
;;
--external-auth-app-id)
external_auth_app_id="${2:-}"
shift 2
;;
--external-auth-app-id=*)
external_auth_app_id="${1#*=}"
shift
;;
--external-auth-app-secret)
external_auth_app_secret="${2:-}"
shift 2
;;
--external-auth-app-secret=*)
external_auth_app_secret="${1#*=}"
shift
;;
--with-qortal)
qortal_mode="${2:-}"
shift 2
;;
--with-qortal=*)
qortal_mode="${1#*=}"
shift
;;
--qortal-node-url)
qortal_node_url="${2:-}"
shift 2
;;
--qortal-node-url=*)
qortal_node_url="${1#*=}"
shift
;;
--qortal-node-api-key)
qortal_node_api_key="${2:-}"
shift 2
;;
--qortal-node-api-key=*)
qortal_node_api_key="${1#*=}"
shift
;;
--service-user)
service_user="${2:-}"
shift 2
;;
--service-user=*)
service_user="${1#*=}"
shift
;;
--service-password)
service_password="${2:-}"
shift 2
;;
--service-password=*)
service_password="${1#*=}"
shift
;;
--broker-db-password)
broker_db_password="${2:-}"
shift 2
;;
--broker-db-password=*)
broker_db_password="${1#*=}"
shift
;;
--oidc-client-id)
oidc_client_id="${2:-}"
shift 2
;;
--oidc-client-id=*)
oidc_client_id="${1#*=}"
shift
;;
--oidc-client-secret)
oidc_client_secret="${2:-}"
shift 2
;;
--oidc-client-secret=*)
oidc_client_secret="${1#*=}"
shift
;;
--broker-internal-api-token)
broker_internal_api_token="${2:-}"
shift 2
;;
--broker-internal-api-token=*)
broker_internal_api_token="${1#*=}"
shift
;;
--external-auth-context)
external_auth_context="${2:-}"
external_auth_context_explicit="1"
shift 2
;;
--external-auth-context=*)
external_auth_context="${1#*=}"
external_auth_context_explicit="1"
shift
;;
--qortal-node-context)
qortal_node_context="${2:-}"
qortal_node_context_explicit="1"
shift 2
;;
--qortal-node-context=*)
qortal_node_context="${1#*=}"
qortal_node_context_explicit="1"
shift
;;
--nextcloud-custom-apps-path)
nextcloud_custom_apps_path="${2:-}"
shift 2
;;
--nextcloud-custom-apps-path=*)
nextcloud_custom_apps_path="${1#*=}"
shift
;;
--broker-port)
broker_port="${2:-}"
shift 2
;;
--broker-port=*)
broker_port="${1#*=}"
shift
;;
--external-auth-port)
external_auth_port="${2:-}"
shift 2
;;
--external-auth-port=*)
external_auth_port="${1#*=}"
shift
;;
--apache-site-dir)
apache_site_dir="${2:-}"
shift 2
;;
--apache-site-dir=*)
apache_site_dir="${1#*=}"
shift
;;
--apache-site-name)
apache_site_name="${2:-}"
shift 2
;;
--apache-site-name=*)
apache_site_name="${1#*=}"
shift
;;
--repo-root)
repo_root="${2:-}"
shift 2
;;
--repo-root=*)
repo_root="${1#*=}"
shift
;;
--dry-run)
dry_run="1"
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage
exit 1
;;
esac
done
echo "Preparing host-nextcloud-hybrid bootstrap..."
echo " repo_root: ${repo_root}"
echo " occ_path: ${occ_path}"
echo " dry_run: ${dry_run}"
if [[ "${repo_root}" != /* ]]; then
repo_root="$(cd "${repo_root}" 2>/dev/null && pwd)"
fi
external_auth_context="${external_auth_context:-../Qortal-External-Auth}"
qortal_node_context="${qortal_node_context:-../qortal}"
external_auth_context="$(resolve_context_path "${external_auth_context}")"
qortal_node_context="$(resolve_context_path "${qortal_node_context}")"
nextcloud_root="$(cd "$(dirname "${occ_path}")" 2>/dev/null && pwd || true)"
if [[ -z "${nextcloud_root}" ]]; then
echo "Unable to derive Nextcloud root from occ path: ${occ_path}" >&2
exit 1
fi
if [[ -z "${nextcloud_custom_apps_path}" ]]; then
nextcloud_custom_apps_path="$(nextcloud_select_apps_path "${nextcloud_root}")"
else
if [[ "${nextcloud_custom_apps_path}" != /* ]]; then
nextcloud_custom_apps_path="$(resolve_context_path "${nextcloud_custom_apps_path}")"
fi
selected_nextcloud_apps_path="$(nextcloud_select_apps_path "${nextcloud_root}" "${nextcloud_custom_apps_path}")"
if [[ -n "${selected_nextcloud_apps_path}" ]]; then
if [[ "${selected_nextcloud_apps_path}" != "${nextcloud_custom_apps_path}" ]]; then
echo "Requested Nextcloud app path is not configured; using ${selected_nextcloud_apps_path} instead."
fi
nextcloud_custom_apps_path="${selected_nextcloud_apps_path}"
fi
fi
if [[ -z "${nextcloud_custom_apps_path}" ]]; then
echo "Unable to determine a usable Nextcloud app path from config.php or ${nextcloud_root}/apps." >&2
exit 1
fi
mkdir -p "${nextcloud_custom_apps_path}"
if [[ "${external_auth_mode}" != "bundled" && "${external_auth_mode}" != "existing" ]]; then
echo "Invalid --external-auth value: ${external_auth_mode}" >&2
exit 1
fi
if [[ "${qortal_mode}" != "bundled" && "${qortal_mode}" != "skip" ]]; then
echo "Invalid --with-qortal value: ${qortal_mode}" >&2
exit 1
fi
if [[ "${external_auth_mode}" == "bundled" && "${qortal_mode}" != "bundled" ]]; then
echo "Bundled external_auth currently requires bundled qortal_node." >&2
exit 1
fi
if [[ "${qortal_mode}" == "skip" && -z "${qortal_node_url}" ]]; then
echo "--qortal-node-url is required when using --with-qortal skip." >&2
exit 1
fi
if [[ -z "${nextcloud_url}" ]]; then
nextcloud_url="$(derive_nextcloud_url)"
fi
nextcloud_url="$(trim "${nextcloud_url}")"
if [[ -z "${nextcloud_url}" ]]; then
echo "Could not detect Nextcloud public URL from occ. Pass --nextcloud-url." >&2
exit 1
fi
nextcloud_host="$(printf '%s' "${nextcloud_url}" | sed -E 's#^[a-z]+://##; s#/.*$##')"
base_domain="$(infer_base_domain "${nextcloud_host}")"
if [[ -z "${broker_host}" ]]; then
broker_host="qortalbroker.${base_domain}"
fi
if [[ -z "${broker_url}" ]]; then
broker_url="https://${broker_host}"
fi
if [[ "${external_auth_mode}" == "bundled" ]]; then
external_auth_url="http://external_auth:3191"
else
external_auth_url="$(trim "${external_auth_url}")"
if [[ -z "${external_auth_url}" ]]; then
echo "--external-auth-url is required when using --external-auth existing" >&2
exit 1
fi
if [[ -z "${external_auth_app_id}" || -z "${external_auth_app_secret}" ]]; then
echo "--external-auth-app-id and --external-auth-app-secret are required for external auth existing mode." >&2
exit 1
fi
fi
if [[ -z "${qortal_node_url}" && "${qortal_mode}" == "bundled" ]]; then
qortal_node_url="http://qortal_node:12391"
fi
host_external_auth_url="${external_auth_url}"
if [[ "${external_auth_mode}" == "bundled" ]]; then
host_external_auth_url="http://127.0.0.1:${external_auth_port}"
fi
host_qortal_node_url="${qortal_node_url}"
if [[ "${qortal_mode}" == "bundled" ]]; then
host_qortal_node_url="http://127.0.0.1:${qortal_node_api_host_port}"
fi
if [[ -z "${service_password}" ]]; then
service_password="$(gen_secret)"
fi
if [[ -z "${broker_db_password}" ]]; then
broker_db_password="$(gen_secret)"
fi
if [[ -z "${oidc_client_secret}" ]]; then
oidc_client_secret="$(gen_secret)"
fi
if [[ -z "${broker_internal_api_token}" ]]; then
broker_internal_api_token="$(gen_secret)"
fi
compose_src="${repo_root}/deploy/docker-compose.host-hybrid.yml"
compose_dst="${broker_dir}/docker-compose.yml"
env_dst="${broker_dir}/.env"
apache_site_path="${apache_site_dir%/}/${apache_site_name}"
apache_preview_path="${broker_dir}/${apache_site_name}"
notes_path="${broker_dir}/NEXT-STEPS.txt"
compose_profiles_list=()
if [[ "${external_auth_mode}" == "bundled" ]]; then
compose_profiles_list+=("external-auth")
fi
if [[ "${qortal_mode}" == "bundled" ]]; then
compose_profiles_list+=("qortal")
fi
compose_profiles="$(IFS=,; printf '%s' "${compose_profiles_list[*]}")"
if [[ ! -f "${compose_src}" ]]; then
echo "Missing compose source file: ${compose_src}" >&2
exit 1
fi
if [[ "${external_auth_mode}" == "bundled" ]]; then
if [[ ! -d "${external_auth_context}/.git" ]]; then
if [[ "${external_auth_context_explicit}" == "1" ]]; then
echo "External Auth context not found or is not a git repo: ${external_auth_context}" >&2
echo "Pass a valid --external-auth-context or omit it to auto-clone the default repo." >&2
exit 1
fi
ensure_context_repo "Qortal External Auth" "${external_auth_context}" "${default_external_auth_repo_url}"
else
echo "Using existing Qortal External Auth repo at ${external_auth_context}"
fi
fi
if [[ "${qortal_mode}" == "bundled" ]]; then
if [[ ! -d "${qortal_node_context}/.git" ]]; then
if [[ "${qortal_node_context_explicit}" == "1" ]]; then
echo "Qortal node context not found or is not a git repo: ${qortal_node_context}" >&2
echo "Pass a valid --qortal-node-context or omit it to auto-clone the default repo." >&2
exit 1
fi
ensure_context_repo "Qortal Core" "${qortal_node_context}" "${default_qortal_repo_url}"
else
echo "Using existing Qortal Core repo at ${qortal_node_context}"
fi
fi
env_content=$(cat <<EOF
BROKER_BUILD_CONTEXT=${repo_root}/services/qortal-oidc-broker
BROKER_PORT=${broker_port}
BROKER_DB_NAME=qortal_broker
BROKER_DB_USER=qortal_broker
BROKER_DB_PASSWORD=${broker_db_password}
BROKER_DATABASE_URL=postgresql://qortal_broker:${broker_db_password}@broker_db:5432/qortal_broker
COMPOSE_PROFILES=${compose_profiles}
COMPOSE_PROJECT_NAME=nuqloud-broker
NEXTCLOUD_BASE_URL=${nextcloud_url}
NEXTCLOUD_PUBLIC_URL=${nextcloud_url}
NEXTCLOUD_ROOT=${nextcloud_root}
NEXTCLOUD_CUSTOM_APPS_PATH=${nextcloud_custom_apps_path}
HOST_NEXTCLOUD_EXTERNAL_AUTH_URL=${host_external_auth_url}
HOST_NEXTCLOUD_QORTAL_NODE_URL=${host_qortal_node_url}
NEXTCLOUD_SERVICE_USER=${service_user}
NEXTCLOUD_SERVICE_PASSWORD=${service_password}
QORTAL_EXTERNAL_AUTH_BASE_URL=${external_auth_url}
QORTAL_EXTERNAL_AUTH_APP_ID=${external_auth_app_id}
QORTAL_EXTERNAL_AUTH_APP_SECRET=${external_auth_app_secret}
BROKER_INTERNAL_API_TOKEN=${broker_internal_api_token}
BROKER_CORS_ALLOWED_ORIGINS=${nextcloud_url}
BROKER_REQUEST_BODY_LIMIT=64mb
OIDC_ISSUER=${broker_url}
OIDC_CLIENT_ID=${oidc_client_id}
OIDC_CLIENT_SECRET=${oidc_client_secret}
OIDC_REDIRECT_URI_ALLOWLIST=${nextcloud_url}/apps/user_oidc/code
OIDC_POLICY_MODE=auto_provision
OIDC_AUTO_PROVISION_GUARD=invite_or_allowlist
OIDC_REQUIRE_EMAIL_FOR_NEW_ACCOUNT=true
OIDC_BRANDING_MODE=powered
OIDC_INVITE_TTL_SECONDS=604800
OIDC_AUTH_REQUEST_TTL_SECONDS=600
OIDC_AUTH_CODE_TTL_SECONDS=120
OIDC_ACCESS_TOKEN_TTL_SECONDS=600
OIDC_ID_TOKEN_TTL_SECONDS=600
OIDC_PRIVATE_KEY_PEM=
OIDC_PRIVATE_KEY_PEM_B64=
OIDC_KEY_ID=
EXTERNAL_AUTH_CONTEXT=${external_auth_context}
EXTERNAL_AUTH_PORT=${external_auth_port}
QORTAL_NODE_CONTEXT=${qortal_node_context}
QORTAL_NODE_URL=${qortal_node_url}
QORTAL_NODE_API_KEY=${qortal_node_api_key}
QORTAL_NODE_GATEWAY_HOST_PORT=${qortal_node_gateway_host_port}
QORTAL_NODE_API_HOST_PORT=${qortal_node_api_host_port}
QORTAL_NODE_P2P_HOST_PORT=${qortal_node_p2p_host_port}
QORTAL_NODE_QDN_HOST_PORT=${qortal_node_qdn_host_port}
QORTAL_AUTH_NODE_URL=${qortal_node_url}
QORTAL_AUTH_NODE_API_KEY=${qortal_node_api_key}
QORTAL_AUTH_NODE_API_KEY_MODE=paths
QORTAL_AUTH_NODE_API_KEY_PATHS=/
HOST_NEXTCLOUD_OCC_PATH=${occ_path}
HOST_NEXTCLOUD_ROOT=${nextcloud_root}
HOST_NEXTCLOUD_CUSTOM_APPS_PATH=${nextcloud_custom_apps_path}
HOST_NEXTCLOUD_BROKER_DIR=${broker_dir}
HOST_NEXTCLOUD_BROKER_HOST=${broker_host}
HOST_NEXTCLOUD_BROKER_URL=${broker_url}
HOST_NEXTCLOUD_APACHE_SITE_DIR=${apache_site_dir}
HOST_NEXTCLOUD_APACHE_SITE_NAME=${apache_site_name}
HOST_NEXTCLOUD_APACHE_SITE_PATH=${apache_site_path}
HOST_NEXTCLOUD_EXTERNAL_AUTH_MODE=${external_auth_mode}
HOST_NEXTCLOUD_EXTERNAL_AUTH_CONTEXT=${external_auth_context}
HOST_NEXTCLOUD_QORTAL_MODE=${qortal_mode}
HOST_NEXTCLOUD_QORTAL_NODE_CONTEXT=${qortal_node_context}
HOST_NEXTCLOUD_EXTERNAL_AUTH_PORT=${external_auth_port}
HOST_NEXTCLOUD_QORTAL_NODE_API_HOST_PORT=${qortal_node_api_host_port}
EOF
)
apache_content=$(cat <<EOF
<VirtualHost *:80>
ServerName ${broker_host}
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:${broker_port}/
ProxyPassReverse / http://127.0.0.1:${broker_port}/
RequestHeader set X-Forwarded-Proto "https"
ErrorLog \${APACHE_LOG_DIR}/qortalbroker_error.log
CustomLog \${APACHE_LOG_DIR}/qortalbroker_access.log combined
</VirtualHost>
EOF
)
notes_content=$(cat <<EOF
Host Nextcloud Hybrid Bootstrap Notes
===================================
This is the low-level bootstrap artifact for host-hybrid adoption.
Use ./scripts/install-host-nextcloud-hybrid.sh for the full install/update flow.
Detected:
occ_path=${occ_path}
nextcloud_url=${nextcloud_url}
broker_url=${broker_url}
external_auth_mode=${external_auth_mode}
external_auth_url=${external_auth_url}
qortal_mode=${qortal_mode}
broker_dir=${broker_dir}
nextcloud_custom_apps_path=${nextcloud_custom_apps_path}
qortal_node_url=${qortal_node_url}
host_external_auth_url=${host_external_auth_url}
host_qortal_node_url=${host_qortal_node_url}
Files written:
${compose_dst}
${env_dst}
${apache_preview_path}
1. Review the generated env file.
2. Start the sidecars:
docker compose -f ${compose_dst} --env-file ${env_dst} up -d
3. If using bundled External Auth, register a broker app after the service is healthy:
curl -sS -X POST http://127.0.0.1:${external_auth_port}/apps/register \\
-H "Content-Type: application/json" \\
-d '{"name":"qortal-nextcloud-integration"}'
Then set the returned values in ${env_dst}:
- QORTAL_EXTERNAL_AUTH_APP_ID
- QORTAL_EXTERNAL_AUTH_APP_SECRET
And restart broker:
docker compose -f ${compose_dst} --env-file ${env_dst} up -d --force-recreate broker
4. Install Apache proxy site:
sudo a2enmod proxy proxy_http headers ssl
sudo cp ${apache_preview_path} ${apache_site_path}
sudo a2ensite ${apache_site_name}
sudo systemctl reload apache2
5. Ensure the dedicated Nextcloud service user exists:
export NC_SERVICE_PASSWORD='${service_password}'
sudo --preserve-env=NC_SERVICE_PASSWORD -u www-data php ${occ_path} user:add --password-from-env --display-name='${service_user}' ${service_user}
6. Copy/enable NuQloud apps as needed:
sudo -u www-data php ${occ_path} app:enable qortal_integration
sudo -u www-data php ${occ_path} app:enable --force user_oidc
7. Configure OIDC provider:
sudo -u www-data php ${occ_path} user_oidc:provider qortal -c ${oidc_client_id} -s '${oidc_client_secret}' -d ${broker_url}/.well-known/openid-configuration --scope='openid profile email' --mapping-uid=sub --mapping-display-name=name --mapping-email=email
8. Optional NuQloud login label:
sudo -u www-data php ${occ_path} config:system:set user_oidc login_label --value='Create Account / Login'
9. Save broker internal token in app config:
sudo -u www-data php ${occ_path} config:app:set qortal_integration broker_internal_api_token --value='${broker_internal_api_token}'
10. Health checks:
curl -sS ${broker_url}/.well-known/openid-configuration
curl -sS ${host_external_auth_url}/health
sudo -u www-data php ${occ_path} app:list | rg 'qortal_integration|user_oidc'
EOF
)
echo "Bootstrapping host-nextcloud-hybrid sidecar files..."
echo " occ_path: ${occ_path}"
echo " nextcloud_url: ${nextcloud_url}"
echo " broker_url: ${broker_url}"
echo " external_auth_mode: ${external_auth_mode}"
echo " qortal_mode: ${qortal_mode}"
echo " broker_dir: ${broker_dir}"
echo " nextcloud_custom_apps_path: ${nextcloud_custom_apps_path}"
echo " qortal_node_url: ${qortal_node_url}"
echo
run_cmd mkdir -p "${broker_dir}"
run_cmd cp "${compose_src}" "${compose_dst}"
if [[ "${dry_run}" == "1" ]]; then
echo "[dry-run] write ${env_dst}"
else
printf '%s\n' "${env_content}" > "${env_dst}"
fi
if [[ "${dry_run}" == "1" ]]; then
echo "[dry-run] write ${apache_preview_path}"
else
printf '%s\n' "${apache_content}" > "${apache_preview_path}"
fi
if [[ "${dry_run}" == "1" ]]; then
echo "[dry-run] write ${notes_path}"
else
printf '%s\n' "${notes_content}" > "${notes_path}"
fi
if [[ "${external_auth_mode}" == "bundled" ]]; then
echo "Bundled external_auth mode selected."
echo "Start the sidecars first, then register an app against http://127.0.0.1:${external_auth_port}/apps/register."
echo
elif [[ -z "${external_auth_app_id}" || -z "${external_auth_app_secret}" ]]; then
echo "Warning: External Auth app credentials are empty in generated .env."
echo "Fill QORTAL_EXTERNAL_AUTH_APP_ID / QORTAL_EXTERNAL_AUTH_APP_SECRET before starting broker."
echo
fi
echo "Bootstrap artifacts ready."
echo " compose: ${compose_dst}"
echo " env: ${env_dst}"
echo " apache: ${apache_preview_path}"
echo " notes: ${notes_path}"