1746 lines
68 KiB
Bash
Executable File
1746 lines
68 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
repo_root="${script_dir}"
|
|
default_external_auth_repo_url="https://gitea.qortal.link/crowetic/Qortal-External-Auth.git"
|
|
source "${repo_root}/scripts/lib-compose-project.sh"
|
|
|
|
env_file="${repo_root}/.env.devprod"
|
|
topology="auto"
|
|
host_broker_dir="/opt/qortal-broker"
|
|
host_broker_dir_explicit="0"
|
|
host_force_refresh_contexts="0"
|
|
host_finish_nextcloud_upgrade="0"
|
|
host_rebuild_qortal="0"
|
|
|
|
resolve_cli_path() {
|
|
local raw="${1:-}"
|
|
if [[ -z "${raw}" ]]; then
|
|
printf '%s' ""
|
|
elif [[ "${raw}" == /* ]]; then
|
|
printf '%s' "${raw}"
|
|
else
|
|
printf '%s' "${repo_root}/${raw}"
|
|
fi
|
|
}
|
|
|
|
delegate_host_hybrid_recreate() {
|
|
local update_args=(--broker-dir "${host_broker_dir}")
|
|
if [[ "${host_force_refresh_contexts}" == "1" ]]; then
|
|
update_args+=(--force-refresh-contexts)
|
|
fi
|
|
if [[ "${host_finish_nextcloud_upgrade}" == "1" ]]; then
|
|
update_args+=(--finish-host-nextcloud-upgrade)
|
|
fi
|
|
|
|
echo "Detected host-hybrid NuQloud install."
|
|
echo "Delegating full repair/update to scripts/update-host-nextcloud-hybrid.sh."
|
|
"${repo_root}/scripts/update-host-nextcloud-hybrid.sh" "${update_args[@]}"
|
|
|
|
if [[ "${host_rebuild_qortal}" == "1" && -x "${repo_root}/scripts/restart-host-nextcloud-hybrid.sh" ]]; then
|
|
echo "Recreating host-hybrid services with qortal_node rebuild..."
|
|
"${repo_root}/scripts/restart-host-nextcloud-hybrid.sh" --broker-dir "${host_broker_dir}" --qortal
|
|
fi
|
|
exit 0
|
|
}
|
|
|
|
repo_devprod_install_present() {
|
|
[[ -f "${env_file}" ]] || return 1
|
|
[[ -f "${repo_root}/docker-compose.devprod.yml" || -f "${repo_root}/docker-compose.devprod.nossl.yml" ]]
|
|
}
|
|
|
|
host_hybrid_install_present() {
|
|
[[ -f "${host_broker_dir}/.env" && -f "${host_broker_dir}/docker-compose.yml" ]]
|
|
}
|
|
|
|
auto_select_host_broker_dir() {
|
|
local candidate
|
|
if [[ "${host_broker_dir_explicit}" == "1" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
for candidate in \
|
|
"/opt/qortal-broker" \
|
|
"${repo_root}/qortal-broker" \
|
|
"${repo_root}/broker"; do
|
|
if [[ -f "${candidate}/.env" && -f "${candidate}/docker-compose.yml" ]]; then
|
|
host_broker_dir="${candidate}"
|
|
return 0
|
|
fi
|
|
done
|
|
}
|
|
|
|
resolve_auto_topology() {
|
|
local repo_present="0"
|
|
local host_present="0"
|
|
local choice=""
|
|
|
|
auto_select_host_broker_dir
|
|
repo_devprod_install_present && repo_present="1"
|
|
host_hybrid_install_present && host_present="1"
|
|
|
|
if [[ "${host_broker_dir_explicit}" == "1" && "${host_present}" == "1" ]]; then
|
|
delegate_host_hybrid_recreate
|
|
fi
|
|
|
|
if [[ "${repo_present}" == "1" && "${host_present}" == "0" ]]; then
|
|
echo "Detected repo devprod NuQloud install."
|
|
return 0
|
|
fi
|
|
|
|
if [[ "${repo_present}" == "0" && "${host_present}" == "1" ]]; then
|
|
delegate_host_hybrid_recreate
|
|
fi
|
|
|
|
if [[ "${repo_present}" == "1" && "${host_present}" == "1" ]]; then
|
|
echo "Detected more than one NuQloud install shape:"
|
|
echo " 1) repo devprod container stack (${env_file})"
|
|
echo " 2) host-hybrid broker stack (${host_broker_dir})"
|
|
if [[ -t 0 ]]; then
|
|
read -r -p "Which install should recreate-devprod repair? [1/2] (default: 1): " choice
|
|
case "${choice}" in
|
|
2)
|
|
delegate_host_hybrid_recreate
|
|
;;
|
|
""|1)
|
|
echo "Using repo devprod container stack."
|
|
return 0
|
|
;;
|
|
*)
|
|
echo "Invalid selection: ${choice}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
echo "Non-interactive shell detected; using repo devprod container stack."
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
topology_next="0"
|
|
broker_dir_next="0"
|
|
for arg in "$@"; do
|
|
if [[ "${topology_next}" == "1" ]]; then
|
|
topology="${arg}"
|
|
topology_next="0"
|
|
continue
|
|
fi
|
|
if [[ "${broker_dir_next}" == "1" ]]; then
|
|
host_broker_dir="${arg}"
|
|
host_broker_dir_explicit="1"
|
|
broker_dir_next="0"
|
|
continue
|
|
fi
|
|
case "${arg}" in
|
|
--topology)
|
|
topology_next="1"
|
|
;;
|
|
--topology=*)
|
|
topology="${arg#*=}"
|
|
;;
|
|
--host-hybrid)
|
|
topology="host-hybrid"
|
|
;;
|
|
--repo-devprod)
|
|
topology="repo-devprod"
|
|
;;
|
|
--broker-dir)
|
|
broker_dir_next="1"
|
|
;;
|
|
--broker-dir=*)
|
|
host_broker_dir="${arg#*=}"
|
|
host_broker_dir_explicit="1"
|
|
;;
|
|
--force-refresh-contexts)
|
|
host_force_refresh_contexts="1"
|
|
;;
|
|
--finish-host-nextcloud-upgrade)
|
|
host_finish_nextcloud_upgrade="1"
|
|
;;
|
|
--qortal|--full|--qortal=*|--qortal-branch|--qortal-branch=*)
|
|
host_rebuild_qortal="1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ "${topology_next}" == "1" ]]; then
|
|
echo "--topology requires a value"
|
|
exit 1
|
|
fi
|
|
if [[ "${broker_dir_next}" == "1" ]]; then
|
|
echo "--broker-dir requires a value"
|
|
exit 1
|
|
fi
|
|
if [[ "${topology}" != "auto" && "${topology}" != "repo-devprod" && "${topology}" != "host-hybrid" ]]; then
|
|
echo "Invalid topology: ${topology}"
|
|
exit 1
|
|
fi
|
|
|
|
host_broker_dir="$(resolve_cli_path "${host_broker_dir}")"
|
|
if [[ "${topology}" == "host-hybrid" ]]; then
|
|
delegate_host_hybrid_recreate
|
|
fi
|
|
if [[ "${topology}" == "auto" ]]; then
|
|
resolve_auto_topology
|
|
fi
|
|
|
|
if [[ ! -f "${env_file}" ]]; then
|
|
echo "Missing ${env_file}. Run ./start-devprod.sh first."
|
|
echo "No host-hybrid broker stack was found at ${host_broker_dir} either."
|
|
echo "For host-hybrid installs, run: ./recreate-devprod.sh --host-hybrid --broker-dir <broker-dir>"
|
|
exit 1
|
|
fi
|
|
|
|
if ! export_compose_project_name_from_env "${env_file}"; then
|
|
warn_missing_compose_project_name "${env_file}"
|
|
fi
|
|
|
|
if [[ -x "${repo_root}/scripts/ensure-env-keys.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-env-keys.sh" \
|
|
--env-file "${env_file}" \
|
|
--template-file "${repo_root}/.env.example"
|
|
fi
|
|
|
|
read_kv() {
|
|
local key="$1"
|
|
local line
|
|
line="$(grep -m1 -E "^${key}=" "${env_file}" || true)"
|
|
if [[ -z "${line}" ]]; then
|
|
return 1
|
|
fi
|
|
echo "${line#*=}"
|
|
}
|
|
|
|
set_kv() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local tmp_file=""
|
|
tmp_file="$(mktemp)"
|
|
awk -v key="${key}" -v value="${value}" '
|
|
BEGIN { found = 0 }
|
|
$0 ~ "^" key "=" {
|
|
print key "=" value
|
|
found = 1
|
|
next
|
|
}
|
|
{ print }
|
|
END {
|
|
if (!found) {
|
|
print key "=" value
|
|
}
|
|
}
|
|
' "${env_file}" > "${tmp_file}"
|
|
mv "${tmp_file}" "${env_file}"
|
|
}
|
|
|
|
nextcloud_image_major() {
|
|
local image="$1"
|
|
local tag="${image##*:}"
|
|
if [[ "${tag}" =~ ^([0-9]+)([.-]|$) ]]; then
|
|
echo "${BASH_REMATCH[1]}"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
nextcloud_version_major() {
|
|
local version="$1"
|
|
if [[ "${version}" =~ ^([0-9]+)([.-]|$) ]]; then
|
|
echo "${BASH_REMATCH[1]}"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
local_nextcloud_config_version() {
|
|
local config_php="${repo_root}/nextcloud/html/config/config.php"
|
|
if [[ ! -f "${config_php}" ]]; then
|
|
return 1
|
|
fi
|
|
sed -n "s/.*['\"]version['\"][[:space:]]*=>[[:space:]]*['\"]\\([^'\"]*\\)['\"].*/\\1/p" "${config_php}" | head -n1
|
|
}
|
|
|
|
nextcloud_image_from_version() {
|
|
local version="$1"
|
|
local major=""
|
|
local minor=""
|
|
local patch=""
|
|
IFS=. read -r major minor patch _rest <<< "${version}"
|
|
if [[ -z "${major}" || -z "${minor}" || -z "${patch}" ]]; then
|
|
return 1
|
|
fi
|
|
echo "nextcloud:${major}.${minor}.${patch}-apache"
|
|
}
|
|
|
|
running_app_image() {
|
|
local container_id=""
|
|
container_id="$(docker compose -f "${compose_file}" --env-file "${env_file}" ps -q app 2>/dev/null | head -n1 || true)"
|
|
if [[ -z "${container_id}" ]]; then
|
|
return 1
|
|
fi
|
|
docker inspect --format '{{.Config.Image}}' "${container_id}" 2>/dev/null
|
|
}
|
|
|
|
preserve_running_nextcloud_image_if_newer() {
|
|
local configured_image=""
|
|
local configured_major=""
|
|
local installed_image=""
|
|
local installed_major=""
|
|
local installed_version=""
|
|
local running_image=""
|
|
local running_major=""
|
|
|
|
if [[ -n "${nextcloud_image_override}" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
configured_image="$(read_kv "NEXTCLOUD_IMAGE" || true)"
|
|
configured_image="${configured_image:-nextcloud:34-apache}"
|
|
configured_major="$(nextcloud_image_major "${configured_image}" || true)"
|
|
|
|
installed_version="$(local_nextcloud_config_version || true)"
|
|
if [[ -n "${installed_version}" ]]; then
|
|
installed_major="$(nextcloud_version_major "${installed_version}" || true)"
|
|
installed_image="$(nextcloud_image_from_version "${installed_version}" || true)"
|
|
if [[ -n "${configured_major}" && -n "${installed_major}" && -n "${installed_image}" ]] && (( installed_major > configured_major )); then
|
|
export NEXTCLOUD_IMAGE="${installed_image}"
|
|
set_kv "NEXTCLOUD_IMAGE" "${installed_image}"
|
|
echo "Detected installed Nextcloud version ${installed_version} is newer than configured image ${configured_image}."
|
|
echo "Preserving installed version and updating ${env_file}: NEXTCLOUD_IMAGE=${installed_image}"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
running_image="$(running_app_image || true)"
|
|
if [[ -z "${running_image}" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
running_major="$(nextcloud_image_major "${running_image}" || true)"
|
|
if [[ -z "${configured_major}" || -z "${running_major}" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if (( running_major > configured_major )); then
|
|
export NEXTCLOUD_IMAGE="${running_image}"
|
|
set_kv "NEXTCLOUD_IMAGE" "${running_image}"
|
|
echo "Detected running Nextcloud image ${running_image} is newer than configured ${configured_image}."
|
|
echo "Preserving running image and updating ${env_file}: NEXTCLOUD_IMAGE=${running_image}"
|
|
fi
|
|
}
|
|
|
|
resolve_path_from_env() {
|
|
local key="$1"
|
|
local default_path="$2"
|
|
local value
|
|
value="$(read_kv "${key}" || true)"
|
|
value="${value:-${default_path}}"
|
|
if [[ "${value}" == /* ]]; then
|
|
echo "${value}"
|
|
else
|
|
echo "${repo_root}/${value}"
|
|
fi
|
|
}
|
|
|
|
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[@]}" "$@"
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
refresh_context_repo() {
|
|
local label="$1"
|
|
local target_path="$2"
|
|
local branch=""
|
|
|
|
if [[ ! -d "${target_path}/.git" ]]; then
|
|
echo "Cannot refresh ${label}; not a git repo: ${target_path}"
|
|
exit 1
|
|
fi
|
|
if ! command -v git >/dev/null 2>&1; then
|
|
echo "git is required to refresh ${label}."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "$(git -C "${target_path}" status --porcelain)" ]]; then
|
|
echo "${label} checkout has local changes; refusing to auto-pull: ${target_path}"
|
|
echo "Commit/stash those changes or update EXTERNAL_AUTH_CONTEXT to a clean checkout."
|
|
exit 1
|
|
fi
|
|
|
|
branch="$(git -C "${target_path}" branch --show-current)"
|
|
if [[ -z "${branch}" ]]; then
|
|
branch="$(git -C "${target_path}" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##' || true)"
|
|
fi
|
|
if [[ -z "${branch}" ]]; then
|
|
branch="main"
|
|
fi
|
|
|
|
echo "Refreshing ${label} repo at ${target_path} (${branch})..."
|
|
git -C "${target_path}" fetch origin
|
|
if git -C "${target_path}" show-ref --verify --quiet "refs/heads/${branch}"; then
|
|
git -C "${target_path}" checkout "${branch}"
|
|
else
|
|
git -C "${target_path}" checkout -B "${branch}" "origin/${branch}"
|
|
fi
|
|
git -C "${target_path}" pull --ff-only origin "${branch}"
|
|
}
|
|
|
|
prompt_qortal_branch_if_needed() {
|
|
local use_specific_branch=""
|
|
local requested_branch=""
|
|
if [[ "${recreate_qortal}" != "1" || -n "${qortal_branch}" || ! -t 0 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
read -r -p "Use a specific Qortal Core branch for this recreate? (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 run: ${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
|
|
"${repo_root}/scripts/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 --user www-data 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
|
|
}
|
|
|
|
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 --user www-data app php occ list 2>/dev/null \
|
|
| grep -qE "^[[:space:]]+${command_name}([[:space:]]|$)"
|
|
}
|
|
|
|
occ_exec_quiet_retry() {
|
|
local rc=1
|
|
local max_attempts="${OCC_RETRY_ATTEMPTS:-6}"
|
|
local retry_sleep="${OCC_RETRY_SLEEP_SECONDS:-4}"
|
|
local attempt
|
|
if [[ $# -eq 0 ]]; then
|
|
return 1
|
|
fi
|
|
for attempt in $(seq 1 "${max_attempts}"); do
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data app php occ "$@" >/dev/null 2>&1
|
|
rc=$?
|
|
if [[ "${rc}" -eq 0 ]]; then
|
|
return 0
|
|
fi
|
|
sleep "${retry_sleep}"
|
|
done
|
|
return "${rc}"
|
|
}
|
|
|
|
verify_cron_container_user() {
|
|
local cron_uid
|
|
cron_uid="$(docker compose -f "${compose_file}" --env-file "${env_file}" exec -T cron id -u 2>/dev/null | tr -d '\r[:space:]' || true)"
|
|
if [[ "${cron_uid}" == "33" ]]; then
|
|
echo "Cron container user verified: 33 (www-data)."
|
|
return 0
|
|
fi
|
|
|
|
echo "Warning: cron container is running as UID ${cron_uid:-unknown}, expected 33 (www-data)."
|
|
echo "Recreate cron container to apply compose user settings:"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} up -d --force-recreate --no-deps cron"
|
|
return 1
|
|
}
|
|
|
|
service_in_resolved_config() {
|
|
local needle="${1:-}"
|
|
local svc
|
|
if [[ -z "${needle}" ]]; then
|
|
return 1
|
|
fi
|
|
for svc in "${services[@]:-}"; do
|
|
if [[ "${svc}" == "${needle}" ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
occ_retoggle_app() {
|
|
local app_name="${1:-}"
|
|
local force_enable="${2:-0}"
|
|
if [[ -z "${app_name}" ]]; then
|
|
return 1
|
|
fi
|
|
# Ignore disable failures (already disabled / transient OCC issues), then enforce enable.
|
|
occ_exec_quiet_retry app:disable "${app_name}" >/dev/null 2>&1 || true
|
|
if [[ "${force_enable}" == "1" ]]; then
|
|
occ_exec_quiet_retry app:enable --force "${app_name}"
|
|
else
|
|
occ_exec_quiet_retry app:enable "${app_name}"
|
|
fi
|
|
}
|
|
|
|
run_quiet_step() {
|
|
local label="${1:-}"
|
|
shift || true
|
|
local start_ts end_ts rc
|
|
start_ts="$(date +%s)"
|
|
echo " ${label}..."
|
|
"$@" >/dev/null 2>&1
|
|
rc=$?
|
|
end_ts="$(date +%s)"
|
|
echo " ${label}: status=${rc} elapsed=$((end_ts - start_ts))s"
|
|
return "${rc}"
|
|
}
|
|
|
|
mode="ssl"
|
|
extauth="0"
|
|
recreate_qortal="0"
|
|
qortal_branch=""
|
|
full_rebuild="0"
|
|
nextcloud_image_override=""
|
|
nextcloud_image_next="0"
|
|
qortal_branch_next="0"
|
|
for arg in "$@"; do
|
|
if [[ "${nextcloud_image_next}" == "1" ]]; then
|
|
nextcloud_image_override="${arg}"
|
|
nextcloud_image_next="0"
|
|
continue
|
|
fi
|
|
if [[ "${qortal_branch_next}" == "1" ]]; then
|
|
qortal_branch="${arg}"
|
|
recreate_qortal="1"
|
|
qortal_branch_next="0"
|
|
continue
|
|
fi
|
|
case "${arg}" in
|
|
--nossl)
|
|
mode="nossl"
|
|
;;
|
|
--ssl)
|
|
mode="ssl"
|
|
;;
|
|
--extauth)
|
|
extauth="1"
|
|
;;
|
|
--qortal)
|
|
recreate_qortal="1"
|
|
;;
|
|
--qortal=*)
|
|
recreate_qortal="1"
|
|
qortal_branch="${arg#*=}"
|
|
;;
|
|
--qortal-branch)
|
|
recreate_qortal="1"
|
|
qortal_branch_next="1"
|
|
;;
|
|
--qortal-branch=*)
|
|
recreate_qortal="1"
|
|
qortal_branch="${arg#*=}"
|
|
;;
|
|
--full)
|
|
full_rebuild="1"
|
|
recreate_qortal="1"
|
|
;;
|
|
--nc34)
|
|
nextcloud_image_override="nextcloud:34-apache"
|
|
;;
|
|
--nc33)
|
|
nextcloud_image_override="nextcloud:33-apache"
|
|
;;
|
|
--nextcloud-image=*)
|
|
nextcloud_image_override="${arg#*=}"
|
|
;;
|
|
--nextcloud-image)
|
|
nextcloud_image_next="1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ "${nextcloud_image_next}" == "1" ]]; then
|
|
echo "--nextcloud-image requires a value"
|
|
exit 1
|
|
fi
|
|
if [[ "${qortal_branch_next}" == "1" ]]; then
|
|
echo "--qortal-branch requires a value"
|
|
exit 1
|
|
fi
|
|
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}" != "nossl" && "${mode}" != "ssl" ]]; then
|
|
mode="ssl"
|
|
fi
|
|
|
|
if [[ -n "${nextcloud_image_override}" ]]; then
|
|
export NEXTCLOUD_IMAGE="${nextcloud_image_override}"
|
|
echo "Overriding NEXTCLOUD_IMAGE for this run: ${NEXTCLOUD_IMAGE}"
|
|
fi
|
|
|
|
if [[ "${mode}" == "ssl" && ! "$*" =~ --ssl && ! "$*" =~ --nossl ]]; then
|
|
read -r -p "Use no-SSL compose (external proxy)? (y/N): " use_nossl
|
|
if [[ "${use_nossl}" == "y" || "${use_nossl}" == "Y" ]]; then
|
|
mode="nossl"
|
|
fi
|
|
fi
|
|
|
|
compose_file="${repo_root}/docker-compose.devprod.yml"
|
|
if [[ "${mode}" == "nossl" ]]; then
|
|
compose_file="${repo_root}/docker-compose.devprod.nossl.yml"
|
|
fi
|
|
|
|
ensure_docker_access
|
|
preserve_running_nextcloud_image_if_newer
|
|
|
|
if [[ -x "${repo_root}/scripts/check-nextcloud-image-update.sh" ]]; then
|
|
"${repo_root}/scripts/check-nextcloud-image-update.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" || true
|
|
fi
|
|
|
|
qortal_context="$(grep -E '^QORTAL_NODE_CONTEXT=' "${env_file}" | tail -n1 | cut -d= -f2- || true)"
|
|
qortal_dockerfile="$(grep -E '^QORTAL_NODE_DOCKERFILE=' "${env_file}" | tail -n1 | cut -d= -f2- || true)"
|
|
echo "Qortal build source: context=${qortal_context:-../qortal} dockerfile=${qortal_dockerfile:-Dockerfile}"
|
|
if [[ -n "${qortal_branch}" ]]; then
|
|
echo "Qortal branch override for this run: ${qortal_branch}"
|
|
fi
|
|
|
|
prompt_qortal_branch_if_needed
|
|
|
|
nextcloud_custom_apps_path="$(resolve_path_from_env "NEXTCLOUD_CUSTOM_APPS_PATH" "./nextcloud/custom_apps")"
|
|
mkdir -p "${nextcloud_custom_apps_path}" "${repo_root}/qortal/data"
|
|
if [[ "${nextcloud_custom_apps_path}" == *"/custom_apps_appstore" ]]; then
|
|
echo "Using app-store mimic custom apps path: ${nextcloud_custom_apps_path}"
|
|
echo "Note: app code is copied into this path; source edits require sync during recreate."
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-user-oidc-app.sh" ]]; then
|
|
NEXTCLOUD_CUSTOM_APPS_PATH="${nextcloud_custom_apps_path}" "${repo_root}/scripts/ensure-user-oidc-app.sh"
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-qortal-integration-app.sh" ]]; then
|
|
NEXTCLOUD_CUSTOM_APPS_PATH="${nextcloud_custom_apps_path}" "${repo_root}/scripts/ensure-qortal-integration-app.sh"
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-qortal-settings.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-qortal-settings.sh"
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/ensure-qortal-start-args.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-qortal-start-args.sh" "${env_file}"
|
|
fi
|
|
|
|
if [[ -x "${repo_root}/scripts/select-qortal-p2p-port.sh" ]]; then
|
|
"${repo_root}/scripts/select-qortal-p2p-port.sh" "${env_file}"
|
|
fi
|
|
if [[ -f "${repo_root}/scripts/ensure-broker-internal-token.sh" ]]; then
|
|
bash "${repo_root}/scripts/ensure-broker-internal-token.sh" "${env_file}"
|
|
fi
|
|
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_kv "BROKER_INTERNAL_API_TOKEN" || true)"
|
|
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_kv "BROKER_CORS_ALLOWED_ORIGINS" || true)"
|
|
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 -E '^COMPOSE_PROFILES=' "${env_file}" || true)"
|
|
fi
|
|
if [[ -n "${profiles_line}" ]]; then
|
|
current_profiles="${profiles_line#COMPOSE_PROFILES=}"
|
|
fi
|
|
|
|
external_auth_base_url="$(read_kv "QORTAL_EXTERNAL_AUTH_BASE_URL" || true)"
|
|
if [[ "${extauth}" == "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 [[ "${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
|
|
|
|
if [[ -n "${current_profiles}" ]]; then
|
|
export COMPOSE_PROFILES="${current_profiles}"
|
|
else
|
|
unset COMPOSE_PROFILES || true
|
|
fi
|
|
echo "Using compose profiles: ${COMPOSE_PROFILES:-<none>}"
|
|
|
|
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
|
|
|
|
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}"
|
|
fi
|
|
|
|
manage_external_auth="0"
|
|
if [[ ",${COMPOSE_PROFILES:-}," == *",external-auth,"* || "${extauth}" == "1" ]]; then
|
|
manage_external_auth="1"
|
|
fi
|
|
external_auth_context_path=""
|
|
external_auth_repo_url=""
|
|
if [[ "${manage_external_auth}" == "1" ]]; then
|
|
external_auth_context_path="$(resolve_path_from_env "EXTERNAL_AUTH_CONTEXT" "../Qortal-External-Auth")"
|
|
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}"
|
|
if [[ "${extauth}" == "1" ]]; then
|
|
refresh_context_repo "Qortal External Auth" "${external_auth_context_path}"
|
|
fi
|
|
fi
|
|
|
|
office_profile_enabled="0"
|
|
talk_profile_enabled="0"
|
|
turn_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
|
|
talk_profile_enabled="1"
|
|
fi
|
|
if [[ ",${COMPOSE_PROFILES:-}," == *",recording,"* ]]; then
|
|
recording_profile_enabled="1"
|
|
talk_profile_enabled="1"
|
|
fi
|
|
|
|
echo "Recreating containers using ${compose_file}..."
|
|
mapfile -t services < <(docker compose -f "${compose_file}" --env-file "${env_file}" config --services)
|
|
|
|
compose_profile_mismatch=0
|
|
missing_profile_services=()
|
|
if [[ "${office_profile_enabled}" == "1" ]] && ! service_in_resolved_config "collabora"; then
|
|
compose_profile_mismatch=1
|
|
missing_profile_services+=("collabora (required by profile: office)")
|
|
fi
|
|
if [[ "${talk_profile_enabled}" == "1" ]] && ! service_in_resolved_config "talk_hpb"; then
|
|
compose_profile_mismatch=1
|
|
missing_profile_services+=("talk_hpb (required by profile: talk/turn/janus)")
|
|
fi
|
|
if [[ "${recording_profile_enabled}" == "1" ]] && ! service_in_resolved_config "talk_recording"; then
|
|
compose_profile_mismatch=1
|
|
missing_profile_services+=("talk_recording (required by profile: recording)")
|
|
fi
|
|
if [[ "${manage_external_auth}" == "1" ]] && ! service_in_resolved_config "external_auth"; then
|
|
compose_profile_mismatch=1
|
|
missing_profile_services+=("external_auth (required by bundled external-auth mode)")
|
|
fi
|
|
|
|
if [[ "${compose_profile_mismatch}" == "1" ]]; then
|
|
echo "Error: Compose profile/service mismatch detected before recreate."
|
|
echo "Compose file: ${compose_file}"
|
|
echo "Env file: ${env_file}"
|
|
echo "Requested profiles: ${COMPOSE_PROFILES:-<none>}"
|
|
echo "Resolved services: ${services[*]:-<none>}"
|
|
echo "Missing expected services:"
|
|
for item in "${missing_profile_services[@]}"; do
|
|
echo " - ${item}"
|
|
done
|
|
echo
|
|
echo "This usually means the compose file does not match the branch/version you expect,"
|
|
echo "or you're recreating from a clone with stale compose files."
|
|
echo "Verify:"
|
|
echo " git status -sb"
|
|
echo " git branch -vv"
|
|
echo " git rev-parse --short HEAD"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} config --services"
|
|
exit 1
|
|
fi
|
|
|
|
target_services=()
|
|
for svc in "${services[@]}"; do
|
|
if [[ "${recreate_qortal}" != "1" && "${svc}" == "qortal_node" ]]; then
|
|
continue
|
|
fi
|
|
if [[ "${manage_external_auth}" == "1" && "${svc}" == "external_auth" ]]; then
|
|
# Handle external_auth in a separate pass so --force-recreate does not
|
|
# implicitly recreate qortal_node through depends_on.
|
|
continue
|
|
fi
|
|
target_services+=("${svc}")
|
|
done
|
|
|
|
if [[ "${#target_services[@]}" -eq 0 ]]; then
|
|
echo "No services selected for recreation."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Recreate target services: ${target_services[*]}"
|
|
if [[ "${manage_external_auth}" == "1" && "${recreate_qortal}" != "1" ]]; then
|
|
echo "external_auth will be recreated separately with --no-deps; qortal_node will remain running."
|
|
fi
|
|
|
|
if [[ "${recreate_qortal}" == "1" ]]; then
|
|
echo "Including qortal_node in force-recreate."
|
|
if [[ "${full_rebuild}" == "1" || -n "${qortal_branch}" ]]; then
|
|
qortal_context_path="$(resolve_path_from_env "QORTAL_NODE_CONTEXT" "../qortal")"
|
|
if [[ -x "${repo_root}/scripts/ensure-qortal-core-repo.sh" ]]; then
|
|
ensure_qortal_args=(
|
|
--target-path "${qortal_context_path}"
|
|
--refresh
|
|
)
|
|
if [[ -n "${qortal_branch}" ]]; then
|
|
ensure_qortal_args+=(--branch "${qortal_branch}")
|
|
fi
|
|
"${repo_root}/scripts/ensure-qortal-core-repo.sh" "${ensure_qortal_args[@]}"
|
|
else
|
|
echo "Warning: scripts/ensure-qortal-core-repo.sh not found; cannot auto-refresh Qortal source from upstream."
|
|
fi
|
|
fi
|
|
if [[ "${full_rebuild}" != "1" ]]; then
|
|
echo "Building qortal_node with --no-cache to ensure Dockerfile/entrypoint changes are applied..."
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" build --no-cache qortal_node
|
|
fi
|
|
else
|
|
echo "Excluding qortal_node from force-recreate (use --qortal to include it)."
|
|
fi
|
|
|
|
if [[ "${full_rebuild}" == "1" ]]; then
|
|
echo "Full rebuild enabled: rebuilding all buildable services with --no-cache..."
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" build --no-cache
|
|
fi
|
|
|
|
compose_up_args=(-d --force-recreate)
|
|
if [[ "${full_rebuild}" != "1" ]]; then
|
|
compose_up_args+=(--build)
|
|
fi
|
|
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" up "${compose_up_args[@]}" "${target_services[@]}"
|
|
|
|
if [[ "${manage_external_auth}" == "1" ]]; then
|
|
echo "Rebuilding and recreating external_auth..."
|
|
if [[ "${full_rebuild}" != "1" ]]; then
|
|
external_auth_build_args=()
|
|
if [[ "${extauth}" == "1" ]]; then
|
|
external_auth_build_args+=(--no-cache)
|
|
fi
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" build "${external_auth_build_args[@]}" external_auth
|
|
fi
|
|
extauth_up_args=(-d --force-recreate)
|
|
if [[ "${full_rebuild}" != "1" ]]; then
|
|
extauth_up_args+=(--build)
|
|
fi
|
|
if [[ "${recreate_qortal}" == "1" ]]; then
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" up "${extauth_up_args[@]}" external_auth
|
|
else
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" up "${extauth_up_args[@]}" --no-deps external_auth
|
|
fi
|
|
fi
|
|
|
|
if [[ "${manage_external_auth}" == "1" && -x "${repo_root}/scripts/finish-initial-setup.sh" ]]; then
|
|
echo "Verifying bundled external-auth registration and Qortal node API key..."
|
|
"${repo_root}/scripts/finish-initial-setup.sh" \
|
|
--mode "${mode}" \
|
|
--env-file "${env_file}" \
|
|
--skip-occ
|
|
fi
|
|
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" ps
|
|
|
|
verify_cron_container_user || true
|
|
|
|
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
|
|
|
|
occ_ready=0
|
|
occ_ready_retries="${OCC_READY_RETRIES:-36}"
|
|
occ_ready_sleep_seconds="${OCC_READY_SLEEP_SECONDS:-5}"
|
|
occ_wait_start_ts="$(date +%s)"
|
|
echo "Waiting for Nextcloud OCC readiness... (timeout=$((occ_ready_retries * occ_ready_sleep_seconds))s)"
|
|
for _ in $(seq 1 "${occ_ready_retries}"); do
|
|
if docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data app php occ status >/dev/null 2>&1; then
|
|
occ_ready=1
|
|
break
|
|
fi
|
|
sleep "${occ_ready_sleep_seconds}"
|
|
done
|
|
occ_wait_end_ts="$(date +%s)"
|
|
echo "Waiting for Nextcloud OCC readiness: status=${occ_ready} elapsed=$((occ_wait_end_ts - occ_wait_start_ts))s"
|
|
|
|
if [[ "${occ_ready}" != "1" ]]; then
|
|
echo "Warning: Nextcloud OCC did not become ready after recreate within $((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 manually once app is healthy:"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ status"
|
|
echo " ./scripts/diagnose-nextcloud-occ-readiness.sh --compose-file ${compose_file} --env-file ${env_file}"
|
|
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-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 --user www-data app php occ app:enable --force user_oidc"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ app:enable --force notify_push"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ app:enable qortal_integration"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ db:add-missing-indices"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ maintenance:repair"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ maintenance:repair --include-expensive"
|
|
echo "Done."
|
|
exit 0
|
|
fi
|
|
|
|
occ_status_json="$(docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data app php occ status --output=json 2>/dev/null || true)"
|
|
if [[ -n "${occ_status_json}" ]]; then
|
|
if echo "${occ_status_json}" | grep -q '"needsDbUpgrade":[[:space:]]*true'; then
|
|
echo "Warning: Nextcloud still requires a database upgrade after recreate."
|
|
echo "Stop here and finish the upgrade before running the broader repair pass."
|
|
echo "Run:"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ upgrade"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ status"
|
|
echo "Then rerun:"
|
|
echo " ./recreate-devprod.sh --${mode}"
|
|
exit 1
|
|
fi
|
|
if echo "${occ_status_json}" | grep -q '"maintenance":[[:space:]]*true'; then
|
|
echo "Warning: Nextcloud is still in maintenance mode after recreate."
|
|
echo "Run:"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ maintenance:mode --off"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ status"
|
|
echo "Then rerun:"
|
|
echo " ./recreate-devprod.sh --${mode}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [[ "${manage_external_auth}" == "1" && -x "${repo_root}/scripts/sync-bundled-runtime-env-from-nextcloud.sh" ]]; then
|
|
run_quiet_step "Syncing bundled runtime credentials" \
|
|
"${repo_root}/scripts/sync-bundled-runtime-env-from-nextcloud.sh" \
|
|
--mode "${mode}" \
|
|
--env-file "${env_file}" \
|
|
--restart-services
|
|
if [[ "$?" -ne 0 ]]; then
|
|
echo "Warning: bundled external-auth credentials are still invalid after recreate."
|
|
echo "Run manually:"
|
|
echo " ./scripts/finish-initial-setup.sh --mode ${mode} --env-file ${env_file} --force-register --force-api-key"
|
|
fi
|
|
fi
|
|
|
|
echo "Refreshing Nextcloud app registration..."
|
|
cleanup_nextcloud_defaults
|
|
oidc_issuer="$(read_kv "OIDC_ISSUER" || true)"
|
|
oidc_client_id="$(read_kv "OIDC_CLIENT_ID" || true)"
|
|
oidc_client_secret="$(read_kv "OIDC_CLIENT_SECRET" || true)"
|
|
oidc_client_id="${oidc_client_id:-nextcloud-local}"
|
|
oidc_provider_config_status=0
|
|
oidc_provider_skipped=0
|
|
oidc_discovery_url=""
|
|
if [[ -n "${oidc_issuer}" ]]; then
|
|
oidc_discovery_url="${oidc_issuer%/}/.well-known/openid-configuration"
|
|
fi
|
|
|
|
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=""
|
|
qortal_sync_verify_status=0
|
|
qortal_sync_verify_skipped=0
|
|
backgroundjobs_mode_status=0
|
|
notify_push_enable_status=0
|
|
notify_push_install_status=0
|
|
notify_push_verify_status=0
|
|
repair_expensive_status=0
|
|
user_oidc_invite_patch_status=0
|
|
user_oidc_invite_patch_skipped=1
|
|
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
|
|
|
|
set +e
|
|
nextcloud_url_sync_status=0
|
|
if [[ -x "${repo_root}/scripts/ensure-nextcloud-url-config.sh" ]]; then
|
|
run_quiet_step "Syncing Nextcloud URL config" \
|
|
"${repo_root}/scripts/ensure-nextcloud-url-config.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}"
|
|
nextcloud_url_sync_status=$?
|
|
fi
|
|
nextcloud_service_auth_status=0
|
|
if [[ -x "${repo_root}/scripts/ensure-nextcloud-service-auth.sh" ]]; then
|
|
run_quiet_step "Checking broker service auth" \
|
|
"${repo_root}/scripts/ensure-nextcloud-service-auth.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" \
|
|
--repair
|
|
nextcloud_service_auth_status=$?
|
|
fi
|
|
run_quiet_step "Setting background job mode" occ_exec_quiet_retry config:system:set backgroundjobs_mode --value=cron
|
|
backgroundjobs_mode_status=$?
|
|
run_quiet_step "Enabling user_oidc" docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data app php occ app:enable --force user_oidc
|
|
enable_oidc_status=$?
|
|
install_oidc_status=0
|
|
if [[ "${enable_oidc_status}" -ne 0 ]]; then
|
|
run_quiet_step "Installing user_oidc" docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data app php occ app:install user_oidc
|
|
install_oidc_status=$?
|
|
run_quiet_step "Re-enabling user_oidc" docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data app php occ app:enable --force user_oidc
|
|
enable_oidc_status=$?
|
|
fi
|
|
if [[ -x "${repo_root}/scripts/patch-user-oidc-invite-flow.sh" ]]; then
|
|
run_quiet_step "Checking user_oidc invite bridge" \
|
|
env NEXTCLOUD_CUSTOM_APPS_PATH="${nextcloud_custom_apps_path}" \
|
|
"${repo_root}/scripts/patch-user-oidc-invite-flow.sh"
|
|
user_oidc_invite_patch_status=$?
|
|
user_oidc_invite_patch_skipped=0
|
|
fi
|
|
run_quiet_step "Enabling notify_push" occ_exec_quiet_retry app:enable --force notify_push
|
|
notify_push_enable_status=$?
|
|
if [[ "${notify_push_enable_status}" -ne 0 ]]; then
|
|
run_quiet_step "Installing notify_push" occ_exec_quiet_retry app:install notify_push
|
|
notify_push_install_status=$?
|
|
run_quiet_step "Re-enabling notify_push" occ_exec_quiet_retry app:enable --force notify_push
|
|
notify_push_enable_status=$?
|
|
fi
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data app php occ app:list >/tmp/occ_app_list_notify_push.txt 2>/dev/null
|
|
if grep -qE '^[[:space:]]+- notify_push([[:space:]:]|$)' /tmp/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/occ_app_list_notify_push.txt >/dev/null 2>&1 || true
|
|
docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data app php occ user_oidc:provider --help >/dev/null 2>&1
|
|
verify_oidc_status=$?
|
|
if [[ -n "${oidc_discovery_url}" && -n "${oidc_client_secret}" ]]; then
|
|
run_quiet_step "Configuring user_oidc provider" docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data 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
|
|
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 --user www-data app php occ config:app:get qortal_integration sc_mode 2>/dev/null | tr -d '\r' || true)"
|
|
if [[ "${sovereign_mode}" == "full_qortal" ]]; then
|
|
run_quiet_step "Clearing user_oidc login label" docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data app php occ config:system:delete user_oidc login_label
|
|
oidc_login_label_status=$?
|
|
else
|
|
run_quiet_step "Setting user_oidc login label" docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data app php occ config:system:set user_oidc login_label --value="NuQloud Login"
|
|
oidc_login_label_status=$?
|
|
fi
|
|
|
|
if [[ "${office_profile_enabled}" == "1" ]]; then
|
|
echo " Configuring Office integration..."
|
|
collabora_public_url="$(read_kv "COLLABORA_PUBLIC_URL" || true)"
|
|
occ_exec_quiet_retry app:enable --force richdocuments
|
|
office_enable_status=$?
|
|
if [[ "${office_enable_status}" -ne 0 ]]; then
|
|
occ_exec_quiet_retry app:install richdocuments
|
|
office_install_status=$?
|
|
occ_exec_quiet_retry app:enable --force richdocuments
|
|
office_enable_status=$?
|
|
fi
|
|
if [[ -n "${collabora_public_url}" ]]; then
|
|
occ_exec_quiet_retry config:app:set richdocuments wopi_url --value="${collabora_public_url}"
|
|
office_wopi_status=$?
|
|
occ_exec_quiet_retry config:app:set richdocuments public_wopi_url --value="${collabora_public_url}"
|
|
office_public_wopi_status=$?
|
|
else
|
|
office_wopi_status=1
|
|
fi
|
|
fi
|
|
|
|
if [[ "${talk_profile_enabled}" == "1" ]]; then
|
|
echo " Configuring Talk integration..."
|
|
talk_signaling_public_url="$(read_kv "TALK_SIGNALING_PUBLIC_URL" || true)"
|
|
talk_signaling_secret="$(read_kv "TALK_SIGNALING_SECRET" || true)"
|
|
talk_stun_server="$(read_kv "TALK_STUN_SERVER" || true)"
|
|
talk_turn_server="$(read_kv "TALK_TURN_SERVER" || true)"
|
|
talk_domain="$(read_kv "SIGNALING_DOMAIN" || true)"
|
|
if [[ -z "${talk_domain}" ]]; then
|
|
talk_domain="$(read_kv "TALK_DOMAIN" || true)"
|
|
fi
|
|
talk_turn_host="$(read_kv "TALK_TURN_HOST" || true)"
|
|
if [[ -z "${talk_turn_host}" ]]; then
|
|
talk_turn_host="$(read_kv "NEXTCLOUD_DOMAIN" || true)"
|
|
fi
|
|
talk_turn_port="$(read_kv "TALK_TURN_PORT" || true)"
|
|
talk_turn_port="${talk_turn_port:-3478}"
|
|
talk_turn_secret="$(read_kv "TALK_TURN_SECRET" || true)"
|
|
if [[ -z "${talk_turn_server}" ]]; then
|
|
talk_turn_server="${talk_turn_host}:${talk_turn_port}"
|
|
fi
|
|
talk_recording_url="$(read_kv "TALK_RECORDING_URL" || true)"
|
|
talk_recording_url="${talk_recording_url:-http://talk_recording:1234}"
|
|
talk_recording_secret="$(read_kv "TALK_RECORDING_SHARED_SECRET" || true)"
|
|
|
|
occ_exec_quiet_retry app:enable --force spreed
|
|
talk_enable_status=$?
|
|
if [[ "${talk_enable_status}" -ne 0 ]]; then
|
|
occ_exec_quiet_retry app:install spreed
|
|
talk_install_status=$?
|
|
occ_exec_quiet_retry app:enable --force spreed
|
|
talk_enable_status=$?
|
|
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
|
|
|
|
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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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 --user www-data 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
|
|
|
|
echo " Refreshing qortal custom app enable state..."
|
|
occ_retoggle_app qortal_integration 1
|
|
enable_qortal_status=$?
|
|
for custom_app in custom_pwa chd_admin qortal_files_bridge qortal_talk_bridge nuqloud_scrum; do
|
|
occ_retoggle_app "${custom_app}" 1 || true
|
|
done
|
|
echo "Rebuilding Nextcloud app cache..."
|
|
if [[ -x "${repo_root}/scripts/clear-nextcloud-appdata-assets.sh" ]]; then
|
|
echo " Clearing Nextcloud appdata asset caches..."
|
|
"${repo_root}/scripts/clear-nextcloud-appdata-assets.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" >/dev/null 2>&1 || true
|
|
fi
|
|
default_app_bundle_status=0
|
|
if [[ -x "${repo_root}/scripts/ensure-nextcloud-default-app-bundle.sh" ]]; then
|
|
run_quiet_step "Applying default app bundle" \
|
|
"${repo_root}/scripts/ensure-nextcloud-default-app-bundle.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}"
|
|
default_app_bundle_status=$?
|
|
fi
|
|
run_quiet_step "Running maintenance:repair" docker compose -f "${compose_file}" --env-file "${env_file}" exec -T --user www-data app php occ maintenance:repair
|
|
repair_status=$?
|
|
run_quiet_step "Running maintenance:repair --include-expensive" occ_exec_quiet_retry maintenance:repair --include-expensive
|
|
repair_expensive_status=$?
|
|
|
|
# Re-assert app enables after repair/cache refresh. Some instances briefly fail earlier
|
|
# during startup and can appear disabled after recreate.
|
|
if [[ "${office_profile_enabled}" == "1" ]]; then
|
|
occ_retoggle_app richdocuments 1
|
|
if [[ $? -ne 0 ]]; then
|
|
occ_exec_quiet_retry app:install richdocuments
|
|
office_install_status=$?
|
|
occ_exec_quiet_retry app:enable --force richdocuments
|
|
office_enable_status=$?
|
|
else
|
|
office_enable_status=0
|
|
fi
|
|
fi
|
|
if [[ "${talk_profile_enabled}" == "1" ]]; then
|
|
occ_retoggle_app spreed 1
|
|
if [[ $? -ne 0 ]]; then
|
|
occ_exec_quiet_retry app:install spreed
|
|
talk_install_status=$?
|
|
occ_exec_quiet_retry app:enable --force spreed
|
|
talk_enable_status=$?
|
|
else
|
|
talk_enable_status=0
|
|
fi
|
|
fi
|
|
|
|
# One more frontend cache clear after final app toggles to reduce stale app menu/app-page UI state.
|
|
if [[ -x "${repo_root}/scripts/clear-nextcloud-appdata-assets.sh" ]]; then
|
|
"${repo_root}/scripts/clear-nextcloud-appdata-assets.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
if [[ -x "${repo_root}/scripts/sync-public-env-urls.sh" ]]; then
|
|
run_quiet_step "Re-syncing public env URLs" \
|
|
"${repo_root}/scripts/sync-public-env-urls.sh" \
|
|
--env-file "${env_file}" \
|
|
--mode "${mode}"
|
|
public_env_resync_status=$?
|
|
fi
|
|
|
|
if [[ -x "${repo_root}/scripts/ensure-nextcloud-url-config.sh" ]]; then
|
|
run_quiet_step "Re-validating Nextcloud URL config" \
|
|
"${repo_root}/scripts/ensure-nextcloud-url-config.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}"
|
|
nextcloud_url_verify_status=$?
|
|
fi
|
|
|
|
if [[ -x "${repo_root}/scripts/ensure-custom-pwa-push-config.sh" ]]; then
|
|
run_quiet_step "Ensuring CustomPWA push config" \
|
|
"${repo_root}/scripts/ensure-custom-pwa-push-config.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}"
|
|
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)"
|
|
echo " Validating Office/Collabora configuration..."
|
|
office_verify_start_ts="$(date +%s)"
|
|
"${repo_root}/scripts/ensure-nextcloud-office-config.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" >"${office_verify_log}" 2>&1
|
|
office_verify_status=$?
|
|
office_verify_end_ts="$(date +%s)"
|
|
echo " Validating Office/Collabora configuration: status=${office_verify_status} elapsed=$((office_verify_end_ts - office_verify_start_ts))s"
|
|
if [[ "${office_verify_status}" -ne 0 && -s "${office_verify_log}" ]]; then
|
|
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)"
|
|
echo " Validating Talk backend configuration..."
|
|
talk_verify_start_ts="$(date +%s)"
|
|
"${repo_root}/scripts/ensure-nextcloud-talk-config.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" >"${talk_verify_log}" 2>&1
|
|
talk_verify_status=$?
|
|
talk_verify_end_ts="$(date +%s)"
|
|
echo " Validating Talk backend configuration: status=${talk_verify_status} elapsed=$((talk_verify_end_ts - talk_verify_start_ts))s"
|
|
if [[ "${talk_verify_status}" -ne 0 && -s "${talk_verify_log}" ]]; then
|
|
sed 's/^/ /' "${talk_verify_log}"
|
|
fi
|
|
rm -f "${talk_verify_log}" >/dev/null 2>&1 || true
|
|
talk_verify_skipped=0
|
|
fi
|
|
|
|
if [[ -x "${repo_root}/scripts/verify-qortal-integration-sync.sh" ]]; then
|
|
echo "Verifying qortal_integration app sync..."
|
|
"${repo_root}/scripts/verify-qortal-integration-sync.sh" \
|
|
--compose-file "${compose_file}" \
|
|
--env-file "${env_file}" \
|
|
--repair
|
|
qortal_sync_verify_status=$?
|
|
else
|
|
qortal_sync_verify_skipped=1
|
|
fi
|
|
set -e
|
|
occ_failure=0
|
|
if [[ "${nextcloud_url_sync_status}" -ne 0 || "${nextcloud_service_auth_status}" -ne 0 || "${enable_oidc_status}" -ne 0 || "${verify_oidc_status}" -ne 0 || "${oidc_provider_config_status}" -ne 0 || "${oidc_login_label_status}" -ne 0 || "${enable_qortal_status}" -ne 0 || "${notify_push_enable_status}" -ne 0 || "${notify_push_verify_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 [[ "${backgroundjobs_mode_status}" -ne 0 ]]; then
|
|
occ_failure=1
|
|
fi
|
|
if [[ "${qortal_sync_verify_status}" -ne 0 ]]; then
|
|
occ_failure=1
|
|
fi
|
|
if [[ "${user_oidc_invite_patch_skipped}" -eq 0 && "${user_oidc_invite_patch_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: one or more OCC refresh steps failed."
|
|
echo "Step status:"
|
|
echo " nextcloud_url_sync_status=${nextcloud_url_sync_status}"
|
|
echo " public_env_resync_status=${public_env_resync_status}"
|
|
echo " nextcloud_url_verify_status=${nextcloud_url_verify_status}"
|
|
echo " nextcloud_service_auth_status=${nextcloud_service_auth_status}"
|
|
echo " backgroundjobs_mode_status=${backgroundjobs_mode_status}"
|
|
echo " enable_oidc_status=${enable_oidc_status}"
|
|
echo " verify_oidc_status=${verify_oidc_status}"
|
|
if [[ "${user_oidc_invite_patch_skipped}" -eq 0 ]]; then
|
|
echo " user_oidc_invite_patch_status=${user_oidc_invite_patch_status}"
|
|
fi
|
|
echo " oidc_provider_config_status=${oidc_provider_config_status}"
|
|
echo " oidc_login_label_status=${oidc_login_label_status}"
|
|
echo " enable_qortal_status=${enable_qortal_status}"
|
|
echo " notify_push_enable_status=${notify_push_enable_status}"
|
|
echo " notify_push_verify_status=${notify_push_verify_status}"
|
|
echo " default_app_bundle_status=${default_app_bundle_status}"
|
|
echo " repair_status=${repair_status}"
|
|
echo " repair_expensive_status=${repair_expensive_status}"
|
|
echo " qortal_sync_verify_status=${qortal_sync_verify_status}"
|
|
if [[ "${custom_pwa_push_skipped}" -eq 0 ]]; then
|
|
echo " custom_pwa_push_status=${custom_pwa_push_status}"
|
|
fi
|
|
if [[ "${qortal_runtime_sync_skipped}" -eq 0 ]]; then
|
|
echo " qortal_runtime_sync_status=${qortal_runtime_sync_status}"
|
|
fi
|
|
if [[ "${office_profile_enabled}" == "1" ]]; then
|
|
echo " office_enable_status=${office_enable_status}"
|
|
echo " office_wopi_status=${office_wopi_status}"
|
|
echo " office_public_wopi_status=${office_public_wopi_status}"
|
|
fi
|
|
if [[ "${office_verify_skipped}" -eq 0 ]]; then
|
|
echo " office_verify_status=${office_verify_status}"
|
|
fi
|
|
if [[ "${talk_profile_enabled}" == "1" ]]; then
|
|
echo " talk_enable_status=${talk_enable_status}"
|
|
echo " talk_signaling_add_status=${talk_signaling_add_status}"
|
|
echo " talk_signaling_prune_status=${talk_signaling_prune_status}"
|
|
echo " talk_signaling_multi_count=${talk_signaling_multi_count}"
|
|
echo " talk_stun_add_status=${talk_stun_add_status}"
|
|
fi
|
|
if [[ "${turn_profile_enabled}" == "1" ]]; then
|
|
echo " talk_turn_add_status=${talk_turn_add_status}"
|
|
fi
|
|
if [[ "${recording_profile_enabled}" == "1" ]]; then
|
|
echo " talk_recording_add_status=${talk_recording_add_status}"
|
|
echo " talk_recording_command_missing=${talk_recording_command_missing}"
|
|
fi
|
|
if [[ "${talk_verify_skipped}" -eq 0 ]]; then
|
|
echo " talk_verify_status=${talk_verify_status}"
|
|
fi
|
|
echo "Run manually:"
|
|
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 --user www-data app php occ config:system:set backgroundjobs_mode --value=cron"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ app:install user_oidc"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ app:enable --force user_oidc"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ app:install notify_push"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ app:enable --force notify_push"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ app:enable --force nuqloud_scrum"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data 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 --user www-data 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 --user www-data app php occ config:system:set user_oidc login_label --value='NuQloud Login'"
|
|
if [[ "${office_profile_enabled}" == "1" ]]; then
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ app:enable --force richdocuments"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ config:app:set richdocuments wopi_url --value='$(read_kv "COLLABORA_PUBLIC_URL" || true)'"
|
|
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 --user www-data app php occ app:enable --force spreed"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ ${talk_cmd}:signaling:add '$(read_kv "TALK_SIGNALING_PUBLIC_URL" || true)' '<signaling-secret>'"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ ${talk_cmd}:signaling:list"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ ${talk_cmd}:stun:add '$(read_kv "TALK_STUN_SERVER" || true)'"
|
|
fi
|
|
if [[ "${turn_profile_enabled}" == "1" ]]; then
|
|
talk_cmd="${talk_command_prefix:-talk}"
|
|
turn_domain="$(read_kv "SIGNALING_DOMAIN" || true)"
|
|
if [[ -z "${turn_domain}" ]]; then
|
|
turn_domain="$(read_kv "TALK_DOMAIN" || true)"
|
|
fi
|
|
turn_port="$(read_kv "TALK_TURN_PORT" || true)"
|
|
turn_port="${turn_port:-3478}"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ ${talk_cmd}:turn:add turn '${turn_domain}:${turn_port}' 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 --user www-data app php occ ${recording_cmd}:recording:add '$(read_kv "TALK_RECORDING_URL" || true)' '<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 --user www-data app php occ db:add-missing-indices"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ app:disable qortal_integration"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ app:enable qortal_integration"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ app:enable --force nuqloud_scrum"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ maintenance:repair"
|
|
echo " docker compose -f ${compose_file} --env-file ${env_file} exec -T --user www-data app php occ maintenance:repair --include-expensive"
|
|
echo " ./scripts/verify-qortal-integration-sync.sh --compose-file ${compose_file} --env-file ${env_file} --repair"
|
|
if [[ "${install_oidc_status}" -ne 0 && "${enable_oidc_status}" -ne 0 ]]; then
|
|
echo " Note: app:install user_oidc fallback failed; 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 [[ "${office_verify_skipped}" -eq 0 && "${office_verify_status}" -ne 0 ]]; then
|
|
echo " Note: final Office validation failed; rerun ./scripts/ensure-nextcloud-office-config.sh directly for detailed diagnostics."
|
|
fi
|
|
if [[ "${talk_verify_skipped}" -eq 1 ]]; then
|
|
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 extra entries 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
|
|
elif [[ "${talk_verify_status}" -ne 0 ]]; then
|
|
echo " Note: final Talk validation failed; rerun ./scripts/ensure-nextcloud-talk-config.sh directly for detailed backend diagnostics."
|
|
fi
|
|
if [[ "${qortal_sync_verify_skipped}" -eq 1 ]]; then
|
|
echo " Note: qortal app sync verification was skipped because scripts/verify-qortal-integration-sync.sh is missing."
|
|
fi
|
|
fi
|
|
echo "Done."
|