🚀 Добавлен мастер-скрипт развертывания с полной автоматизацией
Some checks failed
continuous-integration/drone/push Build is failing
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:
681
scripts/master-deploy.sh
Executable file
681
scripts/master-deploy.sh
Executable 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 "$@"
|
||||
240
scripts/pre-deploy-check.sh
Executable file
240
scripts/pre-deploy-check.sh
Executable file
@@ -0,0 +1,240 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Pre-deployment system check for CatLink
|
||||
# Проверяет готовность системы к развертыванию
|
||||
|
||||
set -e
|
||||
|
||||
# Цвета
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
success() { echo -e "${GREEN}✅ $1${NC}"; }
|
||||
warning() { echo -e "${YELLOW}⚠️ $1${NC}"; }
|
||||
error() { echo -e "${RED}❌ $1${NC}"; }
|
||||
info() { echo -e "${BLUE}ℹ️ $1${NC}"; }
|
||||
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
|
||||
check_and_report() {
|
||||
if $1; then
|
||||
success "$2"
|
||||
else
|
||||
error "$2"
|
||||
((ERRORS++))
|
||||
fi
|
||||
}
|
||||
|
||||
check_and_warn() {
|
||||
if $1; then
|
||||
success "$2"
|
||||
else
|
||||
warning "$2"
|
||||
((WARNINGS++))
|
||||
fi
|
||||
}
|
||||
|
||||
echo "🔍 ===== CatLink Pre-Deployment Check ====="
|
||||
echo ""
|
||||
|
||||
# Проверка операционной системы
|
||||
info "Проверка операционной системы..."
|
||||
OS=$(lsb_release -si 2>/dev/null || echo "Unknown")
|
||||
VERSION=$(lsb_release -sr 2>/dev/null || echo "Unknown")
|
||||
echo " OS: $OS $VERSION"
|
||||
|
||||
# Проверка архитектуры
|
||||
ARCH=$(uname -m)
|
||||
echo " Architecture: $ARCH"
|
||||
|
||||
# Проверка прав sudo
|
||||
check_and_report "sudo -n true 2>/dev/null" "Права sudo доступны"
|
||||
|
||||
# Проверка свободного места
|
||||
info "Проверка дискового пространства..."
|
||||
DISK_SPACE=$(df / | awk 'NR==2 {print $4}')
|
||||
DISK_SPACE_GB=$((DISK_SPACE / 1024 / 1024))
|
||||
echo " Свободно: ${DISK_SPACE_GB}GB"
|
||||
|
||||
if [ $DISK_SPACE_GB -lt 5 ]; then
|
||||
error "Недостаточно места на диске (минимум 5GB)"
|
||||
((ERRORS++))
|
||||
elif [ $DISK_SPACE_GB -lt 10 ]; then
|
||||
warning "Мало места на диске (рекомендуется минимум 10GB)"
|
||||
((WARNINGS++))
|
||||
else
|
||||
success "Достаточно места на диске"
|
||||
fi
|
||||
|
||||
# Проверка RAM
|
||||
info "Проверка оперативной памяти..."
|
||||
RAM_MB=$(free -m | awk 'NR==2{print $2}')
|
||||
RAM_GB=$((RAM_MB / 1024))
|
||||
echo " RAM: ${RAM_GB}GB (${RAM_MB}MB)"
|
||||
|
||||
if [ $RAM_MB -lt 1024 ]; then
|
||||
error "Недостаточно RAM (минимум 1GB)"
|
||||
((ERRORS++))
|
||||
elif [ $RAM_MB -lt 2048 ]; then
|
||||
warning "Мало RAM (рекомендуется минимум 2GB)"
|
||||
((WARNINGS++))
|
||||
else
|
||||
success "Достаточно RAM"
|
||||
fi
|
||||
|
||||
# Проверка Docker
|
||||
echo ""
|
||||
info "Проверка Docker..."
|
||||
check_and_report "command -v docker >/dev/null 2>&1" "Docker установлен"
|
||||
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
DOCKER_VERSION=$(docker --version | cut -d' ' -f3 | cut -d',' -f1)
|
||||
echo " Версия Docker: $DOCKER_VERSION"
|
||||
|
||||
check_and_report "docker ps >/dev/null 2>&1" "Docker daemon запущен"
|
||||
check_and_report "docker info | grep -q 'Server Version'" "Docker доступен без sudo"
|
||||
fi
|
||||
|
||||
# Проверка Docker Compose
|
||||
check_and_report "command -v docker-compose >/dev/null 2>&1" "Docker Compose установлен"
|
||||
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
COMPOSE_VERSION=$(docker-compose --version | cut -d' ' -f3 | cut -d',' -f1)
|
||||
echo " Версия Docker Compose: $COMPOSE_VERSION"
|
||||
fi
|
||||
|
||||
# Проверка портов
|
||||
echo ""
|
||||
info "Проверка портов..."
|
||||
check_port() {
|
||||
! netstat -tuln 2>/dev/null | grep -q ":$1 " && ! ss -tuln 2>/dev/null | grep -q ":$1 "
|
||||
}
|
||||
|
||||
check_and_warn "check_port 80" "Порт 80 свободен"
|
||||
check_and_warn "check_port 443" "Порт 443 свободен"
|
||||
check_and_warn "check_port 3000" "Порт 3000 свободен"
|
||||
check_and_warn "check_port 8000" "Порт 8000 свободен"
|
||||
check_and_warn "check_port 5432" "Порт 5432 свободен"
|
||||
|
||||
# Проверка nginx
|
||||
echo ""
|
||||
info "Проверка nginx..."
|
||||
if command -v nginx >/dev/null 2>&1; then
|
||||
NGINX_VERSION=$(nginx -v 2>&1 | cut -d' ' -f3)
|
||||
success "nginx установлен ($NGINX_VERSION)"
|
||||
|
||||
if systemctl is-active --quiet nginx; then
|
||||
warning "nginx уже запущен (может потребоваться перенастройка)"
|
||||
else
|
||||
success "nginx остановлен (готов к настройке)"
|
||||
fi
|
||||
else
|
||||
warning "nginx не установлен (будет установлен автоматически)"
|
||||
fi
|
||||
|
||||
# Проверка certbot
|
||||
check_and_warn "command -v certbot >/dev/null 2>&1" "certbot установлен (будет установлен при необходимости)"
|
||||
|
||||
# Проверка Python
|
||||
echo ""
|
||||
info "Проверка Python..."
|
||||
check_and_report "command -v python3 >/dev/null 2>&1" "Python 3 установлен"
|
||||
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
PYTHON_VERSION=$(python3 --version | cut -d' ' -f2)
|
||||
echo " Версия Python: $PYTHON_VERSION"
|
||||
fi
|
||||
|
||||
# Проверка openssl
|
||||
check_and_report "command -v openssl >/dev/null 2>&1" "OpenSSL установлен"
|
||||
|
||||
# Проверка файлов проекта
|
||||
echo ""
|
||||
info "Проверка файлов проекта..."
|
||||
check_and_report "[ -f 'docker-compose.yml' ]" "docker-compose.yml найден"
|
||||
check_and_report "[ -f 'Makefile' ]" "Makefile найден"
|
||||
check_and_report "[ -d 'backend' ]" "Директория backend найдена"
|
||||
check_and_report "[ -d 'frontend' ]" "Директория frontend найдена"
|
||||
|
||||
# Проверка git
|
||||
if [ -d ".git" ]; then
|
||||
success "Git репозиторий найден"
|
||||
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
||||
echo " Текущая ветка: $BRANCH"
|
||||
|
||||
if git status --porcelain | grep -q .; then
|
||||
warning "Есть неcommitted изменения"
|
||||
else
|
||||
success "Рабочая директория чистая"
|
||||
fi
|
||||
else
|
||||
warning "Не Git репозиторий"
|
||||
fi
|
||||
|
||||
# Проверка интернет соединения
|
||||
echo ""
|
||||
info "Проверка интернет соединения..."
|
||||
check_and_report "ping -c 1 8.8.8.8 >/dev/null 2>&1" "Интернет соединение доступно"
|
||||
check_and_report "ping -c 1 docker.io >/dev/null 2>&1" "Docker Hub доступен"
|
||||
|
||||
# Проверка DNS
|
||||
if command -v nslookup >/dev/null 2>&1; then
|
||||
check_and_report "nslookup google.com >/dev/null 2>&1" "DNS работает"
|
||||
fi
|
||||
|
||||
# Проверка firewall
|
||||
echo ""
|
||||
info "Проверка firewall..."
|
||||
if command -v ufw >/dev/null 2>&1; then
|
||||
if ufw status | grep -q "Status: active"; then
|
||||
warning "UFW активен (может блокировать порты)"
|
||||
echo " Убедитесь что порты 80, 443 открыты"
|
||||
else
|
||||
info "UFW неактивен"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Проверка SELinux (если есть)
|
||||
if command -v getenforce >/dev/null 2>&1; then
|
||||
SELINUX_STATUS=$(getenforce 2>/dev/null || echo "Unknown")
|
||||
if [ "$SELINUX_STATUS" = "Enforcing" ]; then
|
||||
warning "SELinux в режиме Enforcing (может блокировать Docker)"
|
||||
else
|
||||
info "SELinux: $SELINUX_STATUS"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Итоговая сводка
|
||||
echo ""
|
||||
echo "📊 ===== ИТОГОВАЯ СВОДКА ====="
|
||||
if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
|
||||
success "Система полностью готова к развертыванию!"
|
||||
echo ""
|
||||
echo "🚀 Можете запускать: make deploy"
|
||||
elif [ $ERRORS -eq 0 ]; then
|
||||
warning "Система готова к развертыванию с предупреждениями ($WARNINGS)"
|
||||
echo ""
|
||||
echo "⚡ Можете запускать: make deploy"
|
||||
echo " (предупреждения будут обработаны автоматически)"
|
||||
else
|
||||
error "Обнаружены критические проблемы ($ERRORS ошибок, $WARNINGS предупреждений)"
|
||||
echo ""
|
||||
echo "🔧 Исправьте ошибки перед развертыванием:"
|
||||
echo " - Установите недостающие компоненты"
|
||||
echo " - Освободите дисковое пространство"
|
||||
echo " - Настройте права доступа"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "💡 Полезные команды:"
|
||||
echo " make help - Показать все доступные команды"
|
||||
echo " make deploy - Запустить полное развертывание"
|
||||
echo " make security-audit - Проверить безопасность"
|
||||
echo " make logs - Посмотреть логи"
|
||||
|
||||
exit 0
|
||||
423
scripts/ssl-manager.sh
Executable file
423
scripts/ssl-manager.sh
Executable file
@@ -0,0 +1,423 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SSL Certificate Management для CatLink
|
||||
# Автоматическая настройка SSL с Let's Encrypt и управление сертификатами
|
||||
|
||||
set -e
|
||||
|
||||
# Цвета
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
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; }
|
||||
|
||||
# Проверка переменных окружения
|
||||
check_env() {
|
||||
if [ ! -f ".env" ]; then
|
||||
error ".env файл не найден. Запустите make deploy для его создания."
|
||||
fi
|
||||
|
||||
source .env
|
||||
|
||||
if [ -z "$MAIN_DOMAIN" ]; then
|
||||
error "MAIN_DOMAIN не задан в .env файле"
|
||||
fi
|
||||
|
||||
if [ -z "$LETSENCRYPT_EMAIL" ]; then
|
||||
error "LETSENCRYPT_EMAIL не задан в .env файле"
|
||||
fi
|
||||
}
|
||||
|
||||
# Установка certbot если не установлен
|
||||
install_certbot() {
|
||||
if ! command -v certbot &> /dev/null; then
|
||||
log "Установка certbot..."
|
||||
sudo apt update
|
||||
sudo apt install -y certbot python3-certbot-nginx
|
||||
success "certbot установлен"
|
||||
else
|
||||
success "certbot уже установлен"
|
||||
fi
|
||||
}
|
||||
|
||||
# Проверка DNS записей
|
||||
check_dns() {
|
||||
log "Проверка DNS записей для $MAIN_DOMAIN..."
|
||||
|
||||
# Получаем внешний IP сервера
|
||||
SERVER_IP=$(curl -s https://ipinfo.io/ip || curl -s https://icanhazip.com || echo "unknown")
|
||||
log "IP сервера: $SERVER_IP"
|
||||
|
||||
# Проверяем A запись
|
||||
DOMAIN_IP=$(nslookup $MAIN_DOMAIN | grep -A 1 "Name:" | tail -n1 | awk '{print $2}' || echo "unknown")
|
||||
log "IP домена $MAIN_DOMAIN: $DOMAIN_IP"
|
||||
|
||||
if [ "$SERVER_IP" = "$DOMAIN_IP" ]; then
|
||||
success "DNS запись настроена корректно"
|
||||
return 0
|
||||
else
|
||||
warning "DNS запись может быть настроена неверно"
|
||||
warning "Сервер: $SERVER_IP, Домен: $DOMAIN_IP"
|
||||
read -p "Продолжить? (yes/no): " CONTINUE
|
||||
if [ "$CONTINUE" != "yes" ]; then
|
||||
error "Настройте DNS запись и попробуйте снова"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Получение Let's Encrypt сертификата
|
||||
obtain_letsencrypt() {
|
||||
log "Получение Let's Encrypt сертификата для $MAIN_DOMAIN..."
|
||||
|
||||
# Формируем список доменов
|
||||
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
|
||||
if [ -n "$domain" ]; then
|
||||
CERT_DOMAINS="$CERT_DOMAINS -d $domain"
|
||||
log "Добавлен домен: $domain"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
log "Домены для сертификата: $CERT_DOMAINS"
|
||||
|
||||
# Временно останавливаем nginx для standalone режима
|
||||
if systemctl is-active --quiet nginx; then
|
||||
log "Остановка nginx для получения сертификата..."
|
||||
sudo systemctl stop nginx
|
||||
NGINX_WAS_RUNNING=true
|
||||
else
|
||||
NGINX_WAS_RUNNING=false
|
||||
fi
|
||||
|
||||
# Получаем сертификат в standalone режиме
|
||||
if sudo certbot certonly --standalone \
|
||||
$CERT_DOMAINS \
|
||||
--email "$LETSENCRYPT_EMAIL" \
|
||||
--agree-tos \
|
||||
--non-interactive \
|
||||
--expand; then
|
||||
|
||||
success "Сертификат получен успешно"
|
||||
|
||||
# Запускаем nginx обратно
|
||||
if [ "$NGINX_WAS_RUNNING" = true ]; then
|
||||
sudo systemctl start nginx
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
error "Не удалось получить сертификат"
|
||||
fi
|
||||
}
|
||||
|
||||
# Создание самоподписанного сертификата
|
||||
create_selfsigned() {
|
||||
log "Создание самоподписанного сертификата для $MAIN_DOMAIN..."
|
||||
|
||||
# Создаем директории
|
||||
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=CatLink/CN=${MAIN_DOMAIN}" \
|
||||
-extensions v3_req \
|
||||
-config <(cat <<EOF
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
CN = ${MAIN_DOMAIN}
|
||||
|
||||
[v3_req]
|
||||
keyUsage = keyEncipherment, dataEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = ${MAIN_DOMAIN}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Устанавливаем права
|
||||
sudo chmod 600 "/etc/ssl/private/${MAIN_DOMAIN}.key"
|
||||
sudo chmod 644 "/etc/ssl/certs/${MAIN_DOMAIN}.crt"
|
||||
|
||||
success "Самоподписанный сертификат создан"
|
||||
}
|
||||
|
||||
# Обновление nginx конфигурации для SSL
|
||||
update_nginx_ssl() {
|
||||
local ssl_type=$1
|
||||
|
||||
log "Обновление nginx конфигурации для SSL ($ssl_type)..."
|
||||
|
||||
if [ "$ssl_type" = "letsencrypt" ]; then
|
||||
SSL_CERT="/etc/letsencrypt/live/${MAIN_DOMAIN}/fullchain.pem"
|
||||
SSL_KEY="/etc/letsencrypt/live/${MAIN_DOMAIN}/privkey.pem"
|
||||
else
|
||||
SSL_CERT="/etc/ssl/certs/${MAIN_DOMAIN}.crt"
|
||||
SSL_KEY="/etc/ssl/private/${MAIN_DOMAIN}.key"
|
||||
fi
|
||||
|
||||
# Создаем SSL конфигурацию
|
||||
sudo tee /etc/nginx/sites-available/catlink-ssl << EOF
|
||||
# CatLink SSL nginx configuration
|
||||
# Generated $(date)
|
||||
|
||||
# HTTP -> HTTPS redirect
|
||||
server {
|
||||
listen 80;
|
||||
server_name $MAIN_DOMAIN${ADDITIONAL_DOMAINS:+ $ADDITIONAL_DOMAINS};
|
||||
|
||||
# Let's Encrypt challenge
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/html;
|
||||
}
|
||||
|
||||
# Redirect all other requests to HTTPS
|
||||
location / {
|
||||
return 301 https://\$server_name\$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS server
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name $MAIN_DOMAIN${ADDITIONAL_DOMAINS:+ $ADDITIONAL_DOMAINS};
|
||||
|
||||
# SSL Configuration
|
||||
ssl_certificate $SSL_CERT;
|
||||
ssl_certificate_key $SSL_KEY;
|
||||
|
||||
# SSL Security
|
||||
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:ECDHE-RSA-AES256-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||
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;
|
||||
|
||||
# Frontend (Next.js)
|
||||
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;
|
||||
}
|
||||
|
||||
# Backend API
|
||||
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;
|
||||
}
|
||||
|
||||
# Admin interface
|
||||
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;
|
||||
}
|
||||
|
||||
# Static files
|
||||
location /static/ {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Media files
|
||||
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
|
||||
|
||||
# Включаем SSL конфигурацию
|
||||
sudo ln -sf /etc/nginx/sites-available/catlink-ssl /etc/nginx/sites-enabled/catlink
|
||||
|
||||
# Проверяем конфигурацию
|
||||
if sudo nginx -t; then
|
||||
success "Конфигурация nginx обновлена"
|
||||
sudo systemctl reload nginx
|
||||
else
|
||||
error "Ошибка в конфигурации nginx"
|
||||
fi
|
||||
}
|
||||
|
||||
# Настройка автообновления сертификатов
|
||||
setup_auto_renewal() {
|
||||
log "Настройка автообновления Let's Encrypt сертификатов..."
|
||||
|
||||
# Создаем скрипт обновления
|
||||
sudo tee /usr/local/bin/certbot-renew-catlink << 'EOF'
|
||||
#!/bin/bash
|
||||
# CatLink SSL certificate renewal script
|
||||
|
||||
certbot renew --quiet --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"
|
||||
|
||||
# Проверяем успешность обновления
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "$(date): SSL certificates renewed successfully" >> /var/log/catlink-ssl.log
|
||||
else
|
||||
echo "$(date): SSL certificate renewal failed" >> /var/log/catlink-ssl.log
|
||||
fi
|
||||
EOF
|
||||
|
||||
sudo chmod +x /usr/local/bin/certbot-renew-catlink
|
||||
|
||||
# Добавляем в crontab
|
||||
(sudo crontab -l 2>/dev/null | grep -v certbot-renew-catlink; echo "0 12 * * * /usr/local/bin/certbot-renew-catlink") | sudo crontab -
|
||||
|
||||
success "Автообновление сертификатов настроено"
|
||||
}
|
||||
|
||||
# Проверка статуса SSL
|
||||
check_ssl_status() {
|
||||
log "Проверка статуса SSL..."
|
||||
|
||||
if [ -f "/etc/letsencrypt/live/${MAIN_DOMAIN}/fullchain.pem" ]; then
|
||||
CERT_EXPIRY=$(openssl x509 -enddate -noout -in "/etc/letsencrypt/live/${MAIN_DOMAIN}/fullchain.pem" | cut -d= -f2)
|
||||
success "Let's Encrypt сертификат активен до: $CERT_EXPIRY"
|
||||
elif [ -f "/etc/ssl/certs/${MAIN_DOMAIN}.crt" ]; then
|
||||
CERT_EXPIRY=$(openssl x509 -enddate -noout -in "/etc/ssl/certs/${MAIN_DOMAIN}.crt" | cut -d= -f2)
|
||||
warning "Самоподписанный сертификат активен до: $CERT_EXPIRY"
|
||||
else
|
||||
error "SSL сертификат не найден"
|
||||
fi
|
||||
|
||||
# Проверяем HTTPS соединение
|
||||
if command -v curl &> /dev/null; then
|
||||
if curl -s -I "https://${MAIN_DOMAIN}" | grep -q "HTTP"; then
|
||||
success "HTTPS соединение работает"
|
||||
else
|
||||
warning "HTTPS соединение может не работать"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Основное меню
|
||||
main() {
|
||||
echo "🔒 ===== CatLink SSL Certificate Manager ====="
|
||||
echo ""
|
||||
|
||||
check_env
|
||||
|
||||
echo "Выберите действие:"
|
||||
echo "1) Получить Let's Encrypt сертификат (рекомендуется)"
|
||||
echo "2) Создать самоподписанный сертификат (для тестирования)"
|
||||
echo "3) Проверить статус существующих сертификатов"
|
||||
echo "4) Настроить автообновление"
|
||||
echo "5) Обновить nginx конфигурацию"
|
||||
echo ""
|
||||
read -p "Выберите опцию (1-5): " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
install_certbot
|
||||
check_dns
|
||||
obtain_letsencrypt
|
||||
update_nginx_ssl "letsencrypt"
|
||||
setup_auto_renewal
|
||||
check_ssl_status
|
||||
;;
|
||||
2)
|
||||
create_selfsigned
|
||||
update_nginx_ssl "selfsigned"
|
||||
check_ssl_status
|
||||
;;
|
||||
3)
|
||||
check_ssl_status
|
||||
;;
|
||||
4)
|
||||
setup_auto_renewal
|
||||
;;
|
||||
5)
|
||||
echo "Какой тип SSL использовать?"
|
||||
echo "1) Let's Encrypt"
|
||||
echo "2) Самоподписанный"
|
||||
read -p "Выберите (1-2): " ssl_type
|
||||
if [ "$ssl_type" = "1" ]; then
|
||||
update_nginx_ssl "letsencrypt"
|
||||
else
|
||||
update_nginx_ssl "selfsigned"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
error "Неверный выбор"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
success "SSL настройка завершена!"
|
||||
echo ""
|
||||
echo "🌐 Проверьте сайт: https://${MAIN_DOMAIN}"
|
||||
}
|
||||
|
||||
# Запуск
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user