Files
Qortal-Nextcloud-Integration/scripts/setup-shared-host-global-caddy.sh

319 lines
8.4 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)"
global_caddy_dir="/srv/global-caddy"
container_name="global_caddy"
upstream_host="host.docker.internal"
bootstrap_mode="auto"
public_https_port="443"
instance_dirs=()
usage() {
cat <<'USAGE'
Usage: ./scripts/setup-shared-host-global-caddy.sh [options]
Configures one shared/global Caddy container for multiple repo-based cloud instances
running the no-SSL stack on the same host.
Options:
--instance-dir <path> Cloud repo directory to configure (repeatable)
--global-caddy-dir <path> Shared Caddy config dir (default: /srv/global-caddy)
--container-name <name> Shared Caddy container name (default: global_caddy)
--upstream-host <host> Upstream host for global Caddy (default: host.docker.internal)
--public-https-port <n> Public HTTPS port served by shared Caddy (default: 443)
--bootstrap Force bootstrap/start behavior for the shared Caddy target
--no-bootstrap Assume shared Caddy already exists
-h, --help Show this help
If no --instance-dir values are supplied, the script prompts for them interactively.
USAGE
}
resolve_path() {
local raw_path="$1"
if [[ "${raw_path}" == /* ]]; then
echo "${raw_path}"
else
echo "${PWD}/${raw_path}"
fi
}
read_kv() {
local env_file="$1"
local key="$2"
local line
line="$(grep -m1 -E "^${key}=" "${env_file}" || true)"
if [[ -z "${line}" ]]; then
return 1
fi
echo "${line#*=}"
}
set_kv() {
local env_file="$1"
local key="$2"
local value="$3"
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
printf '\n%s=%s\n' "${key}" "${value}" >> "${env_file}"
fi
}
trim_csv() {
local raw="${1:-}"
local cleaned
cleaned="$(echo "${raw}" | tr -d '[:space:]')"
cleaned="${cleaned#,}"
cleaned="${cleaned%,}"
echo "${cleaned}"
}
profile_enabled() {
local profiles="$1"
local name="$2"
[[ ",${profiles}," == *",${name},"* ]]
}
prompt_instance_dirs() {
local answer=""
echo "Enter cloud repo directories one per line. Leave blank when finished."
while true; do
read -r -p "Instance repo directory: " answer || true
answer="${answer//[$'\r\n']}"
if [[ -z "${answer}" ]]; then
break
fi
instance_dirs+=("$(resolve_path "${answer}")")
done
}
run_in_repo() {
local repo_dir="$1"
shift
(cd "${repo_dir}" && "$@")
}
configure_instance_env() {
local repo_dir="$1"
local env_file="${repo_dir}/.env.devprod"
local profiles gateway_url
profiles="$(trim_csv "$(read_kv "${env_file}" "COMPOSE_PROFILES" || true)")"
set_kv "${env_file}" "EXTERNAL_PROXY_MODE" "global-caddy"
set_kv "${env_file}" "PUBLIC_HTTPS_PORT" "${public_https_port}"
set_kv "${env_file}" "DEVPROD_HTTP_BIND_HOST" "0.0.0.0"
set_kv "${env_file}" "DEVPROD_BROKER_BIND_HOST" "0.0.0.0"
if profile_enabled "${profiles}" "office"; then
set_kv "${env_file}" "COLLABORA_BIND_HOST" "0.0.0.0"
fi
if profile_enabled "${profiles}" "recording"; then
set_kv "${env_file}" "TALK_RECORDING_BIND_HOST" "0.0.0.0"
fi
gateway_url="$(read_kv "${env_file}" "QORTAL_PUBLIC_GATEWAY_URL" || true)"
if [[ -z "${gateway_url}" ]]; then
gateway_url="$(read_kv "${env_file}" "QORTAL_GATEWAY_PUBLIC_URL" || true)"
fi
if [[ -z "${gateway_url}" ]]; then
gateway_url="$(read_kv "${env_file}" "QORTAL_GATEWAY_URL" || true)"
fi
if [[ -z "${gateway_url}" && -t 0 ]]; then
read -r -p "Optional public Qortal gateway URL for ${repo_dir} (blank to skip): " gateway_url || true
gateway_url="${gateway_url//[$'\r\n']}"
if [[ -n "${gateway_url}" ]]; then
set_kv "${env_file}" "QORTAL_PUBLIC_GATEWAY_URL" "${gateway_url}"
fi
fi
if [[ -n "${gateway_url}" ]]; then
set_kv "${env_file}" "QORTAL_NODE_GATEWAY_BIND_HOST" "0.0.0.0"
fi
}
ensure_instance_layout() {
local repo_dir="$1"
local required=(
".env.devprod"
"stop-devprod.sh"
"recreate-devprod.sh"
"scripts/register-global-caddy-proxy.sh"
"scripts/ensure-nextcloud-url-config.sh"
)
local path
if [[ ! -d "${repo_dir}" ]]; then
echo "Instance repo directory not found: ${repo_dir}"
exit 1
fi
for path in "${required[@]}"; do
if [[ ! -e "${repo_dir}/${path}" ]]; then
echo "Missing required file in ${repo_dir}: ${path}"
exit 1
fi
done
}
while [[ $# -gt 0 ]]; do
case "$1" in
--instance-dir)
instance_dirs+=("$(resolve_path "${2:-}")")
shift 2
;;
--instance-dir=*)
instance_dirs+=("$(resolve_path "${1#*=}")")
shift
;;
--global-caddy-dir)
global_caddy_dir="$(resolve_path "${2:-}")"
shift 2
;;
--global-caddy-dir=*)
global_caddy_dir="$(resolve_path "${1#*=}")"
shift
;;
--container-name)
container_name="${2:-}"
shift 2
;;
--container-name=*)
container_name="${1#*=}"
shift
;;
--upstream-host)
upstream_host="${2:-}"
shift 2
;;
--upstream-host=*)
upstream_host="${1#*=}"
shift
;;
--public-https-port)
public_https_port="${2:-}"
shift 2
;;
--public-https-port=*)
public_https_port="${1#*=}"
shift
;;
--bootstrap)
bootstrap_mode="1"
shift
;;
--no-bootstrap)
bootstrap_mode="0"
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1"
usage
exit 1
;;
esac
done
if [[ "${#instance_dirs[@]}" -eq 0 ]]; then
if [[ -t 0 ]]; then
prompt_instance_dirs
fi
fi
if [[ "${#instance_dirs[@]}" -eq 0 ]]; then
echo "No instance directories provided."
usage
exit 1
fi
if [[ "${bootstrap_mode}" == "auto" ]]; then
if [[ -f "${global_caddy_dir}/docker-compose.yml" ]]; then
bootstrap_mode="0"
else
bootstrap_mode="1"
fi
fi
echo "Shared Caddy target: ${global_caddy_dir}"
echo "Shared Caddy container: ${container_name}"
echo "Upstream host: ${upstream_host}"
echo "Public HTTPS port: ${public_https_port}"
echo "Bootstrap shared Caddy: ${bootstrap_mode}"
echo
for repo_dir in "${instance_dirs[@]}"; do
ensure_instance_layout "${repo_dir}"
echo "Configuring instance: ${repo_dir}"
configure_instance_env "${repo_dir}"
echo " Stopping bundled SSL stack if present..."
run_in_repo "${repo_dir}" ./stop-devprod.sh --ssl || true
echo " Recreating no-SSL stack..."
run_in_repo "${repo_dir}" ./recreate-devprod.sh --nossl
echo " Registering instance in shared Caddy..."
if [[ "${bootstrap_mode}" == "1" ]]; then
run_in_repo "${repo_dir}" ./scripts/register-global-caddy-proxy.sh \
--env-file .env.devprod \
--target-dir "${global_caddy_dir}" \
--upstream-host "${upstream_host}" \
--bootstrap \
--start \
--reload \
--container-name "${container_name}"
bootstrap_mode="0"
else
run_in_repo "${repo_dir}" ./scripts/register-global-caddy-proxy.sh \
--env-file .env.devprod \
--target-dir "${global_caddy_dir}" \
--upstream-host "${upstream_host}" \
--reload \
--container-name "${container_name}"
fi
echo " Syncing Nextcloud URL/proxy config..."
run_in_repo "${repo_dir}" ./scripts/ensure-nextcloud-url-config.sh \
--compose-file docker-compose.devprod.nossl.yml \
--env-file .env.devprod
if [[ -x "${repo_dir}/scripts/ensure-qortal-integration-runtime-config.sh" ]]; then
echo " Syncing qortal_integration runtime config..."
run_in_repo "${repo_dir}" ./scripts/ensure-qortal-integration-runtime-config.sh \
--compose-file docker-compose.devprod.nossl.yml \
--env-file .env.devprod
fi
if [[ -x "${repo_dir}/scripts/ensure-nextcloud-office-config.sh" ]]; then
echo " Validating Office/Collabora configuration..."
run_in_repo "${repo_dir}" ./scripts/ensure-nextcloud-office-config.sh \
--compose-file docker-compose.devprod.nossl.yml \
--env-file .env.devprod
fi
if [[ -x "${repo_dir}/scripts/ensure-nextcloud-talk-config.sh" ]]; then
echo " Validating Talk HPB/recording configuration..."
run_in_repo "${repo_dir}" ./scripts/ensure-nextcloud-talk-config.sh \
--compose-file docker-compose.devprod.nossl.yml \
--env-file .env.devprod
fi
echo " Completed: ${repo_dir}"
echo
done
echo "Shared-host global Caddy setup complete."