deploy.sh 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. cd "$(dirname "$0")/.."
  4. ENV_FILE="${1:-deploy/env.prod}"
  5. echo "=== PhotoPlaces — деплой ==="
  6. # 1. Load secrets from external source if configured
  7. # Supported: 1password (op), hashicorp vault, aws secretsmanager, or local file
  8. load_secrets() {
  9. if command -v op >/dev/null 2>&1 && [ -n "${OP_VAULT:-}" ] && [ -n "${OP_ITEM:-}" ]; then
  10. echo ">>> Loading secrets from 1Password..."
  11. eval $(op read "op://${OP_VAULT}/${OP_ITEM}" --format=env 2>/dev/null || true)
  12. elif command -v vault >/dev/null 2>&1 && [ -n "${VAULT_ADDR:-}" ] && [ -n "${VAULT_TOKEN:-}" ] && [ -n "${VAULT_PATH:-}" ]; then
  13. echo ">>> Loading secrets from HashiCorp Vault..."
  14. vault kv get -format=json "${VAULT_PATH}" | jq -r '.data.data | to_entries[] | "export \(.key)=\(.value)"' > /tmp/vault_env.sh
  15. source /tmp/vault_env.sh
  16. rm -f /tmp/vault_env.sh
  17. elif command -v aws >/dev/null 2>&1 && [ -n "${AWS_SECRETS_NAME:-}" ]; then
  18. echo ">>> Loading secrets from AWS Secrets Manager..."
  19. aws secretsmanager get-secret-value --secret-id "${AWS_SECRETS_NAME}" --query SecretString --output text | jq -r 'to_entries[] | "export \(.key)=\(.value)"' > /tmp/aws_env.sh
  20. source /tmp/aws_env.sh
  21. rm -f /tmp/aws_env.sh
  22. elif [ -f "$ENV_FILE" ]; then
  23. echo ">>> Loading secrets from $ENV_FILE..."
  24. else
  25. echo "Ошибка: ни один источник секретов не настроен"
  26. echo " - 1Password: export OP_VAULT=... OP_ITEM=..."
  27. echo " - Vault: export VAULT_ADDR=... VAULT_TOKEN=... VAULT_PATH=..."
  28. echo " - AWS: export AWS_SECRETS_NAME=..."
  29. echo " - Local: создайте $ENV_FILE из deploy/env.prod.example"
  30. exit 1
  31. fi
  32. }
  33. load_secrets
  34. # 2. If using local file, check for CHANGE_ME placeholders and offer to generate
  35. if [ -f "$ENV_FILE" ]; then
  36. set -a
  37. source "$ENV_FILE"
  38. set +a
  39. check_secret() {
  40. local var=$1
  41. local val="${!var:-}"
  42. if [[ "$val" == *"CHANGE_ME"* ]] || [[ -z "$val" ]]; then
  43. echo "!!! $var не настроен. Сгенерировать случайный? [y/N]"
  44. read -r answer
  45. if [[ "$answer" == "y" ]]; then
  46. local secret
  47. secret=$(openssl rand -hex 32)
  48. if [[ "$OSTYPE" == "darwin"* ]]; then
  49. sed -i '' "s/^${var}=.*/${var}=${secret}/" "$ENV_FILE"
  50. else
  51. sed -i "s/^${var}=.*/${var}=${secret}/" "$ENV_FILE"
  52. fi
  53. echo "✓ $var сгенерирован"
  54. export "$var=$secret"
  55. else
  56. echo "Пропущено. Не забудьте настроить вручную!"
  57. fi
  58. fi
  59. }
  60. check_secret "DB_PASSWORD"
  61. check_secret "REDIS_PASSWORD"
  62. check_secret "JWT_SECRET"
  63. check_secret "JWT_REFRESH_SECRET"
  64. check_secret "S3_SECRET_KEY"
  65. fi
  66. # 3. Validate required secrets
  67. required_vars=("DB_PASSWORD" "REDIS_PASSWORD" "JWT_SECRET" "JWT_REFRESH_SECRET" "S3_SECRET_KEY" "S3_PUBLIC_ENDPOINT" "NEXT_PUBLIC_API_URL" "ALLOWED_ORIGINS" "DOMAIN")
  68. missing=()
  69. for var in "${required_vars[@]}"; do
  70. if [ -z "${!var:-}" ]; then
  71. missing+=("$var")
  72. fi
  73. done
  74. if [ ${#missing[@]} -gt 0 ]; then
  75. echo "Ошибка: отсутствуют обязательные переменные:"
  76. printf ' %s\n' "${missing[@]}"
  77. exit 1
  78. fi
  79. # 4. Docker secrets не используем (конфигурация через env vars)
  80. # 5. Build and deploy with --env-file
  81. echo ">>> Собираю и запускаю контейнеры..."
  82. docker compose --env-file "$ENV_FILE" -f deploy/docker-compose.prod.yml up -d --build --remove-orphans
  83. # 6. Health check (Caddy -> backend -> migrations)
  84. # SSL на Synology, Caddy работает на HTTP (порт 80)
  85. echo ">>> Проверяю здоровье (ждём Caddy + миграции + старт)..."
  86. for i in $(seq 1 60); do
  87. # Check Caddy health (port 2019 metrics endpoint) - curl есть в alpine
  88. if docker compose -f deploy/docker-compose.prod.yml exec -T caddy curl -sf http://localhost:2019/metrics >/dev/null 2>&1; then
  89. # Caddy is up, check backend through Caddy (HTTP, SSL на Synology)
  90. HEALTH=$(curl -sf "http://localhost/api/v1/health" 2>/dev/null || curl -sf http://localhost:8080/api/v1/health 2>/dev/null || echo '{"status":"fail"}')
  91. if echo "$HEALTH" | grep -q '"ok"'; then
  92. break
  93. fi
  94. fi
  95. if [ $i -eq 60 ]; then
  96. echo "✗ Таймаут ожидания здоровья API"
  97. docker compose -f deploy/docker-compose.prod.yml logs --tail=30 caddy backend
  98. exit 1
  99. fi
  100. sleep 2
  101. done
  102. echo "✓ Деплой успешен!"
  103. echo "Фронтенд: https://${DOMAIN}"
  104. echo "API: https://api.${DOMAIN}/api/v1"
  105. echo "S3 API: https://api.${DOMAIN}/s3 (presigned URLs)"
  106. echo "MinIO консоль: https://api.${DOMAIN}/minio-console/"