470 lines
16 KiB
Bash
Executable File
470 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
|
echo "Run this installer as root (sudo)."
|
|
exit 1
|
|
fi
|
|
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
repo_root="$(cd "${script_dir}/.." && pwd)"
|
|
|
|
default_nc_path="/var/www/nextcloud"
|
|
read -r -p "Nextcloud path [${default_nc_path}]: " nc_path
|
|
nc_path="${nc_path:-$default_nc_path}"
|
|
|
|
if [[ ! -f "${nc_path}/occ" ]]; then
|
|
echo "Could not find occ at ${nc_path}/occ. Please check the Nextcloud path."
|
|
exit 1
|
|
fi
|
|
|
|
occ() {
|
|
if command -v sudo >/dev/null 2>&1; then
|
|
sudo -u www-data php "${nc_path}/occ" "$@"
|
|
else
|
|
runuser -u www-data -- php "${nc_path}/occ" "$@"
|
|
fi
|
|
}
|
|
|
|
occ_app_get() {
|
|
local app_id="${1:-}"
|
|
local key="${2:-}"
|
|
local value=""
|
|
if [[ -z "${app_id}" || -z "${key}" ]]; then
|
|
return 1
|
|
fi
|
|
value="$(occ config:app:get "${app_id}" "${key}" 2>/dev/null | tr -d '\r' || true)"
|
|
if [[ "${value}" == "false" ]]; then
|
|
value=""
|
|
fi
|
|
echo "${value}"
|
|
}
|
|
|
|
normalize_bool_01() {
|
|
local raw="${1:-}"
|
|
case "$(echo "${raw}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" in
|
|
1|true|yes|y|on)
|
|
echo "1"
|
|
;;
|
|
0|false|no|n|off)
|
|
echo "0"
|
|
;;
|
|
*)
|
|
echo ""
|
|
;;
|
|
esac
|
|
}
|
|
|
|
gen_secret() {
|
|
tr -dc 'a-zA-Z0-9' </dev/urandom | head -c 32
|
|
}
|
|
|
|
detect_nc_url() {
|
|
local url
|
|
set +e
|
|
url="$(occ config:system:get overwrite.cli.url 2>/dev/null)"
|
|
if [[ -z "$url" ]]; then
|
|
url="$(occ config:system:get trusted_domains 0 2>/dev/null)"
|
|
if [[ -n "$url" && "$url" != http* ]]; then
|
|
url="https://${url}"
|
|
fi
|
|
fi
|
|
set -e
|
|
echo "$url"
|
|
}
|
|
|
|
nc_url="$(detect_nc_url)"
|
|
read -r -p "Nextcloud URL [${nc_url:-https://cloud.example.com}]: " nc_url_input
|
|
nc_url="${nc_url_input:-$nc_url}"
|
|
if [[ -z "$nc_url" ]]; then
|
|
echo "Nextcloud URL is required."
|
|
exit 1
|
|
fi
|
|
|
|
nc_host="${nc_url#*://}"
|
|
nc_host="${nc_host%%/*}"
|
|
base_domain="$nc_host"
|
|
if [[ "$nc_host" == *.* ]]; then
|
|
base_domain="${nc_host#*.}"
|
|
fi
|
|
|
|
default_broker_host="qortalbroker.${base_domain}"
|
|
read -r -p "Broker hostname [${default_broker_host}]: " broker_host
|
|
broker_host="${broker_host:-$default_broker_host}"
|
|
|
|
read -r -p "Broker external URL [https://${broker_host}]: " broker_url
|
|
broker_url="${broker_url:-https://${broker_host}}"
|
|
|
|
read -r -p "Nextcloud service user [nuqloud_oidc_service]: " nc_service_user
|
|
nc_service_user="${nc_service_user:-nuqloud_oidc_service}"
|
|
default_nc_service_password="$(gen_secret)"
|
|
read -r -p "Nextcloud service password [${default_nc_service_password}]: " nc_service_password
|
|
nc_service_password="${nc_service_password:-$default_nc_service_password}"
|
|
|
|
read -r -p "External Auth base URL [http://127.0.0.1:3191]: " ext_auth_url
|
|
ext_auth_url="${ext_auth_url:-http://127.0.0.1:3191}"
|
|
read -r -p "External Auth app ID: " ext_auth_app_id
|
|
read -r -p "External Auth app secret: " ext_auth_app_secret
|
|
|
|
if [[ ! "${ext_auth_url}" =~ ^https?://(127\.0\.0\.1|localhost) ]]; then
|
|
echo "Warning: External Auth is not local. Ensure secondary wallet encryption is enabled."
|
|
fi
|
|
|
|
default_db_pass="$(gen_secret)"
|
|
read -r -p "Broker DB password [${default_db_pass}]: " broker_db_password
|
|
broker_db_password="${broker_db_password:-$default_db_pass}"
|
|
|
|
default_oidc_secret="$(gen_secret)"
|
|
read -r -p "OIDC client secret [${default_oidc_secret}]: " oidc_secret
|
|
oidc_secret="${oidc_secret:-$default_oidc_secret}"
|
|
|
|
default_broker_internal_token="$(gen_secret)"
|
|
read -r -p "Broker internal API token [${default_broker_internal_token}]: " broker_internal_token
|
|
broker_internal_token="${broker_internal_token:-$default_broker_internal_token}"
|
|
|
|
libresign_default_cn() {
|
|
if [[ -n "${LIBRESIGN_CERT_CN:-}" ]]; then
|
|
echo "${LIBRESIGN_CERT_CN}"
|
|
return 0
|
|
fi
|
|
if [[ -n "${nc_host:-}" ]]; then
|
|
echo "NuQloud ${nc_host} Root CA"
|
|
return 0
|
|
fi
|
|
echo "NuQloud Root CA"
|
|
}
|
|
|
|
configure_libresign_vm() {
|
|
local auto_setup="1"
|
|
local auto_install="1"
|
|
local java_path=""
|
|
local jsignpdf_path=""
|
|
local pdftk_path=""
|
|
local root_cert=""
|
|
local engine=""
|
|
local cn=""
|
|
local org=""
|
|
local country=""
|
|
local state=""
|
|
local locality=""
|
|
local ou_raw=""
|
|
local status_local=0
|
|
local occ_args=()
|
|
local ou_value=""
|
|
|
|
auto_setup="$(normalize_bool_01 "${LIBRESIGN_AUTO_SETUP:-1}")"
|
|
if [[ -z "${auto_setup}" ]]; then
|
|
auto_setup="1"
|
|
fi
|
|
auto_install="$(normalize_bool_01 "${LIBRESIGN_AUTO_INSTALL_BINARIES:-1}")"
|
|
if [[ -z "${auto_install}" ]]; then
|
|
auto_install="1"
|
|
fi
|
|
|
|
java_path="$(occ_app_get "libresign" "java_path")"
|
|
jsignpdf_path="$(occ_app_get "libresign" "jsignpdf_jar_path")"
|
|
pdftk_path="$(occ_app_get "libresign" "pdftk_path")"
|
|
root_cert="$(occ_app_get "libresign" "rootCert")"
|
|
|
|
if [[ "${auto_install}" == "1" && -z "${java_path}" ]]; then
|
|
occ libresign:install --java >/dev/null 2>&1 || status_local=1
|
|
java_path="$(occ_app_get "libresign" "java_path")"
|
|
fi
|
|
if [[ "${auto_install}" == "1" && -z "${jsignpdf_path}" ]]; then
|
|
occ libresign:install --jsignpdf >/dev/null 2>&1 || status_local=1
|
|
jsignpdf_path="$(occ_app_get "libresign" "jsignpdf_jar_path")"
|
|
fi
|
|
if [[ "${auto_install}" == "1" && -z "${pdftk_path}" ]]; then
|
|
occ libresign:install --pdftk >/dev/null 2>&1 || status_local=1
|
|
pdftk_path="$(occ_app_get "libresign" "pdftk_path")"
|
|
fi
|
|
|
|
if [[ "${auto_setup}" == "1" && -z "${root_cert}" ]]; then
|
|
engine="$(echo "${LIBRESIGN_CERT_ENGINE:-openssl}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
|
if [[ "${engine}" != "cfssl" ]]; then
|
|
engine="openssl"
|
|
fi
|
|
cn="$(libresign_default_cn)"
|
|
org="${LIBRESIGN_CERT_O:-${NUQLOUD_BILLING_COMPANY_NAME:-NuQloud}}"
|
|
country="${LIBRESIGN_CERT_C:-}"
|
|
state="${LIBRESIGN_CERT_ST:-}"
|
|
locality="${LIBRESIGN_CERT_L:-}"
|
|
ou_raw="${LIBRESIGN_CERT_OU:-Document Signing}"
|
|
occ_args=("libresign:configure:${engine}" "--cn=${cn}")
|
|
if [[ -n "${org}" ]]; then
|
|
occ_args+=("--o=${org}")
|
|
fi
|
|
if [[ -n "${country}" ]]; then
|
|
occ_args+=("--c=${country}")
|
|
fi
|
|
if [[ -n "${state}" ]]; then
|
|
occ_args+=("--st=${state}")
|
|
fi
|
|
if [[ -n "${locality}" ]]; then
|
|
occ_args+=("--l=${locality}")
|
|
fi
|
|
IFS=',;' read -r -a ou_values <<< "${ou_raw}"
|
|
for ou_value in "${ou_values[@]}"; do
|
|
ou_value="$(echo "${ou_value}" | xargs)"
|
|
if [[ -n "${ou_value}" ]]; then
|
|
occ_args+=("--ou=${ou_value}")
|
|
fi
|
|
done
|
|
occ "${occ_args[@]}" >/dev/null 2>&1 || status_local=1
|
|
root_cert="$(occ_app_get "libresign" "rootCert")"
|
|
fi
|
|
|
|
if [[ "${auto_install}" == "1" ]]; then
|
|
if [[ -z "${java_path}" || -z "${jsignpdf_path}" || -z "${pdftk_path}" ]]; then
|
|
status_local=1
|
|
fi
|
|
fi
|
|
if [[ "${auto_setup}" == "1" && -z "${root_cert}" ]]; then
|
|
status_local=1
|
|
fi
|
|
|
|
return "${status_local}"
|
|
}
|
|
|
|
broker_dir="/opt/qortal-broker"
|
|
mkdir -p "${broker_dir}"
|
|
|
|
compose_src="${repo_root}/deploy/docker-compose.broker.yml"
|
|
compose_dst="${broker_dir}/docker-compose.yml"
|
|
cp "${compose_src}" "${compose_dst}"
|
|
|
|
cat > "${broker_dir}/.env" <<EOF
|
|
BROKER_BUILD_CONTEXT=${repo_root}/services/qortal-oidc-broker
|
|
BROKER_PORT=3000
|
|
BROKER_DB_NAME=qortal_broker
|
|
BROKER_DB_USER=qortal_broker
|
|
BROKER_DB_PASSWORD=${broker_db_password}
|
|
BROKER_DATABASE_URL=postgresql://qortal_broker:${broker_db_password}@broker_db:5432/qortal_broker
|
|
|
|
NEXTCLOUD_BASE_URL=${nc_url}
|
|
NEXTCLOUD_PUBLIC_URL=${nc_url}
|
|
NEXTCLOUD_SERVICE_USER=${nc_service_user}
|
|
NEXTCLOUD_SERVICE_PASSWORD=${nc_service_password}
|
|
|
|
QORTAL_EXTERNAL_AUTH_BASE_URL=${ext_auth_url}
|
|
QORTAL_EXTERNAL_AUTH_APP_ID=${ext_auth_app_id}
|
|
QORTAL_EXTERNAL_AUTH_APP_SECRET=${ext_auth_app_secret}
|
|
BROKER_INTERNAL_API_TOKEN=${broker_internal_token}
|
|
# Optional comma-separated browser origins for CORS.
|
|
BROKER_CORS_ALLOWED_ORIGINS=${nc_url}
|
|
|
|
OIDC_ISSUER=${broker_url}
|
|
OIDC_CLIENT_ID=nextcloud-local
|
|
OIDC_CLIENT_SECRET=${oidc_secret}
|
|
OIDC_REDIRECT_URI_ALLOWLIST=${nc_url}/apps/user_oidc/code
|
|
OIDC_POLICY_MODE=auto_provision
|
|
OIDC_AUTO_PROVISION_GUARD=invite_or_allowlist
|
|
OIDC_REQUIRE_EMAIL_FOR_NEW_ACCOUNT=true
|
|
OIDC_INVITE_TTL_SECONDS=604800
|
|
OIDC_AUTH_REQUEST_TTL_SECONDS=600
|
|
OIDC_AUTH_CODE_TTL_SECONDS=120
|
|
OIDC_ACCESS_TOKEN_TTL_SECONDS=600
|
|
OIDC_ID_TOKEN_TTL_SECONDS=600
|
|
EOF
|
|
|
|
echo "Copying Nextcloud app..."
|
|
if [[ -x "${repo_root}/scripts/ensure-user-oidc-app.sh" ]]; then
|
|
"${repo_root}/scripts/ensure-user-oidc-app.sh"
|
|
fi
|
|
install_custom_pwa="${INSTALL_CUSTOM_PWA:-1}"
|
|
install_custom_pwa="$(echo "${install_custom_pwa}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
|
custom_pwa_enabled="1"
|
|
case "${install_custom_pwa}" in
|
|
0|false|no|n|off)
|
|
custom_pwa_enabled="0"
|
|
;;
|
|
esac
|
|
mkdir -p "${nc_path}/custom_apps"
|
|
app_dirs=(
|
|
"qortal_integration"
|
|
"chd_admin"
|
|
"qortal_files_bridge"
|
|
"qortal_talk_bridge"
|
|
"nuqloud_scrum"
|
|
)
|
|
if [[ "${custom_pwa_enabled}" == "1" ]]; then
|
|
app_dirs=("qortal_integration" "custom_pwa" "chd_admin" "qortal_files_bridge" "qortal_talk_bridge" "nuqloud_scrum")
|
|
fi
|
|
if command -v rsync >/dev/null 2>&1; then
|
|
for app_name in "${app_dirs[@]}"; do
|
|
if [[ -d "${repo_root}/nextcloud/custom_apps/${app_name}" ]]; then
|
|
rsync -a "${repo_root}/nextcloud/custom_apps/${app_name}/" "${nc_path}/custom_apps/${app_name}/"
|
|
fi
|
|
done
|
|
if [[ -d "${repo_root}/nextcloud/custom_apps/user_oidc" ]]; then
|
|
rsync -a "${repo_root}/nextcloud/custom_apps/user_oidc/" "${nc_path}/custom_apps/user_oidc/"
|
|
fi
|
|
else
|
|
for app_name in "${app_dirs[@]}"; do
|
|
if [[ -d "${repo_root}/nextcloud/custom_apps/${app_name}" ]]; then
|
|
rm -rf "${nc_path}/custom_apps/${app_name}"
|
|
cp -R "${repo_root}/nextcloud/custom_apps/${app_name}" "${nc_path}/custom_apps/${app_name}"
|
|
fi
|
|
done
|
|
if [[ -d "${repo_root}/nextcloud/custom_apps/user_oidc" ]]; then
|
|
rm -rf "${nc_path}/custom_apps/user_oidc"
|
|
cp -R "${repo_root}/nextcloud/custom_apps/user_oidc" "${nc_path}/custom_apps/user_oidc"
|
|
fi
|
|
fi
|
|
|
|
echo "Enabling Nextcloud apps..."
|
|
set +e
|
|
install_libresign="$(normalize_bool_01 "${INSTALL_LIBRESIGN:-1}")"
|
|
if [[ -z "${install_libresign}" ]]; then
|
|
install_libresign="1"
|
|
fi
|
|
enable_list=(chd_admin qortal_files_bridge qortal_talk_bridge nuqloud_scrum)
|
|
if [[ "${custom_pwa_enabled}" == "1" ]]; then
|
|
enable_list=(custom_pwa "${enable_list[@]}")
|
|
fi
|
|
for app_name in "${enable_list[@]}"; do
|
|
occ app:enable "${app_name}" >/dev/null 2>&1
|
|
done
|
|
occ app:enable --force user_oidc >/dev/null 2>&1
|
|
enable_oidc_status=$?
|
|
install_oidc_status=0
|
|
if [[ "${enable_oidc_status}" -ne 0 ]]; then
|
|
occ app:install user_oidc >/dev/null 2>&1
|
|
install_oidc_status=$?
|
|
occ app:enable --force user_oidc >/dev/null 2>&1
|
|
enable_oidc_status=$?
|
|
fi
|
|
occ app:enable qortal_integration >/dev/null 2>&1
|
|
enable_qortal_status=$?
|
|
occ user_oidc:provider --help >/dev/null 2>&1
|
|
verify_oidc_status=$?
|
|
libresign_enable_status=0
|
|
libresign_install_status=0
|
|
libresign_setup_status=0
|
|
if [[ "${install_libresign}" == "1" ]]; then
|
|
occ app:enable --force libresign >/dev/null 2>&1
|
|
libresign_enable_status=$?
|
|
if [[ "${libresign_enable_status}" -ne 0 ]]; then
|
|
occ app:install libresign >/dev/null 2>&1
|
|
libresign_install_status=$?
|
|
occ app:enable --force libresign >/dev/null 2>&1
|
|
libresign_enable_status=$?
|
|
fi
|
|
if [[ "${libresign_enable_status}" -eq 0 ]]; then
|
|
configure_libresign_vm
|
|
libresign_setup_status=$?
|
|
fi
|
|
fi
|
|
occ config:app:set qortal_integration broker_internal_api_token --value="${broker_internal_token}" >/dev/null 2>&1
|
|
libresign_failure=0
|
|
if [[ "${install_libresign}" == "1" && ( "${libresign_enable_status}" -ne 0 || "${libresign_setup_status}" -ne 0 ) ]]; then
|
|
libresign_failure=1
|
|
fi
|
|
set -e
|
|
if [[ "${enable_qortal_status}" -ne 0 || "${enable_oidc_status}" -ne 0 || "${verify_oidc_status}" -ne 0 || "${libresign_failure}" -ne 0 ]]; then
|
|
echo "Warning: one or more app enable checks failed."
|
|
echo "Run manually:"
|
|
echo " sudo -u www-data php ${nc_path}/occ app:enable qortal_integration"
|
|
echo " sudo -u www-data php ${nc_path}/occ app:install user_oidc"
|
|
echo " sudo -u www-data php ${nc_path}/occ app:enable --force user_oidc"
|
|
echo " sudo -u www-data php ${nc_path}/occ user_oidc:provider --help"
|
|
if [[ "${install_libresign}" == "1" ]]; then
|
|
echo " sudo -u www-data php ${nc_path}/occ app:install libresign"
|
|
echo " sudo -u www-data php ${nc_path}/occ app:enable --force libresign"
|
|
echo " sudo -u www-data php ${nc_path}/occ libresign:install --all"
|
|
echo " sudo -u www-data php ${nc_path}/occ libresign:configure:openssl --cn=\"$(libresign_default_cn)\" --o=\"${LIBRESIGN_CERT_O:-${NUQLOUD_BILLING_COMPANY_NAME:-NuQloud}}\" --ou=\"${LIBRESIGN_CERT_OU:-Document Signing}\""
|
|
fi
|
|
if [[ "${install_oidc_status}" -ne 0 && "${enable_oidc_status}" -ne 0 ]]; then
|
|
echo " Note: app:install user_oidc fallback failed; check appstore connectivity or custom_apps path."
|
|
fi
|
|
if [[ "${install_libresign}" == "1" && "${libresign_install_status}" -ne 0 && "${libresign_enable_status}" -ne 0 ]]; then
|
|
echo " Note: app:install libresign fallback failed; check appstore connectivity."
|
|
fi
|
|
fi
|
|
|
|
echo "Configuring user_oidc provider..."
|
|
set +e
|
|
occ user_oidc:provider qortal \
|
|
-c nextcloud-local \
|
|
-s "${oidc_secret}" \
|
|
-d "${broker_url}/.well-known/openid-configuration" \
|
|
--scope="openid profile email" \
|
|
--mapping-uid=sub \
|
|
--mapping-display-name=name \
|
|
--mapping-email=email
|
|
provider_config_status=$?
|
|
oidc_login_label_status=0
|
|
sovereign_mode="$(occ config:app:get qortal_integration sc_mode 2>/dev/null | tr -d '\r' || true)"
|
|
if [[ "${sovereign_mode}" == "full_qortal" ]]; then
|
|
occ config:system:delete user_oidc login_label >/dev/null 2>&1
|
|
oidc_login_label_status=$?
|
|
else
|
|
occ config:system:set user_oidc login_label --value="Create Account / Login" >/dev/null 2>&1
|
|
oidc_login_label_status=$?
|
|
fi
|
|
set -e
|
|
if [[ "${provider_config_status}" -ne 0 ]]; then
|
|
echo "Warning: failed to configure user_oidc provider automatically."
|
|
echo "Run manually:"
|
|
echo " sudo -u www-data php ${nc_path}/occ user_oidc:provider qortal -c nextcloud-local -s '<client-secret>' -d ${broker_url}/.well-known/openid-configuration --scope='openid profile email' --mapping-uid=sub --mapping-display-name=name --mapping-email=email"
|
|
fi
|
|
if [[ "${oidc_login_label_status}" -ne 0 ]]; then
|
|
echo "Warning: failed to set user_oidc login label."
|
|
echo "Run manually:"
|
|
echo " sudo -u www-data php ${nc_path}/occ config:system:set user_oidc login_label --value='Create Account / Login'"
|
|
fi
|
|
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo "Docker is required but not installed. Install Docker and re-run docker compose manually."
|
|
exit 1
|
|
fi
|
|
|
|
if docker compose version >/dev/null 2>&1; then
|
|
docker_compose="docker compose"
|
|
elif command -v docker-compose >/dev/null 2>&1; then
|
|
docker_compose="docker-compose"
|
|
else
|
|
echo "Docker Compose not found."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Starting broker containers..."
|
|
${docker_compose} -f "${compose_dst}" --env-file "${broker_dir}/.env" up -d
|
|
|
|
echo "Configuring Apache reverse proxy..."
|
|
if ! a2enmod proxy proxy_http headers ssl >/dev/null 2>&1; then
|
|
echo "Warning: failed to enable Apache modules (proxy/proxy_http/headers/ssl)."
|
|
fi
|
|
|
|
apache_site="/etc/apache2/sites-available/qortalbroker.conf"
|
|
cat > "${apache_site}" <<EOF
|
|
<VirtualHost *:80>
|
|
ServerName ${broker_host}
|
|
ProxyPreserveHost On
|
|
ProxyPass / http://127.0.0.1:3000/
|
|
ProxyPassReverse / http://127.0.0.1:3000/
|
|
RequestHeader set X-Forwarded-Proto "https"
|
|
ErrorLog \${APACHE_LOG_DIR}/qortalbroker_error.log
|
|
CustomLog \${APACHE_LOG_DIR}/qortalbroker_access.log combined
|
|
</VirtualHost>
|
|
EOF
|
|
|
|
a2ensite qortalbroker.conf >/dev/null 2>&1 || true
|
|
systemctl reload apache2 || true
|
|
|
|
read -r -p "Run certbot for ${broker_host}? (y/N): " run_certbot
|
|
if [[ "${run_certbot}" == "y" || "${run_certbot}" == "Y" ]]; then
|
|
read -r -p "Let's Encrypt email: " le_email
|
|
if command -v certbot >/dev/null 2>&1; then
|
|
certbot --apache -d "${broker_host}" -m "${le_email}" --agree-tos --redirect --non-interactive
|
|
else
|
|
echo "certbot is not installed. Install it and rerun manually."
|
|
fi
|
|
fi
|
|
|
|
echo "Install complete."
|
|
echo "Broker URL: ${broker_url}"
|
|
echo "Nextcloud URL: ${nc_url}"
|
|
echo "OIDC provider configured with client_id=nextcloud-local"
|
|
echo "If login fails, verify DNS for ${broker_host} and Apache proxy."
|