#!/bin/bash # ═══════════════════════════════════════════════════════════════ # Vortex Messenger — полный деплой на чистый VPS # ═══════════════════════════════════════════════════════════════ # # Требования: # - Чистый VPS с Ubuntu 22.04 или 24.04 # - Домен с A-записью, направленной на IP сервера # # Инструкция: # 1. Загрузите весь проект на сервер: # scp -r ./* root@:/var/www/vortex/ # 2. Зайдите на сервер: ssh root@ # 3. Запустите: # chmod +x /var/www/vortex/deploy.sh # /var/www/vortex/deploy.sh # # ═══════════════════════════════════════════════════════════════ set -e # ─── Цвета ─── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' print_step() { echo -e "\n${CYAN}━━━ $1 ━━━${NC}\n"; } print_ok() { echo -e "${GREEN}✅ $1${NC}"; } print_warn() { echo -e "${YELLOW}⚠️ $1${NC}"; } print_error(){ echo -e "${RED}❌ $1${NC}"; } # ─── Проверки ─── if [ "$EUID" -ne 0 ]; then print_error "Запустите с правами root: sudo ./deploy.sh" exit 1 fi APP_DIR="/var/www/vortex" if [ ! -f "$APP_DIR/package.json" ]; then print_error "Файлы проекта не найдены в $APP_DIR!" echo "" echo "Сначала загрузите проект на сервер:" echo " scp -r ./* root@:$APP_DIR/" exit 1 fi # ─── Ввод параметров ─── echo -e "${CYAN}" echo "╔═══════════════════════════════════════════════════╗" echo "║ 🌀 Vortex Messenger — Деплой ║" echo "╚═══════════════════════════════════════════════════╝" echo -e "${NC}" read -p "Введите домен (например messenger.ru): " DOMAIN if [ -z "$DOMAIN" ]; then print_error "Домен не указан!" exit 1 fi read -p "Введите email для SSL-сертификата: " SSL_EMAIL if [ -z "$SSL_EMAIL" ]; then print_error "Email не указан!" exit 1 fi # Генерация безопасных ключей DB_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32) JWT_SECRET=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 64) ENCRYPTION_KEY=$(openssl rand -hex 32) TURN_SECRET=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32) echo "" print_ok "Параметры приняты. Начинаю установку..." echo "" # ═══════════════════════════════════════ # Шаг 1: Обновление системы # ═══════════════════════════════════════ print_step "1/10 — Обновление системы" export DEBIAN_FRONTEND=noninteractive apt update && apt upgrade -y apt install -y curl wget gnupg2 software-properties-common git unzip build-essential print_ok "Система обновлена" # ═══════════════════════════════════════ # Шаг 2: Node.js 20 # ═══════════════════════════════════════ print_step "2/10 — Установка Node.js 20" if ! command -v node &>/dev/null || [[ $(node -v | cut -d. -f1 | tr -d v) -lt 20 ]]; then curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt install -y nodejs fi npm install -g pm2 print_ok "Node.js $(node -v), npm $(npm -v), PM2 установлен" # ═══════════════════════════════════════ # Шаг 3: PostgreSQL # ═══════════════════════════════════════ print_step "3/10 — Установка PostgreSQL" apt install -y postgresql postgresql-contrib systemctl enable postgresql systemctl start postgresql sudo -u postgres psql -c "DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'vortex') THEN CREATE ROLE vortex WITH LOGIN PASSWORD '${DB_PASSWORD}'; END IF; END \$\$;" sudo -u postgres psql -c "SELECT 1 FROM pg_database WHERE datname = 'vortex'" | grep -q 1 || \ sudo -u postgres createdb -O vortex vortex print_ok "PostgreSQL готов (БД: vortex, пользователь: vortex)" # ═══════════════════════════════════════ # Шаг 4: Nginx # ═══════════════════════════════════════ print_step "4/10 — Установка Nginx" apt install -y nginx systemctl enable nginx print_ok "Nginx установлен" # ═══════════════════════════════════════ # Шаг 5: Certbot (SSL) # ═══════════════════════════════════════ print_step "5/10 — Установка Certbot" apt install -y certbot python3-certbot-nginx print_ok "Certbot установлен" # ═══════════════════════════════════════ # Шаг 6: coturn (TURN-сервер для звонков) # ═══════════════════════════════════════ print_step "6/10 — Установка coturn (TURN)" apt install -y coturn cat > /etc/turnserver.conf << EOF listening-port=3478 tls-listening-port=5349 realm=${DOMAIN} server-name=${DOMAIN} lt-cred-mech use-auth-secret static-auth-secret=${TURN_SECRET} fingerprint no-cli no-tlsv1 no-tlsv1_1 EOF sed -i 's/#TURNSERVER_ENABLED=1/TURNSERVER_ENABLED=1/' /etc/default/coturn 2>/dev/null || true systemctl enable coturn systemctl restart coturn print_ok "coturn настроен" # ═══════════════════════════════════════ # Шаг 7: Настройка окружения (.env) # ═══════════════════════════════════════ print_step "7/10 — Создание .env" mkdir -p ${APP_DIR}/apps/server/uploads/avatars cat > ${APP_DIR}/apps/server/.env << EOF DATABASE_URL=postgresql://vortex:${DB_PASSWORD}@localhost:5432/vortex JWT_SECRET=${JWT_SECRET} ENCRYPTION_KEY=${ENCRYPTION_KEY} PORT=3001 NODE_ENV=production CORS_ORIGINS=https://${DOMAIN} MAX_REGISTRATIONS_PER_IP=5 TURN_URL=turn:${DOMAIN}:3478 TURN_SECRET=${TURN_SECRET} STUN_URLS=stun:stun.l.google.com:19302,stun:${DOMAIN}:3478 EOF print_ok "Файл .env создан" # ═══════════════════════════════════════ # Шаг 8: Установка зависимостей и сборка # ═══════════════════════════════════════ print_step "8/10 — Установка зависимостей и сборка" cd ${APP_DIR} # Установка зависимостей (npm workspaces) npm install --legacy-peer-deps print_ok "Зависимости установлены" # --- Бэкенд --- cd ${APP_DIR}/apps/server npx prisma generate npx prisma db push --accept-data-loss print_ok "БД синхронизирована" npx tsc print_ok "Бэкенд скомпилирован (dist/)" # --- Фронтенд --- cd ${APP_DIR}/apps/web npx vite build print_ok "Фронтенд собран (dist/)" # ═══════════════════════════════════════ # Шаг 9: Nginx конфигурация # ═══════════════════════════════════════ print_step "9/10 — Настройка Nginx" cat > /etc/nginx/sites-available/vortex << EOF server { listen 80; server_name ${DOMAIN}; root ${APP_DIR}/apps/web/dist; index index.html; client_max_body_size 50M; gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript; gzip_min_length 1000; location / { try_files \$uri \$uri/ /index.html; location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { expires 30d; add_header Cache-Control "public, immutable"; } } location /api/ { proxy_pass http://127.0.0.1:3001; 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; client_max_body_size 50M; } location /socket.io/ { proxy_pass http://127.0.0.1:3001; 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_read_timeout 86400; } location /uploads/ { proxy_pass http://127.0.0.1:3001/uploads/; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; } } EOF # Блокировка доступа по голому IP cat > /etc/nginx/sites-available/block-ip << EOF server { listen 80 default_server; server_name _; return 444; } EOF ln -sf /etc/nginx/sites-available/vortex /etc/nginx/sites-enabled/ ln -sf /etc/nginx/sites-available/block-ip /etc/nginx/sites-enabled/ rm -f /etc/nginx/sites-enabled/default nginx -t && systemctl reload nginx print_ok "Nginx настроен" # --- SSL --- print_step "Получение SSL-сертификата" certbot --nginx -d ${DOMAIN} --non-interactive --agree-tos -m ${SSL_EMAIL} 2>&1 || { print_warn "SSL не удалось получить автоматически." print_warn "Проверьте что DNS A-запись ${DOMAIN} указывает на IP этого сервера." print_warn "После настройки DNS выполните вручную:" echo " certbot --nginx -d ${DOMAIN}" } # Обновить block-ip для HTTPS тоже if [ -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]; then cat > /etc/nginx/sites-available/block-ip << EOF server { listen 80 default_server; listen 443 ssl default_server; server_name _; ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem; return 444; } EOF nginx -t && systemctl reload nginx print_ok "Доступ по IP заблокирован (HTTP и HTTPS)" fi # ═══════════════════════════════════════ # Шаг 10: Запуск через PM2 # ═══════════════════════════════════════ print_step "10/10 — Запуск сервера" cd ${APP_DIR}/apps/server pm2 delete vortex-server 2>/dev/null || true pm2 start dist/index.js --name vortex-server --cwd ${APP_DIR}/apps/server pm2 save pm2 startup systemd -u root --hp /root 2>/dev/null || pm2 startup print_ok "Сервер запущен" # ═══════════════════════════════════════ # Файрвол # ═══════════════════════════════════════ if command -v ufw &>/dev/null; then ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw allow 3478/tcp ufw allow 3478/udp ufw --force enable print_ok "Файрвол настроен" fi # ═══════════════════════════════════════ # Скрипт обновления (для будущих апдейтов) # ═══════════════════════════════════════ cat > ${APP_DIR}/update.sh << 'UPDATESCRIPT' #!/bin/bash set -e APP_DIR="/var/www/vortex" ARCHIVE="/root/vortex-deploy.tar.gz" ENV_BACKUP="/root/vortex-env-backup" echo "" echo "VORTEX - Update server" echo "" if [ ! -f "$ARCHIVE" ]; then echo "Архив не найден: $ARCHIVE"; exit 1 fi echo "1/7 Остановка сервера" pm2 stop vortex-server 2>/dev/null || true echo "2/7 Бэкап .env" if [ -f "$APP_DIR/apps/server/.env" ]; then cp "$APP_DIR/apps/server/.env" "$ENV_BACKUP" fi echo "3/7 Бэкап аватаров" if [ -d "$APP_DIR/apps/server/uploads/avatars" ]; then cp -r "$APP_DIR/apps/server/uploads/avatars" /root/vortex-avatars-backup 2>/dev/null || true fi echo "4/7 Распаковка обновления" tar -xzf "$ARCHIVE" -C "$APP_DIR" if [ -f "$ENV_BACKUP" ]; then cp "$ENV_BACKUP" "$APP_DIR/apps/server/.env" fi if [ -d "/root/vortex-avatars-backup" ]; then mkdir -p "$APP_DIR/apps/server/uploads/avatars" cp -r /root/vortex-avatars-backup/* "$APP_DIR/apps/server/uploads/avatars/" 2>/dev/null || true rm -rf /root/vortex-avatars-backup fi echo "5/7 Установка зависимостей" cd "$APP_DIR" npm install --legacy-peer-deps echo "6/7 Сборка сервера" cd "$APP_DIR/apps/server" npx prisma generate npx prisma db push --accept-data-loss npx tsc echo "7/7 Запуск сервера" pm2 restart vortex-server pm2 save rm -f "$ARCHIVE" echo "" echo "Готово! Проверка: pm2 logs vortex-server --lines 10" UPDATESCRIPT chmod +x ${APP_DIR}/update.sh # ═══════════════════════════════════════ # Итог # ═══════════════════════════════════════ SERVER_IP=$(curl -s4 ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}') echo "" echo -e "${GREEN}╔═══════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ 🎉 Vortex Messenger развёрнут! ║${NC}" echo -e "${GREEN}╠═══════════════════════════════════════════════════╣${NC}" echo -e "${GREEN}║${NC} ${GREEN}║${NC}" echo -e "${GREEN}║${NC} 🌐 Сайт: https://${DOMAIN}${NC}" echo -e "${GREEN}║${NC} 📡 IP: ${SERVER_IP}${NC}" echo -e "${GREEN}║${NC} ${GREEN}║${NC}" echo -e "${GREEN}║${NC} 📂 Проект: ${APP_DIR}${NC}" echo -e "${GREEN}║${NC} 🔧 Env: ${APP_DIR}/apps/server/.env${NC}" echo -e "${GREEN}║${NC} 🔄 Апдейт: ${APP_DIR}/update.sh${NC}" echo -e "${GREEN}║${NC} ${GREEN}║${NC}" echo -e "${GREEN}╠═══════════════════════════════════════════════════╣${NC}" echo -e "${GREEN}║${NC} Команды: ${GREEN}║${NC}" echo -e "${GREEN}║${NC} pm2 logs vortex-server — логи ${GREEN}║${NC}" echo -e "${GREEN}║${NC} pm2 restart vortex-server — перезапуск ${GREEN}║${NC}" echo -e "${GREEN}║${NC} pm2 monit — мониторинг ${GREEN}║${NC}" echo -e "${GREEN}╚═══════════════════════════════════════════════════╝${NC}" # Сохранить credentials cat > ${APP_DIR}/CREDENTIALS.txt << EOF === Vortex Messenger — Credentials === Дата: $(date) Домен: ${DOMAIN} IP: ${SERVER_IP} --- База данных --- Host: localhost:5432 Database: vortex User: vortex Password: ${DB_PASSWORD} --- Приложение --- JWT_SECRET=${JWT_SECRET} ENCRYPTION_KEY=${ENCRYPTION_KEY} --- TURN сервер --- TURN_URL=turn:${DOMAIN}:3478 TURN_SECRET=${TURN_SECRET} EOF chmod 600 ${APP_DIR}/CREDENTIALS.txt echo "" print_ok "Credentials сохранены в ${APP_DIR}/CREDENTIALS.txt" echo ""