diff --git a/install.sh b/install.sh index 845f10a..6ce0105 100755 --- a/install.sh +++ b/install.sh @@ -1,127 +1,192 @@ -#!/bin/bash +#!/usr/bin/env bash +set -Eeuo pipefail -# Color codes for output formatting -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -echo -e "${GREEN}=====================================================${NC}" -echo -e "${GREEN} АО КМУ «Гидромонтаж» - Адресная книга ${NC}" -echo -e "${GREEN} Проверка окружения и безопасности ${NC}" -echo -e "${GREEN}=====================================================${NC}" - -# 1. Check if folder already exists and is not empty +APP_NAME="corp-address-book" +CONTAINER_NAME="corporate-address-book" INSTALL_DIR="/opt/corp-address-book" -if [ -d "$INSTALL_DIR" ] && [ "$(ls -A "$INSTALL_DIR" 2>/dev/null)" ]; then - echo -e "${RED}[X] ОШИБКА: Директория установки $INSTALL_DIR уже существует и не пуста!${NC}" - echo -e "${RED} Во избежание перезаписи чужих файлов установка остановлена.${NC}" +REPO_URL="https://git.h0melab.ru/fabritsky/corp-address-book.git" +HOST_PORT="8180" +CONTAINER_PORT="3000" +IMAGE_NAME="corp-address-book:latest" + +log() { + printf '\n[%s] %s\n' "$(date +'%H:%M:%S')" "$*" +} + +die() { + printf '\nERROR: %s\n' "$*" >&2 exit 1 -fi +} -# 2. Check if port 8180 is busy on the server -if command -v lsof >/dev/null 2>&1; then - if lsof -i :8180 >/dev/null 2>&1; then - echo -e "${RED}[X] ОШИБКА: Порт 8180 уже занят другим процессом!${NC}" - lsof -i :8180 - exit 1 +need_root_or_sudo() { + if [ "${EUID:-$(id -u)}" -ne 0 ] && ! command -v sudo >/dev/null 2>&1; then + die "Run as root or install sudo first." fi -elif command -v netstat >/dev/null 2>&1; then - if netstat -tuln | grep -q ":8180 "; then - echo -e "${RED}[X] ОШИБКА: Порт 8180 уже занят другим процессом!${NC}" - exit 1 +} + +as_root() { + if [ "${EUID:-$(id -u)}" -eq 0 ]; then + "$@" + else + sudo "$@" fi -fi +} -# 3. Check if docker container with name 'corporate-address-book' already exists -if command -v docker >/dev/null 2>&1; then - if docker ps -a --format '{{.Names}}' | grep -Eq "^corporate-address-book$"; then - echo -e "${RED}[X] ОШИБКА: Контейнер с именем 'corporate-address-book' уже существует!${NC}" - echo -e "${RED} Пожалуйста, удалите или переименуйте существующий контейнер перед установкой.${NC}" - exit 1 +install_base_packages() { + log "Installing required base packages" + as_root apt-get update + as_root DEBIAN_FRONTEND=noninteractive apt-get install -y \ + ca-certificates \ + curl \ + git \ + gnupg +} + +install_docker_if_needed() { + if command -v docker >/dev/null 2>&1; then + log "Docker is already installed" + else + log "Installing Docker Engine from the official Docker repository" + as_root install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | as_root gpg --dearmor -o /etc/apt/keyrings/docker.gpg + as_root chmod a+r /etc/apt/keyrings/docker.gpg + + . /etc/os-release + arch="$(dpkg --print-architecture)" + codename="${VERSION_CODENAME:-}" + [ -n "$codename" ] || die "Cannot determine Ubuntu codename." + + printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu %s stable\n' "$arch" "$codename" | + as_root tee /etc/apt/sources.list.d/docker.list >/dev/null + + as_root apt-get update + as_root DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce docker-ce-cli containerd.io fi -fi -echo -e "${GREEN}[✓] Проверка пройдена. Конфликтов не обнаружено.${NC}" -echo -e "${YELLOW}[i] Начинаем установку...${NC}" + if ! docker compose version >/dev/null 2>&1; then + log "Installing Docker Compose plugin" + as_root apt-get update + as_root DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin + else + log "Docker Compose plugin is already installed" + fi -# Verify if Docker is installed -if ! [ -x "$(command -v docker)" ]; then - echo -e "${YELLOW}[!] Docker не установлен. Устанавливаем Docker...${NC}" - sudo apt-get update - sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" - sudo apt-get update - sudo apt-get install -y docker-ce - sudo systemctl start docker - sudo systemctl enable docker - echo -e "${GREEN}[✓] Docker успешно установлен!${NC}" -else - echo -e "${GREEN}[✓] Docker обнаружен.${NC}" -fi + as_root systemctl enable --now docker +} -# Verify if Docker Compose is installed -if ! docker compose version >/dev/null 2>&1 && ! [ -x "$(command -v docker-compose)" ]; then - echo -e "${YELLOW}[!] Docker Compose не установлен. Устанавливаем...${NC}" - sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - sudo chmod +x /usr/local/bin/docker-compose - echo -e "${GREEN}[✓] Docker Compose установлен!${NC}" -else - echo -e "${GREEN}[✓] Docker Compose обнаружен.${NC}" -fi +prepare_repo() { + log "Preparing repository in ${INSTALL_DIR}" + as_root mkdir -p "$INSTALL_DIR" -# Create directory structure -sudo mkdir -p "$INSTALL_DIR" -sudo cp -r . "$INSTALL_DIR" -cd "$INSTALL_DIR" + if [ -d "${INSTALL_DIR}/.git" ]; then + as_root git -C "$INSTALL_DIR" remote set-url origin "$REPO_URL" + as_root git -C "$INSTALL_DIR" fetch --prune origin + as_root git -C "$INSTALL_DIR" pull --ff-only origin main + elif [ -z "$(as_root find "$INSTALL_DIR" -mindepth 1 -maxdepth 1 -print -quit)" ]; then + as_root git clone "$REPO_URL" "$INSTALL_DIR" + else + die "${INSTALL_DIR} exists and is not a git repository. Move it aside or initialize it manually." + fi +} -# Create .env file with secure settings if it doesn't exist -if [ ! -f .env ]; then - echo -e "${YELLOW}[!] Файл настроек .env отсутствует. Создаем новый...${NC}" - - # Generate random admin password and JWT secret - RANDOM_PASS=$(openssl rand -base64 12 | tr -d '/+=') - JWT_SEC=$(openssl rand -base64 32 | tr -d '/+=') +prepare_env_and_data() { + log "Preparing persistent data and environment" + as_root mkdir -p "${INSTALL_DIR}/data" - cat < .env -# Настройки учетных данных администратора + if [ ! -f "${INSTALL_DIR}/.env" ]; then + jwt_secret="$(openssl rand -hex 32 2>/dev/null || date +%s%N)" + admin_password="$(openssl rand -base64 24 2>/dev/null | tr -d '\n' || date +%s%N)" + as_root tee "${INSTALL_DIR}/.env" >/dev/null </dev/null </dev/null 2>&1; then - sudo docker compose -p corp-address-book up -d --build -else - sudo docker-compose -p corp-address-book up -d --build -fi +replace_container() { + log "Starting ${CONTAINER_NAME} on port ${HOST_PORT}" -if [ $? -eq 0 ]; then - echo -e "${GREEN}=====================================================${NC}" - echo -e "${GREEN}[✓] УСТАНОВКА ЗАВЕРШЕНА УСПЕШНО!${NC}" - echo -e "${GREEN}=====================================================${NC}" - echo -e "Сайт адресной книги доступен по адресу:" - echo -e "🔗 ${YELLOW}http://192.168.1.250:8180/${NC} (или по IP-адресу вашего сервера на порту 8180)" - echo -e "" - echo -e "Данные для входа в панель администратора:" - echo -e "Логин: ${YELLOW}$ADMIN_USERNAME${NC}" - echo -e "Пароль: ${YELLOW}$ADMIN_PASSWORD${NC}" - echo -e "" - echo -e "${RED}ВАЖНО: Сохраните эти учетные данные! Вы можете изменить их в файле .env${NC}" - echo -e "=====================================================" -else - echo -e "${RED}[X] Произошла ошибка во время сборки или запуска контейнеров.${NC}" - exit 1 -fi + old_name="" + if as_root docker container inspect "$CONTAINER_NAME" >/dev/null 2>&1; then + old_name="${CONTAINER_NAME}-old-$(date +%Y%m%d-%H%M%S)" + as_root docker stop "$CONTAINER_NAME" + as_root docker rename "$CONTAINER_NAME" "$old_name" + log "Previous container renamed to ${old_name}" + fi + + if ! as_root docker run -d \ + --name "$CONTAINER_NAME" \ + --restart unless-stopped \ + --env-file "${INSTALL_DIR}/.env" \ + -p "${HOST_PORT}:${CONTAINER_PORT}" \ + -v "${INSTALL_DIR}/data:/app/data" \ + "$IMAGE_NAME"; then + if [ -n "$old_name" ]; then + log "New container failed to start; attempting rollback to ${old_name}" + as_root docker start "$old_name" || true + fi + die "Failed to start ${CONTAINER_NAME}." + fi + + sleep 5 + if ! curl -fsSI "http://127.0.0.1:${HOST_PORT}/" >/dev/null; then + as_root docker logs --tail=80 "$CONTAINER_NAME" || true + if [ -n "$old_name" ]; then + log "Health check failed; attempting rollback to ${old_name}" + as_root docker stop "$CONTAINER_NAME" || true + as_root docker rename "$CONTAINER_NAME" "${CONTAINER_NAME}-failed-$(date +%Y%m%d-%H%M%S)" || true + as_root docker start "$old_name" || true + fi + die "Health check failed on http://127.0.0.1:${HOST_PORT}/." + fi +} + +print_result() { + server_ip="$(hostname -I 2>/dev/null | awk '{print $1}')" + if [ -z "${server_ip:-}" ]; then + server_ip="SERVER_IP" + fi + + log "Installation completed" + printf 'Local check: http://127.0.0.1:%s/\n' "$HOST_PORT" + printf 'Open: http://%s:%s/\n' "$server_ip" "$HOST_PORT" +} + +main() { + need_root_or_sudo + install_base_packages + install_docker_if_needed + prepare_repo + prepare_env_and_data + build_image + replace_container + print_result +} + +main "$@"