deploy-remote.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. #!/usr/bin/env bash
  2. # ===========================================
  3. # PhotoPlaces — Удалённый деплой на тестовый сервер
  4. # ===========================================
  5. # Запуск на сервере 192.168.88.128:
  6. # curl -fsSL http://192.168.88.96:10880/foxtime/photoplaces/raw/main/deploy/deploy-remote.sh | bash
  7. # Или склонировав репо и запустив:
  8. # cd /home/foxtime/photoplaces && bash deploy/deploy-remote.sh
  9. # ===========================================
  10. # ВНИМАНИЕ: Если репозиторий приватный, нужен токен доступа:
  11. # git clone http://TOKEN@192.168.88.96:10880/foxtime/photoplaces.git
  12. set -euo pipefail
  13. # Цвета для вывода
  14. RED='\033[0;31m'
  15. GREEN='\033[0;32m'
  16. YELLOW='\033[1;33m'
  17. BLUE='\033[0;34m'
  18. NC='\033[0m' # No Color
  19. log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
  20. log_ok() { echo -e "${GREEN}[OK]${NC} $*"; }
  21. log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
  22. log_err() { echo -e "${RED}[ERR]${NC} $*"; }
  23. # Конфигурация
  24. REPO_URL="http://192.168.88.96:10880/foxtime/photoplaces.git"
  25. PROJECT_DIR="/home/foxtime/photoplaces"
  26. BRANCH="main"
  27. ENV_FILE="$PROJECT_DIR/deploy/env.prod"
  28. log_info "=== PhotoPlaces Remote Deploy ==="
  29. log_info "Сервер: $(hostname) ($(hostname -I | awk '{print $1}'))"
  30. log_info "Пользователь: $(whoami)"
  31. log_info "Директория: $PROJECT_DIR"
  32. # 1. Проверка зависимостей и прав доступа
  33. check_deps() {
  34. log_info "Проверка зависимостей..."
  35. local missing=()
  36. for cmd in docker git curl openssl; do
  37. if ! command -v "$cmd" >/dev/null 2>&1; then
  38. missing+=("$cmd")
  39. fi
  40. done
  41. if ! docker compose version >/dev/null 2>&1; then
  42. missing+=("docker compose")
  43. fi
  44. if [ ${#missing[@]} -gt 0 ]; then
  45. log_err "Отсутствуют: ${missing[*]}"
  46. log_info "Установите: sudo apt update && sudo apt install -y docker.io docker-compose-plugin git curl openssl"
  47. exit 1
  48. fi
  49. # Проверка прав доступа к docker
  50. if ! docker info >/dev/null 2>&1; then
  51. log_warn "Нет доступа к docker daemon. Пытаюсь добавить пользователя в группу docker..."
  52. if sudo usermod -aG docker "$(whoami)" 2>/dev/null; then
  53. log_ok "Пользователь добавлен в группу docker. Перезапустите скрипт или выполните: newgrp docker"
  54. log_warn "Выполните: newgrp docker && bash $0"
  55. exit 0
  56. else
  57. log_err "Не удалось добавить в группу docker. Запустите от sudo или настройте права вручную."
  58. exit 1
  59. fi
  60. fi
  61. log_ok "Все зависимости установлены, права доступа к docker OK"
  62. }
  63. # 2. Клонирование/обновление репозитория
  64. setup_repo() {
  65. log_info "Настройка репозитория..."
  66. if [ -d "$PROJECT_DIR/.git" ]; then
  67. log_info "Репозиторий существует, обновляю..."
  68. cd "$PROJECT_DIR"
  69. # Сохраняем env.prod перед сбросом (файл больше не в git)
  70. cp -f deploy/env.prod /tmp/env.prod.save 2>/dev/null || true
  71. git fetch origin
  72. git reset --hard "origin/$BRANCH"
  73. git clean -fd
  74. # Восстанавливаем env.prod (девственный template из git не нужен)
  75. cp -f /tmp/env.prod.save deploy/env.prod 2>/dev/null || true
  76. else
  77. log_info "Клонирую репозиторий..."
  78. if ! git clone --branch "$BRANCH" "$REPO_URL" "$PROJECT_DIR" 2>&1; then
  79. log_err "Не удалось клонировать репозиторий. Возможно, он приватный."
  80. log_info "Создайте токен доступа в Git"
  81. log_info "И запустите: git clone http://TOKEN@192.168.88.96:10880/foxtime/photoplaces.git $PROJECT_DIR"
  82. exit 1
  83. fi
  84. cd "$PROJECT_DIR"
  85. fi
  86. log_ok "Репозиторий готов: $(git rev-parse --short HEAD)"
  87. }
  88. # 3. Настройка env.prod
  89. setup_env() {
  90. log_info "Настройка переменных окружения..."
  91. cd "$PROJECT_DIR"
  92. if [ ! -f "$ENV_FILE" ]; then
  93. cp deploy/env.prod.example "$ENV_FILE"
  94. log_info "Создан $ENV_FILE из шаблона"
  95. fi
  96. # Автогенерация секретов если пустые или CHANGE_ME
  97. generate_secret() {
  98. local var="$1"
  99. local current="${!var:-}"
  100. if [[ -z "$current" || "$current" == *"CHANGE_ME"* ]]; then
  101. local secret
  102. secret=$(openssl rand -hex 32)
  103. if [[ "$OSTYPE" == "darwin"* ]]; then
  104. sed -i '' "s/^${var}=.*/${var}=${secret}/" "$ENV_FILE"
  105. else
  106. sed -i "s/^${var}=.*/${var}=${secret}/" "$ENV_FILE"
  107. fi
  108. export "$var=$secret"
  109. log_ok "$var сгенерирован автоматически"
  110. fi
  111. }
  112. # Загружаем текущие значения
  113. set -a
  114. source "$ENV_FILE"
  115. set +a
  116. generate_secret "DB_PASSWORD"
  117. generate_secret "REDIS_PASSWORD"
  118. generate_secret "JWT_SECRET"
  119. generate_secret "JWT_REFRESH_SECRET"
  120. generate_secret "S3_SECRET_KEY"
  121. # Хелпер для чтения из терминала (работает и при curl | bash)
  122. read_from_tty() {
  123. local prompt="$1"
  124. local var_name="$2"
  125. if [ -t 0 ]; then
  126. # stdin — терминал
  127. echo -n "$prompt"
  128. read -r "$var_name"
  129. elif [ -e /dev/tty ]; then
  130. # stdin — пайп, но есть /dev/tty
  131. echo -n "$prompt" > /dev/tty
  132. read -r "$var_name" < /dev/tty
  133. else
  134. # Нет доступа к терминалу
  135. log_warn "Нет интерактивного терминала, пропуск ввода $var_name"
  136. eval "$var_name=\"\""
  137. fi
  138. }
  139. # Интерактивный ввод CloudPayments (опционально)
  140. prompt_for_cloudpayments() {
  141. local public_id="${CLOUDPAYMENTS_PUBLIC_ID:-}"
  142. local api_secret="${CLOUDPAYMENTS_API_SECRET:-}"
  143. if [ -z "$public_id" ] || [ -z "$api_secret" ]; then
  144. echo
  145. log_info "CloudPayments (опционально, для платежей):"
  146. read_from_tty "CLOUDPAYMENTS_PUBLIC_ID (Enter чтобы пропустить): " cp_public
  147. if [ -n "$cp_public" ]; then
  148. if [[ "$OSTYPE" == "darwin"* ]]; then
  149. sed -i '' "s/^CLOUDPAYMENTS_PUBLIC_ID=.*/CLOUDPAYMENTS_PUBLIC_ID=${cp_public}/" "$ENV_FILE"
  150. else
  151. sed -i "s/^CLOUDPAYMENTS_PUBLIC_ID=.*/CLOUDPAYMENTS_PUBLIC_ID=${cp_public}/" "$ENV_FILE"
  152. fi
  153. export CLOUDPAYMENTS_PUBLIC_ID="$cp_public"
  154. fi
  155. read_from_tty "CLOUDPAYMENTS_API_SECRET (Enter чтобы пропустить): " cp_secret
  156. if [ -n "$cp_secret" ]; then
  157. if [[ "$OSTYPE" == "darwin"* ]]; then
  158. sed -i '' "s/^CLOUDPAYMENTS_API_SECRET=.*/CLOUDPAYMENTS_API_SECRET=${cp_secret}/" "$ENV_FILE"
  159. else
  160. sed -i "s/^CLOUDPAYMENTS_API_SECRET=.*/CLOUDPAYMENTS_API_SECRET=${cp_secret}/" "$ENV_FILE"
  161. fi
  162. export CLOUDPAYMENTS_API_SECRET="$cp_secret"
  163. fi
  164. fi
  165. }
  166. prompt_for_cloudpayments
  167. # Интерактивный ввод DOMAIN если не задан
  168. prompt_for_domain() {
  169. local current="${DOMAIN:-}"
  170. if [ -z "$current" ] || [[ "$current" == *"CHANGE_ME"* ]] || [[ "$current" == *"photoplaces.ru"* && "$current" != "photoplaces.ru" ]]; then
  171. echo
  172. log_info "DOMAIN (основной домен, например photoplaces.ru):"
  173. read_from_tty "DOMAIN (Enter для photoplaces.ru): " domain_input
  174. if [ -n "$domain_input" ]; then
  175. if [[ "$OSTYPE" == "darwin"* ]]; then
  176. sed -i '' "s/^DOMAIN=.*/DOMAIN=${domain_input}/" "$ENV_FILE"
  177. else
  178. sed -i "s/^DOMAIN=.*/DOMAIN=${domain_input}/" "$ENV_FILE"
  179. fi
  180. export DOMAIN="$domain_input"
  181. log_ok "DOMAIN сохранён: $domain_input"
  182. else
  183. # default
  184. if [[ "$OSTYPE" == "darwin"* ]]; then
  185. sed -i '' "s/^DOMAIN=.*/DOMAIN=photoplaces.ru/" "$ENV_FILE"
  186. else
  187. sed -i "s/^DOMAIN=.*/DOMAIN=photoplaces.ru/" "$ENV_FILE"
  188. fi
  189. export DOMAIN="photoplaces.ru"
  190. log_ok "DOMAIN установлен по умолчанию: photoplaces.ru"
  191. fi
  192. fi
  193. }
  194. prompt_for_domain
  195. # Проверка обязательных переменных
  196. local required=("DB_PASSWORD" "REDIS_PASSWORD" "JWT_SECRET" "JWT_REFRESH_SECRET" "S3_SECRET_KEY" "YANDEX_MAPS_API_KEY" "DOMAIN")
  197. local missing=()
  198. for var in "${required[@]}"; do
  199. if [ -z "${!var:-}" ]; then
  200. missing+=("$var")
  201. fi
  202. done
  203. if [ ${#missing[@]} -gt 0 ]; then
  204. log_warn "Требуют ручного заполнения в $ENV_FILE:"
  205. for var in "${missing[@]}"; do
  206. echo " - $var"
  207. done
  208. log_info "Откройте $ENV_FILE и заполните недостающие значения, затем запустите скрипт снова"
  209. exit 1
  210. fi
  211. log_ok "Все секреты настроены"
  212. }
  213. # 4. Docker secrets не используем (только env vars) — функция оставлена для совместимости
  214. create_docker_secrets() {
  215. log_info "Docker secrets не используются (конфигурация через env vars)"
  216. }
  217. # 5. Деплой через docker compose
  218. deploy() {
  219. log_info "Запуск docker compose..."
  220. cd "$PROJECT_DIR"
  221. # Версия сайта (git hash) для отображения в правом нижнем углу
  222. export NEXT_PUBLIC_GIT_HASH="${NEXT_PUBLIC_GIT_HASH:-$(git rev-parse --short HEAD 2>/dev/null || echo dev)}"
  223. # Загружаем переменные окружения для docker compose
  224. if [ -f "$ENV_FILE" ]; then
  225. docker compose --env-file "$ENV_FILE" -f deploy/docker-compose.prod.yml up -d --build --remove-orphans
  226. else
  227. docker compose -f deploy/docker-compose.prod.yml up -d --build --remove-orphans
  228. fi
  229. log_ok "Контейнеры запущены"
  230. }
  231. # 6. Health checks
  232. health_check() {
  233. log_info "Ожидание готовности сервисов (до 120 сек)..."
  234. local max_attempts=60
  235. local attempt=0
  236. while [ $attempt -lt $max_attempts ]; do
  237. attempt=$((attempt + 1))
  238. # Проверка Caddy (wget есть в caddy:alpine по умолчанию)
  239. if docker compose -f deploy/docker-compose.prod.yml exec -T caddy wget -qO- http://localhost:2019/metrics >/dev/null 2>&1; then
  240. # Проверяем backend через Caddy (HTTP, SSL на Synology)
  241. if curl -sf "http://localhost/api/v1/health" >/dev/null 2>&1; then
  242. log_ok "Backend здоров"
  243. break
  244. fi
  245. fi
  246. if [ $attempt -eq $max_attempts ]; then
  247. log_err "Таймаут ожидания здоровья"
  248. docker compose -f deploy/docker-compose.prod.yml logs --tail=50 caddy backend
  249. exit 1
  250. fi
  251. sleep 2
  252. done
  253. # Финальная проверка через HTTPS (Synology)
  254. log_info "Проверка через HTTPS (Synology)..."
  255. if curl -sf "https://photoplaces.ru/api/v1/health" >/dev/null 2>&1; then
  256. log_ok "HTTPS health check пройден"
  257. else
  258. log_warn "HTTPS check не прошёл (проверьте настройки Synology Reverse Proxy)"
  259. fi
  260. }
  261. # 7. Вывод результатов
  262. show_result() {
  263. echo
  264. log_ok "=== ДЕПЛОЙ УСПЕШНО ЗАВЕРШЁН ==="
  265. echo
  266. echo " Фронтенд: https://photoplaces.ru"
  267. echo " API: https://api.photoplaces.ru/api/v1"
  268. echo " S3 API: https://api.photoplaces.ru/s3"
  269. echo " MinIO Console: https://api.photoplaces.ru/minio-console/"
  270. echo
  271. echo " Логи: cd $PROJECT_DIR && docker compose -f deploy/docker-compose.prod.yml logs -f"
  272. echo " Статус: cd $PROJECT_DIR && docker compose -f deploy/docker-compose.prod.yml ps"
  273. echo " Остановка: cd $PROJECT_DIR && docker compose -f deploy/docker-compose.prod.yml down"
  274. echo
  275. }
  276. # ===========================================
  277. # MAIN
  278. # ===========================================
  279. main() {
  280. check_deps
  281. setup_repo
  282. setup_env
  283. create_docker_secrets
  284. deploy
  285. health_check
  286. show_result
  287. }
  288. main "$@"