#!/bin/bash # CatLink Master Deployment Script # Полное автоматическое развертывание проекта с генерацией всех ключей и настроек set -e # Цвета для вывода RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Логирование log() { echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" } success() { echo -e "${GREEN}✅ $1${NC}" } warning() { echo -e "${YELLOW}⚠️ $1${NC}" } error() { echo -e "${RED}❌ $1${NC}" exit 1 } # Функция для генерации случайных паролей generate_password() { local length=${1:-32} openssl rand -base64 $length | tr -d "=+/" | cut -c1-$length } # Функция для генерации Django secret key generate_django_secret() { python3 -c " import secrets import string chars = string.ascii_letters + string.digits + '!@#$%^&*(-_=+)' print(''.join(secrets.choice(chars) for i in range(50))) " } # Глобальная переменная для команды Docker Compose DOCKER_COMPOSE_CMD="" # Проверка требований check_requirements() { log "🔍 Проверка системных требований..." # Проверяем Docker if ! command -v docker &> /dev/null; then error "Docker не установлен. Установите Docker и попробуйте снова." fi # Проверяем Docker Compose (v1 или v2) DOCKER_COMPOSE_CMD=$(./scripts/detect-docker-compose.sh) if [[ -z "$DOCKER_COMPOSE_CMD" ]]; then error "Docker Compose не установлен. Установите Docker Compose и попробуйте снова." fi if [[ "$DOCKER_COMPOSE_CMD" == "docker-compose" ]]; then log "✅ Найден Docker Compose v1" else log "✅ Найден Docker Compose v2" fi # Проверяем nginx if ! command -v nginx &> /dev/null; then warning "nginx не установлен. Установка nginx..." sudo apt update sudo apt install -y nginx fi # Проверяем certbot if ! command -v certbot &> /dev/null; then warning "certbot не установлен. Установка certbot..." sudo apt update sudo apt install -y certbot python3-certbot-nginx fi # Проверяем openssl if ! command -v openssl &> /dev/null; then sudo apt install -y openssl fi # Проверяем python3 if ! command -v python3 &> /dev/null; then sudo apt install -y python3 fi success "Все системные требования выполнены" } # Сбор информации от пользователя collect_deployment_info() { log "📋 Сбор информации для развертывания..." # Домен while true; do read -p "🌐 Введите основной домен (например: links.shareon.kr): " MAIN_DOMAIN if [[ $MAIN_DOMAIN =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then break else warning "Неверный формат домена. Попробуйте снова." fi done # Дополнительные домены read -p "🌐 Введите дополнительные домены через запятую (или Enter для пропуска): " ADDITIONAL_DOMAINS # Email для Let's Encrypt while true; do read -p "📧 Введите email для Let's Encrypt: " LETSENCRYPT_EMAIL if [[ $LETSENCRYPT_EMAIL =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then break else warning "Неверный формат email. Попробуйте снова." fi done # Режим SSL echo "🔒 Выберите режим SSL:" echo "1) Автоматически получить Let's Encrypt сертификаты" echo "2) Использовать самоподписанные сертификаты (для тестирования)" echo "3) Пропустить SSL (только HTTP)" read -p "Выберите опцию (1-3): " SSL_MODE # Окружение echo "🏷️ Выберите окружение:" echo "1) production (рекомендуется для живого сайта)" echo "2) staging (для тестирования)" echo "3) development (для разработки)" read -p "Выберите опцию (1-3): " ENV_MODE case $ENV_MODE in 1) ENVIRONMENT="production" ;; 2) ENVIRONMENT="staging" ;; 3) ENVIRONMENT="development" ;; *) ENVIRONMENT="production" ;; esac success "Информация собрана" } # Генерация .env файла generate_env_file() { log "⚙️ Генерация .env файла с безопасными настройками..." # Генерируем пароли DJANGO_SECRET_KEY=$(generate_django_secret) DB_PASSWORD=$(generate_password 32) # Формируем список доменов if [[ -n "$ADDITIONAL_DOMAINS" ]]; then ALL_DOMAINS="$MAIN_DOMAIN,$ADDITIONAL_DOMAINS,localhost,127.0.0.1" else ALL_DOMAINS="$MAIN_DOMAIN,localhost,127.0.0.1" fi # Определяем API URL if [[ "$SSL_MODE" == "1" ]] || [[ "$SSL_MODE" == "2" ]]; then API_URL="https://$MAIN_DOMAIN" else API_URL="http://$MAIN_DOMAIN" fi # Создаем .env файл cat > .env << EOF # ============================================================================= # CatLink Configuration - Generated $(date) # Environment: $ENVIRONMENT # ============================================================================= # Django настройки DJANGO_SECRET_KEY=$DJANGO_SECRET_KEY DJANGO_DEBUG=$([ "$ENVIRONMENT" = "development" ] && echo "True" || echo "False") DJANGO_ALLOWED_HOSTS=$ALL_DOMAINS # База данных PostgreSQL DATABASE_ENGINE=django.db.backends.postgresql DATABASE_NAME=links_db DATABASE_USER=links_user DATABASE_PASSWORD=$DB_PASSWORD DATABASE_HOST=db DATABASE_PORT=5432 # PostgreSQL настройки для контейнера POSTGRES_DB=links_db POSTGRES_USER=links_user POSTGRES_PASSWORD=$DB_PASSWORD # Frontend настройки NEXT_PUBLIC_API_URL=$API_URL # SSL/Security настройки DJANGO_SECURE_SSL_REDIRECT=$([ "$SSL_MODE" = "1" ] && echo "True" || echo "False") DJANGO_SECURE_HSTS_SECONDS=$([ "$SSL_MODE" = "1" ] && echo "31536000" || echo "0") DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS=$([ "$SSL_MODE" = "1" ] && echo "True" || echo "False") DJANGO_SECURE_HSTS_PRELOAD=$([ "$SSL_MODE" = "1" ] && echo "True" || echo "False") # Let's Encrypt настройки LETSENCRYPT_EMAIL=$LETSENCRYPT_EMAIL MAIN_DOMAIN=$MAIN_DOMAIN ADDITIONAL_DOMAINS=$ADDITIONAL_DOMAINS # Environment ENVIRONMENT=$ENVIRONMENT # Session/Cookie настройки DJANGO_SESSION_COOKIE_SECURE=$([ "$SSL_MODE" = "1" ] && echo "True" || echo "False") DJANGO_CSRF_COOKIE_SECURE=$([ "$SSL_MODE" = "1" ] && echo "True" || echo "False") # Email настройки (опционально - настройте при необходимости) # DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend # DJANGO_EMAIL_HOST=smtp.gmail.com # DJANGO_EMAIL_PORT=587 # DJANGO_EMAIL_HOST_USER=your-email@gmail.com # DJANGO_EMAIL_HOST_PASSWORD=your-app-password # DJANGO_EMAIL_USE_TLS=True # DJANGO_DEFAULT_FROM_EMAIL=noreply@$MAIN_DOMAIN # Backup настройки BACKUP_SCHEDULE="0 2 * * *" # Ежедневно в 2:00 BACKUP_RETENTION_DAYS=30 EOF success ".env файл создан с безопасными настройками" # Создаем backup .env файла cp .env ".env.backup.$(date +%Y%m%d_%H%M%S)" log "📦 Backup .env файла создан" } # Настройка nginx setup_nginx() { log "🌐 Настройка nginx..." # Создаем конфигурацию nginx sudo tee /etc/nginx/sites-available/catlink << EOF # CatLink nginx configuration # Generated $(date) server { listen 80; server_name $MAIN_DOMAIN${ADDITIONAL_DOMAINS:+ $ADDITIONAL_DOMAINS}; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; proxy_cache_bypass \$http_upgrade; # Timeout settings proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } location /api/ { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; # Timeout settings proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } location /admin/ { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; } location /static/ { proxy_pass http://127.0.0.1:8000; expires 1y; add_header Cache-Control "public, immutable"; } location /media/ { proxy_pass http://127.0.0.1:8000; expires 1y; add_header Cache-Control "public"; } # Security location ~ /\\.ht { deny all; } location = /robots.txt { proxy_pass http://127.0.0.1:8000; access_log off; } location = /favicon.ico { proxy_pass http://127.0.0.1:3000; access_log off; } } EOF # Включаем сайт sudo ln -sf /etc/nginx/sites-available/catlink /etc/nginx/sites-enabled/ # Удаляем дефолтный сайт sudo rm -f /etc/nginx/sites-enabled/default # Проверяем конфигурацию if sudo nginx -t; then success "Конфигурация nginx корректна" sudo systemctl reload nginx else error "Ошибка в конфигурации nginx" fi } # Настройка SSL setup_ssl() { if [[ "$SSL_MODE" == "1" ]]; then log "🔒 Получение Let's Encrypt сертификатов..." # Формируем список доменов для certbot CERT_DOMAINS="-d $MAIN_DOMAIN" if [[ -n "$ADDITIONAL_DOMAINS" ]]; then IFS=',' read -ra DOMAINS <<< "$ADDITIONAL_DOMAINS" for domain in "${DOMAINS[@]}"; do domain=$(echo $domain | xargs) # trim whitespace CERT_DOMAINS="$CERT_DOMAINS -d $domain" done fi # Получаем сертификаты sudo certbot --nginx $CERT_DOMAINS --email $LETSENCRYPT_EMAIL --agree-tos --non-interactive --redirect if [[ $? -eq 0 ]]; then success "SSL сертификаты получены успешно" # Настраиваем автообновление echo "0 12 * * * /usr/bin/certbot renew --quiet" | sudo crontab - log "📅 Настроено автообновление SSL сертификатов" else warning "Не удалось получить SSL сертификаты. Продолжаем без SSL." SSL_MODE="3" fi elif [[ "$SSL_MODE" == "2" ]]; then log "🔒 Создание самоподписанных SSL сертификатов..." # Создаем директорию для сертификатов sudo mkdir -p /etc/ssl/private /etc/ssl/certs # Генерируем самоподписанный сертификат sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/ssl/private/$MAIN_DOMAIN.key \ -out /etc/ssl/certs/$MAIN_DOMAIN.crt \ -subj "/C=US/ST=State/L=City/O=Organization/CN=$MAIN_DOMAIN" # Обновляем nginx конфигурацию для SSL sudo tee -a /etc/nginx/sites-available/catlink << EOF server { listen 443 ssl http2; server_name $MAIN_DOMAIN${ADDITIONAL_DOMAINS:+ $ADDITIONAL_DOMAINS}; ssl_certificate /etc/ssl/certs/$MAIN_DOMAIN.crt; ssl_certificate_key /etc/ssl/private/$MAIN_DOMAIN.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; # Redirect HTTP to HTTPS if (\$scheme != "https") { return 301 https://\$host\$request_uri; } # Include all location blocks from HTTP server include /etc/nginx/snippets/catlink-locations.conf; } EOF # Создаем общие location блоки sudo tee /etc/nginx/snippets/catlink-locations.conf << 'EOF' location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } location /api/ { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } EOF sudo systemctl reload nginx success "Самоподписанные SSL сертификаты созданы" else log "🔓 SSL пропущен - использование только HTTP" fi } # Подготовка базы данных setup_database() { log "🗄️ Настройка и проверка безопасности базы данных..." # Ждем запуска контейнеров log "⏳ Ожидание запуска контейнеров..." sleep 15 # Проверяем статус контейнеров if ! make status | grep -q "Up"; then warning "Контейнеры могут быть не готовы, ожидаем еще..." sleep 10 fi # Применяем миграции log "🔄 Применение миграций базы данных..." if ! make migrate; then error "Ошибка применения миграций" fi # Создаем суперпользователя log "👤 Настройка суперпользователя..." cat > /tmp/create_superuser.py << 'PYTHON_EOF' from django.contrib.auth import get_user_model User = get_user_model() if not User.objects.filter(username='admin').exists(): User.objects.create_superuser('admin', 'admin@example.com', 'admin123') print('✅ Суперпользователь admin создан с паролем admin123') print('⚠️ ВАЖНО: Смените пароль после первого входа!') else: print('ℹ️ Суперпользователь уже существует') PYTHON_EOF # Выполняем скрипт создания суперпользователя if make shell-exec CMD="python manage.py shell < /tmp/create_superuser.py" 2>/dev/null; then success "Суперпользователь настроен" else # Fallback - используем прямую команду $(./scripts/detect-docker-compose.sh) exec web python manage.py shell < /tmp/create_superuser.py || warning "Не удалось создать суперпользователя автоматически" fi # Удаляем временный файл rm -f /tmp/create_superuser.py from django.contrib.auth import get_user_model User = get_user_model() if not User.objects.filter(username='admin').exists(): User.objects.create_superuser('admin', 'admin@example.com', 'admin123') print("Суперпользователь 'admin' создан с паролем 'admin123'") print("ВАЖНО: Смените пароль после первого входа!") else: print("Суперпользователь уже существует") EOF # Запускаем аудит безопасности БД if [[ -f "./scripts/audit-db-security.sh" ]]; then log "🔍 Запуск аудита безопасности базы данных..." ./scripts/audit-db-security.sh fi # Применяем настройки безопасности БД if [[ -f "./scripts/setup-db-security.sh" ]]; then log "🔒 Применение настроек безопасности базы данных..." ./scripts/setup-db-security.sh fi success "База данных настроена и защищена" } # Создание backup системы setup_backup_system() { log "💾 Настройка системы резервного копирования..." # Создаем директории для backup mkdir -p backups/{database,files,configs} # Создаем скрипт backup cat > scripts/auto-backup.sh << 'EOF' #!/bin/bash # Автоматический backup CatLink set -e BACKUP_DIR="/opt/links/backups" DATE=$(date +%Y%m%d_%H%M%S) # Database backup docker exec links-db-1 pg_dump -U postgres links_db | gzip > "$BACKUP_DIR/database/links_db_$DATE.sql.gz" # Files backup tar -czf "$BACKUP_DIR/files/media_$DATE.tar.gz" backend/storage/ # Config backup cp .env "$BACKUP_DIR/configs/env_$DATE" cp docker-compose.yml "$BACKUP_DIR/configs/docker-compose_$DATE.yml" # Cleanup old backups (keep 30 days) find "$BACKUP_DIR" -name "*.gz" -mtime +30 -delete find "$BACKUP_DIR" -name "*.sql" -mtime +30 -delete echo "Backup completed: $DATE" EOF chmod +x scripts/auto-backup.sh # Добавляем в crontab (crontab -l 2>/dev/null || true; echo "0 2 * * * cd /opt/links && ./scripts/auto-backup.sh >> logs/backup.log 2>&1") | crontab - success "Система backup настроена (ежедневно в 2:00)" } # Создание мониторинга setup_monitoring() { log "📊 Настройка базового мониторинга..." # Создаем скрипт мониторинга cat > scripts/health-check.sh << 'EOF' #!/bin/bash # Health check для CatLink check_service() { if docker-compose ps | grep -q "$1.*Up"; then echo "✅ $1: OK" return 0 else echo "❌ $1: FAILED" return 1 fi } echo "🏥 CatLink Health Check - $(date)" echo "================================" check_service "web" check_service "db" check_service "frontend" echo "" echo "🌐 URL checks:" curl -s -o /dev/null -w "Frontend: %{http_code}\n" http://localhost:3000/ || echo "Frontend: FAILED" curl -s -o /dev/null -w "Backend: %{http_code}\n" http://localhost:8000/api/ || echo "Backend: FAILED" echo "" echo "💾 Disk usage:" df -h | grep -E "/(|opt|var)" echo "" echo "🐳 Docker resources:" docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}" EOF chmod +x scripts/health-check.sh success "Мониторинг настроен (./scripts/health-check.sh)" } # Финальная проверка final_verification() { log "🔍 Финальная проверка развертывания..." # Проверяем контейнеры if ! docker-compose ps | grep -q "Up"; then error "Контейнеры не запущены" fi # Проверяем nginx if ! sudo systemctl is-active --quiet nginx; then error "nginx не запущен" fi # Проверяем доступность сервисов sleep 10 if curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/ | grep -q "200"; then success "Frontend доступен" else warning "Frontend может быть недоступен" fi if curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/api/ | grep -q "200\|404"; then success "Backend доступен" else warning "Backend может быть недоступен" fi success "Развертывание завершено успешно!" } # Отображение итогов show_deployment_summary() { echo "" echo "🎉 ===== РАЗВЕРТЫВАНИЕ ЗАВЕРШЕНО ===== 🎉" echo "" echo "📋 Информация о развертывании:" echo " 🌐 Основной домен: $MAIN_DOMAIN" echo " 🔒 SSL режим: $([ "$SSL_MODE" = "1" ] && echo "Let's Encrypt" || [ "$SSL_MODE" = "2" ] && echo "Self-signed" || echo "Disabled")" echo " 🏷️ Окружение: $ENVIRONMENT" echo " 📧 Email: $LETSENCRYPT_EMAIL" echo "" echo "🔗 Ссылки:" if [[ "$SSL_MODE" == "1" ]] || [[ "$SSL_MODE" == "2" ]]; then echo " 🏠 Сайт: https://$MAIN_DOMAIN" echo " 🔧 Админка: https://$MAIN_DOMAIN/admin/" else echo " 🏠 Сайт: http://$MAIN_DOMAIN" echo " 🔧 Админка: http://$MAIN_DOMAIN/admin/" fi echo "" echo "👤 Учетные данные:" echo " 👨‍💼 Админ: admin / admin123 (СМЕНИТЕ ПАРОЛЬ!)" echo "" echo "📂 Важные файлы:" echo " ⚙️ Конфигурация: .env" echo " 💾 Backup: backups/" echo " 📊 Логи: logs/" echo "" echo "🛠️ Полезные команды:" echo " 📊 Проверка здоровья: ./scripts/health-check.sh" echo " 💾 Backup: ./scripts/auto-backup.sh" echo " 🔍 Аудит БД: make security-audit" echo " 📝 Логи: make logs" echo " 🔄 Перезапуск: make restart" echo "" echo "🆘 Поддержка:" echo " 📖 Документация: README.md" echo " 🔒 Безопасность: SECURITY.md" echo "" success "Проект готов к использованию!" } # Основная функция main() { echo "🚀 ========================================" echo "🚀 CatLink Master Deployment Script " echo "🚀 ========================================" echo "" # Проверяем что мы в правильной директории if [ ! -f "docker-compose.yml" ]; then error "Файл docker-compose.yml не найден. Запустите скрипт из корня проекта." fi # Создаем необходимые директории mkdir -p {logs,backups,scripts} # Основной процесс развертывания check_requirements collect_deployment_info generate_env_file setup_nginx log "🏗️ Сборка и запуск контейнеров..." # Используем Makefile команды которые автоматически определяют Docker Compose if ! make build-prod; then error "Ошибка сборки контейнеров" fi if ! make up-prod; then error "Ошибка запуска контейнеров" fi setup_database setup_ssl setup_backup_system setup_monitoring final_verification show_deployment_summary echo "" echo "🎊 Развертывание CatLink завершено успешно!" } # Обработка сигналов trap 'error "Развертывание прервано пользователем"' INT TERM # Запуск main "$@"