2896 lines
110 KiB
Bash
Executable File
2896 lines
110 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"
|
|
source "${script_dir}/lib-compose-project.sh"
|
|
|
|
mode="nossl"
|
|
with_external_auth="auto"
|
|
with_external_auth_explicit="0"
|
|
with_qortal="1"
|
|
with_qortal_explicit="0"
|
|
with_office="auto"
|
|
with_office_explicit="0"
|
|
with_signaling="auto"
|
|
with_signaling_explicit="0"
|
|
with_talk="auto"
|
|
with_talk_explicit="0"
|
|
with_turn="auto"
|
|
with_turn_explicit="0"
|
|
with_janus="auto"
|
|
with_janus_explicit="0"
|
|
with_recording="auto"
|
|
with_recording_explicit="0"
|
|
qortal_branch=""
|
|
env_file="${repo_root}/.env.devprod"
|
|
accept_defaults="0"
|
|
skip_occ="0"
|
|
guided_mode="auto"
|
|
configure_only="0"
|
|
auto_finish="auto"
|
|
post_finish_recreate="auto"
|
|
one_click="0"
|
|
auto_generate_secrets="0"
|
|
nextcloud_domain_input=""
|
|
broker_domain_input=""
|
|
collabora_domain_input=""
|
|
signaling_domain_input=""
|
|
install_custom_pwa_override=""
|
|
install_default_app_bundle_override=""
|
|
log_file=""
|
|
log_enabled="1"
|
|
default_nextcloud_image="nextcloud:34-apache"
|
|
tested_nextcloud_image_note="Tested image majors are 33 and 34."
|
|
original_args=("$@")
|
|
|
|
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
|
|
--qortal-branch <name> Build bundled Qortal Core from a specific branch
|
|
--with-office Enable Office/Collabora profile + auto-config
|
|
--without-office Disable Office/Collabora profile
|
|
--with-signaling Enable signaling stack (talk + turn + janus + recording) + auto-config
|
|
--without-signaling Disable signaling stack profiles
|
|
--with-talk-signaling Enable Talk HPB signaling profile + auto-config
|
|
--without-talk-signaling Disable Talk HPB signaling profile
|
|
--with-turn Enable TURN configuration (implies Talk HPB)
|
|
--without-turn Disable TURN configuration profile
|
|
--with-janus Enable Janus media profile (uses Talk HPB container)
|
|
--without-janus Disable Janus profile
|
|
--with-recording Enable Talk recording backend profile
|
|
--without-recording Disable Talk recording backend profile
|
|
--nextcloud-domain <fqdn> Set NEXTCLOUD_DOMAIN
|
|
--broker-domain <fqdn> Set BROKER_DOMAIN
|
|
--office-domain <fqdn> Set COLLABORA_DOMAIN
|
|
--signaling-domain <fqdn> Set SIGNALING_DOMAIN
|
|
--env-file <path> Env file to use (default: .env.devprod)
|
|
--guided Run interactive guided env setup
|
|
--no-guided Skip guided env setup
|
|
--one-click Non-interactive full NuQloud auto-configuration + start + finish
|
|
--configure-only Only write/validate configuration; do not start containers
|
|
--auto-finish Run finish-initial-setup automatically after install
|
|
--no-auto-finish Skip automatic finish-initial-setup call
|
|
--post-finish-recreate Recreate app/broker/external_auth after auto-finish
|
|
--no-post-finish-recreate Skip post-finish recreate step
|
|
--with-custom-pwa Include/enable custom_pwa app
|
|
--without-custom-pwa Do not include/enable custom_pwa app
|
|
--with-default-app-bundle Install/enable the recommended default Nextcloud apps bundle
|
|
--without-default-app-bundle
|
|
Skip the recommended default Nextcloud apps bundle
|
|
--log-file <path> Write live installer output to this log file
|
|
--no-log Do not write a persistent installer log
|
|
--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
|
|
}
|
|
|
|
start_install_log() {
|
|
local log_dir=""
|
|
local timestamp=""
|
|
|
|
case "$(printf '%s' "${NUQLOUD_INSTALL_LOG:-${log_enabled}}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" in
|
|
0|false|no|n|off)
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
timestamp="$(date +%Y%m%d-%H%M%S)"
|
|
if [[ -z "${log_file}" ]]; then
|
|
log_file="${NUQLOUD_INSTALL_LOG_FILE:-${repo_root}/logs/install-production-docker-${timestamp}.log}"
|
|
elif [[ "${log_file}" != /* ]]; then
|
|
log_file="${repo_root}/${log_file}"
|
|
fi
|
|
|
|
log_dir="$(dirname "${log_file}")"
|
|
mkdir -p "${log_dir}"
|
|
chmod 700 "${log_dir}" >/dev/null 2>&1 || true
|
|
touch "${log_file}"
|
|
chmod 600 "${log_file}" >/dev/null 2>&1 || true
|
|
ln -sfn "$(basename "${log_file}")" "${log_dir}/install-production-docker-latest.log" >/dev/null 2>&1 || true
|
|
|
|
exec > >(tee -a "${log_file}") 2>&1
|
|
echo "Installer log: ${log_file}"
|
|
echo "Started: $(date -Is)"
|
|
printf 'Command: %q' "$0"
|
|
printf ' %q' "$@"
|
|
printf '\n'
|
|
trap finish_install_log EXIT
|
|
}
|
|
|
|
finish_install_log() {
|
|
local rc=$?
|
|
echo "Finished: $(date -Is) status=${rc}"
|
|
echo "Installer log saved: ${log_file}"
|
|
exit "${rc}"
|
|
}
|
|
|
|
is_port_in_use() {
|
|
local host="${1:-}"
|
|
local port="${2:-}"
|
|
if [[ -z "${port}" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if command -v ss >/dev/null 2>&1; then
|
|
if [[ -n "${host}" && "${host}" != "0.0.0.0" && "${host}" != "::" ]]; then
|
|
if ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "(^|[\\[\\]:])${host}:${port}$|:${port}$"; then
|
|
return 0
|
|
fi
|
|
if ss -H -lun 2>/dev/null | awk '{print $5}' | grep -Eq "(^|[\\[\\]:])${host}:${port}$|:${port}$"; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
fi
|
|
|
|
if ss -H -ltn "( sport = :${port} )" 2>/dev/null | grep -q .; then
|
|
return 0
|
|
fi
|
|
if ss -H -lun "( sport = :${port} )" 2>/dev/null | grep -q .; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
fi
|
|
|
|
if command -v lsof >/dev/null 2>&1; then
|
|
lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
|
|
return $?
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
occ_has_command() {
|
|
local command_name="${1:-}"
|
|
if [[ -z "${command_name}" ]]; then
|
|
return 1
|
|
fi
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ list 2>/dev/null \
|
|
| grep -qE "^[[:space:]]+${command_name}([[:space:]]|$)"
|
|
}
|
|
|
|
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[@]}" "$@"
|
|
}
|
|
|
|
prompt_qortal_branch_if_needed() {
|
|
local use_specific_branch=""
|
|
local requested_branch=""
|
|
if [[ "${with_qortal}" != "1" || -n "${qortal_branch}" || "${one_click}" == "1" || ! -t 0 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
read -r -p "Use a specific Qortal Core branch for this install? (y/N): " use_specific_branch
|
|
if [[ ! "${use_specific_branch}" =~ ^[Yy]$ ]]; then
|
|
return 0
|
|
fi
|
|
|
|
while true; do
|
|
read -r -p "Qortal branch name: " requested_branch
|
|
requested_branch="${requested_branch//[$'\r\n']}"
|
|
if [[ -z "${requested_branch}" ]]; then
|
|
echo "Branch name cannot be empty."
|
|
continue
|
|
fi
|
|
if git check-ref-format --branch "${requested_branch}" >/dev/null 2>&1; then
|
|
qortal_branch="${requested_branch}"
|
|
echo "Using Qortal branch override for this install: ${qortal_branch}"
|
|
return 0
|
|
fi
|
|
echo "Invalid Qortal branch name: ${requested_branch}"
|
|
done
|
|
}
|
|
|
|
cleanup_nextcloud_defaults() {
|
|
local defaults_config_status=0
|
|
local firstrun_disable_status=0
|
|
local errexit_was_set="0"
|
|
|
|
[[ $- == *e* ]] && errexit_was_set="1"
|
|
set +e
|
|
"${script_dir}/configure-nextcloud-defaults.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" \
|
|
--restore-core-skeleton >/dev/null 2>&1
|
|
defaults_config_status=$?
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:disable firstrunwizard >/dev/null 2>&1
|
|
firstrun_disable_status=$?
|
|
if [[ "${errexit_was_set}" == "1" ]]; then
|
|
set -e
|
|
fi
|
|
|
|
if [[ "${defaults_config_status}" -ne 0 ]]; then
|
|
echo "Warning: could not configure Nextcloud default onboarding/skeleton settings."
|
|
fi
|
|
if [[ "${firstrun_disable_status}" -ne 0 ]]; then
|
|
echo "Warning: could not disable the Nextcloud First Run Wizard app."
|
|
fi
|
|
}
|
|
|
|
apply_nextcloud_local_patch_if_present() {
|
|
local patch_file="$1"
|
|
local target_root="$2"
|
|
local marker="${3:-}"
|
|
local target_file_rel="${4:-}"
|
|
local target_file=""
|
|
local patch_rc=0
|
|
local patch_name=""
|
|
local patch_log=""
|
|
|
|
patch_name="$(basename "${patch_file}")"
|
|
|
|
nextcloud_patch_effective() {
|
|
local file_path="${1:-}"
|
|
if [[ -z "${file_path}" || ! -f "${file_path}" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ -n "${marker}" ]] && grep -qF "${marker}" "${file_path}" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
|
|
# PWA theming patch can be effectively present even if context/line numbers shifted.
|
|
if [[ "${patch_name}" == "nextcloud-pwa-theming-fix.patch" ]]; then
|
|
if grep -q "theming.Theming.getManifest" "${file_path}" 2>/dev/null \
|
|
&& grep -q "'app' => 'core'" "${file_path}" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
if [[ -z "${patch_file}" || -z "${target_root}" ]]; then
|
|
return 0
|
|
fi
|
|
if [[ ! -f "${patch_file}" ]]; then
|
|
return 0
|
|
fi
|
|
if [[ ! -d "${target_root}" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ -n "${target_file_rel}" ]]; then
|
|
target_file="${target_root}/${target_file_rel}"
|
|
if [[ ! -f "${target_file}" ]]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${target_file}" ]] && nextcloud_patch_effective "${target_file}"; then
|
|
echo "Nextcloud patch already applied/effective: ${patch_name}"
|
|
return 0
|
|
fi
|
|
|
|
if command -v patch >/dev/null 2>&1; then
|
|
patch_log="$(mktemp)"
|
|
patch --forward --batch -d "${target_root}" -p1 < "${patch_file}" >"${patch_log}" 2>&1 || patch_rc=$?
|
|
if [[ "${patch_rc}" -eq 0 ]]; then
|
|
rm -f "${patch_log}" >/dev/null 2>&1 || true
|
|
echo "Applied Nextcloud patch: ${patch_name}"
|
|
return 0
|
|
fi
|
|
# patch exits 1 for already-applied/reversed hunks when using --forward.
|
|
if [[ "${patch_rc}" -eq 1 ]]; then
|
|
if [[ -n "${target_file}" ]] && nextcloud_patch_effective "${target_file}"; then
|
|
rm -f "${patch_log}" >/dev/null 2>&1 || true
|
|
echo "Nextcloud patch already applied/effective: ${patch_name}"
|
|
return 0
|
|
fi
|
|
fi
|
|
if [[ -s "${patch_log}" ]]; then
|
|
echo "Warning: patch(1) could not apply ${patch_name}; trying git apply fallback."
|
|
sed -n '1,10p' "${patch_log}" | sed 's/^/ patch: /'
|
|
fi
|
|
rm -f "${patch_log}" >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
if command -v git >/dev/null 2>&1; then
|
|
if git -C "${target_root}" apply --check "${patch_file}" >/dev/null 2>&1 || git apply --check --directory="${target_root}" "${patch_file}" >/dev/null 2>&1; then
|
|
if git -C "${target_root}" apply "${patch_file}" >/dev/null 2>&1 || git apply --directory="${target_root}" "${patch_file}" >/dev/null 2>&1; then
|
|
echo "Applied Nextcloud patch: ${patch_name}"
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${target_file}" ]] && nextcloud_patch_effective "${target_file}"; then
|
|
echo "Nextcloud patch already applied/effective: ${patch_name}"
|
|
return 0
|
|
fi
|
|
|
|
echo "Warning: could not apply Nextcloud patch ${patch_name} (patch and git apply failed)"
|
|
return 1
|
|
}
|
|
|
|
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"
|
|
with_external_auth_explicit="1"
|
|
shift
|
|
;;
|
|
--without-external-auth)
|
|
with_external_auth="0"
|
|
with_external_auth_explicit="1"
|
|
shift
|
|
;;
|
|
--with-qortal)
|
|
with_qortal="1"
|
|
with_qortal_explicit="1"
|
|
shift
|
|
;;
|
|
--without-qortal)
|
|
with_qortal="0"
|
|
with_qortal_explicit="1"
|
|
shift
|
|
;;
|
|
--qortal-branch)
|
|
qortal_branch="${2:-}"
|
|
shift 2
|
|
;;
|
|
--qortal-branch=*)
|
|
qortal_branch="${1#*=}"
|
|
shift
|
|
;;
|
|
--with-office)
|
|
with_office="1"
|
|
with_office_explicit="1"
|
|
shift
|
|
;;
|
|
--without-office)
|
|
with_office="0"
|
|
with_office_explicit="1"
|
|
shift
|
|
;;
|
|
--with-signaling)
|
|
with_signaling="1"
|
|
with_signaling_explicit="1"
|
|
shift
|
|
;;
|
|
--without-signaling)
|
|
with_signaling="0"
|
|
with_signaling_explicit="1"
|
|
shift
|
|
;;
|
|
--with-talk-signaling|--with-talk)
|
|
with_talk="1"
|
|
with_talk_explicit="1"
|
|
shift
|
|
;;
|
|
--without-talk-signaling|--without-talk)
|
|
with_talk="0"
|
|
with_talk_explicit="1"
|
|
shift
|
|
;;
|
|
--with-turn)
|
|
with_turn="1"
|
|
with_turn_explicit="1"
|
|
shift
|
|
;;
|
|
--without-turn)
|
|
with_turn="0"
|
|
with_turn_explicit="1"
|
|
shift
|
|
;;
|
|
--with-janus)
|
|
with_janus="1"
|
|
with_janus_explicit="1"
|
|
shift
|
|
;;
|
|
--without-janus)
|
|
with_janus="0"
|
|
with_janus_explicit="1"
|
|
shift
|
|
;;
|
|
--with-recording)
|
|
with_recording="1"
|
|
with_recording_explicit="1"
|
|
shift
|
|
;;
|
|
--without-recording)
|
|
with_recording="0"
|
|
with_recording_explicit="1"
|
|
shift
|
|
;;
|
|
--nextcloud-domain)
|
|
nextcloud_domain_input="${2:-}"
|
|
shift 2
|
|
;;
|
|
--nextcloud-domain=*)
|
|
nextcloud_domain_input="${1#*=}"
|
|
shift
|
|
;;
|
|
--broker-domain)
|
|
broker_domain_input="${2:-}"
|
|
shift 2
|
|
;;
|
|
--broker-domain=*)
|
|
broker_domain_input="${1#*=}"
|
|
shift
|
|
;;
|
|
--collabora-domain|--office-domain)
|
|
collabora_domain_input="${2:-}"
|
|
shift 2
|
|
;;
|
|
--collabora-domain=*|--office-domain=*)
|
|
collabora_domain_input="${1#*=}"
|
|
shift
|
|
;;
|
|
--signaling-domain|--talk-domain|--talk-signaling-domain)
|
|
signaling_domain_input="${2:-}"
|
|
shift 2
|
|
;;
|
|
--signaling-domain=*|--talk-domain=*|--talk-signaling-domain=*)
|
|
signaling_domain_input="${1#*=}"
|
|
shift
|
|
;;
|
|
--env-file)
|
|
env_file="${2:-}"
|
|
shift 2
|
|
;;
|
|
--env-file=*)
|
|
env_file="${1#*=}"
|
|
shift
|
|
;;
|
|
--guided)
|
|
guided_mode="1"
|
|
shift
|
|
;;
|
|
--no-guided)
|
|
guided_mode="0"
|
|
shift
|
|
;;
|
|
--one-click)
|
|
one_click="1"
|
|
guided_mode="0"
|
|
accept_defaults="1"
|
|
auto_finish="1"
|
|
shift
|
|
;;
|
|
--configure-only)
|
|
configure_only="1"
|
|
shift
|
|
;;
|
|
--auto-finish)
|
|
auto_finish="1"
|
|
shift
|
|
;;
|
|
--no-auto-finish)
|
|
auto_finish="0"
|
|
shift
|
|
;;
|
|
--post-finish-recreate)
|
|
post_finish_recreate="1"
|
|
shift
|
|
;;
|
|
--no-post-finish-recreate)
|
|
post_finish_recreate="0"
|
|
shift
|
|
;;
|
|
--with-custom-pwa)
|
|
install_custom_pwa_override="1"
|
|
shift
|
|
;;
|
|
--without-custom-pwa)
|
|
install_custom_pwa_override="0"
|
|
shift
|
|
;;
|
|
--with-default-app-bundle)
|
|
install_default_app_bundle_override="1"
|
|
shift
|
|
;;
|
|
--without-default-app-bundle)
|
|
install_default_app_bundle_override="0"
|
|
shift
|
|
;;
|
|
--log-file)
|
|
log_file="${2:-}"
|
|
shift 2
|
|
;;
|
|
--log-file=*)
|
|
log_file="${1#*=}"
|
|
shift
|
|
;;
|
|
--no-log)
|
|
log_enabled="0"
|
|
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
|
|
|
|
start_install_log "${original_args[@]}"
|
|
|
|
if [[ -n "${qortal_branch}" ]] && ! git check-ref-format --branch "${qortal_branch}" >/dev/null 2>&1; then
|
|
echo "Invalid Qortal branch name: ${qortal_branch}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "${mode}" != "ssl" && "${mode}" != "nossl" ]]; then
|
|
echo "Invalid mode: ${mode}"
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "${one_click}" == "1" ]]; then
|
|
if [[ "${with_external_auth_explicit}" != "1" ]]; then
|
|
with_external_auth="1"
|
|
fi
|
|
if [[ "${with_qortal_explicit}" != "1" ]]; then
|
|
with_qortal="1"
|
|
fi
|
|
if [[ "${with_office_explicit}" != "1" ]]; then
|
|
with_office="1"
|
|
fi
|
|
if [[ "${with_signaling_explicit}" != "1" ]]; then
|
|
with_signaling="1"
|
|
fi
|
|
if [[ "${with_talk_explicit}" != "1" ]]; then
|
|
with_talk="1"
|
|
fi
|
|
if [[ "${with_turn_explicit}" != "1" ]]; then
|
|
with_turn="1"
|
|
fi
|
|
if [[ "${with_janus_explicit}" != "1" ]]; then
|
|
with_janus="1"
|
|
fi
|
|
if [[ "${with_recording_explicit}" != "1" ]]; then
|
|
with_recording="1"
|
|
fi
|
|
if [[ -z "${install_custom_pwa_override}" ]]; then
|
|
install_custom_pwa_override="1"
|
|
fi
|
|
if [[ -z "${install_default_app_bundle_override}" ]]; then
|
|
install_default_app_bundle_override="1"
|
|
fi
|
|
fi
|
|
|
|
if [[ "${with_office}" == "auto" ]]; then
|
|
with_office="0"
|
|
fi
|
|
if [[ "${with_signaling}" == "auto" ]]; then
|
|
with_signaling="0"
|
|
fi
|
|
if [[ "${with_talk}" == "auto" ]]; then
|
|
with_talk="0"
|
|
fi
|
|
if [[ "${with_turn}" == "auto" ]]; then
|
|
with_turn="0"
|
|
fi
|
|
if [[ "${with_janus}" == "auto" ]]; then
|
|
with_janus="0"
|
|
fi
|
|
if [[ "${with_recording}" == "auto" ]]; then
|
|
with_recording="0"
|
|
fi
|
|
|
|
if [[ "${with_turn}" == "1" || "${with_janus}" == "1" ]]; then
|
|
with_talk="1"
|
|
fi
|
|
if [[ "${with_recording}" == "1" ]]; then
|
|
with_talk="1"
|
|
fi
|
|
|
|
if [[ "${with_signaling_explicit}" == "1" ]]; then
|
|
if [[ "${with_signaling}" == "1" ]]; then
|
|
if [[ "${with_talk_explicit}" != "1" ]]; then
|
|
with_talk="1"
|
|
fi
|
|
if [[ "${with_turn_explicit}" != "1" ]]; then
|
|
with_turn="1"
|
|
fi
|
|
if [[ "${with_janus_explicit}" != "1" ]]; then
|
|
with_janus="1"
|
|
fi
|
|
if [[ "${with_recording_explicit}" != "1" ]]; then
|
|
with_recording="1"
|
|
fi
|
|
else
|
|
if [[ "${with_talk_explicit}" != "1" ]]; then
|
|
with_talk="0"
|
|
fi
|
|
if [[ "${with_turn_explicit}" != "1" ]]; then
|
|
with_turn="0"
|
|
fi
|
|
if [[ "${with_janus_explicit}" != "1" ]]; then
|
|
with_janus="0"
|
|
fi
|
|
if [[ "${with_recording_explicit}" != "1" ]]; then
|
|
with_recording="0"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ "${with_talk}" == "0" ]]; then
|
|
if [[ "${with_turn}" == "1" || "${with_janus}" == "1" ]]; then
|
|
echo "--with-turn/--with-janus require Talk HPB. Re-run with --with-talk-signaling."
|
|
exit 1
|
|
fi
|
|
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}"
|
|
}
|
|
|
|
install_custom_pwa_enabled() {
|
|
local raw
|
|
raw="$(read_env_value "INSTALL_CUSTOM_PWA" "1")"
|
|
case "$(echo "${raw}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" in
|
|
0|false|no|n|off)
|
|
return 1
|
|
;;
|
|
*)
|
|
return 0
|
|
;;
|
|
esac
|
|
}
|
|
|
|
install_default_app_bundle_enabled() {
|
|
local raw
|
|
raw="$(read_env_value "INSTALL_DEFAULT_APP_BUNDLE" "0")"
|
|
case "$(echo "${raw}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" in
|
|
1|true|yes|y|on)
|
|
return 0
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
set_env_value() {
|
|
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
|
|
}
|
|
|
|
prompt_env_value() {
|
|
local key="$1"
|
|
local fallback_default="$2"
|
|
local label="$3"
|
|
local secret="${4:-0}"
|
|
local current_value
|
|
local value
|
|
|
|
current_value="$(read_env_value "${key}" "${fallback_default}")"
|
|
if [[ "${secret}" == "1" ]]; then
|
|
read -r -s -p "${label} [hidden, Enter to keep current]: " value
|
|
echo
|
|
value="${value:-${current_value}}"
|
|
else
|
|
read -r -p "${label} [${current_value}]: " value
|
|
value="${value:-${current_value}}"
|
|
fi
|
|
set_env_value "${key}" "${value}"
|
|
}
|
|
|
|
prompt_yes_no() {
|
|
local label="$1"
|
|
local default_answer="${2:-N}"
|
|
local answer
|
|
|
|
if [[ "${default_answer}" =~ ^[Yy]$ ]]; then
|
|
read -r -p "${label} (Y/n): " answer
|
|
answer="${answer:-Y}"
|
|
else
|
|
read -r -p "${label} (y/N): " answer
|
|
answer="${answer:-N}"
|
|
fi
|
|
|
|
if [[ "${answer}" =~ ^[Yy]$ ]]; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
is_reserved_nonpublic_domain() {
|
|
local domain="${1:-}"
|
|
domain="$(echo "${domain}" | tr '[:upper:]' '[:lower:]' | xargs)"
|
|
domain="${domain%.}"
|
|
|
|
if [[ -z "${domain}" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
case "${domain}" in
|
|
localhost|localhost.localdomain|*.localhost|*.local|*.test|*.example|*.invalid|example.test)
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
return 1
|
|
}
|
|
|
|
is_valid_public_contact_email() {
|
|
local email="${1:-}"
|
|
local domain=""
|
|
|
|
email="$(echo "${email}" | tr '[:upper:]' '[:lower:]' | xargs)"
|
|
if [[ ! "${email}" =~ ^[^@[:space:]]+@[^@[:space:]]+\.[^@[:space:]]+$ ]]; then
|
|
return 1
|
|
fi
|
|
|
|
domain="${email#*@}"
|
|
if is_reserved_nonpublic_domain "${domain}"; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
default_caddy_email_for_domain() {
|
|
local domain="${1:-}"
|
|
domain="$(echo "${domain}" | tr '[:upper:]' '[:lower:]' | xargs)"
|
|
domain="${domain%.}"
|
|
if [[ -z "${domain}" ]]; then
|
|
echo "admin@example.test"
|
|
return 0
|
|
fi
|
|
echo "admin@${domain}"
|
|
}
|
|
|
|
validate_caddy_public_tls_settings() {
|
|
local tls_mode caddy_email nextcloud_domain broker_domain collabora_domain signaling_domain
|
|
local invalid_domains=()
|
|
|
|
if [[ "${mode}" != "ssl" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
tls_mode="$(read_env_value "CADDY_TLS" "")"
|
|
tls_mode="$(echo "${tls_mode}" | tr '[:upper:]' '[:lower:]' | xargs)"
|
|
if [[ "${tls_mode}" == "tls internal" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
caddy_email="$(read_env_value "CADDY_EMAIL" "")"
|
|
if ! is_valid_public_contact_email "${caddy_email}"; then
|
|
echo "Invalid Caddy ACME contact email for public TLS: ${caddy_email:-<empty>}"
|
|
echo "Set CADDY_EMAIL to a real email address on a public domain, or set CADDY_TLS=tls internal for self-signed/local testing."
|
|
exit 1
|
|
fi
|
|
|
|
nextcloud_domain="$(read_env_value "NEXTCLOUD_DOMAIN" "")"
|
|
broker_domain="$(read_env_value "BROKER_DOMAIN" "")"
|
|
collabora_domain="$(read_env_value "COLLABORA_DOMAIN" "")"
|
|
signaling_domain="$(read_env_value "SIGNALING_DOMAIN" "")"
|
|
|
|
for domain in "${nextcloud_domain}" "${broker_domain}" "${collabora_domain}" "${signaling_domain}"; do
|
|
if is_reserved_nonpublic_domain "${domain}"; then
|
|
invalid_domains+=("${domain}")
|
|
fi
|
|
done
|
|
|
|
if [[ "${#invalid_domains[@]}" -gt 0 ]]; then
|
|
echo "Caddy public TLS is configured, but these domains are not valid public ACME targets:"
|
|
for domain in "${invalid_domains[@]}"; do
|
|
echo " - ${domain}"
|
|
done
|
|
echo "Use public DNS names for SSL mode, or set CADDY_TLS=tls internal for self-signed/local testing."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
is_tested_nextcloud_image() {
|
|
local image="$1"
|
|
[[ "${image}" =~ (^|[^0-9])(33|34)([^0-9]|$) ]]
|
|
}
|
|
|
|
prompt_nextcloud_image_selection() {
|
|
local current_value choice custom_value selected_image
|
|
current_value="$(read_env_value "NEXTCLOUD_IMAGE" "${default_nextcloud_image}")"
|
|
if [[ -z "${current_value}" ]]; then
|
|
current_value="${default_nextcloud_image}"
|
|
fi
|
|
|
|
while true; do
|
|
echo "Select Nextcloud Docker image:"
|
|
echo " 1) nextcloud:34-apache (recommended default)"
|
|
echo " 2) nextcloud:33-apache (tested fallback)"
|
|
echo " 3) Keep current (${current_value})"
|
|
echo " 4) Enter a custom image tag"
|
|
read -r -p "Choice [1/2/3/4] (default: 1): " choice
|
|
choice="${choice:-1}"
|
|
case "${choice}" in
|
|
1)
|
|
selected_image="nextcloud:34-apache"
|
|
;;
|
|
2)
|
|
selected_image="nextcloud:33-apache"
|
|
;;
|
|
3)
|
|
selected_image="${current_value}"
|
|
;;
|
|
4)
|
|
read -r -p "Custom Nextcloud image (for example nextcloud:34.0.1-apache): " custom_value
|
|
custom_value="${custom_value//[[:space:]]/}"
|
|
if [[ -z "${custom_value}" ]]; then
|
|
echo "Custom image cannot be empty."
|
|
continue
|
|
fi
|
|
selected_image="${custom_value}"
|
|
;;
|
|
*)
|
|
echo "Invalid choice: ${choice}"
|
|
continue
|
|
;;
|
|
esac
|
|
|
|
if ! is_tested_nextcloud_image "${selected_image}"; then
|
|
echo "Warning: ${selected_image} has not been tested extensively in this repo."
|
|
echo "${tested_nextcloud_image_note}"
|
|
if ! prompt_yes_no "Use ${selected_image} anyway?" "N"; then
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
set_env_value "NEXTCLOUD_IMAGE" "${selected_image}"
|
|
echo "Using NEXTCLOUD_IMAGE=${selected_image}"
|
|
break
|
|
done
|
|
}
|
|
|
|
compute_public_url() {
|
|
local domain="$1"
|
|
local https_port="$2"
|
|
if [[ "${https_port}" == "443" ]]; then
|
|
echo "https://${domain}"
|
|
else
|
|
echo "https://${domain}:${https_port}"
|
|
fi
|
|
}
|
|
|
|
stun_server_should_follow_talk_domain() {
|
|
local stun_server="$1"
|
|
local talk_domain="$2"
|
|
local legacy_domain="${3:-}"
|
|
local host_fqdn host_short host_fqdn_local host_short_local stun_host
|
|
|
|
if [[ -z "${stun_server}" || "${stun_server}" == "stun.nextcloud.com:443" || "${stun_server}" == "stun.nextcloud.com" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
stun_host="${stun_server%%:*}"
|
|
host_fqdn="$(hostname -f 2>/dev/null || true)"
|
|
host_short="$(hostname -s 2>/dev/null || true)"
|
|
host_fqdn_local="${host_fqdn}.local"
|
|
host_short_local="${host_short}.local"
|
|
|
|
if [[ "${stun_host}" == "${talk_domain}" || -n "${legacy_domain}" && "${stun_host}" == "${legacy_domain}" || "${stun_host}" == "${host_fqdn}" || "${stun_host}" == "${host_short}" || "${stun_host}" == "${host_fqdn_local}" || "${stun_host}" == "${host_short_local}" || "${stun_host}" == "localhost" || "${stun_host}" == "127.0.0.1" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
janus_stun_server_should_follow_default() {
|
|
local stun_server="$1"
|
|
local legacy_domain_a="${2:-}"
|
|
local legacy_domain_b="${3:-}"
|
|
local host_fqdn host_short host_fqdn_local host_short_local
|
|
|
|
if [[ -z "${stun_server}" || "${stun_server}" == "stun.nextcloud.com" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
host_fqdn="$(hostname -f 2>/dev/null || true)"
|
|
host_short="$(hostname -s 2>/dev/null || true)"
|
|
host_fqdn_local="${host_fqdn}.local"
|
|
host_short_local="${host_short}.local"
|
|
|
|
if [[ "${stun_server}" == "${legacy_domain_a}" || "${stun_server}" == "${legacy_domain_b}" || "${stun_server}" == "${host_fqdn}" || "${stun_server}" == "${host_short}" || "${stun_server}" == "${host_fqdn_local}" || "${stun_server}" == "${host_short_local}" || "${stun_server}" == "localhost" || "${stun_server}" == "127.0.0.1" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
turn_server_should_follow_talk_domain() {
|
|
local turn_server="$1"
|
|
local talk_domain="$2"
|
|
local talk_turn_port="$3"
|
|
local legacy_domain="${4:-}"
|
|
local host_fqdn host_short host_fqdn_local host_short_local turn_host turn_port
|
|
|
|
if [[ -z "${turn_server}" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
turn_host="${turn_server%%:*}"
|
|
turn_port="${turn_server##*:}"
|
|
host_fqdn="$(hostname -f 2>/dev/null || true)"
|
|
host_short="$(hostname -s 2>/dev/null || true)"
|
|
host_fqdn_local="${host_fqdn}.local"
|
|
host_short_local="${host_short}.local"
|
|
|
|
if [[ "${turn_port}" != "${talk_turn_port}" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ "${turn_host}" == "${talk_domain}" || -n "${legacy_domain}" && "${turn_host}" == "${legacy_domain}" || "${turn_host}" == "${host_fqdn}" || "${turn_host}" == "${host_short}" || "${turn_host}" == "${host_fqdn_local}" || "${turn_host}" == "${host_short_local}" || "${turn_host}" == "localhost" || "${turn_host}" == "127.0.0.1" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
derive_default_nextcloud_domain() {
|
|
local host_fqdn host_short
|
|
host_fqdn="$(hostname -f 2>/dev/null || true)"
|
|
host_short="$(hostname -s 2>/dev/null || true)"
|
|
if [[ -n "${host_fqdn}" && "${host_fqdn}" != "(none)" && "${host_fqdn}" != "localhost" ]]; then
|
|
if [[ "${host_fqdn}" != *.* ]]; then
|
|
echo "${host_fqdn}.local"
|
|
return
|
|
fi
|
|
echo "${host_fqdn}"
|
|
return
|
|
fi
|
|
if [[ -n "${host_short}" && "${host_short}" != "(none)" && "${host_short}" != "localhost" ]]; then
|
|
echo "${host_short}.local"
|
|
return
|
|
fi
|
|
echo "cloud.local"
|
|
}
|
|
|
|
derive_broker_domain() {
|
|
local nextcloud_domain="$1"
|
|
if [[ "${nextcloud_domain}" == *.*.* ]]; then
|
|
local tail="${nextcloud_domain#*.}"
|
|
if [[ -n "${tail}" && "${tail}" != "${nextcloud_domain}" ]]; then
|
|
echo "broker.${tail}"
|
|
return
|
|
fi
|
|
fi
|
|
echo "broker.${nextcloud_domain}"
|
|
}
|
|
|
|
derive_prefixed_domain() {
|
|
local prefix="$1"
|
|
local nextcloud_domain="$2"
|
|
if [[ -z "${nextcloud_domain}" ]]; then
|
|
echo "${prefix}.local"
|
|
return
|
|
fi
|
|
if [[ "${nextcloud_domain}" == "${prefix}."* ]]; then
|
|
echo "${nextcloud_domain}"
|
|
return
|
|
fi
|
|
echo "${prefix}.${nextcloud_domain}"
|
|
}
|
|
|
|
random_hex() {
|
|
local bytes="${1:-16}"
|
|
if command -v openssl >/dev/null 2>&1; then
|
|
openssl rand -hex "${bytes}"
|
|
return
|
|
fi
|
|
od -An -N "${bytes}" -tx1 /dev/urandom | tr -d ' \n'
|
|
}
|
|
|
|
ensure_secret_if_placeholder() {
|
|
local key="$1"
|
|
local bytes="$2"
|
|
shift 2
|
|
local current
|
|
current="$(read_env_value "${key}" "")"
|
|
if [[ -z "${current}" ]]; then
|
|
set_env_value "${key}" "$(random_hex "${bytes}")"
|
|
return
|
|
fi
|
|
if [[ "${auto_generate_secrets}" != "1" ]]; then
|
|
return
|
|
fi
|
|
for placeholder in "$@"; do
|
|
if [[ "${current}" == "${placeholder}" ]]; then
|
|
set_env_value "${key}" "$(random_hex "${bytes}")"
|
|
return
|
|
fi
|
|
done
|
|
}
|
|
|
|
auto_derive_env_defaults() {
|
|
local nextcloud_domain broker_domain collabora_domain signaling_domain https_port
|
|
local nc_public_url broker_public_url collabora_public_url signaling_public_url caddy_email
|
|
local external_auth_base_url talk_turn_port_value talk_stun_server_value
|
|
local admin_user_current service_user_current service_password_current admin_password_current
|
|
local service_user_default="nuqloud_oidc_service"
|
|
|
|
nextcloud_domain="$(read_env_value "NEXTCLOUD_DOMAIN" "")"
|
|
if [[ -z "${nextcloud_domain}" || "${nextcloud_domain}" == "cloud.example.test" ]]; then
|
|
nextcloud_domain="$(derive_default_nextcloud_domain)"
|
|
set_env_value "NEXTCLOUD_DOMAIN" "${nextcloud_domain}"
|
|
fi
|
|
|
|
broker_domain="$(read_env_value "BROKER_DOMAIN" "")"
|
|
if [[ -z "${broker_domain}" || "${broker_domain}" == "qortalbroker.example.test" ]]; then
|
|
broker_domain="$(derive_broker_domain "${nextcloud_domain}")"
|
|
set_env_value "BROKER_DOMAIN" "${broker_domain}"
|
|
fi
|
|
|
|
collabora_domain="$(read_env_value "COLLABORA_DOMAIN" "")"
|
|
if [[ -z "${collabora_domain}" || "${collabora_domain}" == "office.cloud.example.test" ]]; then
|
|
collabora_domain="$(derive_prefixed_domain "office" "${nextcloud_domain}")"
|
|
set_env_value "COLLABORA_DOMAIN" "${collabora_domain}"
|
|
fi
|
|
|
|
signaling_domain="$(read_env_value "SIGNALING_DOMAIN" "")"
|
|
if [[ -z "${signaling_domain}" ]]; then
|
|
signaling_domain="$(read_env_value "TALK_DOMAIN" "")"
|
|
fi
|
|
if [[ -z "${signaling_domain}" || "${signaling_domain}" == "talk.cloud.example.test" ]]; then
|
|
signaling_domain="$(derive_prefixed_domain "talk" "${nextcloud_domain}")"
|
|
set_env_value "SIGNALING_DOMAIN" "${signaling_domain}"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_DOMAIN" "")" || "$(read_env_value "TALK_DOMAIN" "")" == "talk.cloud.example.test" ]]; then
|
|
set_env_value "TALK_DOMAIN" "${signaling_domain}"
|
|
fi
|
|
|
|
if [[ "${mode}" == "nossl" ]]; then
|
|
if [[ -z "$(read_env_value "EXTERNAL_PROXY_MODE" "")" ]]; then
|
|
set_env_value "EXTERNAL_PROXY_MODE" "manual"
|
|
fi
|
|
if [[ -z "$(read_env_value "DEVPROD_HTTP_PORT" "")" ]]; then
|
|
set_env_value "DEVPROD_HTTP_PORT" "8081"
|
|
fi
|
|
if [[ -z "$(read_env_value "DEVPROD_BROKER_PORT" "")" ]]; then
|
|
set_env_value "DEVPROD_BROKER_PORT" "3001"
|
|
fi
|
|
if [[ -z "$(read_env_value "PUBLIC_HTTPS_PORT" "")" ]]; then
|
|
set_env_value "PUBLIC_HTTPS_PORT" "443"
|
|
fi
|
|
https_port="$(read_env_value "PUBLIC_HTTPS_PORT" "443")"
|
|
else
|
|
if [[ -z "$(read_env_value "EXTERNAL_PROXY_MODE" "")" ]]; then
|
|
set_env_value "EXTERNAL_PROXY_MODE" "none"
|
|
fi
|
|
if [[ -z "$(read_env_value "CADDY_HTTP_PORT" "")" ]]; then
|
|
set_env_value "CADDY_HTTP_PORT" "80"
|
|
fi
|
|
if [[ -z "$(read_env_value "CADDY_HTTPS_PORT" "")" ]]; then
|
|
set_env_value "CADDY_HTTPS_PORT" "443"
|
|
fi
|
|
https_port="$(read_env_value "CADDY_HTTPS_PORT" "443")"
|
|
caddy_email="$(read_env_value "CADDY_EMAIL" "")"
|
|
if [[ -z "${caddy_email}" || "${caddy_email}" == "admin@example.test" ]]; then
|
|
set_env_value "CADDY_EMAIL" "admin@${nextcloud_domain}"
|
|
fi
|
|
fi
|
|
|
|
nc_public_url="$(compute_public_url "${nextcloud_domain}" "${https_port}")"
|
|
broker_public_url="$(compute_public_url "${broker_domain}" "${https_port}")"
|
|
collabora_public_url="$(compute_public_url "${collabora_domain}" "${https_port}")"
|
|
signaling_public_url="$(compute_public_url "${signaling_domain}" "${https_port}")"
|
|
|
|
set_env_value "NEXTCLOUD_TRUSTED_DOMAINS" "${nextcloud_domain}"
|
|
set_env_value "NEXTCLOUD_BASE_URL" "http://app"
|
|
set_env_value "NEXTCLOUD_PUBLIC_URL" "${nc_public_url}"
|
|
set_env_value "OIDC_ISSUER" "${broker_public_url}"
|
|
set_env_value "OIDC_REDIRECT_URI_ALLOWLIST" "${nc_public_url}/apps/user_oidc/code"
|
|
set_env_value "COLLABORA_PUBLIC_URL" "${collabora_public_url}"
|
|
set_env_value "TALK_SIGNALING_PUBLIC_URL" "${signaling_public_url}"
|
|
|
|
if [[ -z "$(read_env_value "BROKER_CORS_ALLOWED_ORIGINS" "")" ]]; then
|
|
set_env_value "BROKER_CORS_ALLOWED_ORIGINS" "${nc_public_url}"
|
|
fi
|
|
if [[ -z "$(read_env_value "BROKER_REQUEST_BODY_LIMIT" "")" ]]; then
|
|
set_env_value "BROKER_REQUEST_BODY_LIMIT" "64mb"
|
|
fi
|
|
if [[ -z "$(read_env_value "NEXTCLOUD_PHP_UPLOAD_MAX_FILESIZE" "")" ]]; then
|
|
set_env_value "NEXTCLOUD_PHP_UPLOAD_MAX_FILESIZE" "5G"
|
|
fi
|
|
if [[ -z "$(read_env_value "NEXTCLOUD_PHP_POST_MAX_SIZE" "")" ]]; then
|
|
set_env_value "NEXTCLOUD_PHP_POST_MAX_SIZE" "5G"
|
|
fi
|
|
if [[ -z "$(read_env_value "OIDC_POLICY_MODE" "")" ]]; then
|
|
set_env_value "OIDC_POLICY_MODE" "auto_provision"
|
|
fi
|
|
if [[ -z "$(read_env_value "OIDC_AUTO_PROVISION_GUARD" "")" ]]; then
|
|
set_env_value "OIDC_AUTO_PROVISION_GUARD" "invite_or_allowlist"
|
|
fi
|
|
if [[ -z "$(read_env_value "OIDC_REQUIRE_EMAIL_FOR_NEW_ACCOUNT" "")" ]]; then
|
|
set_env_value "OIDC_REQUIRE_EMAIL_FOR_NEW_ACCOUNT" "true"
|
|
fi
|
|
if [[ -z "$(read_env_value "NEXTCLOUD_IMAGE" "")" ]]; then
|
|
set_env_value "NEXTCLOUD_IMAGE" "${default_nextcloud_image}"
|
|
fi
|
|
|
|
if [[ -z "$(read_env_value "NEXTCLOUD_ADMIN_USER" "")" ]]; then
|
|
set_env_value "NEXTCLOUD_ADMIN_USER" "admin"
|
|
fi
|
|
ensure_secret_if_placeholder "NEXTCLOUD_ADMIN_PASSWORD" 16 "admin123"
|
|
if [[ -z "$(read_env_value "COLLABORA_ADMIN_USER" "")" ]]; then
|
|
set_env_value "COLLABORA_ADMIN_USER" "admin"
|
|
fi
|
|
ensure_secret_if_placeholder "COLLABORA_ADMIN_PASSWORD" 16
|
|
|
|
admin_user_current="$(read_env_value "NEXTCLOUD_ADMIN_USER" "admin")"
|
|
service_user_current="$(read_env_value "NEXTCLOUD_SERVICE_USER" "")"
|
|
if [[ -z "${service_user_current}" ]]; then
|
|
set_env_value "NEXTCLOUD_SERVICE_USER" "${service_user_default}"
|
|
service_user_current="${service_user_default}"
|
|
elif [[ "${auto_generate_secrets}" == "1" && "${service_user_current}" == "${admin_user_current}" && "${service_user_default}" != "${admin_user_current}" ]]; then
|
|
# For auto-generated fresh installs, split service auth away from the human admin account.
|
|
set_env_value "NEXTCLOUD_SERVICE_USER" "${service_user_default}"
|
|
service_user_current="${service_user_default}"
|
|
fi
|
|
|
|
ensure_secret_if_placeholder "NEXTCLOUD_SERVICE_PASSWORD" 16 "admin123" "service-oidc-change-me"
|
|
service_password_current="$(read_env_value "NEXTCLOUD_SERVICE_PASSWORD" "")"
|
|
admin_password_current="$(read_env_value "NEXTCLOUD_ADMIN_PASSWORD" "")"
|
|
if [[ -z "${service_password_current}" ]]; then
|
|
set_env_value "NEXTCLOUD_SERVICE_PASSWORD" "$(random_hex 16)"
|
|
service_password_current="$(read_env_value "NEXTCLOUD_SERVICE_PASSWORD" "")"
|
|
fi
|
|
if [[ "${auto_generate_secrets}" == "1" && -n "${admin_password_current}" && "${service_user_current}" != "${admin_user_current}" && "${service_password_current}" == "${admin_password_current}" ]]; then
|
|
set_env_value "NEXTCLOUD_SERVICE_PASSWORD" "$(random_hex 16)"
|
|
fi
|
|
|
|
if [[ -z "$(read_env_value "MYSQL_DATABASE" "")" ]]; then
|
|
set_env_value "MYSQL_DATABASE" "nextcloud"
|
|
fi
|
|
if [[ -z "$(read_env_value "MYSQL_USER" "")" ]]; then
|
|
set_env_value "MYSQL_USER" "nextcloud"
|
|
fi
|
|
ensure_secret_if_placeholder "MYSQL_ROOT_PASSWORD" 16 "rootpass"
|
|
ensure_secret_if_placeholder "MYSQL_PASSWORD" 16 "nextcloudpass"
|
|
|
|
if [[ -z "$(read_env_value "BROKER_DB_NAME" "")" ]]; then
|
|
set_env_value "BROKER_DB_NAME" "qortal_broker"
|
|
fi
|
|
if [[ -z "$(read_env_value "BROKER_DB_USER" "")" ]]; then
|
|
set_env_value "BROKER_DB_USER" "qortal_broker"
|
|
fi
|
|
ensure_secret_if_placeholder "BROKER_DB_PASSWORD" 16 "qortal_brokerpass"
|
|
|
|
if [[ -z "$(read_env_value "OIDC_CLIENT_ID" "")" ]]; then
|
|
set_env_value "OIDC_CLIENT_ID" "nextcloud-local"
|
|
fi
|
|
ensure_secret_if_placeholder "OIDC_CLIENT_SECRET" 24 "dev-secret"
|
|
if [[ -z "$(read_env_value "COLLABORA_PORT" "")" ]]; then
|
|
set_env_value "COLLABORA_PORT" "9980"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_SIGNALING_PORT" "")" ]]; then
|
|
set_env_value "TALK_SIGNALING_PORT" "8081"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_SIGNALING_CONTAINER_PORT" "")" ]]; then
|
|
set_env_value "TALK_SIGNALING_CONTAINER_PORT" "8081"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_SIGNALING_BIND_HOST" "")" ]]; then
|
|
set_env_value "TALK_SIGNALING_BIND_HOST" "0.0.0.0"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_ETURNAL_EPMD_PORT" "")" ]]; then
|
|
set_env_value "TALK_ETURNAL_EPMD_PORT" "3470"
|
|
fi
|
|
talk_signaling_port_value="$(read_env_value "TALK_SIGNALING_PORT" "8081")"
|
|
talk_signaling_upstream_host_value="$(read_env_value "TALK_SIGNALING_UPSTREAM_HOST" "")"
|
|
if [[ -z "${talk_signaling_upstream_host_value}" || "${talk_signaling_upstream_host_value}" == "talk_hpb" ]]; then
|
|
set_env_value "TALK_SIGNALING_UPSTREAM_HOST" "host.docker.internal"
|
|
fi
|
|
talk_signaling_upstream_port_value="$(read_env_value "TALK_SIGNALING_UPSTREAM_PORT" "")"
|
|
if [[ -z "${talk_signaling_upstream_port_value}" || "${talk_signaling_upstream_port_value}" == "8081" || "${talk_signaling_upstream_port_value}" == "8082" ]]; then
|
|
set_env_value "TALK_SIGNALING_UPSTREAM_PORT" "${talk_signaling_port_value}"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_HPB_IMAGE" "")" ]]; then
|
|
set_env_value "TALK_HPB_IMAGE" "nuqloud-talk-hpb:local"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_HPB_BASE_IMAGE" "")" ]]; then
|
|
set_env_value "TALK_HPB_BASE_IMAGE" "ghcr.io/nextcloud-releases/aio-talk:latest"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_TURN_BIND_HOST" "")" ]]; then
|
|
set_env_value "TALK_TURN_BIND_HOST" "0.0.0.0"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_TURN_PORT" "")" ]]; then
|
|
set_env_value "TALK_TURN_PORT" "3478"
|
|
fi
|
|
signaling_domain_value="$(read_env_value "SIGNALING_DOMAIN" "")"
|
|
talk_turn_host_value="$(read_env_value "TALK_TURN_HOST" "")"
|
|
if [[ -z "${talk_turn_host_value}" || "${talk_turn_host_value}" == "${signaling_domain_value}" ]]; then
|
|
talk_turn_host_value="${nextcloud_domain}"
|
|
set_env_value "TALK_TURN_HOST" "${talk_turn_host_value}"
|
|
fi
|
|
talk_janus_stun_server_value="$(read_env_value "TALK_JANUS_STUN_SERVER" "")"
|
|
if janus_stun_server_should_follow_default "${talk_janus_stun_server_value}" "${signaling_domain_value}" "${talk_turn_host_value}"; then
|
|
set_env_value "TALK_JANUS_STUN_SERVER" "${talk_turn_host_value}"
|
|
fi
|
|
talk_turn_port_value="$(read_env_value "TALK_TURN_PORT" "3478")"
|
|
talk_janus_stun_port_value="$(read_env_value "TALK_JANUS_STUN_PORT" "")"
|
|
if [[ -z "${talk_janus_stun_port_value}" || "${talk_janus_stun_port_value}" == "3478" || "${talk_janus_stun_port_value}" == "${talk_turn_port_value}" ]]; then
|
|
set_env_value "TALK_JANUS_STUN_PORT" "${talk_turn_port_value}"
|
|
fi
|
|
talk_stun_server_value="$(read_env_value "TALK_STUN_SERVER" "")"
|
|
talk_turn_server_value="$(read_env_value "TALK_TURN_SERVER" "")"
|
|
if turn_server_should_follow_talk_domain "${talk_turn_server_value}" "${talk_turn_host_value}" "${talk_turn_port_value}" "${signaling_domain_value}"; then
|
|
set_env_value "TALK_TURN_SERVER" "${talk_turn_host_value}:${talk_turn_port_value}"
|
|
fi
|
|
if stun_server_should_follow_talk_domain "${talk_stun_server_value}" "${talk_turn_host_value}" "${signaling_domain_value}"; then
|
|
set_env_value "TALK_STUN_SERVER" "${talk_turn_host_value}:${talk_turn_port_value}"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_RECORDING_IMAGE" "")" ]]; then
|
|
set_env_value "TALK_RECORDING_IMAGE" "nextcloud/aio-talk-recording:latest"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_RECORDING_PORT" "")" ]]; then
|
|
set_env_value "TALK_RECORDING_PORT" "1234"
|
|
fi
|
|
if [[ -z "$(read_env_value "TALK_RECORDING_URL" "")" ]]; then
|
|
set_env_value "TALK_RECORDING_URL" "http://talk_recording:1234"
|
|
fi
|
|
ensure_secret_if_placeholder "TALK_SIGNALING_SECRET" 24
|
|
ensure_secret_if_placeholder "TALK_INTERNAL_SECRET" 24
|
|
ensure_secret_if_placeholder "TALK_TURN_SECRET" 24
|
|
ensure_secret_if_placeholder "TALK_RECORDING_SHARED_SECRET" 24
|
|
|
|
if [[ "${with_external_auth}" == "1" ]]; then
|
|
set_env_value "QORTAL_EXTERNAL_AUTH_BASE_URL" "http://external_auth:3191"
|
|
if [[ -z "$(read_env_value "EXTERNAL_AUTH_PORT" "")" ]]; then
|
|
set_env_value "EXTERNAL_AUTH_PORT" "3191"
|
|
fi
|
|
if [[ "${with_qortal}" == "1" ]]; then
|
|
set_env_value "QORTAL_AUTH_NODE_URL" "http://qortal_node:12391"
|
|
fi
|
|
set_profile_enabled "external-auth" "1"
|
|
elif [[ "${with_external_auth}" == "0" ]]; then
|
|
external_auth_base_url="$(read_env_value "QORTAL_EXTERNAL_AUTH_BASE_URL" "")"
|
|
if [[ -z "${external_auth_base_url}" || "${external_auth_base_url}" == "http://external_auth:3191" ]]; then
|
|
set_env_value "QORTAL_EXTERNAL_AUTH_BASE_URL" "http://gateway.docker.internal:3191"
|
|
fi
|
|
set_profile_enabled "external-auth" "0"
|
|
else
|
|
external_auth_base_url="$(read_env_value "QORTAL_EXTERNAL_AUTH_BASE_URL" "")"
|
|
if [[ "${external_auth_base_url}" == *"external_auth"* ]]; then
|
|
set_profile_enabled "external-auth" "1"
|
|
fi
|
|
fi
|
|
|
|
if [[ "${with_office}" == "1" ]]; then
|
|
set_profile_enabled "office" "1"
|
|
elif [[ "${with_office}" == "0" ]]; then
|
|
set_profile_enabled "office" "0"
|
|
fi
|
|
|
|
if [[ "${with_talk}" == "1" ]]; then
|
|
set_profile_enabled "talk" "1"
|
|
elif [[ "${with_talk}" == "0" ]]; then
|
|
set_profile_enabled "talk" "0"
|
|
fi
|
|
|
|
if [[ "${with_turn}" == "1" ]]; then
|
|
set_profile_enabled "turn" "1"
|
|
elif [[ "${with_turn}" == "0" ]]; then
|
|
set_profile_enabled "turn" "0"
|
|
fi
|
|
|
|
if [[ "${with_janus}" == "1" ]]; then
|
|
set_profile_enabled "janus" "1"
|
|
elif [[ "${with_janus}" == "0" ]]; then
|
|
set_profile_enabled "janus" "0"
|
|
fi
|
|
|
|
if [[ "${with_recording}" == "1" ]]; then
|
|
set_profile_enabled "recording" "1"
|
|
elif [[ "${with_recording}" == "0" ]]; then
|
|
set_profile_enabled "recording" "0"
|
|
fi
|
|
|
|
sync_broker_database_url
|
|
}
|
|
|
|
set_profile_enabled() {
|
|
local profile="$1"
|
|
local enabled="$2"
|
|
local current profiles result
|
|
local filtered=()
|
|
local p p_trimmed
|
|
|
|
current="$(read_env_value "COMPOSE_PROFILES" "")"
|
|
IFS=',' read -r -a profiles <<< "${current}"
|
|
for p in "${profiles[@]}"; do
|
|
p_trimmed="$(echo "${p}" | tr -d '[:space:]')"
|
|
[[ -z "${p_trimmed}" ]] && continue
|
|
[[ "${p_trimmed}" == "${profile}" ]] && continue
|
|
filtered+=("${p_trimmed}")
|
|
done
|
|
|
|
if [[ "${enabled}" == "1" ]]; then
|
|
filtered+=("${profile}")
|
|
fi
|
|
|
|
if [[ "${#filtered[@]}" -gt 0 ]]; then
|
|
result="$(IFS=','; echo "${filtered[*]}")"
|
|
set_env_value "COMPOSE_PROFILES" "${result}"
|
|
else
|
|
set_env_value "COMPOSE_PROFILES" ""
|
|
fi
|
|
}
|
|
|
|
sync_profile_options_from_env() {
|
|
local current_profiles
|
|
|
|
current_profiles="$(read_env_value "COMPOSE_PROFILES" "")"
|
|
|
|
if [[ "${with_office_explicit}" != "1" ]]; then
|
|
if [[ ",${current_profiles}," == *",office,"* ]]; then
|
|
with_office="1"
|
|
else
|
|
with_office="0"
|
|
fi
|
|
fi
|
|
if [[ "${with_talk_explicit}" != "1" ]]; then
|
|
if [[ ",${current_profiles}," == *",talk,"* ]]; then
|
|
with_talk="1"
|
|
else
|
|
with_talk="0"
|
|
fi
|
|
fi
|
|
if [[ "${with_turn_explicit}" != "1" ]]; then
|
|
if [[ ",${current_profiles}," == *",turn,"* ]]; then
|
|
with_turn="1"
|
|
else
|
|
with_turn="0"
|
|
fi
|
|
fi
|
|
if [[ "${with_janus_explicit}" != "1" ]]; then
|
|
if [[ ",${current_profiles}," == *",janus,"* ]]; then
|
|
with_janus="1"
|
|
else
|
|
with_janus="0"
|
|
fi
|
|
fi
|
|
if [[ "${with_recording_explicit}" != "1" ]]; then
|
|
if [[ ",${current_profiles}," == *",recording,"* ]]; then
|
|
with_recording="1"
|
|
else
|
|
with_recording="0"
|
|
fi
|
|
fi
|
|
if [[ "${with_signaling_explicit}" != "1" ]]; then
|
|
if [[ "${with_talk}" == "1" || "${with_turn}" == "1" || "${with_janus}" == "1" || "${with_recording}" == "1" ]]; then
|
|
with_signaling="1"
|
|
else
|
|
with_signaling="0"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
has_meaningful_env_value() {
|
|
local key="$1"
|
|
local value placeholder
|
|
shift || true
|
|
value="$(read_env_value "${key}" "")"
|
|
value="$(echo "${value}" | tr -d '\r' | xargs)"
|
|
if [[ -z "${value}" ]]; then
|
|
return 1
|
|
fi
|
|
for placeholder in "$@"; do
|
|
if [[ -n "${placeholder}" && "${value}" == "${placeholder}" ]]; then
|
|
return 1
|
|
fi
|
|
done
|
|
return 0
|
|
}
|
|
|
|
sync_broker_database_url() {
|
|
local db_name db_user db_password expected current
|
|
db_name="$(read_env_value "BROKER_DB_NAME" "qortal_broker")"
|
|
db_user="$(read_env_value "BROKER_DB_USER" "qortal_broker")"
|
|
db_password="$(read_env_value "BROKER_DB_PASSWORD" "qortal_brokerpass")"
|
|
expected="postgresql://${db_user}:${db_password}@broker_db:5432/${db_name}"
|
|
current="$(read_env_value "BROKER_DATABASE_URL" "")"
|
|
if [[ "${current}" != "${expected}" ]]; then
|
|
echo "Aligning BROKER_DATABASE_URL with BROKER_DB_* values."
|
|
set_env_value "BROKER_DATABASE_URL" "${expected}"
|
|
fi
|
|
}
|
|
|
|
run_guided_setup() {
|
|
local admin_user admin_password service_user_default service_password_default
|
|
local nc_domain broker_domain collabora_domain signaling_domain https_port nc_public_url broker_public_url collabora_public_url signaling_public_url
|
|
local external_auth_use_bundled="0"
|
|
local node_api_key_required="0"
|
|
local msp_installation="0"
|
|
local current_external_auth_url
|
|
local current_profiles prompt_default
|
|
local show_advanced_prompts="0"
|
|
|
|
echo
|
|
echo "Guided setup for ${env_file}"
|
|
echo "Press Enter to keep current values."
|
|
echo "Standard guided mode auto-handles host ports and generates missing service/DB secrets."
|
|
echo "Only cloud domains and the human Nextcloud admin login are prompted by default."
|
|
echo
|
|
|
|
prompt_env_value "NEXTCLOUD_DOMAIN" "cloud.example.test" "Nextcloud domain (public)"
|
|
prompt_env_value "BROKER_DOMAIN" "qortalbroker.example.test" "Broker domain (public)"
|
|
prompt_env_value "COLLABORA_DOMAIN" "$(derive_prefixed_domain "office" "$(read_env_value "NEXTCLOUD_DOMAIN" "cloud.example.test")")" "Collabora domain (public)"
|
|
prompt_env_value "SIGNALING_DOMAIN" "$(derive_prefixed_domain "talk" "$(read_env_value "NEXTCLOUD_DOMAIN" "cloud.example.test")")" "Signaling domain (public)"
|
|
prompt_nextcloud_image_selection
|
|
prompt_env_value "NEXTCLOUD_CUSTOM_APPS_PATH" "./nextcloud/custom_apps" "Nextcloud custom_apps host path"
|
|
|
|
if prompt_yes_no "Show advanced prompts (host ports, service/DB credentials, extra secrets)?" "N"; then
|
|
show_advanced_prompts="1"
|
|
fi
|
|
|
|
if [[ "${show_advanced_prompts}" == "1" ]]; then
|
|
if [[ "${mode}" == "nossl" ]]; then
|
|
prompt_env_value "DEVPROD_HTTP_PORT" "8081" "Nextcloud host HTTP port (internal)"
|
|
prompt_env_value "DEVPROD_BROKER_PORT" "3001" "Broker host HTTP port (internal)"
|
|
prompt_env_value "PUBLIC_HTTPS_PORT" "443" "Public HTTPS port from reverse proxy"
|
|
set_env_value "CADDY_HTTP_PORT" "$(read_env_value "CADDY_HTTP_PORT" "80")"
|
|
set_env_value "CADDY_HTTPS_PORT" "$(read_env_value "CADDY_HTTPS_PORT" "443")"
|
|
else
|
|
prompt_env_value "CADDY_HTTP_PORT" "80" "Caddy HTTP port"
|
|
prompt_env_value "CADDY_HTTPS_PORT" "443" "Caddy HTTPS port"
|
|
prompt_env_value "CADDY_EMAIL" "$(default_caddy_email_for_domain "$(read_env_value "NEXTCLOUD_DOMAIN" "cloud.example.test")")" "Caddy email"
|
|
prompt_env_value "CADDY_TLS" "" "Caddy TLS directive (blank or 'tls internal')"
|
|
fi
|
|
elif [[ "${mode}" == "nossl" ]]; then
|
|
set_env_value "CADDY_HTTP_PORT" "$(read_env_value "CADDY_HTTP_PORT" "80")"
|
|
set_env_value "CADDY_HTTPS_PORT" "$(read_env_value "CADDY_HTTPS_PORT" "443")"
|
|
fi
|
|
|
|
prompt_env_value "NEXTCLOUD_ADMIN_USER" "admin" "Nextcloud admin user"
|
|
prompt_env_value "NEXTCLOUD_ADMIN_PASSWORD" "admin123" "Nextcloud admin password" "1"
|
|
if prompt_yes_no "Is this an MSP installation (enable delegated MSP admin group support)?" "N"; then
|
|
msp_installation="1"
|
|
fi
|
|
set_env_value "MSP_MODE_ENABLED" "${msp_installation}"
|
|
if [[ "${msp_installation}" == "1" ]]; then
|
|
prompt_env_value "MSP_ADMIN_GROUP" "MSP_Admin" "Delegated MSP admin group ID"
|
|
else
|
|
set_env_value "MSP_ADMIN_GROUP" "$(read_env_value "MSP_ADMIN_GROUP" "MSP_Admin")"
|
|
fi
|
|
|
|
admin_user="$(read_env_value "NEXTCLOUD_ADMIN_USER" "admin")"
|
|
admin_password="$(read_env_value "NEXTCLOUD_ADMIN_PASSWORD" "admin123")"
|
|
service_user_default="$(read_env_value "NEXTCLOUD_SERVICE_USER" "nuqloud_oidc_service")"
|
|
service_password_default="$(read_env_value "NEXTCLOUD_SERVICE_PASSWORD" "")"
|
|
if [[ "${show_advanced_prompts}" == "1" ]]; then
|
|
prompt_env_value "NEXTCLOUD_SERVICE_USER" "${service_user_default}" "Broker Nextcloud service user"
|
|
prompt_env_value "NEXTCLOUD_SERVICE_PASSWORD" "${service_password_default}" "Broker Nextcloud service password" "1"
|
|
|
|
prompt_env_value "MYSQL_ROOT_PASSWORD" "rootpass" "MariaDB root password" "1"
|
|
prompt_env_value "MYSQL_DATABASE" "nextcloud" "MariaDB Nextcloud database"
|
|
prompt_env_value "MYSQL_USER" "nextcloud" "MariaDB Nextcloud user"
|
|
prompt_env_value "MYSQL_PASSWORD" "nextcloudpass" "MariaDB Nextcloud user password" "1"
|
|
|
|
prompt_env_value "BROKER_DB_NAME" "qortal_broker" "Broker PostgreSQL database"
|
|
prompt_env_value "BROKER_DB_USER" "qortal_broker" "Broker PostgreSQL user"
|
|
prompt_env_value "BROKER_DB_PASSWORD" "qortal_brokerpass" "Broker PostgreSQL password" "1"
|
|
fi
|
|
|
|
nc_domain="$(read_env_value "NEXTCLOUD_DOMAIN" "cloud.example.test")"
|
|
broker_domain="$(read_env_value "BROKER_DOMAIN" "qortalbroker.example.test")"
|
|
if [[ "${mode}" == "nossl" ]]; then
|
|
https_port="$(read_env_value "PUBLIC_HTTPS_PORT" "443")"
|
|
else
|
|
https_port="$(read_env_value "CADDY_HTTPS_PORT" "443")"
|
|
fi
|
|
nc_public_url="$(compute_public_url "${nc_domain}" "${https_port}")"
|
|
broker_public_url="$(compute_public_url "${broker_domain}" "${https_port}")"
|
|
collabora_domain="$(read_env_value "COLLABORA_DOMAIN" "")"
|
|
if [[ -z "${collabora_domain}" || "${collabora_domain}" == "office.cloud.example.test" ]]; then
|
|
collabora_domain="$(derive_prefixed_domain "office" "${nc_domain}")"
|
|
set_env_value "COLLABORA_DOMAIN" "${collabora_domain}"
|
|
fi
|
|
signaling_domain="$(read_env_value "SIGNALING_DOMAIN" "")"
|
|
if [[ -z "${signaling_domain}" ]]; then
|
|
signaling_domain="$(read_env_value "TALK_DOMAIN" "")"
|
|
fi
|
|
if [[ -z "${signaling_domain}" || "${signaling_domain}" == "talk.cloud.example.test" ]]; then
|
|
signaling_domain="$(derive_prefixed_domain "talk" "${nc_domain}")"
|
|
set_env_value "SIGNALING_DOMAIN" "${signaling_domain}"
|
|
fi
|
|
set_env_value "TALK_DOMAIN" "${signaling_domain}"
|
|
collabora_public_url="$(compute_public_url "${collabora_domain}" "${https_port}")"
|
|
signaling_public_url="$(compute_public_url "${signaling_domain}" "${https_port}")"
|
|
|
|
set_env_value "NEXTCLOUD_TRUSTED_DOMAINS" "${nc_domain}"
|
|
set_env_value "NEXTCLOUD_BASE_URL" "http://app"
|
|
set_env_value "NEXTCLOUD_PUBLIC_URL" "${nc_public_url}"
|
|
set_env_value "BROKER_CORS_ALLOWED_ORIGINS" "${nc_public_url}"
|
|
set_env_value "OIDC_ISSUER" "${broker_public_url}"
|
|
set_env_value "OIDC_REDIRECT_URI_ALLOWLIST" "${nc_public_url}/apps/user_oidc/code"
|
|
set_env_value "COLLABORA_PUBLIC_URL" "${collabora_public_url}"
|
|
set_env_value "TALK_SIGNALING_PUBLIC_URL" "${signaling_public_url}"
|
|
|
|
prompt_env_value "OIDC_CLIENT_ID" "nextcloud-local" "OIDC client ID"
|
|
if [[ "${show_advanced_prompts}" == "1" ]]; then
|
|
prompt_env_value "OIDC_CLIENT_SECRET" "dev-secret" "OIDC client secret" "1"
|
|
fi
|
|
prompt_env_value "OIDC_POLICY_MODE" "auto_provision" "OIDC policy mode (link_only/auto_provision)"
|
|
prompt_env_value "OIDC_AUTO_PROVISION_GUARD" "invite_or_allowlist" "OIDC auto-provision guard"
|
|
prompt_env_value "OIDC_REQUIRE_EMAIL_FOR_NEW_ACCOUNT" "true" "Require email for new accounts (true/false)"
|
|
|
|
if [[ "${with_external_auth}" == "1" ]]; then
|
|
external_auth_use_bundled="1"
|
|
elif [[ "${with_external_auth}" == "0" ]]; then
|
|
external_auth_use_bundled="0"
|
|
else
|
|
current_external_auth_url="$(read_env_value "QORTAL_EXTERNAL_AUTH_BASE_URL" "http://gateway.docker.internal:3191")"
|
|
if [[ "${current_external_auth_url}" == *"external_auth"* ]]; then
|
|
if prompt_yes_no "Use bundled External Auth container for this cloud (docker service 'external_auth')?" "Y"; then
|
|
external_auth_use_bundled="1"
|
|
fi
|
|
else
|
|
if prompt_yes_no "Use bundled External Auth container for this cloud (docker service 'external_auth')?" "N"; then
|
|
external_auth_use_bundled="1"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ "${external_auth_use_bundled}" == "1" ]]; then
|
|
if [[ "${with_qortal}" != "1" ]]; then
|
|
echo "Bundled external_auth requires bundled qortal_node; enabling --with-qortal."
|
|
with_qortal="1"
|
|
fi
|
|
with_external_auth="1"
|
|
if [[ "${show_advanced_prompts}" == "1" ]]; then
|
|
prompt_env_value "EXTERNAL_AUTH_PORT" "3191" "Bundled External Auth host port"
|
|
fi
|
|
set_env_value "QORTAL_EXTERNAL_AUTH_BASE_URL" "http://external_auth:3191"
|
|
set_profile_enabled "external-auth" "1"
|
|
if [[ "${with_qortal}" == "1" ]]; then
|
|
set_env_value "QORTAL_AUTH_NODE_URL" "http://qortal_node:12391"
|
|
if prompt_yes_no "Does your Qortal node require X-API-KEY?" "N"; then
|
|
node_api_key_required="1"
|
|
fi
|
|
if [[ "${node_api_key_required}" == "1" ]]; then
|
|
prompt_env_value "QORTAL_AUTH_NODE_API_KEY" "" "Qortal node API key" "1"
|
|
set_env_value "QORTAL_AUTH_NODE_API_KEY_MODE" "paths"
|
|
set_env_value "QORTAL_AUTH_NODE_API_KEY_PATHS" "/"
|
|
else
|
|
set_env_value "QORTAL_AUTH_NODE_API_KEY" ""
|
|
fi
|
|
fi
|
|
else
|
|
with_external_auth="0"
|
|
prompt_env_value "QORTAL_EXTERNAL_AUTH_BASE_URL" "http://gateway.docker.internal:3191" "External Auth base URL"
|
|
set_profile_enabled "external-auth" "0"
|
|
fi
|
|
|
|
prompt_env_value "QORTAL_EXTERNAL_AUTH_APP_ID" "" "External Auth app ID"
|
|
prompt_env_value "QORTAL_EXTERNAL_AUTH_APP_SECRET" "" "External Auth app secret" "1"
|
|
|
|
current_profiles="$(read_env_value "COMPOSE_PROFILES" "")"
|
|
|
|
prompt_default="N"
|
|
if [[ ",${current_profiles}," == *",office,"* ]]; then
|
|
prompt_default="Y"
|
|
elif has_meaningful_env_value "COLLABORA_DOMAIN" "office.cloud.example.test"; then
|
|
prompt_default="Y"
|
|
elif has_meaningful_env_value "COLLABORA_PUBLIC_URL"; then
|
|
prompt_default="Y"
|
|
fi
|
|
if prompt_yes_no "Enable Nextcloud Office (Collabora) profile?" "${prompt_default}"; then
|
|
set_profile_enabled "office" "1"
|
|
else
|
|
set_profile_enabled "office" "0"
|
|
fi
|
|
|
|
prompt_default="N"
|
|
if [[ ",${current_profiles}," == *",talk,"* || ",${current_profiles}," == *",turn,"* || ",${current_profiles}," == *",janus,"* ]]; then
|
|
prompt_default="Y"
|
|
elif has_meaningful_env_value "SIGNALING_DOMAIN" "talk.cloud.example.test"; then
|
|
prompt_default="Y"
|
|
elif has_meaningful_env_value "TALK_SIGNALING_PUBLIC_URL"; then
|
|
prompt_default="Y"
|
|
elif has_meaningful_env_value "TALK_SIGNALING_SECRET"; then
|
|
prompt_default="Y"
|
|
elif has_meaningful_env_value "TALK_INTERNAL_SECRET"; then
|
|
prompt_default="Y"
|
|
elif has_meaningful_env_value "TALK_TURN_SECRET"; then
|
|
prompt_default="Y"
|
|
elif has_meaningful_env_value "TALK_RECORDING_SHARED_SECRET"; then
|
|
prompt_default="Y"
|
|
fi
|
|
if prompt_yes_no "Enable signaling stack (talk + turn + janus + recording)?" "${prompt_default}"; then
|
|
set_profile_enabled "talk" "1"
|
|
set_profile_enabled "turn" "1"
|
|
set_profile_enabled "janus" "1"
|
|
set_profile_enabled "recording" "1"
|
|
else
|
|
set_profile_enabled "talk" "0"
|
|
set_profile_enabled "turn" "0"
|
|
set_profile_enabled "janus" "0"
|
|
set_profile_enabled "recording" "0"
|
|
fi
|
|
|
|
prompt_default="Y"
|
|
if ! install_custom_pwa_enabled; then
|
|
prompt_default="N"
|
|
fi
|
|
if prompt_yes_no "Install/enable CustomPWA app?" "${prompt_default}"; then
|
|
set_env_value "INSTALL_CUSTOM_PWA" "1"
|
|
else
|
|
set_env_value "INSTALL_CUSTOM_PWA" "0"
|
|
fi
|
|
|
|
prompt_default_app_bundle_selection
|
|
|
|
sync_broker_database_url
|
|
|
|
echo
|
|
echo "Guided setup complete."
|
|
echo
|
|
}
|
|
|
|
prompt_default_app_bundle_selection() {
|
|
local prompt_default="N"
|
|
if install_default_app_bundle_enabled; then
|
|
prompt_default="Y"
|
|
fi
|
|
if prompt_yes_no "Install/enable the recommended default Nextcloud app bundle?" "${prompt_default}"; then
|
|
set_env_value "INSTALL_DEFAULT_APP_BUNDLE" "1"
|
|
else
|
|
set_env_value "INSTALL_DEFAULT_APP_BUNDLE" "0"
|
|
fi
|
|
}
|
|
|
|
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 [[ "${configure_only}" != "1" ]]; then
|
|
ensure_docker_access
|
|
if ! docker compose version >/dev/null 2>&1; then
|
|
echo "docker compose plugin is required"
|
|
exit 1
|
|
fi
|
|
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" ]]; then
|
|
seeded_compose_project_name="$(derive_compose_project_name_seed "${env_file}")"
|
|
set_compose_project_name_in_env "${env_file}" "${seeded_compose_project_name}"
|
|
export COMPOSE_PROJECT_NAME="${seeded_compose_project_name}"
|
|
echo "Pinned COMPOSE_PROJECT_NAME=${seeded_compose_project_name}"
|
|
else
|
|
if ! export_compose_project_name_from_env "${env_file}"; then
|
|
warn_missing_compose_project_name "${env_file}"
|
|
fi
|
|
fi
|
|
|
|
if [[ "${created_env}" == "1" || "${one_click}" == "1" ]]; then
|
|
auto_generate_secrets="1"
|
|
fi
|
|
|
|
if [[ -n "${nextcloud_domain_input}" ]]; then
|
|
set_env_value "NEXTCLOUD_DOMAIN" "${nextcloud_domain_input}"
|
|
fi
|
|
if [[ -n "${broker_domain_input}" ]]; then
|
|
set_env_value "BROKER_DOMAIN" "${broker_domain_input}"
|
|
fi
|
|
if [[ -n "${collabora_domain_input}" ]]; then
|
|
set_env_value "COLLABORA_DOMAIN" "${collabora_domain_input}"
|
|
fi
|
|
if [[ -n "${signaling_domain_input}" ]]; then
|
|
set_env_value "SIGNALING_DOMAIN" "${signaling_domain_input}"
|
|
set_env_value "TALK_DOMAIN" "${signaling_domain_input}"
|
|
fi
|
|
if [[ -n "${install_custom_pwa_override}" ]]; then
|
|
set_env_value "INSTALL_CUSTOM_PWA" "${install_custom_pwa_override}"
|
|
fi
|
|
if [[ -n "${install_default_app_bundle_override}" ]]; then
|
|
set_env_value "INSTALL_DEFAULT_APP_BUNDLE" "${install_default_app_bundle_override}"
|
|
fi
|
|
|
|
auto_derive_env_defaults
|
|
|
|
run_guided="0"
|
|
if [[ "${guided_mode}" == "1" ]]; then
|
|
run_guided="1"
|
|
elif [[ "${guided_mode}" == "auto" ]]; then
|
|
if [[ "${created_env}" == "1" && "${accept_defaults}" != "1" ]]; then
|
|
run_guided="1"
|
|
elif [[ "${created_env}" == "0" && "${accept_defaults}" != "1" && -t 0 ]]; then
|
|
read -r -p "Run guided setup to review/update ${env_file}? (y/N): " run_guided_prompt
|
|
if [[ "${run_guided_prompt}" =~ ^[Yy]$ ]]; then
|
|
run_guided="1"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ "${run_guided}" == "1" ]]; then
|
|
if [[ ! -t 0 ]]; then
|
|
echo "Guided setup requires an interactive terminal."
|
|
echo "Use --accept-defaults for non-interactive smoke testing."
|
|
exit 1
|
|
fi
|
|
run_guided_setup
|
|
sync_profile_options_from_env
|
|
# Re-derive dependent URLs and Talk defaults after interactive domain/profile edits.
|
|
auto_derive_env_defaults
|
|
sync_broker_database_url
|
|
if [[ "${configure_only}" != "1" ]]; then
|
|
if ! prompt_yes_no "Configuration complete. Start/rebuild containers now?" "N"; then
|
|
configure_only="1"
|
|
fi
|
|
fi
|
|
elif [[ "${created_env}" == "1" && "${accept_defaults}" != "1" ]]; then
|
|
cat <<MSG
|
|
Edit ${env_file} with production values, rerun with --guided, or run with --accept-defaults for smoke testing.
|
|
MSG
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "${run_guided}" != "1" && "${one_click}" != "1" && "${accept_defaults}" != "1" && "${guided_mode}" != "0" && -t 0 ]]; then
|
|
if [[ -z "${install_default_app_bundle_override}" ]]; then
|
|
prompt_default_app_bundle_selection
|
|
fi
|
|
prompt_nextcloud_image_selection
|
|
fi
|
|
|
|
sync_broker_database_url
|
|
|
|
default_warnings=()
|
|
nextcloud_domain_val="$(read_env_value "NEXTCLOUD_DOMAIN" "")"
|
|
broker_domain_val="$(read_env_value "BROKER_DOMAIN" "")"
|
|
nextcloud_admin_password_val="$(read_env_value "NEXTCLOUD_ADMIN_PASSWORD" "")"
|
|
nextcloud_admin_user_val="$(read_env_value "NEXTCLOUD_ADMIN_USER" "admin")"
|
|
nextcloud_service_user_val="$(read_env_value "NEXTCLOUD_SERVICE_USER" "")"
|
|
nextcloud_service_password_val="$(read_env_value "NEXTCLOUD_SERVICE_PASSWORD" "")"
|
|
mysql_root_password_val="$(read_env_value "MYSQL_ROOT_PASSWORD" "")"
|
|
broker_internal_token_val="$(read_env_value "BROKER_INTERNAL_API_TOKEN" "")"
|
|
nextcloud_image_val="$(read_env_value "NEXTCLOUD_IMAGE" "${default_nextcloud_image}")"
|
|
if [[ "${nextcloud_domain_val}" == "cloud.example.test" ]]; then
|
|
default_warnings+=("NEXTCLOUD_DOMAIN is still cloud.example.test")
|
|
fi
|
|
if [[ "${broker_domain_val}" == "qortalbroker.example.test" ]]; then
|
|
default_warnings+=("BROKER_DOMAIN is still qortalbroker.example.test")
|
|
fi
|
|
if [[ "${nextcloud_admin_password_val}" == "admin123" ]]; then
|
|
default_warnings+=("NEXTCLOUD_ADMIN_PASSWORD is still admin123")
|
|
fi
|
|
if [[ "${mysql_root_password_val}" == "rootpass" ]]; then
|
|
default_warnings+=("MYSQL_ROOT_PASSWORD is still rootpass")
|
|
fi
|
|
if [[ "${nextcloud_service_user_val}" == "${nextcloud_admin_user_val}" ]]; then
|
|
default_warnings+=("NEXTCLOUD_SERVICE_USER matches NEXTCLOUD_ADMIN_USER; use a dedicated service user")
|
|
fi
|
|
if [[ "${nextcloud_service_password_val}" == "admin123" || "${nextcloud_service_password_val}" == "service-oidc-change-me" ]]; then
|
|
default_warnings+=("NEXTCLOUD_SERVICE_PASSWORD is a placeholder/default value")
|
|
fi
|
|
if [[ -z "${broker_internal_token_val}" ]]; then
|
|
default_warnings+=("BROKER_INTERNAL_API_TOKEN is empty before generation")
|
|
fi
|
|
if ! is_tested_nextcloud_image "${nextcloud_image_val}"; then
|
|
default_warnings+=("NEXTCLOUD_IMAGE=${nextcloud_image_val} is not extensively tested here (${tested_nextcloud_image_note})")
|
|
fi
|
|
if [[ "${#default_warnings[@]}" -gt 0 ]]; then
|
|
echo "Configuration warnings in ${env_file}:"
|
|
for warning in "${default_warnings[@]}"; do
|
|
echo " - ${warning}"
|
|
done
|
|
fi
|
|
|
|
validate_caddy_public_tls_settings
|
|
|
|
if [[ "${configure_only}" == "1" ]]; then
|
|
if [[ -f "${repo_root}/scripts/ensure-broker-internal-token.sh" ]]; then
|
|
bash "${repo_root}/scripts/ensure-broker-internal-token.sh" "${env_file}"
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-oidc-signing-key.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-oidc-signing-key.sh" "${env_file}"
|
|
fi
|
|
sync_broker_database_url
|
|
echo "Configuration saved to ${env_file}."
|
|
echo "Start later with: ./start-devprod.sh --${mode}"
|
|
exit 0
|
|
fi
|
|
|
|
compose_file="${repo_root}/docker-compose.devprod.nossl.yml"
|
|
if [[ "${mode}" == "ssl" ]]; then
|
|
compose_file="${repo_root}/docker-compose.devprod.yml"
|
|
fi
|
|
|
|
if [[ -f "${repo_root}/scripts/ensure-broker-internal-token.sh" ]]; then
|
|
bash "${repo_root}/scripts/ensure-broker-internal-token.sh" "${env_file}"
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-oidc-signing-key.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-oidc-signing-key.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" "")"
|
|
export BROKER_CORS_ALLOWED_ORIGINS="${broker_cors_allowed_origins:-}"
|
|
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
|
|
external_auth_base_url="$(read_env_value "QORTAL_EXTERNAL_AUTH_BASE_URL" "")"
|
|
|
|
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[*]}")"
|
|
elif [[ "${with_external_auth}" == "auto" ]]; then
|
|
# If broker points to bundled service hostname, enable profile automatically.
|
|
if [[ "${external_auth_base_url}" == *"external_auth"* ]]; then
|
|
if [[ -z "${current_profiles}" ]]; then
|
|
current_profiles="external-auth"
|
|
elif [[ ",${current_profiles}," != *",external-auth,"* ]]; then
|
|
current_profiles="${current_profiles},external-auth"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${current_profiles}" ]]; then
|
|
export COMPOSE_PROFILES="${current_profiles}"
|
|
else
|
|
unset COMPOSE_PROFILES || true
|
|
fi
|
|
|
|
if [[ "${with_qortal}" == "0" && ",${COMPOSE_PROFILES:-}," == *",external-auth,"* ]]; then
|
|
echo "Cannot run --without-qortal while external-auth profile is enabled."
|
|
exit 1
|
|
fi
|
|
|
|
office_profile_enabled="0"
|
|
talk_profile_enabled="0"
|
|
turn_profile_enabled="0"
|
|
janus_profile_enabled="0"
|
|
recording_profile_enabled="0"
|
|
if [[ ",${COMPOSE_PROFILES:-}," == *",office,"* ]]; then
|
|
office_profile_enabled="1"
|
|
fi
|
|
if [[ ",${COMPOSE_PROFILES:-}," == *",talk,"* ]]; then
|
|
talk_profile_enabled="1"
|
|
fi
|
|
if [[ ",${COMPOSE_PROFILES:-}," == *",turn,"* ]]; then
|
|
turn_profile_enabled="1"
|
|
talk_profile_enabled="1"
|
|
fi
|
|
if [[ ",${COMPOSE_PROFILES:-}," == *",janus,"* ]]; then
|
|
janus_profile_enabled="1"
|
|
talk_profile_enabled="1"
|
|
fi
|
|
if [[ ",${COMPOSE_PROFILES:-}," == *",recording,"* ]]; then
|
|
recording_profile_enabled="1"
|
|
talk_profile_enabled="1"
|
|
fi
|
|
|
|
nextcloud_custom_apps_raw="$(read_env_value "NEXTCLOUD_CUSTOM_APPS_PATH" "./nextcloud/custom_apps")"
|
|
nextcloud_custom_apps_path="$(resolve_context_path "${nextcloud_custom_apps_raw}")"
|
|
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
|
|
|
|
prompt_qortal_branch_if_needed
|
|
|
|
if [[ "${with_qortal}" == "1" ]]; then
|
|
qortal_context_raw="$(read_env_value "QORTAL_NODE_CONTEXT" "../qortal")"
|
|
qortal_context_path="$(resolve_context_path "${qortal_context_raw}")"
|
|
if [[ -x "${repo_root}/scripts/ensure-qortal-core-repo.sh" ]]; then
|
|
ensure_qortal_args=(--target-path "${qortal_context_path}")
|
|
if [[ -n "${qortal_branch}" ]]; then
|
|
ensure_qortal_args+=(--branch "${qortal_branch}" --refresh)
|
|
fi
|
|
"${repo_root}/scripts/ensure-qortal-core-repo.sh" "${ensure_qortal_args[@]}"
|
|
else
|
|
ensure_context_repo "Qortal Core" "${qortal_context_path}" "${default_qortal_repo_url}"
|
|
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
|
|
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 [[ -x "${repo_root}/scripts/select-host-service-ports.sh" ]]; then
|
|
"${repo_root}/scripts/select-host-service-ports.sh" \
|
|
--env-file "${env_file}" \
|
|
--mode "${mode}" \
|
|
--compose-file "${compose_file}" \
|
|
--skip-when-running
|
|
fi
|
|
|
|
existing_stack_ids="$(docker compose -f "${compose_file}" --env-file "${env_file}" ps -q 2>/dev/null || true)"
|
|
if [[ -z "${existing_stack_ids}" ]]; then
|
|
port_conflicts=()
|
|
if [[ "${mode}" == "nossl" ]]; then
|
|
devprod_http_port="$(read_env_value "DEVPROD_HTTP_PORT" "8081")"
|
|
devprod_broker_port="$(read_env_value "DEVPROD_BROKER_PORT" "3001")"
|
|
if is_port_in_use "127.0.0.1" "${devprod_http_port}"; then
|
|
port_conflicts+=("127.0.0.1:${devprod_http_port} (DEVPROD_HTTP_PORT)")
|
|
fi
|
|
if is_port_in_use "127.0.0.1" "${devprod_broker_port}"; then
|
|
port_conflicts+=("127.0.0.1:${devprod_broker_port} (DEVPROD_BROKER_PORT)")
|
|
fi
|
|
else
|
|
caddy_http_port="$(read_env_value "CADDY_HTTP_PORT" "80")"
|
|
caddy_https_port="$(read_env_value "CADDY_HTTPS_PORT" "443")"
|
|
if is_port_in_use "" "${caddy_http_port}"; then
|
|
port_conflicts+=("0.0.0.0:${caddy_http_port} (CADDY_HTTP_PORT)")
|
|
fi
|
|
if is_port_in_use "" "${caddy_https_port}"; then
|
|
port_conflicts+=("0.0.0.0:${caddy_https_port} (CADDY_HTTPS_PORT)")
|
|
fi
|
|
fi
|
|
|
|
if [[ ",${COMPOSE_PROFILES:-}," == *",external-auth,"* ]]; then
|
|
external_auth_port="$(read_env_value "EXTERNAL_AUTH_PORT" "3191")"
|
|
if is_port_in_use "127.0.0.1" "${external_auth_port}"; then
|
|
port_conflicts+=("127.0.0.1:${external_auth_port} (EXTERNAL_AUTH_PORT)")
|
|
fi
|
|
fi
|
|
|
|
if [[ "${office_profile_enabled}" == "1" ]]; then
|
|
collabora_port="$(read_env_value "COLLABORA_PORT" "9980")"
|
|
if is_port_in_use "127.0.0.1" "${collabora_port}"; then
|
|
port_conflicts+=("127.0.0.1:${collabora_port} (COLLABORA_PORT)")
|
|
fi
|
|
fi
|
|
|
|
if [[ "${talk_profile_enabled}" == "1" ]]; then
|
|
talk_signaling_bind_host="$(read_env_value "TALK_SIGNALING_BIND_HOST" "0.0.0.0")"
|
|
talk_signaling_port="$(read_env_value "TALK_SIGNALING_PORT" "8081")"
|
|
if is_port_in_use "${talk_signaling_bind_host}" "${talk_signaling_port}"; then
|
|
port_conflicts+=("${talk_signaling_bind_host}:${talk_signaling_port} (TALK_SIGNALING_PORT)")
|
|
fi
|
|
if [[ "${mode}" == "nossl" && "${devprod_http_port:-}" == "${talk_signaling_port}" ]]; then
|
|
port_conflicts+=("127.0.0.1:${devprod_http_port} (DEVPROD_HTTP_PORT conflicts with TALK_SIGNALING_PORT)")
|
|
fi
|
|
fi
|
|
|
|
if [[ "${talk_profile_enabled}" == "1" ]]; then
|
|
talk_turn_bind_host="$(read_env_value "TALK_TURN_BIND_HOST" "0.0.0.0")"
|
|
talk_turn_port="$(read_env_value "TALK_TURN_PORT" "3478")"
|
|
if is_port_in_use "${talk_turn_bind_host}" "${talk_turn_port}"; then
|
|
port_conflicts+=("${talk_turn_bind_host}:${talk_turn_port} (TALK_TURN_PORT)")
|
|
fi
|
|
fi
|
|
|
|
if [[ "${recording_profile_enabled}" == "1" ]]; then
|
|
talk_recording_port="$(read_env_value "TALK_RECORDING_PORT" "1234")"
|
|
if is_port_in_use "127.0.0.1" "${talk_recording_port}"; then
|
|
port_conflicts+=("127.0.0.1:${talk_recording_port} (TALK_RECORDING_PORT)")
|
|
fi
|
|
fi
|
|
|
|
if [[ "${with_qortal}" == "1" ]]; then
|
|
qortal_gateway_port="$(read_env_value "QORTAL_NODE_GATEWAY_HOST_PORT" "12390")"
|
|
qortal_api_port="$(read_env_value "QORTAL_NODE_API_HOST_PORT" "12391")"
|
|
qortal_p2p_port="$(read_env_value "QORTAL_NODE_P2P_HOST_PORT" "12392")"
|
|
qortal_qdn_port="$(read_env_value "QORTAL_NODE_QDN_HOST_PORT" "12394")"
|
|
qortal_gateway_bind="$(read_env_value "QORTAL_NODE_GATEWAY_BIND_HOST" "127.0.0.1")"
|
|
qortal_api_bind="$(read_env_value "QORTAL_NODE_API_BIND_HOST" "127.0.0.1")"
|
|
qortal_p2p_bind="$(read_env_value "QORTAL_NODE_P2P_BIND_HOST" "0.0.0.0")"
|
|
qortal_qdn_bind="$(read_env_value "QORTAL_NODE_QDN_BIND_HOST" "0.0.0.0")"
|
|
if is_port_in_use "${qortal_gateway_bind}" "${qortal_gateway_port}"; then
|
|
port_conflicts+=("${qortal_gateway_bind}:${qortal_gateway_port} (QORTAL_NODE_GATEWAY_HOST_PORT)")
|
|
fi
|
|
if is_port_in_use "${qortal_api_bind}" "${qortal_api_port}"; then
|
|
port_conflicts+=("${qortal_api_bind}:${qortal_api_port} (QORTAL_NODE_API_HOST_PORT)")
|
|
fi
|
|
if is_port_in_use "${qortal_p2p_bind}" "${qortal_p2p_port}"; then
|
|
port_conflicts+=("${qortal_p2p_bind}:${qortal_p2p_port} (QORTAL_NODE_P2P_HOST_PORT)")
|
|
fi
|
|
if is_port_in_use "${qortal_qdn_bind}" "${qortal_qdn_port}"; then
|
|
port_conflicts+=("${qortal_qdn_bind}:${qortal_qdn_port} (QORTAL_NODE_QDN_HOST_PORT)")
|
|
fi
|
|
fi
|
|
|
|
if [[ "${#port_conflicts[@]}" -gt 0 ]]; then
|
|
echo "Port conflict detected. Update these values in ${env_file}:"
|
|
for entry in "${port_conflicts[@]}"; do
|
|
echo " - ${entry}"
|
|
done
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "Existing compose project detected; skipping host port conflict preflight."
|
|
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
|
|
|
|
# Apply local Nextcloud core/theming patches after first container startup so the bind-mounted
|
|
# ./nextcloud/html tree has been populated by the Nextcloud image entrypoint.
|
|
nextcloud_html_root="${repo_root}/nextcloud/html"
|
|
pwa_theming_patch="${repo_root}/patches/nextcloud-pwa-theming-fix.patch"
|
|
if [[ -f "${pwa_theming_patch}" ]]; then
|
|
for _ in $(seq 1 24); do
|
|
if [[ -f "${nextcloud_html_root}/apps/theming/lib/ThemingDefaults.php" ]]; then
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
if [[ -f "${nextcloud_html_root}/apps/theming/lib/ThemingDefaults.php" ]]; then
|
|
apply_nextcloud_local_patch_if_present \
|
|
"${pwa_theming_patch}" \
|
|
"${nextcloud_html_root}" \
|
|
"Force instance branding for the installable shell manifest." \
|
|
"apps/theming/lib/ThemingDefaults.php" || true
|
|
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 [[ "${skip_occ}" != "1" ]]; then
|
|
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 docker compose -f "${compose_file}" --env-file "${env_file}" 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
|
|
oidc_issuer="$(read_env_value "OIDC_ISSUER" "")"
|
|
oidc_client_id="$(read_env_value "OIDC_CLIENT_ID" "nextcloud-local")"
|
|
oidc_client_secret="$(read_env_value "OIDC_CLIENT_SECRET" "")"
|
|
collabora_public_url="$(read_env_value "COLLABORA_PUBLIC_URL" "")"
|
|
talk_signaling_public_url="$(read_env_value "TALK_SIGNALING_PUBLIC_URL" "")"
|
|
talk_signaling_secret="$(read_env_value "TALK_SIGNALING_SECRET" "")"
|
|
talk_stun_server="$(read_env_value "TALK_STUN_SERVER" "")"
|
|
talk_turn_server="$(read_env_value "TALK_TURN_SERVER" "")"
|
|
talk_domain="$(read_env_value "SIGNALING_DOMAIN" "")"
|
|
talk_turn_host="$(read_env_value "TALK_TURN_HOST" "")"
|
|
if [[ -z "${talk_turn_host}" ]]; then
|
|
talk_turn_host="$(read_env_value "NEXTCLOUD_DOMAIN" "")"
|
|
fi
|
|
talk_turn_port="$(read_env_value "TALK_TURN_PORT" "3478")"
|
|
talk_turn_secret="$(read_env_value "TALK_TURN_SECRET" "")"
|
|
if [[ -z "${talk_turn_server}" ]]; then
|
|
talk_turn_server="${talk_turn_host}:${talk_turn_port}"
|
|
fi
|
|
talk_recording_url="$(read_env_value "TALK_RECORDING_URL" "http://talk_recording:1234")"
|
|
talk_recording_secret="$(read_env_value "TALK_RECORDING_SHARED_SECRET" "")"
|
|
oidc_provider_config_status=0
|
|
oidc_provider_skipped=0
|
|
office_install_status=0
|
|
office_enable_status=0
|
|
office_wopi_status=0
|
|
office_public_wopi_status=0
|
|
talk_install_status=0
|
|
talk_enable_status=0
|
|
talk_signaling_add_status=0
|
|
talk_signaling_skipped=0
|
|
talk_stun_add_status=0
|
|
talk_stun_skipped=0
|
|
talk_turn_add_status=0
|
|
talk_turn_skipped=0
|
|
talk_recording_add_status=0
|
|
talk_recording_skipped=0
|
|
talk_recording_command_missing=0
|
|
talk_namespace_missing=0
|
|
talk_command_prefix=""
|
|
talk_recording_prefix=""
|
|
notify_push_enable_status=0
|
|
notify_push_install_status=0
|
|
notify_push_verify_status=0
|
|
repair_expensive_status=0
|
|
talk_signaling_prune_status=0
|
|
talk_signaling_multi_count=0
|
|
office_verify_status=0
|
|
office_verify_skipped=1
|
|
talk_verify_status=0
|
|
talk_verify_skipped=1
|
|
public_env_resync_status=0
|
|
nextcloud_url_verify_status=0
|
|
custom_pwa_push_status=0
|
|
custom_pwa_push_skipped=1
|
|
qortal_runtime_sync_status=0
|
|
qortal_runtime_sync_skipped=1
|
|
oidc_discovery_url=""
|
|
if [[ -n "${oidc_issuer}" ]]; then
|
|
oidc_discovery_url="${oidc_issuer%/}/.well-known/openid-configuration"
|
|
fi
|
|
|
|
if occ_has_command "talk:signaling:list"; then
|
|
talk_command_prefix="talk"
|
|
elif occ_has_command "spreed:signaling:list"; then
|
|
talk_command_prefix="spreed"
|
|
fi
|
|
|
|
if occ_has_command "talk:recording:add"; then
|
|
talk_recording_prefix="talk"
|
|
elif occ_has_command "spreed:recording:add"; then
|
|
talk_recording_prefix="spreed"
|
|
fi
|
|
|
|
set +e
|
|
cleanup_nextcloud_defaults
|
|
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
|
|
custom_apps_to_enable=(chd_admin qortal_files_bridge qortal_talk_bridge nuqloud_scrum)
|
|
if install_custom_pwa_enabled; then
|
|
custom_apps_to_enable=(custom_pwa "${custom_apps_to_enable[@]}")
|
|
fi
|
|
for custom_app in "${custom_apps_to_enable[@]}"; do
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:enable "${custom_app}" >/dev/null 2>&1 || true
|
|
done
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" 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
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:install user_oidc >/dev/null 2>&1
|
|
install_oidc_status=$?
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:enable --force user_oidc >/dev/null 2>&1
|
|
enable_oidc_status=$?
|
|
fi
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:enable qortal_integration >/dev/null 2>&1
|
|
enable_qortal_status=$?
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" 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
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:install notify_push >/dev/null 2>&1
|
|
notify_push_install_status=$?
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:enable --force notify_push >/dev/null 2>&1
|
|
notify_push_enable_status=$?
|
|
fi
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:list >/tmp/install_occ_app_list_notify_push.txt 2>/dev/null
|
|
if grep -qE '^[[:space:]]+- notify_push([[:space:]:]|$)' /tmp/install_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/install_occ_app_list_notify_push.txt >/dev/null 2>&1 || true
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" 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
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" 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="$(docker compose -f "${compose_file}" --env-file "${env_file}" 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
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ config:system:delete user_oidc login_label >/dev/null 2>&1
|
|
oidc_login_label_status=$?
|
|
else
|
|
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" >/dev/null 2>&1
|
|
oidc_login_label_status=$?
|
|
fi
|
|
|
|
if [[ "${office_profile_enabled}" == "1" ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:enable --force richdocuments >/dev/null 2>&1
|
|
office_enable_status=$?
|
|
if [[ "${office_enable_status}" -ne 0 ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:install richdocuments >/dev/null 2>&1
|
|
office_install_status=$?
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:enable --force richdocuments >/dev/null 2>&1
|
|
office_enable_status=$?
|
|
fi
|
|
if [[ -n "${collabora_public_url}" ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ config:app:set richdocuments wopi_url --value="${collabora_public_url}" >/dev/null 2>&1
|
|
office_wopi_status=$?
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ config:app:set richdocuments public_wopi_url --value="${collabora_public_url}" >/dev/null 2>&1
|
|
office_public_wopi_status=$?
|
|
else
|
|
office_wopi_status=1
|
|
fi
|
|
fi
|
|
|
|
if [[ "${talk_profile_enabled}" == "1" ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:enable --force spreed >/dev/null 2>&1
|
|
talk_enable_status=$?
|
|
if [[ "${talk_enable_status}" -ne 0 ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:install spreed >/dev/null 2>&1
|
|
talk_install_status=$?
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ app:enable --force spreed >/dev/null 2>&1
|
|
talk_enable_status=$?
|
|
fi
|
|
|
|
if [[ -z "${talk_command_prefix}" ]]; then
|
|
talk_namespace_missing=1
|
|
talk_signaling_skipped=1
|
|
talk_stun_skipped=1
|
|
if [[ "${turn_profile_enabled}" == "1" ]]; then
|
|
talk_turn_skipped=1
|
|
fi
|
|
else
|
|
if [[ -n "${talk_signaling_public_url}" && -n "${talk_signaling_secret}" ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:signaling:list" >/tmp/occ_talk_signaling_list.txt 2>/dev/null
|
|
if grep -Fq "${talk_signaling_public_url}" /tmp/occ_talk_signaling_list.txt 2>/dev/null; then
|
|
talk_signaling_add_status=0
|
|
else
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:signaling:add" "${talk_signaling_public_url}" "${talk_signaling_secret}" >/dev/null 2>&1
|
|
talk_signaling_add_status=$?
|
|
if [[ "${talk_signaling_add_status}" -ne 0 ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:signaling:list" >/tmp/occ_talk_signaling_list_retry.txt 2>/dev/null
|
|
if grep -Fq "${talk_signaling_public_url}" /tmp/occ_talk_signaling_list_retry.txt 2>/dev/null; then
|
|
talk_signaling_add_status=0
|
|
fi
|
|
rm -f /tmp/occ_talk_signaling_list_retry.txt >/dev/null 2>&1 || true
|
|
fi
|
|
fi
|
|
rm -f /tmp/occ_talk_signaling_list.txt >/dev/null 2>&1 || true
|
|
|
|
if occ_has_command "${talk_command_prefix}:signaling:delete"; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:signaling:list" >/tmp/occ_talk_signaling_list_prune.txt 2>/dev/null
|
|
mapfile -t talk_signaling_delete_ids < <(
|
|
awk -v keep="${talk_signaling_public_url}" '
|
|
/https?:\/\// {
|
|
if (keep != "" && index($0, keep) > 0) {
|
|
next;
|
|
}
|
|
if (match($0, /\|[[:space:]]*([0-9]+)[[:space:]]*\|/, m)) {
|
|
print m[1];
|
|
next;
|
|
}
|
|
if (match($0, /^[[:space:]]*([0-9]+)[[:space:]]+/, m)) {
|
|
print m[1];
|
|
next;
|
|
}
|
|
}
|
|
' /tmp/occ_talk_signaling_list_prune.txt 2>/dev/null | sort -u
|
|
)
|
|
for signaling_id in "${talk_signaling_delete_ids[@]}"; do
|
|
if [[ -n "${signaling_id}" ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:signaling:delete" "${signaling_id}" >/dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
rm -f /tmp/occ_talk_signaling_list_prune.txt >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:signaling:list" >/tmp/occ_talk_signaling_list_final.txt 2>/dev/null
|
|
talk_signaling_multi_count="$(
|
|
grep -Eo 'https?://[^[:space:]|]+' /tmp/occ_talk_signaling_list_final.txt 2>/dev/null | sort -u | wc -l | tr -d '[:space:]'
|
|
)"
|
|
rm -f /tmp/occ_talk_signaling_list_final.txt >/dev/null 2>&1 || true
|
|
if [[ -n "${talk_signaling_multi_count}" && "${talk_signaling_multi_count}" -gt 1 ]]; then
|
|
talk_signaling_prune_status=1
|
|
else
|
|
talk_signaling_prune_status=0
|
|
fi
|
|
else
|
|
talk_signaling_skipped=1
|
|
fi
|
|
|
|
if [[ -n "${talk_stun_server}" ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:stun:list" >/tmp/occ_talk_stun_list.txt 2>/dev/null
|
|
if grep -Fq "${talk_stun_server}" /tmp/occ_talk_stun_list.txt 2>/dev/null; then
|
|
talk_stun_add_status=0
|
|
else
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:stun:add" "${talk_stun_server}" >/dev/null 2>&1
|
|
talk_stun_add_status=$?
|
|
if [[ "${talk_stun_add_status}" -ne 0 ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:stun:list" >/tmp/occ_talk_stun_list_retry.txt 2>/dev/null
|
|
if grep -Fq "${talk_stun_server}" /tmp/occ_talk_stun_list_retry.txt 2>/dev/null; then
|
|
talk_stun_add_status=0
|
|
fi
|
|
rm -f /tmp/occ_talk_stun_list_retry.txt >/dev/null 2>&1 || true
|
|
fi
|
|
fi
|
|
if occ_has_command "${talk_command_prefix}:stun:delete"; then
|
|
mapfile -t talk_stun_delete_ids < <(
|
|
awk -v keep="${talk_stun_server}" '
|
|
{
|
|
if (keep != "" && index($0, keep) > 0) {
|
|
next;
|
|
}
|
|
if (match($0, /\|[[:space:]]*([0-9]+)[[:space:]]*\|/, m)) {
|
|
print m[1];
|
|
next;
|
|
}
|
|
if (match($0, /^[[:space:]]*([0-9]+)[[:space:]]+/, m)) {
|
|
print m[1];
|
|
next;
|
|
}
|
|
}
|
|
' /tmp/occ_talk_stun_list.txt 2>/dev/null | sort -u
|
|
)
|
|
for stun_id in "${talk_stun_delete_ids[@]}"; do
|
|
if [[ -n "${stun_id}" ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:stun:delete" "${stun_id}" >/dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
fi
|
|
rm -f /tmp/occ_talk_stun_list.txt >/dev/null 2>&1 || true
|
|
else
|
|
talk_stun_skipped=1
|
|
fi
|
|
|
|
if [[ "${turn_profile_enabled}" == "1" && -n "${talk_turn_secret}" && -n "${talk_domain}" ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:turn:list" >/tmp/occ_talk_turn_list.txt 2>/dev/null
|
|
if grep -Fq "${talk_turn_server}" /tmp/occ_talk_turn_list.txt 2>/dev/null; then
|
|
talk_turn_add_status=0
|
|
else
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:turn:add" turn "${talk_turn_server}" udp,tcp --secret "${talk_turn_secret}" >/dev/null 2>&1
|
|
talk_turn_add_status=$?
|
|
if [[ "${talk_turn_add_status}" -ne 0 ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:turn:list" >/tmp/occ_talk_turn_list_retry.txt 2>/dev/null
|
|
if grep -Fq "${talk_turn_server}" /tmp/occ_talk_turn_list_retry.txt 2>/dev/null; then
|
|
talk_turn_add_status=0
|
|
fi
|
|
rm -f /tmp/occ_talk_turn_list_retry.txt >/dev/null 2>&1 || true
|
|
fi
|
|
fi
|
|
if occ_has_command "${talk_command_prefix}:turn:delete"; then
|
|
mapfile -t talk_turn_delete_ids < <(
|
|
awk -v keep="${talk_turn_server}" '
|
|
{
|
|
if (keep != "" && index($0, keep) > 0) {
|
|
next;
|
|
}
|
|
if (match($0, /\|[[:space:]]*([0-9]+)[[:space:]]*\|/, m)) {
|
|
print m[1];
|
|
next;
|
|
}
|
|
if (match($0, /^[[:space:]]*([0-9]+)[[:space:]]+/, m)) {
|
|
print m[1];
|
|
next;
|
|
}
|
|
}
|
|
' /tmp/occ_talk_turn_list.txt 2>/dev/null | sort -u
|
|
)
|
|
for turn_id in "${talk_turn_delete_ids[@]}"; do
|
|
if [[ -n "${turn_id}" ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_command_prefix}:turn:delete" "${turn_id}" >/dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
fi
|
|
rm -f /tmp/occ_talk_turn_list.txt >/dev/null 2>&1 || true
|
|
elif [[ "${turn_profile_enabled}" == "1" ]]; then
|
|
talk_turn_skipped=1
|
|
fi
|
|
fi
|
|
|
|
if [[ "${recording_profile_enabled}" == "1" && -n "${talk_recording_url}" && -n "${talk_recording_secret}" ]]; then
|
|
if [[ -n "${talk_recording_prefix}" ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_recording_prefix}:recording:add" "${talk_recording_url}" "${talk_recording_secret}" >/dev/null 2>&1
|
|
talk_recording_add_status=$?
|
|
if [[ "${talk_recording_add_status}" -ne 0 ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ "${talk_recording_prefix}:recording:set" "${talk_recording_url}" "${talk_recording_secret}" >/dev/null 2>&1
|
|
talk_recording_add_status=$?
|
|
fi
|
|
else
|
|
talk_recording_command_missing=1
|
|
talk_recording_skipped=1
|
|
fi
|
|
elif [[ "${recording_profile_enabled}" == "1" ]]; then
|
|
talk_recording_skipped=1
|
|
fi
|
|
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
|
|
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ maintenance:repair >/dev/null 2>&1
|
|
repair_status=$?
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T app php occ maintenance:repair --include-expensive >/dev/null 2>&1
|
|
repair_expensive_status=$?
|
|
|
|
if [[ -x "${repo_root}/scripts/sync-public-env-urls.sh" ]]; then
|
|
"${repo_root}/scripts/sync-public-env-urls.sh" \
|
|
--env-file "${env_file}" \
|
|
--mode "${mode}" >/dev/null 2>&1
|
|
public_env_resync_status=$?
|
|
fi
|
|
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_verify_status=$?
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-custom-pwa-push-config.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-custom-pwa-push-config.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" >/dev/null 2>&1
|
|
custom_pwa_push_status=$?
|
|
custom_pwa_push_skipped=0
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-qortal-integration-runtime-config.sh" ]]; then
|
|
qortal_runtime_log="$(mktemp)"
|
|
"${repo_root}/scripts/ensure-qortal-integration-runtime-config.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" >"${qortal_runtime_log}" 2>&1
|
|
qortal_runtime_sync_status=$?
|
|
if [[ "${qortal_runtime_sync_status}" -ne 0 && -s "${qortal_runtime_log}" ]]; then
|
|
echo "Qortal runtime sync details:"
|
|
sed 's/^/ /' "${qortal_runtime_log}"
|
|
fi
|
|
rm -f "${qortal_runtime_log}" >/dev/null 2>&1 || true
|
|
qortal_runtime_sync_skipped=0
|
|
fi
|
|
|
|
if [[ "${office_profile_enabled}" == "1" && -x "${repo_root}/scripts/ensure-nextcloud-office-config.sh" ]]; then
|
|
office_verify_log="$(mktemp)"
|
|
"${repo_root}/scripts/ensure-nextcloud-office-config.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" >"${office_verify_log}" 2>&1
|
|
office_verify_status=$?
|
|
if [[ "${office_verify_status}" -ne 0 && -s "${office_verify_log}" ]]; then
|
|
echo "Office validation details:"
|
|
sed 's/^/ /' "${office_verify_log}"
|
|
fi
|
|
rm -f "${office_verify_log}" >/dev/null 2>&1 || true
|
|
office_verify_skipped=0
|
|
fi
|
|
|
|
if [[ ( "${talk_profile_enabled}" == "1" || "${recording_profile_enabled}" == "1" ) && -x "${repo_root}/scripts/ensure-nextcloud-talk-config.sh" ]]; then
|
|
talk_verify_log="$(mktemp)"
|
|
"${repo_root}/scripts/ensure-nextcloud-talk-config.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" >"${talk_verify_log}" 2>&1
|
|
talk_verify_status=$?
|
|
if [[ "${talk_verify_status}" -ne 0 && -s "${talk_verify_log}" ]]; then
|
|
echo "Talk validation details:"
|
|
sed 's/^/ /' "${talk_verify_log}"
|
|
fi
|
|
rm -f "${talk_verify_log}" >/dev/null 2>&1 || true
|
|
talk_verify_skipped=0
|
|
fi
|
|
set -e
|
|
occ_failure="0"
|
|
if [[ "${nextcloud_url_sync_status}" -ne 0 || "${nextcloud_service_auth_status}" -ne 0 || "${enable_qortal_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 || "${default_app_bundle_status}" -ne 0 || "${repair_status}" -ne 0 || "${repair_expensive_status}" -ne 0 || "${public_env_resync_status}" -ne 0 || "${nextcloud_url_verify_status}" -ne 0 ]]; then
|
|
occ_failure="1"
|
|
fi
|
|
if [[ "${custom_pwa_push_skipped}" -eq 0 && "${custom_pwa_push_status}" -ne 0 ]]; then
|
|
occ_failure="1"
|
|
fi
|
|
if [[ "${qortal_runtime_sync_skipped}" -eq 0 && "${qortal_runtime_sync_status}" -ne 0 ]]; then
|
|
occ_failure="1"
|
|
fi
|
|
if [[ "${office_verify_skipped}" -eq 0 ]]; then
|
|
if [[ "${office_verify_status}" -ne 0 ]]; then
|
|
occ_failure="1"
|
|
fi
|
|
else
|
|
if [[ "${office_profile_enabled}" == "1" && ( "${office_enable_status}" -ne 0 || "${office_wopi_status}" -ne 0 || "${office_public_wopi_status}" -ne 0 ) ]]; then
|
|
occ_failure="1"
|
|
fi
|
|
fi
|
|
if [[ "${talk_verify_skipped}" -eq 0 ]]; then
|
|
if [[ "${talk_verify_status}" -ne 0 ]]; then
|
|
occ_failure="1"
|
|
fi
|
|
else
|
|
if [[ "${talk_profile_enabled}" == "1" && ( "${talk_enable_status}" -ne 0 || "${talk_signaling_add_status}" -ne 0 || "${talk_stun_add_status}" -ne 0 ) ]]; then
|
|
occ_failure="1"
|
|
fi
|
|
if [[ "${talk_signaling_prune_status}" -ne 0 ]]; then
|
|
occ_failure="1"
|
|
fi
|
|
if [[ "${turn_profile_enabled}" == "1" && "${talk_turn_add_status}" -ne 0 ]]; then
|
|
occ_failure="1"
|
|
fi
|
|
if [[ "${recording_profile_enabled}" == "1" && "${talk_recording_add_status}" -ne 0 && "${talk_recording_command_missing}" -ne 1 ]]; then
|
|
occ_failure="1"
|
|
fi
|
|
fi
|
|
|
|
if [[ "${occ_failure}" == "1" ]]; then
|
|
echo "Warning: OCC post-setup command(s) did not fully complete. You can rerun:"
|
|
echo " ./scripts/sync-public-env-urls.sh --env-file ${env_file} --mode ${mode}"
|
|
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-custom-pwa-push-config.sh --compose-file ${compose_file} --env-file ${env_file}"
|
|
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:enable qortal_integration"
|
|
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'"
|
|
if [[ "${office_profile_enabled}" == "1" ]]; then
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:enable --force richdocuments"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ config:app:set richdocuments wopi_url --value='${collabora_public_url}'"
|
|
fi
|
|
if [[ "${talk_profile_enabled}" == "1" ]]; then
|
|
talk_cmd="${talk_command_prefix:-talk}"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:enable --force spreed"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ ${talk_cmd}:signaling:add '${talk_signaling_public_url}' '<signaling-secret>'"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ ${talk_cmd}:signaling:list"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ ${talk_cmd}:stun:add '${talk_stun_server}'"
|
|
fi
|
|
if [[ "${turn_profile_enabled}" == "1" ]]; then
|
|
talk_cmd="${talk_command_prefix:-talk}"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ ${talk_cmd}:turn:add turn '${talk_turn_server}' udp,tcp --secret '<turn-secret>'"
|
|
fi
|
|
if [[ "${recording_profile_enabled}" == "1" ]]; then
|
|
recording_cmd="${talk_recording_prefix:-${talk_command_prefix:-talk}}"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ ${recording_cmd}:recording:add '${talk_recording_url}' '<recording-secret>'"
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-nextcloud-talk-config.sh" ]]; then
|
|
echo " ./scripts/ensure-nextcloud-talk-config.sh --compose-file ${compose_file} --env-file ${env_file}"
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-nextcloud-office-config.sh" ]]; then
|
|
echo " ./scripts/ensure-nextcloud-office-config.sh --compose-file ${compose_file} --env-file ${env_file}"
|
|
fi
|
|
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; verify appstore connectivity and install/enable it."
|
|
fi
|
|
if [[ "${talk_signaling_skipped}" -eq 1 ]]; then
|
|
echo " Note: Talk signaling auto-config skipped because TALK_SIGNALING_PUBLIC_URL or TALK_SIGNALING_SECRET is empty."
|
|
fi
|
|
if [[ "${talk_signaling_prune_status}" -ne 0 ]]; then
|
|
echo " Note: multiple signaling backends are still configured (count=${talk_signaling_multi_count}); remove extras in Talk admin or via ${talk_command_prefix:-talk}:signaling:delete."
|
|
fi
|
|
if [[ "${talk_stun_skipped}" -eq 1 ]]; then
|
|
echo " Note: Talk STUN auto-config skipped because TALK_STUN_SERVER is empty."
|
|
fi
|
|
if [[ "${talk_turn_skipped}" -eq 1 ]]; then
|
|
echo " Note: TURN auto-config skipped because TALK_TURN_SECRET or SIGNALING_DOMAIN/TALK_TURN_PORT is empty."
|
|
fi
|
|
if [[ "${talk_namespace_missing}" -eq 1 ]]; then
|
|
echo " Note: Talk OCC namespace was not found (expected talk:* or spreed:*). Verify Nextcloud Talk app activation."
|
|
fi
|
|
if [[ "${talk_recording_command_missing}" -eq 1 ]]; then
|
|
echo " Note: Talk recording OCC command not available (checked talk:* and spreed:*); configure recording backend URL/secret in Talk admin settings."
|
|
elif [[ "${talk_recording_skipped}" -eq 1 ]]; then
|
|
echo " Note: Talk recording auto-config skipped because TALK_RECORDING_URL or TALK_RECORDING_SHARED_SECRET is empty."
|
|
fi
|
|
if [[ "${talk_verify_skipped}" -eq 0 ]]; then
|
|
echo " Note: Talk backend verification status=${talk_verify_status}."
|
|
fi
|
|
if [[ "${office_verify_skipped}" -eq 0 ]]; then
|
|
echo " Note: Office backend verification status=${office_verify_status}."
|
|
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:enable qortal_integration"
|
|
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"
|
|
if [[ "${office_profile_enabled}" == "1" ]]; then
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:enable --force richdocuments"
|
|
fi
|
|
if [[ "${talk_profile_enabled}" == "1" ]]; then
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ app:enable --force spreed"
|
|
fi
|
|
if [[ "${recording_profile_enabled}" == "1" ]]; then
|
|
echo " Then configure Talk recording backend URL/secret in Nextcloud Talk admin settings."
|
|
fi
|
|
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 --include-expensive"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T app php occ maintenance:repair"
|
|
fi
|
|
fi
|
|
|
|
should_auto_finish="0"
|
|
if [[ "${auto_finish}" == "1" ]]; then
|
|
should_auto_finish="1"
|
|
elif [[ "${auto_finish}" == "auto" ]]; then
|
|
if [[ "${with_qortal}" == "1" && ",${COMPOSE_PROFILES:-}," == *",external-auth,"* ]]; then
|
|
should_auto_finish="1"
|
|
fi
|
|
fi
|
|
|
|
if [[ "${should_auto_finish}" == "1" ]]; then
|
|
finish_status="failed"
|
|
if [[ -x "${repo_root}/scripts/finish-initial-setup.sh" ]]; then
|
|
echo "Running finish-initial-setup automatically..."
|
|
finish_args=(--mode "${mode}" --env-file "${env_file}" --skip-occ)
|
|
if "${repo_root}/scripts/finish-initial-setup.sh" "${finish_args[@]}"; then
|
|
finish_status="success"
|
|
else
|
|
echo "Warning: automatic finish-initial-setup did not complete successfully."
|
|
echo "You can rerun manually:"
|
|
echo " ./scripts/finish-initial-setup.sh --mode ${mode} --env-file ${env_file}"
|
|
fi
|
|
else
|
|
finish_status="skipped (script missing)"
|
|
echo "Warning: scripts/finish-initial-setup.sh not found; skipping automatic finish."
|
|
fi
|
|
should_post_finish_recreate="0"
|
|
if [[ "${finish_status}" == "success" ]]; then
|
|
if [[ "${post_finish_recreate}" == "1" ]]; then
|
|
should_post_finish_recreate="1"
|
|
elif [[ "${post_finish_recreate}" == "auto" ]]; then
|
|
should_post_finish_recreate="1"
|
|
fi
|
|
fi
|
|
if [[ "${should_post_finish_recreate}" == "1" ]]; then
|
|
echo "Running post-finish recreate for app + broker + external_auth..."
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" up -d --build --force-recreate app broker external_auth || true
|
|
fi
|
|
else
|
|
finish_status="skipped"
|
|
fi
|
|
|
|
cat <<DONE
|
|
Installation bootstrap complete.
|
|
|
|
Next steps:
|
|
1. Verify running services:
|
|
docker compose -f ${compose_file} --env-file ${env_file} ps
|
|
2. Automatic finish status: ${finish_status}
|
|
If needed, run manually:
|
|
./scripts/finish-initial-setup.sh --${mode}
|
|
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
|