#!/usr/bin/env bash set -euo pipefail cd "$(dirname "$0")/.." ENV_FILE="${1:-deploy/env.prod}" echo "=== PhotoPlaces — деплой ===" # 1. Load secrets from external source if configured # Supported: 1password (op), hashicorp vault, aws secretsmanager, or local file load_secrets() { if command -v op >/dev/null 2>&1 && [ -n "${OP_VAULT:-}" ] && [ -n "${OP_ITEM:-}" ]; then echo ">>> Loading secrets from 1Password..." eval $(op read "op://${OP_VAULT}/${OP_ITEM}" --format=env 2>/dev/null || true) elif command -v vault >/dev/null 2>&1 && [ -n "${VAULT_ADDR:-}" ] && [ -n "${VAULT_TOKEN:-}" ] && [ -n "${VAULT_PATH:-}" ]; then echo ">>> Loading secrets from HashiCorp Vault..." vault kv get -format=json "${VAULT_PATH}" | jq -r '.data.data | to_entries[] | "export \(.key)=\(.value)"' > /tmp/vault_env.sh source /tmp/vault_env.sh rm -f /tmp/vault_env.sh elif command -v aws >/dev/null 2>&1 && [ -n "${AWS_SECRETS_NAME:-}" ]; then echo ">>> Loading secrets from AWS Secrets Manager..." 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 source /tmp/aws_env.sh rm -f /tmp/aws_env.sh elif [ -f "$ENV_FILE" ]; then echo ">>> Loading secrets from $ENV_FILE..." else echo "Ошибка: ни один источник секретов не настроен" echo " - 1Password: export OP_VAULT=... OP_ITEM=..." echo " - Vault: export VAULT_ADDR=... VAULT_TOKEN=... VAULT_PATH=..." echo " - AWS: export AWS_SECRETS_NAME=..." echo " - Local: создайте $ENV_FILE из deploy/env.prod.example" exit 1 fi } load_secrets # 2. If using local file, check for CHANGE_ME placeholders and offer to generate if [ -f "$ENV_FILE" ]; then set -a source "$ENV_FILE" set +a check_secret() { local var=$1 local val="${!var:-}" if [[ "$val" == *"CHANGE_ME"* ]] || [[ -z "$val" ]]; then echo "!!! $var не настроен. Сгенерировать случайный? [y/N]" read -r answer if [[ "$answer" == "y" ]]; 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 echo "✓ $var сгенерирован" export "$var=$secret" else echo "Пропущено. Не забудьте настроить вручную!" fi fi } check_secret "DB_PASSWORD" check_secret "REDIS_PASSWORD" check_secret "JWT_SECRET" check_secret "JWT_REFRESH_SECRET" check_secret "S3_SECRET_KEY" fi # 3. Validate required secrets required_vars=("DB_PASSWORD" "REDIS_PASSWORD" "JWT_SECRET" "JWT_REFRESH_SECRET" "S3_SECRET_KEY" "S3_PUBLIC_ENDPOINT" "NEXT_PUBLIC_API_URL" "ALLOWED_ORIGINS" "DOMAIN") missing=() for var in "${required_vars[@]}"; do if [ -z "${!var:-}" ]; then missing+=("$var") fi done if [ ${#missing[@]} -gt 0 ]; then echo "Ошибка: отсутствуют обязательные переменные:" printf ' %s\n' "${missing[@]}" exit 1 fi # 4. Docker secrets не используем (конфигурация через env vars) # 5. Build and deploy with --env-file echo ">>> Собираю и запускаю контейнеры..." export NEXT_PUBLIC_GIT_HASH="${NEXT_PUBLIC_GIT_HASH:-$(git rev-parse --short HEAD 2>/dev/null || echo dev)}" docker compose --env-file "$ENV_FILE" -f deploy/docker-compose.prod.yml up -d --build --remove-orphans # 6. Health check (Caddy -> backend -> migrations) # SSL на Synology, Caddy работает на HTTP (порт 80) echo ">>> Проверяю здоровье (ждём Caddy + миграции + старт)..." for i in $(seq 1 60); do # Check Caddy health (port 2019 metrics endpoint) - curl есть в alpine if docker compose -f deploy/docker-compose.prod.yml exec -T caddy curl -sf http://localhost:2019/metrics >/dev/null 2>&1; then # Caddy is up, check backend through Caddy (HTTP, SSL на Synology) 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"}') if echo "$HEALTH" | grep -q '"ok"'; then break fi fi if [ $i -eq 60 ]; then echo "✗ Таймаут ожидания здоровья API" docker compose -f deploy/docker-compose.prod.yml logs --tail=30 caddy backend exit 1 fi sleep 2 done echo "✓ Деплой успешен!" echo "Фронтенд: https://${DOMAIN}" echo "API: https://api.${DOMAIN}/api/v1" echo "S3 API: https://api.${DOMAIN}/s3 (presigned URLs)" echo "MinIO консоль: https://api.${DOMAIN}/minio-console/"