#!/usr/bin/env bash set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd "${script_dir}/.." && pwd)" source "${script_dir}/lib-compose-project.sh" compose_file="${repo_root}/docker-compose.devprod.yml" env_file="${repo_root}/.env.devprod" skip_indices="0" skip_bundle="0" usage() { cat < Compose file to use (default: docker-compose.devprod.yml) --env-file Env file to use (default: .env.devprod) --skip-indices Do not run occ db:add-missing-indices --skip-bundle Do not install/enable the default app bundle -h, --help Show this help USAGE } read_env_value() { local key="$1" local default_value="${2:-}" local value="" if command -v rg >/dev/null 2>&1; then value="$(rg -m1 "^${key}=" "${env_file}" | cut -d= -f2- || true)" else value="$(grep -m1 -E "^${key}=" "${env_file}" | cut -d= -f2- || true)" fi if [[ -z "${value}" ]]; then echo "${default_value}" return 0 fi if [[ "${value}" =~ ^\".*\"$ ]]; then value="${value:1:${#value}-2}" elif [[ "${value}" =~ ^\'.*\'$ ]]; then value="${value:1:${#value}-2}" 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 } trim_csv() { local raw="${1:-}" echo "${raw}" \ | tr '\n' ',' \ | sed -E 's/[[:space:]]*,[[:space:]]*/,/g; s/^,+//; s/,+$//; s/,+/,/g' } 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}") 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}") 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}") echo "Using sudo for docker commands." return 0 fi echo "Unable to access docker, even with sudo." exit 1 } docker() { "${DOCKER_CMD[@]}" "$@" } compose_cmd() { docker compose -f "${compose_file}" --env-file "${env_file}" "$@" } ensure_app_enabled() { local app_id="${1:-}" if [[ -z "${app_id}" ]]; then return 1 fi compose_cmd exec -T --user www-data app php occ app:enable --force "${app_id}" >/dev/null 2>&1 if [[ $? -eq 0 ]]; then return 0 fi compose_cmd exec -T --user www-data app php occ app:install "${app_id}" >/dev/null 2>&1 if [[ $? -ne 0 ]]; then return 1 fi compose_cmd exec -T --user www-data app php occ app:enable --force "${app_id}" >/dev/null 2>&1 } occ_app_get() { local app_id="${1:-}" local key="${2:-}" local value="" if [[ -z "${app_id}" || -z "${key}" ]]; then return 1 fi value="$(compose_cmd exec -T --user www-data app php occ config:app:get "${app_id}" "${key}" 2>/dev/null | tr -d '\r' || true)" if [[ "${value}" == "false" ]]; then value="" fi echo "${value}" } is_app_enabled() { local app_id="${1:-}" local app_list_file="" if [[ -z "${app_id}" ]]; then return 1 fi app_list_file="$(mktemp)" if ! compose_cmd exec -T --user www-data app php occ app:list >"${app_list_file}" 2>/dev/null; then rm -f "${app_list_file}" >/dev/null 2>&1 || true return 1 fi if grep -qE "^[[:space:]]+- ${app_id}([[:space:]:]|$)" "${app_list_file}"; then rm -f "${app_list_file}" >/dev/null 2>&1 || true return 0 fi rm -f "${app_list_file}" >/dev/null 2>&1 || true return 1 } libresign_default_cn() { local cn="" local domain="" cn="$(read_env_value "LIBRESIGN_CERT_CN" "")" if [[ -n "${cn}" ]]; then echo "${cn}" return 0 fi domain="$(read_env_value "NEXTCLOUD_DOMAIN" "")" if [[ -z "${domain}" ]]; then domain="$(read_env_value "NEXTCLOUD_TRUSTED_DOMAINS" "")" domain="${domain%%,*}" domain="${domain%% *}" fi if [[ -n "${domain}" ]]; then echo "NuQloud ${domain} Root CA" return 0 fi echo "NuQloud Root CA" } ensure_libresign_setup() { local auto_setup_raw="" local auto_setup="1" local auto_install_raw="" 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="" if ! is_app_enabled "libresign"; then return 0 fi auto_setup_raw="$(read_env_value "LIBRESIGN_AUTO_SETUP" "1")" auto_setup="$(normalize_bool_01 "${auto_setup_raw}")" if [[ -z "${auto_setup}" ]]; then auto_setup="1" fi auto_install_raw="$(read_env_value "LIBRESIGN_AUTO_INSTALL_BINARIES" "1")" auto_install="$(normalize_bool_01 "${auto_install_raw}")" 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 if ! compose_cmd exec -T --user www-data app php occ libresign:install --java >/dev/null 2>&1; then echo "Warning: could not install LibreSign Java runtime." status_local=1 fi java_path="$(occ_app_get "libresign" "java_path")" fi if [[ "${auto_install}" == "1" && -z "${jsignpdf_path}" ]]; then if ! compose_cmd exec -T --user www-data app php occ libresign:install --jsignpdf >/dev/null 2>&1; then echo "Warning: could not install LibreSign JSignPdf runtime." status_local=1 fi jsignpdf_path="$(occ_app_get "libresign" "jsignpdf_jar_path")" fi if [[ "${auto_install}" == "1" && -z "${pdftk_path}" ]]; then if ! compose_cmd exec -T --user www-data app php occ libresign:install --pdftk >/dev/null 2>&1; then echo "Warning: could not install LibreSign PDFtk runtime." status_local=1 fi pdftk_path="$(occ_app_get "libresign" "pdftk_path")" fi if [[ "${auto_setup}" == "1" && -z "${root_cert}" ]]; then engine="$(echo "$(read_env_value "LIBRESIGN_CERT_ENGINE" "openssl")" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" if [[ "${engine}" != "cfssl" ]]; then engine="openssl" fi cn="$(libresign_default_cn)" org="$(read_env_value "LIBRESIGN_CERT_O" "$(read_env_value "NUQLOUD_BILLING_COMPANY_NAME" "NuQloud")")" country="$(read_env_value "LIBRESIGN_CERT_C" "")" state="$(read_env_value "LIBRESIGN_CERT_ST" "")" locality="$(read_env_value "LIBRESIGN_CERT_L" "")" ou_raw="$(read_env_value "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 if ! compose_cmd exec -T --user www-data app php occ "${occ_args[@]}" >/dev/null 2>&1; then echo "Warning: could not configure LibreSign root certificate." status_local=1 fi 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}" } while [[ $# -gt 0 ]]; do case "$1" in --compose-file) compose_file="${2:-}" shift 2 ;; --compose-file=*) compose_file="${1#*=}" shift ;; --env-file) env_file="${2:-}" shift 2 ;; --env-file=*) env_file="${1#*=}" shift ;; --skip-indices) skip_indices="1" shift ;; --skip-bundle) skip_bundle="1" shift ;; -h|--help) usage exit 0 ;; *) echo "Unknown option: $1" usage exit 1 ;; esac done if [[ "${compose_file}" != /* ]]; then compose_file="${repo_root}/${compose_file}" fi if [[ "${env_file}" != /* ]]; then env_file="${repo_root}/${env_file}" fi if [[ ! -f "${compose_file}" ]]; then echo "Missing compose file: ${compose_file}" exit 1 fi if [[ ! -f "${env_file}" ]]; then echo "Missing env file: ${env_file}" exit 1 fi export_compose_project_name_from_env "${env_file}" || true ensure_docker_access bundle_enabled_raw="$(read_env_value "INSTALL_DEFAULT_APP_BUNDLE" "$(read_env_value "INSTALL_DEFAULT_NEXTCLOUD_APP_BUNDLE" "0")")" bundle_enabled="$(normalize_bool_01 "${bundle_enabled_raw}")" if [[ -z "${bundle_enabled}" ]]; then bundle_enabled="0" fi default_app_bundle="$(trim_csv "$(read_env_value "NEXTCLOUD_DEFAULT_APP_BUNDLE" "notify_push,user_oidc,activity,announcementcenter,calendar,collectives,systemtags,comments,contacts,deck,external,files_accesscontrol,libresign,mail,notes,passwords,circles,groupfolders")")" default_app_order="$(trim_csv "$(read_env_value "NEXTCLOUD_DEFAULT_APP_ORDER" "dashboard,files,calendar,mail,deck,contacts,notes,collectives")")" status=0 if [[ "${skip_bundle}" != "1" && "${bundle_enabled}" == "1" && -n "${default_app_bundle}" ]]; then IFS=',' read -r -a app_ids <<< "${default_app_bundle}" for raw_app_id in "${app_ids[@]}"; do app_id="$(echo "${raw_app_id}" | xargs)" if [[ -z "${app_id}" ]]; then continue fi if ensure_app_enabled "${app_id}"; then continue fi echo "Warning: could not install/enable Nextcloud app bundle item '${app_id}'." status=1 done if [[ -n "${default_app_order}" ]]; then if ! compose_cmd exec -T --user www-data app php occ config:system:set defaultapp --value="${default_app_order}" >/dev/null 2>&1; then echo "Warning: could not set Nextcloud defaultapp order to '${default_app_order}'." status=1 fi fi if ! ensure_libresign_setup; then status=1 fi fi if [[ "${skip_indices}" != "1" ]]; then if ! compose_cmd exec -T --user www-data app php occ db:add-missing-indices >/dev/null 2>&1; then echo "Warning: occ db:add-missing-indices did not complete successfully." status=1 fi fi exit "${status}"