#!/usr/bin/env bash # =========================================== # PhotoPlaces — Удалённый деплой на тестовый сервер # =========================================== # Запуск на сервере 192.168.88.128: # curl -fsSL https://raw.githubusercontent.com/foxtime/photoplaces/main/deploy/deploy-remote.sh | bash # Или склонировав репо и запустив: # cd /home/foxtime/photoplaces && bash deploy/deploy-remote.sh # =========================================== set -euo pipefail # Цвета для вывода RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color log_info() { echo -e "${BLUE}[INFO]${NC} $*"; } log_ok() { echo -e "${GREEN}[OK]${NC} $*"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } log_err() { echo -e "${RED}[ERR]${NC} $*"; } # Конфигурация REPO_URL="http://192.168.88.96:10880/foxtime/photoplaces.git" PROJECT_DIR="/home/foxtime/photoplaces" BRANCH="main" ENV_FILE="$PROJECT_DIR/deploy/env.prod" log_info "=== PhotoPlaces Remote Deploy ===" log_info "Сервер: $(hostname) ($(hostname -I | awk '{print $1}'))" log_info "Пользователь: $(whoami)" log_info "Директория: $PROJECT_DIR" # 1. Проверка зависимостей check_deps() { log_info "Проверка зависимостей..." local missing=() for cmd in docker git curl openssl; do if ! command -v "$cmd" >/dev/null 2>&1; then missing+=("$cmd") fi done if ! docker compose version >/dev/null 2>&1; then missing+=("docker compose") fi if [ ${#missing[@]} -gt 0 ]; then log_err "Отсутствуют: ${missing[*]}" log_info "Установите: sudo apt update && sudo apt install -y docker.io docker-compose-plugin git curl openssl" exit 1 fi log_ok "Все зависимости установлены" } # 2. Клонирование/обновление репозитория setup_repo() { log_info "Настройка репозитория..." if [ -d "$PROJECT_DIR/.git" ]; then log_info "Репозиторий существует, обновляю..." cd "$PROJECT_DIR" git fetch origin git reset --hard "origin/$BRANCH" git clean -fd else log_info "Клонирую репозиторий..." git clone --branch "$BRANCH" "$REPO_URL" "$PROJECT_DIR" cd "$PROJECT_DIR" fi log_ok "Репозиторий готов: $(git rev-parse --short HEAD)" } # 3. Настройка env.prod setup_env() { log_info "Настройка переменных окружения..." cd "$PROJECT_DIR" if [ ! -f "$ENV_FILE" ]; then cp deploy/env.prod.example "$ENV_FILE" log_info "Создан $ENV_FILE из шаблона" fi # Автогенерация секретов если пустые или CHANGE_ME generate_secret() { local var="$1" local current="${!var:-}" if [[ -z "$current" || "$current" == *"CHANGE_ME"* ]]; then local secret secret=$(openssl rand -hex 32) if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s/^${var}=.*/${var}=${secret}/" "$ENV_FILE" else sed -i "s/^${var}=.*/${var}=${secret}/" "$ENV_FILE" fi export "$var=$secret" log_ok "$var сгенерирован автоматически" fi } # Загружаем текущие значения set -a source "$ENV_FILE" set +a generate_secret "DB_PASSWORD" generate_secret "REDIS_PASSWORD" generate_secret "JWT_SECRET" generate_secret "JWT_REFRESH_SECRET" generate_secret "S3_SECRET_KEY" # Проверка обязательных переменных с интерактивным вводом local required=("DB_PASSWORD" "REDIS_PASSWORD" "JWT_SECRET" "JWT_REFRESH_SECRET" "S3_SECRET_KEY" "YANDEX_MAPS_API_KEY") local missing=() for var in "${required[@]}"; do if [ -z "${!var:-}" ]; then missing+=("$var") fi done if [ ${#missing[@]} -gt 0 ]; then log_warn "Необходимо заполнить обязательные переменные:" for var in "${missing[@]}"; do prompt_for_var "$var" done # Перезагружаем env после ввода set -a source "$ENV_FILE" set +a fi # Опциональные переменные (интерактивно, можно пропустить Enter) prompt_optional "CLOUDPAYMENTS_PUBLIC_ID" "CloudPayments Public ID (для платежей, Enter для пропуска)" prompt_optional "CLOUDPAYMENTS_API_SECRET" "CloudPayments API Secret (для платежей, Enter для пропуска)" log_ok "Все секреты настроены" } # Интерактивный запрос обязательной переменной prompt_for_var() { local var="$1" local current="${!var:-}" while [ -z "${!var:-}" ]; do if [ -n "$current" ]; then echo -ne "${YELLOW}$var${NC} (текущее: $current): " else echo -ne "${YELLOW}$var${NC}: " fi read -r input if [ -n "$input" ]; then # Экранируем спецсимволы для sed local escaped escaped=$(printf '%s\n' "$input" | sed 's/[&/\]/\\&/g') if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s/^${var}=.*/${var}=${escaped}/" "$ENV_FILE" else sed -i "s/^${var}=.*/${var}=${escaped}/" "$ENV_FILE" fi export "$var=$input" log_ok "$var сохранено" elif [ -n "$current" ]; then export "$var=$current" log_ok "$var оставлено как есть" else log_err "Значение не может быть пустым!" fi done } # Интерактивный запрос опциональной переменной prompt_optional() { local var="$1" local prompt="$2" local current="${!var:-}" echo -ne "${BLUE}$prompt${NC} " if [ -n "$current" ]; then echo -ne "[$current]: " else echo -n ": " fi read -r input if [ -n "$input" ]; then local escaped escaped=$(printf '%s\n' "$input" | sed 's/[&/\]/\\&/g') if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s/^${var}=.*/${var}=${escaped}/" "$ENV_FILE" else sed -i "s/^${var}=.*/${var}=${escaped}/" "$ENV_FILE" fi export "$var=$input" log_ok "$var сохранено" elif [ -n "$current" ]; then log_ok "$var оставлено как есть" else log_info "$var пропущено" fi } # 4. Создание Docker secrets (если Swarm) create_docker_secrets() { if docker info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null | grep -q "active"; then log_info "Swarm mode: создание Docker secrets..." printf "%s" "$DB_PASSWORD" | docker secret create db_password - 2>/dev/null || docker secret rm db_password && printf "%s" "$DB_PASSWORD" | docker secret create db_password - printf "%s" "$REDIS_PASSWORD" | docker secret create redis_password - 2>/dev/null || docker secret rm redis_password && printf "%s" "$REDIS_PASSWORD" | docker secret create redis_password - printf "%s" "$JWT_SECRET" | docker secret create jwt_secret - 2>/dev/null || docker secret rm jwt_secret && printf "%s" "$JWT_SECRET" | docker secret create jwt_secret - printf "%s" "$JWT_REFRESH_SECRET" | docker secret create jwt_refresh_secret - 2>/dev/null || docker secret rm jwt_refresh_secret && printf "%s" "$JWT_REFRESH_SECRET" | docker secret create jwt_refresh_secret - printf "%s" "$S3_SECRET_KEY" | docker secret create s3_secret_key - 2>/dev/null || docker secret rm s3_secret_key && printf "%s" "$S3_SECRET_KEY" | docker secret create s3_secret_key - log_ok "Docker secrets созданы" fi } # 5. Деплой через docker compose deploy() { log_info "Запуск docker compose..." cd "$PROJECT_DIR" docker compose -f deploy/docker-compose.prod.yml up -d --build --remove-orphans log_ok "Контейнеры запущены" } # 6. Health checks health_check() { log_info "Ожидание готовности сервисов (до 120 сек)..." local max_attempts=60 local attempt=0 while [ $attempt -lt $max_attempts ]; do attempt=$((attempt + 1)) # Проверка Caddy (порт 2019 metrics) if docker compose -f deploy/docker-compose.prod.yml exec -T caddy wget -q --spider http://localhost:2019/metrics 2>/dev/null; then # Проверка backend через Caddy (HTTP, SSL на Synology) if curl -sf "http://localhost/api/v1/health" >/dev/null 2>&1; then log_ok "Backend здоров" break fi fi if [ $attempt -eq $max_attempts ]; then log_err "Таймаут ожидания здоровья" docker compose -f deploy/docker-compose.prod.yml logs --tail=50 caddy backend exit 1 fi sleep 2 done # Финальная проверка через HTTPS (Synology) log_info "Проверка через HTTPS (Synology)..." if curl -sf "https://photoplaces.ru/api/v1/health" >/dev/null 2>&1; then log_ok "HTTPS health check пройден" else log_warn "HTTPS check не прошёл (проверьте настройки Synology Reverse Proxy)" fi } # 7. Вывод результатов show_result() { echo log_ok "=== ДЕПЛОЙ УСПЕШНО ЗАВЕРШЁН ===" echo echo " Фронтенд: https://photoplaces.ru" echo " API: https://api.photoplaces.ru/api/v1" echo " S3 API: https://api.photoplaces.ru/s3" echo " MinIO Console: https://api.photoplaces.ru/minio-console/" echo echo " Логи: cd $PROJECT_DIR && docker compose -f deploy/docker-compose.prod.yml logs -f" echo " Статус: cd $PROJECT_DIR && docker compose -f deploy/docker-compose.prod.yml ps" echo " Остановка: cd $PROJECT_DIR && docker compose -f deploy/docker-compose.prod.yml down" echo } # =========================================== # MAIN # =========================================== main() { check_deps setup_repo setup_env create_docker_secrets deploy health_check show_result } main "$@"