🚀 Добавлен мастер-скрипт развертывания с полной автоматизацией
Some checks failed
continuous-integration/drone/push Build is failing

 Новые возможности:
- Мастер-развертывание с автоматической настройкой всех компонентов
- Генерация безопасных .env файлов с криптографически стойкими ключами
- Полная изоляция и защита PostgreSQL
- Автоматическая настройка Let's Encrypt SSL
- Система backup и мониторинга
- Comprehensive security audit для БД

🔧 Новые команды:
- make deploy - мастер-развертывание
- make pre-deploy-check - проверка готовности системы
- make security-audit - аудит безопасности PostgreSQL
- make ssl-setup - интерактивная настройка SSL
- make update-production-security - безопасное обновление в продакшене

📁 Новые файлы:
- scripts/master-deploy.sh - основной скрипт развертывания
- scripts/pre-deploy-check.sh - проверка системы
- scripts/ssl-manager.sh - управление SSL сертификатами
- scripts/audit-db-security.sh - аудит безопасности БД
- DEPLOYMENT.md - полное руководство по развертыванию
- COMMANDS.md - справочник команд
- SECURITY.md - документация по безопасности

🔒 Улучшения безопасности:
- Изоляция PostgreSQL в Docker сети (без внешних портов)
- SCRAM-SHA-256 аутентификация
- TLSv1.3 для БД соединений
- Удаление прав суперпользователя у приложения
- Детальное логирование всех операций БД
- Security headers в nginx
- Автообновление SSL сертификатов
This commit is contained in:
2025-11-04 14:07:58 +09:00
parent 735c1984f9
commit e1bb1ab90a
7 changed files with 1933 additions and 22 deletions

681
scripts/master-deploy.sh Executable file
View File

@@ -0,0 +1,681 @@
#!/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)))
"
}
# Проверка требований
check_requirements() {
log "🔍 Проверка системных требований..."
# Проверяем Docker
if ! command -v docker &> /dev/null; then
error "Docker не установлен. Установите Docker и попробуйте снова."
fi
# Проверяем Docker Compose
if ! command -v docker-compose &> /dev/null; then
error "Docker Compose не установлен. Установите Docker Compose и попробуйте снова."
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 "🗄️ Настройка и проверка безопасности базы данных..."
# Проверяем что контейнеры запущены
if ! docker-compose ps | grep -q "Up"; then
log "Запуск контейнеров для настройки БД..."
docker-compose up -d
sleep 15
fi
# Применяем миграции
log "🔄 Применение миграций базы данных..."
docker-compose exec -T web python manage.py migrate
# Создаем суперпользователя (если не существует)
log "👤 Создание суперпользователя..."
docker-compose exec -T web python manage.py shell << '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("Суперпользователь уже существует")
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 "🏗️ Сборка и запуск контейнеров..."
make build-prod
make up-prod
setup_database
setup_ssl
setup_backup_system
setup_monitoring
final_verification
show_deployment_summary
echo ""
echo "🎊 Развертывание CatLink завершено успешно!"
}
# Обработка сигналов
trap 'error "Развертывание прервано пользователем"' INT TERM
# Запуск
main "$@"