nextcloud-install/lib/common.sh
2026-06-24 09:23:58 +03:00

443 lines
14 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Общие функции для скриптов nextcloud_install
# Подключение: source "$(dirname "$0")/../lib/common.sh"
# Пользователь, от имени которого запущен sudo (или текущий)
get_invoke_user() {
if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != root ]]; then
echo "${SUDO_USER}"
else
id -un
fi
}
# Требует root; если нет — перезапускает скрипт через sudo -E
require_root() {
if [[ "${EUID}" -ne 0 ]]; then
exec sudo -E bash "$(readlink -f "${BASH_SOURCE[1]}")" "$@"
fi
}
# Docker-скрипты не должны работать от root: перезапуск от SUDO_USER
drop_root_if_sudo() {
local script="${1:-${BASH_SOURCE[1]:-}}"
script="$(readlink -f "${script}")"
[[ "${EUID}" -ne 0 ]] && return 0
local u="${SUDO_USER:-}"
if [[ -z "${u}" || "${u}" == root ]]; then
echo "[ERROR] Не запускайте docker-скрипты от root." >&2
echo " Используйте: ./$(basename "${script}")" >&2
exit 1
fi
local home
home="$(getent passwd "${u}" | cut -d: -f6)"
# sudo -u сбрасывает группу docker — перезапуск через sg docker
if command -v sg &>/dev/null && id -nG "${u}" | grep -qw docker; then
exec sudo -u "${u}" -E env HOME="${home}" USER="${u}" LOGNAME="${u}" \
sg docker -c "bash $(printf '%q' "${script}") $(printf '%q ' "${@:2}")"
fi
exec sudo -u "${u}" -E env HOME="${home}" USER="${u}" LOGNAME="${u}" \
bash "${script}" "${@:2}"
}
# docker compose (plugin) или docker-compose (standalone)
compose_bin() {
if docker compose version &>/dev/null 2>&1; then
echo "docker compose"
elif command -v docker-compose &>/dev/null && docker-compose version &>/dev/null 2>&1; then
echo "docker-compose"
else
return 1
fi
}
# Создать каталог на хосте (нужен sudo для /var/nextcloud и т.п.)
ensure_host_dir() {
local d="$1"
[[ -z "${d}" ]] && return 1
if [[ -d "${d}" ]]; then
return 0
fi
if mkdir -p "${d}" 2>/dev/null; then
return 0
fi
sudo mkdir -p "${d}"
}
# Права www-data (uid 33) для каталогов Nextcloud
chown_wwwdata_dir() {
local d="$1"
sudo chown 33:33 "${d}"
sudo chmod 0775 "${d}"
}
# Каталог config.php: всегда www-data, иначе веб-установщик и occ ломаются
fix_config_permissions() {
local cfg="$1"
[[ -d "${cfg}" ]] || return 1
sudo chown -R 33:33 "${cfg}"
sudo find "${cfg}" -type d -exec chmod 775 {} \;
sudo find "${cfg}" -type f -exec chmod 664 {} \;
[[ -f "${cfg}/config.php" ]] && sudo chmod 660 "${cfg}/config.php"
}
# Запись файла в config (через sudo — каталог принадлежит www-data)
config_write_file() {
local f="$1"
ensure_host_dir "$(dirname "$f")"
sudo tee "${f}" >/dev/null
sudo chown 33:33 "${f}"
sudo chmod 664 "${f}"
}
# Скопировать config.defaults → NEXTCLOUD_CONFIG_DIR
restore_config_templates() {
local src="$1" cfg="$2" instanceid="$3" force="${4:-false}"
ensure_host_dir "${cfg}"
if [[ "${force}" == true ]]; then
sudo find "${cfg}" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
fi
if [[ -d "${src}" ]]; then
sudo cp -a "${src}/." "${cfg}/"
fi
config_write_file "${cfg}/instanceid.config.php" <<EOF
<?php
\$CONFIG = array (
'instanceid' => '${instanceid}',
);
EOF
config_write_file "${cfg}/datadir.config.php" <<'EOF'
<?php
$CONFIG = array (
'check_data_directory_permissions' => false,
);
EOF
fix_config_permissions "${cfg}"
}
backup_and_remove_config_php() {
local cfg="$1" reason="${2:-reinstall}"
[[ -f "${cfg}/config.php" ]] || return 0
local ts
ts="$(date +%Y%m%d-%H%M%S)"
echo "[WARN] ${reason}: старый config.php сохранён как config.php.bak.${ts}"
sudo cp -a "${cfg}/config.php" "${cfg}/config.php.bak.${ts}" 2>/dev/null || true
sudo rm -f "${cfg}/config.php" "${cfg}/CAN_INSTALL"
}
config_php_has_instanceid() {
local cfg="$1" instanceid="$2"
[[ -f "${cfg}/config.php" ]] || return 1
grep -q "'instanceid' => '${instanceid}'" "${cfg}/config.php" 2>/dev/null
}
docker_volume_exists() {
local volume="$1"
if docker info &>/dev/null 2>&1; then
docker volume inspect "${volume}" &>/dev/null
elif command -v sg &>/dev/null && id -nG | grep -qw docker; then
sg docker -c "docker volume inspect $(printf '%q' "${volume}")" &>/dev/null
else
return 1
fi
}
prepare_config_for_reinstall() {
local src="$1" cfg="$2" instanceid="$3"
ensure_host_dir "${cfg}"
if [[ -f "${cfg}/config.php" && ! -s "${cfg}/config.php" ]]; then
backup_and_remove_config_php "${cfg}" "Пустой config.php"
fi
if [[ -f "${cfg}/config.php" ]] && ! config_php_has_instanceid "${cfg}" "${instanceid}"; then
backup_and_remove_config_php "${cfg}" "instanceid в .env изменился"
fi
# Если БД volume отсутствует, это новая установка после purge. Старый config.php
# приведёт к ошибке Nextcloud про CAN_INSTALL, поэтому убираем его заранее.
if [[ -f "${cfg}/config.php" ]] && ! docker_volume_exists nextcloud_db_data; then
backup_and_remove_config_php "${cfg}" "БД volume отсутствует после purge"
fi
restore_config_templates "${src}" "${cfg}" "${instanceid}" false
}
# occ maintenance:install (нужен запущенный контейнер app)
occ_maintenance_install() {
# shellcheck disable=SC1091
source "$(pwd)/.env"
run_compose exec -T -u www-data app php occ maintenance:install \
--database mysql \
--database-name "${MYSQL_DATABASE}" \
--database-user "${MYSQL_USER}" \
--database-pass "${MYSQL_PASSWORD}" \
--database-host db \
--admin-user "${NEXTCLOUD_ADMIN_USER}" \
--admin-pass "${NEXTCLOUD_ADMIN_PASSWORD}" \
--data-dir /var/www/html/data
}
# Установить Nextcloud если status.php: installed=false
ensure_nextcloud_installed() {
local port="${1:-8080}"
local installed
installed="$(curl -sf "http://127.0.0.1:${port}/status.php" 2>/dev/null | grep -o '"installed":[^,]*' | cut -d: -f2 || echo false)"
if [[ "${installed}" == "true" ]]; then
return 0
fi
# shellcheck disable=SC1091
source "$(pwd)/.env"
if [[ -f "${NEXTCLOUD_CONFIG_DIR}/config.php" ]]; then
backup_and_remove_config_php "${NEXTCLOUD_CONFIG_DIR}" "Nextcloud не установлен, но config.php уже есть"
restore_config_templates "$(pwd)/config.defaults" "${NEXTCLOUD_CONFIG_DIR}" "${NEXTCLOUD_INSTANCEID}" false
run_compose restart app
sleep 5
fi
echo "[INFO] Nextcloud не установлен — occ maintenance:install..."
if ! occ_maintenance_install; then
echo "[WARN] occ install не удался. Попробуйте: ./restore-config.sh --force" >&2
return 1
fi
fix_config_permissions "${NEXTCLOUD_CONFIG_DIR}"
return 0
}
# Вернуть владельца .env пользователю, вызвавшему sudo
fix_env_ownership() {
local env_file="${1:-.env}"
local user
user="$(get_invoke_user)"
if [[ -f "${env_file}" && "${user}" != root && "${EUID}" -eq 0 ]]; then
chown "${user}:${user}" "${env_file}"
fi
}
# Запуск setup-env от имени обычного пользователя (не root)
run_setup_env() {
local script_dir="$1"
local domain="${2:-}"
local user
user="$(get_invoke_user)"
if [[ "${EUID}" -eq 0 && "${user}" != root ]]; then
sudo -u "${user}" bash "${script_dir}/setup-env.sh" ${domain:+"${domain}"}
fix_env_ownership "${script_dir}/.env"
else
bash "${script_dir}/setup-env.sh" ${domain:+"${domain}"}
fi
}
# Проверка: бинарник docker в PATH
docker_is_installed() {
command -v docker &>/dev/null
}
# Проверка: docker compose v2 доступен
docker_compose_available() {
docker compose version &>/dev/null 2>&1
}
# Проверка доступности docker (пробует несколько способов)
check_docker_access() {
if docker info &>/dev/null 2>&1; then
return 0
fi
# Группа docker ещё не подхватилась в текущей сессии после usermod
if command -v sg &>/dev/null && sg docker -c "docker info" &>/dev/null 2>&1; then
return 0
fi
if sudo docker info &>/dev/null 2>&1; then
return 0
fi
if [[ "${EUID}" -eq 0 ]]; then
echo "[ERROR] Docker недоступен. Не запускайте docker-скрипты через sudo." >&2
echo " Используйте: ./install.sh (без sudo)" >&2
else
echo "[ERROR] Docker недоступен." >&2
echo " Добавьте пользователя в группу docker: sudo usermod -aG docker \$USER" >&2
echo " Или перезапустите install.sh — предложит установку Docker." >&2
fi
return 1
}
# Выполнить docker-команду (docker / sg docker / sudo docker)
run_docker() {
if docker info &>/dev/null 2>&1; then
docker "$@"
elif command -v sg &>/dev/null && id -nG | grep -qw docker; then
sg docker -c "docker $(printf '%q ' "$@")"
elif command -v sg &>/dev/null && sg docker -c "docker $(printf '%q ' "$@")" 2>/dev/null; then
:
else
echo "[ERROR] Docker недоступен" >&2
return 1
fi
}
# Файлы compose (через COMPOSE_FILE — без -f, чтобы не ломать sudo/sg)
compose_env_setup() {
local dir
dir="$(pwd)"
export COMPOSE_FILE="${dir}/docker-compose.yml"
if [[ -f "${dir}/.ssl-enabled" ]]; then
export COMPOSE_FILE="${COMPOSE_FILE}:${dir}/docker-compose.ssl.yml"
fi
}
run_compose() {
if [[ "${EUID}" -eq 0 ]]; then
echo "[ERROR] Docker Compose нельзя запускать от root." >&2
echo " Запустите: ./install.sh (без sudo)" >&2
return 1
fi
compose_env_setup
local dir cb
dir="$(pwd)"
cb="$(compose_bin)" || {
echo "[ERROR] Нужен Docker Compose v2 (docker compose) или docker-compose." >&2
echo " sudo apt install docker-compose-plugin" >&2
return 1
}
_compose_run() {
# shellcheck disable=SC2086
(cd "${dir}" && ${cb} "$@")
}
if docker info &>/dev/null 2>&1; then
_compose_run "$@"
return $?
fi
if command -v sg &>/dev/null && id -nG | grep -qw docker; then
sg docker -c "cd $(printf '%q' "${dir}") && COMPOSE_FILE=$(printf '%q' "${COMPOSE_FILE}") ${cb} $(printf '%q ' "$@")"
return $?
fi
echo "[ERROR] Docker недоступен." >&2
echo " sudo usermod -aG docker $(id -un) && newgrp docker" >&2
return 1
}
app_code_ready() {
run_compose exec -T app test -f /var/www/html/lib/versioncheck.php &>/dev/null
}
wait_for_app_code() {
local waited=0 limit="${1:-90}"
while [[ "${waited}" -lt "${limit}" ]]; do
app_code_ready && return 0
sleep 3
waited=$((waited + 3))
done
return 1
}
repair_app_code_volume() {
echo "[WARN] Код Nextcloud в volume nextcloud_app_data неполный."
echo "[WARN] Пересоздаю только nextcloud_app_data (config/data/БД не трогаются)."
run_compose down --remove-orphans
run_docker volume rm nextcloud_app_data >/dev/null 2>&1 || true
run_compose up -d
wait_for_app_code 120
}
ensure_app_code_ready() {
if wait_for_app_code 90; then
return 0
fi
repair_app_code_volume || {
echo "[ERROR] Не удалось подготовить код Nextcloud в /var/www/html." >&2
echo " Проверьте: docker logs nextcloud-app" >&2
return 1
}
}
# Предложить/выполнить установку Docker (вызывается из install.sh)
# $1 = путь к install-docker.sh, $2 = auto (yes/no/skip/prompt)
ensure_docker() {
local installer="$1"
local auto="${2:-prompt}"
# Docker уже работает (напрямую, через sg или sudo)
if docker info &>/dev/null 2>&1 && docker compose version &>/dev/null 2>&1; then
return 0
fi
if command -v sg &>/dev/null && sg docker -c "docker info" &>/dev/null 2>&1 \
&& sg docker -c "docker compose version" &>/dev/null 2>&1; then
return 0
fi
if sudo docker info &>/dev/null 2>&1 && sudo docker compose version &>/dev/null 2>&1; then
return 0
fi
if [[ "${auto}" == "skip" ]]; then
check_docker_access || return 1
return 0
fi
# Docker есть, но нет compose plugin
if docker_is_installed && ! docker_compose_available; then
echo "[ERROR] Docker установлен, но нет Compose v2." >&2
echo " sudo apt install docker-compose-plugin" >&2
return 1
fi
echo ""
if docker_is_installed; then
echo "Docker установлен, но недоступен без sudo (возможно, нужен перелогин)."
echo "Скрипт попробует добавить вас в группу docker."
else
echo "Docker не найден."
echo "Скрипт может установить Docker Engine + Compose v2 (Ubuntu/Debian)."
fi
echo "Потребуется sudo и доступ к интернету."
echo ""
local answer="${auto}"
if [[ "${answer}" == "prompt" ]]; then
read -r -p "Продолжить установку/настройку Docker? (yes/no): " answer
fi
if [[ "${answer}" != "yes" ]]; then
echo "[INFO] Пропущено. Вручную: sudo ${installer} $(id -un)"
return 1
fi
[[ -f "${installer}" ]] || { echo "[ERROR] Не найден ${installer}" >&2; return 1; }
echo "[INFO] Запуск (sudo)..."
sudo bash "${installer}" "$(id -un)"
sleep 2
if docker info &>/dev/null 2>&1 || sg docker -c "docker info" &>/dev/null 2>&1 \
|| sudo docker info &>/dev/null 2>&1; then
echo "[INFO] Docker готов."
return 0
fi
echo "[WARN] Docker установлен. Перелогиньтесь или: newgrp docker"
return 1
}
# occ от www-data (работает и под sudo, и под root)
run_occ() {
local nc_dir="$1"
shift
if [[ "${EUID}" -eq 0 ]]; then
sudo -u www-data php "${nc_dir}/occ" "$@"
else
sudo -u www-data php "${nc_dir}/occ" "$@"
fi
}