| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- #!/usr/bin/env bash
- # ===========================================
- # PhotoPlaces — Удалённый деплой на тестовый сервер
- # ===========================================
- # Запуск на сервере 192.168.88.128:
- # curl -fsSL http://192.168.88.96:10880/foxtime/photoplaces/raw/main/deploy/deploy-remote.sh | bash
- # Или склонировав репо и запустив:
- # cd /home/foxtime/photoplaces && bash deploy/deploy-remote.sh
- # ===========================================
- # ВНИМАНИЕ: Если репозиторий приватный, нужен токен доступа:
- # git clone http://TOKEN@192.168.88.96:10880/foxtime/photoplaces.git
- 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
- # Проверка прав доступа к docker
- if ! docker info >/dev/null 2>&1; then
- log_warn "Нет доступа к docker daemon. Пытаюсь добавить пользователя в группу docker..."
- if sudo usermod -aG docker "$(whoami)" 2>/dev/null; then
- log_ok "Пользователь добавлен в группу docker. Перезапустите скрипт или выполните: newgrp docker"
- log_warn "Выполните: newgrp docker && bash $0"
- exit 0
- else
- log_err "Не удалось добавить в группу docker. Запустите от sudo или настройте права вручную."
- exit 1
- fi
- fi
- log_ok "Все зависимости установлены, права доступа к docker OK"
- }
- # 2. Клонирование/обновление репозитория
- setup_repo() {
- log_info "Настройка репозитория..."
- if [ -d "$PROJECT_DIR/.git" ]; then
- log_info "Репозиторий существует, обновляю..."
- cd "$PROJECT_DIR"
- # Сохраняем env.prod перед сбросом (файл больше не в git)
- cp -f deploy/env.prod /tmp/env.prod.save 2>/dev/null || true
- git fetch origin
- git reset --hard "origin/$BRANCH"
- git clean -fd
- # Восстанавливаем env.prod (девственный template из git не нужен)
- cp -f /tmp/env.prod.save deploy/env.prod 2>/dev/null || true
- else
- log_info "Клонирую репозиторий..."
- if ! git clone --branch "$BRANCH" "$REPO_URL" "$PROJECT_DIR" 2>&1; then
- log_err "Не удалось клонировать репозиторий. Возможно, он приватный."
- log_info "Создайте токен доступа в Gitea (Settings → Applications → Generate Token)"
- log_info "И запустите: git clone http://TOKEN@192.168.88.96:10880/foxtime/photoplaces.git $PROJECT_DIR"
- exit 1
- fi
- 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"
- # Хелпер для чтения из терминала (работает и при curl | bash)
- read_from_tty() {
- local prompt="$1"
- local var_name="$2"
- if [ -t 0 ]; then
- # stdin — терминал
- echo -n "$prompt"
- read -r "$var_name"
- elif [ -e /dev/tty ]; then
- # stdin — пайп, но есть /dev/tty
- echo -n "$prompt" > /dev/tty
- read -r "$var_name" < /dev/tty
- else
- # Нет доступа к терминалу
- log_warn "Нет интерактивного терминала, пропуск ввода $var_name"
- eval "$var_name=\"\""
- fi
- }
- # Интерактивный ввод CloudPayments (опционально)
- prompt_for_cloudpayments() {
- local public_id="${CLOUDPAYMENTS_PUBLIC_ID:-}"
- local api_secret="${CLOUDPAYMENTS_API_SECRET:-}"
- if [ -z "$public_id" ] || [ -z "$api_secret" ]; then
- echo
- log_info "CloudPayments (опционально, для платежей):"
- read_from_tty "CLOUDPAYMENTS_PUBLIC_ID (Enter чтобы пропустить): " cp_public
- if [ -n "$cp_public" ]; then
- if [[ "$OSTYPE" == "darwin"* ]]; then
- sed -i '' "s/^CLOUDPAYMENTS_PUBLIC_ID=.*/CLOUDPAYMENTS_PUBLIC_ID=${cp_public}/" "$ENV_FILE"
- else
- sed -i "s/^CLOUDPAYMENTS_PUBLIC_ID=.*/CLOUDPAYMENTS_PUBLIC_ID=${cp_public}/" "$ENV_FILE"
- fi
- export CLOUDPAYMENTS_PUBLIC_ID="$cp_public"
- fi
- read_from_tty "CLOUDPAYMENTS_API_SECRET (Enter чтобы пропустить): " cp_secret
- if [ -n "$cp_secret" ]; then
- if [[ "$OSTYPE" == "darwin"* ]]; then
- sed -i '' "s/^CLOUDPAYMENTS_API_SECRET=.*/CLOUDPAYMENTS_API_SECRET=${cp_secret}/" "$ENV_FILE"
- else
- sed -i "s/^CLOUDPAYMENTS_API_SECRET=.*/CLOUDPAYMENTS_API_SECRET=${cp_secret}/" "$ENV_FILE"
- fi
- export CLOUDPAYMENTS_API_SECRET="$cp_secret"
- fi
- fi
- }
- prompt_for_cloudpayments
- # Интерактивный ввод DOMAIN если не задан
- prompt_for_domain() {
- local current="${DOMAIN:-}"
- if [ -z "$current" ] || [[ "$current" == *"CHANGE_ME"* ]] || [[ "$current" == *"photoplaces.ru"* && "$current" != "photoplaces.ru" ]]; then
- echo
- log_info "DOMAIN (основной домен, например photoplaces.ru):"
- read_from_tty "DOMAIN (Enter для photoplaces.ru): " domain_input
- if [ -n "$domain_input" ]; then
- if [[ "$OSTYPE" == "darwin"* ]]; then
- sed -i '' "s/^DOMAIN=.*/DOMAIN=${domain_input}/" "$ENV_FILE"
- else
- sed -i "s/^DOMAIN=.*/DOMAIN=${domain_input}/" "$ENV_FILE"
- fi
- export DOMAIN="$domain_input"
- log_ok "DOMAIN сохранён: $domain_input"
- else
- # default
- if [[ "$OSTYPE" == "darwin"* ]]; then
- sed -i '' "s/^DOMAIN=.*/DOMAIN=photoplaces.ru/" "$ENV_FILE"
- else
- sed -i "s/^DOMAIN=.*/DOMAIN=photoplaces.ru/" "$ENV_FILE"
- fi
- export DOMAIN="photoplaces.ru"
- log_ok "DOMAIN установлен по умолчанию: photoplaces.ru"
- fi
- fi
- }
- prompt_for_domain
- # Проверка обязательных переменных
- local required=("DB_PASSWORD" "REDIS_PASSWORD" "JWT_SECRET" "JWT_REFRESH_SECRET" "S3_SECRET_KEY" "YANDEX_MAPS_API_KEY" "DOMAIN")
- local missing=()
- for var in "${required[@]}"; do
- if [ -z "${!var:-}" ]; then
- missing+=("$var")
- fi
- done
- if [ ${#missing[@]} -gt 0 ]; then
- log_warn "Требуют ручного заполнения в $ENV_FILE:"
- for var in "${missing[@]}"; do
- echo " - $var"
- done
- log_info "Откройте $ENV_FILE и заполните недостающие значения, затем запустите скрипт снова"
- exit 1
- fi
- log_ok "Все секреты настроены"
- }
- # 4. Docker secrets не используем (только env vars) — функция оставлена для совместимости
- create_docker_secrets() {
- log_info "Docker secrets не используются (конфигурация через env vars)"
- }
- # 5. Деплой через docker compose
- deploy() {
- log_info "Запуск docker compose..."
- cd "$PROJECT_DIR"
- # Версия сайта (git hash) для отображения в правом нижнем углу
- export NEXT_PUBLIC_GIT_HASH="${NEXT_PUBLIC_GIT_HASH:-$(git rev-parse --short HEAD 2>/dev/null || echo dev)}"
- # Загружаем переменные окружения для docker compose
- if [ -f "$ENV_FILE" ]; then
- docker compose --env-file "$ENV_FILE" -f deploy/docker-compose.prod.yml up -d --build --remove-orphans
- else
- docker compose -f deploy/docker-compose.prod.yml up -d --build --remove-orphans
- fi
- 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 (wget есть в caddy:alpine по умолчанию)
- if docker compose -f deploy/docker-compose.prod.yml exec -T caddy wget -qO- http://localhost:2019/metrics >/dev/null 2>&1; 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 "$@"
|