deploy-remote.sh 11 KB

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