1043 lines
36 KiB
Bash
1043 lines
36 KiB
Bash
#!/bin/bash
|
||
|
||
# =============================================================================
|
||
# MY UPLOADER BOT - ПОЛНАЯ АВТОМАТИЧЕСКАЯ УСТАНОВКА НА PRODUCTION СЕРВЕР
|
||
# =============================================================================
|
||
# Клонирует все модули из projscale.dev и настраивает полную систему
|
||
# Поддерживает типы нод: main (основная) и regular (обычная)
|
||
# =============================================================================
|
||
|
||
set -euo pipefail # Выход при любой ошибке
|
||
|
||
# Цвета для вывода
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
PURPLE='\033[0;35m'
|
||
CYAN='\033[0;36m'
|
||
WHITE='\033[1;37m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Конфигурация
|
||
DOMAIN="$1"
|
||
NODE_TYPE="${2:-regular}" # main или regular, по умолчанию: regular
|
||
EMAIL="${3:-admin@${1}}"
|
||
PROJECT_DIR="/home/myuploader"
|
||
SERVICE_USER="myuploader"
|
||
PROGRESS_FILE="/home/myuploader/.installation_progress"
|
||
|
||
# Репозитории для клонирования
|
||
UPLOADER_REPO="https://git.projscale.dev/my-dev/uploader-bot"
|
||
CONVERTER_REPO="https://git.projscale.dev/my-dev/converter-module"
|
||
WEB2_REPO="https://git.projscale.dev/my-dev/web2-client"
|
||
|
||
# Функция логирования
|
||
log() {
|
||
echo -e "${WHITE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
|
||
}
|
||
|
||
log_error() {
|
||
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||
}
|
||
|
||
log_success() {
|
||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||
}
|
||
|
||
log_warning() {
|
||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||
}
|
||
|
||
log_info() {
|
||
echo -e "${BLUE}[INFO]${NC} $1"
|
||
}
|
||
|
||
# Функция записи прогресса
|
||
log_progress() {
|
||
local step="$1"
|
||
echo "$step:$(date '+%Y-%m-%d %H:%M:%S')" >> "$PROGRESS_FILE"
|
||
log_success "Этап завершен: $step"
|
||
}
|
||
|
||
# Функция проверки завершенности этапа
|
||
is_step_completed() {
|
||
local step="$1"
|
||
if [ -f "$PROGRESS_FILE" ]; then
|
||
grep -q "^$step:" "$PROGRESS_FILE"
|
||
else
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# Функция пропуска уже выполненных этапов
|
||
check_and_skip() {
|
||
local step="$1"
|
||
local description="$2"
|
||
|
||
if is_step_completed "$step"; then
|
||
log_info "⏭️ Пропускаем: $description (уже выполнено)"
|
||
return 0
|
||
else
|
||
log "🔄 Выполняется: $description"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# Проверка аргументов
|
||
if [ $# -lt 1 ]; then
|
||
echo -e "${RED}Использование: $0 <domain> [node_type] [email]${NC}"
|
||
echo "Примеры:"
|
||
echo " $0 my-uploader.example.com regular"
|
||
echo " $0 my-uploader.example.com main admin@example.com"
|
||
echo ""
|
||
echo "Типы нод:"
|
||
echo " main - Основная нода (с web2-client, полные возможности)"
|
||
echo " regular - Обычная нода (только uploader-bot + converter-module)"
|
||
exit 1
|
||
fi
|
||
|
||
# Создание директории прогресса
|
||
mkdir -p "$(dirname "$PROGRESS_FILE")"
|
||
touch "$PROGRESS_FILE"
|
||
|
||
echo ""
|
||
echo -e "${PURPLE}================================================${NC}"
|
||
echo -e "${WHITE}🚀 MY UPLOADER BOT - PRODUCTION SETUP${NC}"
|
||
echo -e "${PURPLE}================================================${NC}"
|
||
echo -e "${CYAN}Домен:${NC} $DOMAIN"
|
||
echo -e "${CYAN}Тип ноды:${NC} $NODE_TYPE"
|
||
echo -e "${CYAN}Email:${NC} $EMAIL"
|
||
echo -e "${CYAN}Пользователь:${NC} $SERVICE_USER"
|
||
echo -e "${CYAN}Директория:${NC} $PROJECT_DIR"
|
||
echo -e "${CYAN}Прогресс файл:${NC} $PROGRESS_FILE"
|
||
echo ""
|
||
echo -e "${CYAN}Репозитории:${NC}"
|
||
echo -e " 📦 Uploader Bot: $UPLOADER_REPO"
|
||
echo -e " 🔄 Converter: $CONVERTER_REPO"
|
||
if [ "$NODE_TYPE" = "main" ]; then
|
||
echo -e " 🌐 Web2 Client: $WEB2_REPO"
|
||
else
|
||
echo -e " 🌐 Web2 Client: ${YELLOW}пропускается (обычная нода)${NC}"
|
||
fi
|
||
echo -e "${PURPLE}================================================${NC}"
|
||
echo ""
|
||
|
||
# Проверка прав root
|
||
if [ "$EUID" -ne 0 ]; then
|
||
log_error "Этот скрипт должен запускаться с правами root"
|
||
exit 1
|
||
fi
|
||
|
||
# Проверка интернет соединения
|
||
if ! ping -c 1 google.com &> /dev/null; then
|
||
log_error "Нет интернет соединения"
|
||
exit 1
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} 🔄 ШАГ 1: ОБНОВЛЕНИЕ СИСТЕМЫ${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "system_update" "Обновление системы"; then
|
||
log "Обновление списка пакетов..."
|
||
apt update -y
|
||
|
||
log "Обновление системы..."
|
||
apt upgrade -y
|
||
|
||
log "Установка базовых пакетов..."
|
||
apt install -y \
|
||
curl \
|
||
wget \
|
||
git \
|
||
unzip \
|
||
htop \
|
||
nano \
|
||
vim \
|
||
tree \
|
||
jq \
|
||
software-properties-common \
|
||
apt-transport-https \
|
||
ca-certificates \
|
||
gnupg \
|
||
lsb-release \
|
||
ufw \
|
||
fail2ban \
|
||
netstat-nat \
|
||
python3 \
|
||
python3-pip \
|
||
nodejs \
|
||
npm
|
||
|
||
log_progress "system_update"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} 🐳 ШАГ 2: УСТАНОВКА DOCKER${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "docker_install" "Установка Docker"; then
|
||
# Удаление старых версий Docker
|
||
log "Удаление старых версий Docker..."
|
||
apt remove -y docker docker-engine docker.io containerd runc 2>/dev/null || true
|
||
|
||
# Установка Docker
|
||
log "Добавление Docker GPG ключа..."
|
||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||
|
||
log "Добавление Docker репозитория..."
|
||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||
|
||
log "Обновление списка пакетов..."
|
||
apt update -y
|
||
|
||
log "Установка Docker..."
|
||
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||
|
||
# Установка Docker Compose standalone
|
||
log "Установка Docker Compose..."
|
||
DOCKER_COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep 'tag_name' | cut -d\" -f4)
|
||
curl -L "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||
chmod +x /usr/local/bin/docker-compose
|
||
|
||
# Запуск Docker
|
||
log "Запуск Docker..."
|
||
systemctl start docker
|
||
systemctl enable docker
|
||
|
||
# Проверка Docker
|
||
log "Проверка Docker..."
|
||
docker --version
|
||
docker-compose --version
|
||
|
||
log_progress "docker_install"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} 🌐 ШАГ 3: УСТАНОВКА NGINX${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "nginx_install" "Установка Nginx"; then
|
||
log "Установка Nginx..."
|
||
apt install -y nginx
|
||
|
||
log "Запуск Nginx..."
|
||
systemctl start nginx
|
||
systemctl enable nginx
|
||
|
||
log "Nginx версия: $(nginx -v 2>&1)"
|
||
log_progress "nginx_install"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} 🔐 ШАГ 4: УСТАНОВКА CERTBOT${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "certbot_install" "Установка Certbot"; then
|
||
log "Установка snapd (если нужно)..."
|
||
apt install -y snapd
|
||
|
||
log "Установка Certbot через snap..."
|
||
snap install core; snap refresh core
|
||
snap install --classic certbot
|
||
|
||
log "Создание симлинка для certbot..."
|
||
ln -sf /snap/bin/certbot /usr/bin/certbot
|
||
|
||
log "Certbot версия: $(certbot --version)"
|
||
log_progress "certbot_install"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} 👤 ШАГ 5: СОЗДАНИЕ ПОЛЬЗОВАТЕЛЯ СЕРВИСА${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "service_user" "Создание пользователя сервиса"; then
|
||
log "Создание пользователя $SERVICE_USER..."
|
||
if ! id "$SERVICE_USER" &>/dev/null; then
|
||
useradd -m -s /bin/bash "$SERVICE_USER"
|
||
usermod -aG sudo "$SERVICE_USER"
|
||
usermod -aG docker "$SERVICE_USER"
|
||
log_success "Пользователь $SERVICE_USER создан"
|
||
else
|
||
usermod -aG docker "$SERVICE_USER"
|
||
log_info "Пользователь $SERVICE_USER уже существует"
|
||
fi
|
||
log_progress "service_user"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} 📂 ШАГ 6: КЛОНИРОВАНИЕ РЕПОЗИТОРИЕВ${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "repositories_clone" "Клонирование репозиториев"; then
|
||
log "Создание директории проекта..."
|
||
mkdir -p "$PROJECT_DIR"
|
||
chown "$SERVICE_USER:$SERVICE_USER" "$PROJECT_DIR"
|
||
|
||
# Переключение на пользователя myuploader для клонирования
|
||
cd "$PROJECT_DIR"
|
||
|
||
log "Клонирование uploader-bot..."
|
||
if [ -d "uploader-bot" ]; then
|
||
log_warning "Директория uploader-bot уже существует, удаляем..."
|
||
rm -rf uploader-bot
|
||
fi
|
||
sudo -u "$SERVICE_USER" git clone "$UPLOADER_REPO" uploader-bot
|
||
log_success "uploader-bot склонирован"
|
||
|
||
log "Клонирование converter-module..."
|
||
if [ -d "converter-module" ]; then
|
||
log_warning "Директория converter-module уже существует, удаляем..."
|
||
rm -rf converter-module
|
||
fi
|
||
sudo -u "$SERVICE_USER" git clone "$CONVERTER_REPO" converter-module
|
||
log_success "converter-module склонирован"
|
||
|
||
# Клонирование web2-client только для основной ноды
|
||
if [ "$NODE_TYPE" = "main" ]; then
|
||
log "Клонирование web2-client..."
|
||
if [ -d "web2-client" ]; then
|
||
log_warning "Директория web2-client уже существует, удаляем..."
|
||
rm -rf web2-client
|
||
fi
|
||
sudo -u "$SERVICE_USER" git clone "$WEB2_REPO" web2-client
|
||
log_success "web2-client склонирован"
|
||
else
|
||
log_info "⏭️ Пропускаем web2-client (обычная нода)"
|
||
fi
|
||
|
||
# Проверка структуры
|
||
log "Структура проекта:"
|
||
sudo -u "$SERVICE_USER" tree -L 2 "$PROJECT_DIR" || ls -la "$PROJECT_DIR"
|
||
|
||
log_progress "repositories_clone"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} ⚙️ ШАГ 7: НАСТРОЙКА ОКРУЖЕНИЯ${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "environment_setup" "Настройка окружения"; then
|
||
# Переход в основную директорию uploader-bot
|
||
MAIN_PROJECT_DIR="$PROJECT_DIR/uploader-bot"
|
||
cd "$MAIN_PROJECT_DIR"
|
||
|
||
log "Генерация .env файла для uploader-bot..."
|
||
|
||
# Генерация случайных ключей
|
||
SECRET_KEY=$(openssl rand -hex 32)
|
||
JWT_SECRET=$(openssl rand -hex 32)
|
||
ENCRYPTION_KEY=$(openssl rand -hex 16)
|
||
POSTGRES_PASSWORD=$(openssl rand -hex 16)
|
||
NODE_ID="$NODE_TYPE-node-$(date +%s)"
|
||
|
||
# Создание .env файла
|
||
cat > "$MAIN_PROJECT_DIR/.env" << EOF
|
||
# MY UPLOADER BOT - PRODUCTION CONFIGURATION
|
||
NODE_ENV=production
|
||
DEBUG=false
|
||
|
||
# Node Configuration
|
||
NODE_TYPE=$NODE_TYPE
|
||
|
||
# Domain
|
||
MY_NETWORK_DOMAIN=$DOMAIN
|
||
|
||
# Database
|
||
DATABASE_URL=postgresql://my_user:$POSTGRES_PASSWORD@postgres:5432/my_uploader_db
|
||
POSTGRES_HOST=postgres
|
||
POSTGRES_PORT=5432
|
||
POSTGRES_DB=my_uploader_db
|
||
POSTGRES_USER=my_user
|
||
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
||
|
||
# Redis
|
||
REDIS_URL=redis://redis:6379/0
|
||
REDIS_HOST=redis
|
||
REDIS_PORT=6379
|
||
|
||
# Security Keys
|
||
SECRET_KEY=$SECRET_KEY
|
||
JWT_SECRET=$JWT_SECRET
|
||
ENCRYPTION_KEY=$ENCRYPTION_KEY
|
||
|
||
# MY Network Settings
|
||
MY_NETWORK_NODE_ID=$NODE_ID
|
||
MY_NETWORK_PORT=15100
|
||
MY_NETWORK_HOST=0.0.0.0
|
||
MY_NETWORK_DOMAIN=$DOMAIN
|
||
MY_NETWORK_SSL_ENABLED=true
|
||
|
||
# API Settings
|
||
API_HOST=0.0.0.0
|
||
API_PORT=15100
|
||
API_WORKERS=4
|
||
MAX_UPLOAD_SIZE=100MB
|
||
|
||
# Converter Settings (On-Demand)
|
||
CONVERTER_DOCKER_IMAGE=my-converter:latest
|
||
CONVERTER_SHARED_PATH=/shared/converter
|
||
CONVERTER_MAX_PARALLEL=3
|
||
CONVERTER_TIMEOUT=300
|
||
|
||
# Logging
|
||
LOG_LEVEL=INFO
|
||
LOG_FORMAT=json
|
||
|
||
# Monitoring
|
||
GRAFANA_PASSWORD=admin123
|
||
EOF
|
||
|
||
chown "$SERVICE_USER:$SERVICE_USER" "$MAIN_PROJECT_DIR/.env"
|
||
log_success "Файл .env создан"
|
||
|
||
# Создание симлинков для модулей в uploader-bot
|
||
log "Создание симлинков для модулей..."
|
||
sudo -u "$SERVICE_USER" mkdir -p "$MAIN_PROJECT_DIR/modules"
|
||
sudo -u "$SERVICE_USER" ln -sf "$PROJECT_DIR/converter-module" "$MAIN_PROJECT_DIR/modules/converter-module"
|
||
|
||
if [ "$NODE_TYPE" = "main" ]; then
|
||
sudo -u "$SERVICE_USER" ln -sf "$PROJECT_DIR/web2-client" "$MAIN_PROJECT_DIR/modules/web2-client"
|
||
fi
|
||
|
||
log_success "Симлинки созданы"
|
||
log_progress "environment_setup"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} 🌐 ШАГ 8: НАСТРОЙКА NGINX${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "nginx_config" "Настройка Nginx"; then
|
||
log "Настройка Nginx конфигурации..."
|
||
|
||
# Сначала добавляем rate limiting в основной конфиг nginx
|
||
log "Добавление rate limiting в nginx.conf..."
|
||
|
||
# Создаем backup оригинального конфига
|
||
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup
|
||
|
||
# Добавляем rate limiting в блок http
|
||
if ! grep -q "limit_req_zone" /etc/nginx/nginx.conf; then
|
||
sed -i '/http {/a\\n\t# Rate limiting zones\n\tlimit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;\n\tlimit_req_zone $binary_remote_addr zone=upload:10m rate=5r/s;\n' /etc/nginx/nginx.conf
|
||
fi
|
||
|
||
log "Создание site конфигурации для типа ноды: $NODE_TYPE..."
|
||
|
||
# Создание базового nginx конфига для сайта
|
||
mkdir -p /etc/nginx/sites-available
|
||
mkdir -p /etc/nginx/sites-enabled
|
||
|
||
if [ "$NODE_TYPE" = "main" ]; then
|
||
# Конфигурация для основной ноды (с web2-client)
|
||
cat > "/etc/nginx/sites-available/$DOMAIN" << EOF
|
||
server {
|
||
listen 80;
|
||
server_name $DOMAIN;
|
||
|
||
# Redirect all HTTP requests to HTTPS
|
||
return 301 https://\$server_name\$request_uri;
|
||
}
|
||
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name $DOMAIN;
|
||
|
||
# SSL configuration will be added by certbot
|
||
|
||
# Security headers
|
||
add_header X-Frame-Options DENY;
|
||
add_header X-Content-Type-Options nosniff;
|
||
add_header X-XSS-Protection "1; mode=block";
|
||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||
|
||
# Web2 Client (main page for main nodes)
|
||
location / {
|
||
limit_req zone=api burst=20 nodelay;
|
||
|
||
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;
|
||
}
|
||
|
||
# API endpoints
|
||
location /api/ {
|
||
limit_req zone=api burst=20 nodelay;
|
||
|
||
proxy_pass http://127.0.0.1:15100/;
|
||
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;
|
||
proxy_read_timeout 300s;
|
||
proxy_connect_timeout 75s;
|
||
}
|
||
|
||
# Upload endpoint with stricter rate limiting
|
||
location /api/upload {
|
||
limit_req zone=upload burst=10 nodelay;
|
||
|
||
proxy_pass http://127.0.0.1:15100/upload;
|
||
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;
|
||
proxy_read_timeout 600s;
|
||
proxy_send_timeout 600s;
|
||
client_max_body_size 100M;
|
||
}
|
||
|
||
# Converter API (on-demand, через uploader-bot)
|
||
location /converter/ {
|
||
limit_req zone=upload burst=10 nodelay;
|
||
|
||
proxy_pass http://127.0.0.1:15100/api/convert/;
|
||
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;
|
||
proxy_read_timeout 600s;
|
||
proxy_send_timeout 600s;
|
||
client_max_body_size 100M;
|
||
}
|
||
|
||
# Health check (no rate limiting)
|
||
location /health {
|
||
proxy_pass http://127.0.0.1:15100/health;
|
||
access_log off;
|
||
}
|
||
|
||
# Static files
|
||
location /static/ {
|
||
alias $MAIN_PROJECT_DIR/static/;
|
||
expires 30d;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
|
||
# Disable access to hidden files
|
||
location ~ /\. {
|
||
deny all;
|
||
access_log off;
|
||
log_not_found off;
|
||
}
|
||
}
|
||
EOF
|
||
else
|
||
# Конфигурация для обычной ноды (только API)
|
||
cat > "/etc/nginx/sites-available/$DOMAIN" << EOF
|
||
server {
|
||
listen 80;
|
||
server_name $DOMAIN;
|
||
|
||
# Redirect all HTTP requests to HTTPS
|
||
return 301 https://\$server_name\$request_uri;
|
||
}
|
||
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name $DOMAIN;
|
||
|
||
# SSL configuration will be added by certbot
|
||
|
||
# Security headers
|
||
add_header X-Frame-Options DENY;
|
||
add_header X-Content-Type-Options nosniff;
|
||
add_header X-XSS-Protection "1; mode=block";
|
||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||
|
||
# Main API (uploader-bot)
|
||
location / {
|
||
limit_req zone=api burst=20 nodelay;
|
||
|
||
proxy_pass http://127.0.0.1:15100;
|
||
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;
|
||
proxy_read_timeout 300s;
|
||
proxy_connect_timeout 75s;
|
||
}
|
||
|
||
# Upload endpoint with stricter rate limiting
|
||
location /upload {
|
||
limit_req zone=upload burst=10 nodelay;
|
||
|
||
proxy_pass http://127.0.0.1:15100/upload;
|
||
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;
|
||
proxy_read_timeout 600s;
|
||
proxy_send_timeout 600s;
|
||
client_max_body_size 100M;
|
||
}
|
||
|
||
# Converter API (on-demand, через uploader-bot)
|
||
location /converter/ {
|
||
limit_req zone=upload burst=10 nodelay;
|
||
|
||
proxy_pass http://127.0.0.1:15100/api/convert/;
|
||
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;
|
||
proxy_read_timeout 600s;
|
||
proxy_send_timeout 600s;
|
||
client_max_body_size 100M;
|
||
}
|
||
|
||
# Health check (no rate limiting)
|
||
location /health {
|
||
proxy_pass http://127.0.0.1:15100/health;
|
||
access_log off;
|
||
}
|
||
|
||
# Static files
|
||
location /static/ {
|
||
alias $MAIN_PROJECT_DIR/static/;
|
||
expires 30d;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
|
||
# Disable access to hidden files
|
||
location ~ /\. {
|
||
deny all;
|
||
access_log off;
|
||
log_not_found off;
|
||
}
|
||
}
|
||
EOF
|
||
fi
|
||
|
||
# Активация сайта
|
||
ln -sf "/etc/nginx/sites-available/$DOMAIN" "/etc/nginx/sites-enabled/"
|
||
|
||
# Удаление дефолтного сайта
|
||
rm -f /etc/nginx/sites-enabled/default
|
||
|
||
# Проверка конфигурации
|
||
log "Проверка конфигурации Nginx..."
|
||
if nginx -t; then
|
||
systemctl reload nginx
|
||
log_success "Nginx конфигурация создана и применена"
|
||
else
|
||
log_error "Ошибка в конфигурации Nginx"
|
||
# Показываем конкретную ошибку
|
||
nginx -t
|
||
exit 1
|
||
fi
|
||
|
||
log_progress "nginx_config"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} 🔐 ШАГ 9: ПОЛУЧЕНИЕ SSL СЕРТИФИКАТА${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "ssl_certificate" "Получение SSL сертификата"; then
|
||
log "Получение SSL сертификата для $DOMAIN..."
|
||
|
||
# Проверка что домен указывает на этот сервер
|
||
DOMAIN_IP=$(dig +short "$DOMAIN" | tail -n1)
|
||
SERVER_IP=$(curl -s ifconfig.me || curl -s ipinfo.io/ip)
|
||
|
||
if [ "$DOMAIN_IP" != "$SERVER_IP" ]; then
|
||
log_warning "Домен $DOMAIN не указывает на этот сервер"
|
||
log_warning "Домен IP: $DOMAIN_IP, Сервер IP: $SERVER_IP"
|
||
log_warning "Попытка получения сертификата может не удаться"
|
||
fi
|
||
|
||
# Получение SSL сертификата
|
||
if certbot --nginx -d "$DOMAIN" --non-interactive --agree-tos --email "$EMAIL" --redirect; then
|
||
log_success "SSL сертификат получен"
|
||
log_progress "ssl_certificate"
|
||
else
|
||
log_error "Не удалось получить SSL сертификат"
|
||
log_warning "Можете получить его позже командой:"
|
||
log_warning "sudo certbot --nginx -d $DOMAIN"
|
||
fi
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} 🔒 ШАГ 10: НАСТРОЙКА БЕЗОПАСНОСТИ${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "security_setup" "Настройка безопасности"; then
|
||
log "Настройка UFW firewall..."
|
||
|
||
# Сброс UFW
|
||
ufw --force reset
|
||
|
||
# Базовые правила
|
||
ufw default deny incoming
|
||
ufw default allow outgoing
|
||
|
||
# Разрешение SSH
|
||
ufw allow ssh
|
||
|
||
# Разрешение HTTP/HTTPS
|
||
ufw allow 80/tcp
|
||
ufw allow 443/tcp
|
||
|
||
# Включение UFW
|
||
ufw --force enable
|
||
|
||
log "Настройка Fail2Ban..."
|
||
|
||
# Создание кастомного фильтра для nginx
|
||
cat > /etc/fail2ban/filter.d/nginx-my-uploader.conf << 'EOF'
|
||
[Definition]
|
||
failregex = ^<HOST> -.*"(GET|POST|HEAD).*" (4\d\d|5\d\d) .*$
|
||
^<HOST> -.*".*" (4\d\d|5\d\d) .*$
|
||
ignoreregex =
|
||
EOF
|
||
|
||
# Конфигурация jail для MY Uploader
|
||
cat > /etc/fail2ban/jail.d/my-uploader.conf << 'EOF'
|
||
[nginx-my-uploader]
|
||
enabled = true
|
||
port = http,https
|
||
filter = nginx-my-uploader
|
||
logpath = /var/log/nginx/access.log
|
||
maxretry = 5
|
||
findtime = 600
|
||
bantime = 3600
|
||
action = iptables-multiport[name=nginx-my-uploader, port="http,https", protocol=tcp]
|
||
EOF
|
||
|
||
# Перезапуск Fail2Ban
|
||
systemctl restart fail2ban
|
||
systemctl enable fail2ban
|
||
|
||
log_success "Безопасность настроена"
|
||
log_progress "security_setup"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} 🐳 ШАГ 11: СБОРКА И ЗАПУСК${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "docker_build" "Сборка и запуск"; then
|
||
MAIN_PROJECT_DIR="$PROJECT_DIR/uploader-bot"
|
||
cd "$MAIN_PROJECT_DIR"
|
||
|
||
log "Сборка Docker образов..."
|
||
|
||
# Проверяем наличие docker-compose файлов
|
||
if [ -f "docker-compose.production.yml" ]; then
|
||
COMPOSE_FILE="docker-compose.production.yml"
|
||
elif [ -f "docker-compose.yml" ]; then
|
||
COMPOSE_FILE="docker-compose.yml"
|
||
else
|
||
log_error "Не найден docker-compose файл"
|
||
exit 1
|
||
fi
|
||
|
||
log "Используется $COMPOSE_FILE"
|
||
|
||
# Сборка образов от имени пользователя сервиса
|
||
log "Сборка основного приложения..."
|
||
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" build
|
||
|
||
# Сборка converter image (если есть профиль build-only)
|
||
log "Сборка converter image..."
|
||
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" --profile build-only build converter-build 2>/dev/null || \
|
||
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" build converter 2>/dev/null || \
|
||
log_warning "Converter build пропущен (нет соответствующего сервиса)"
|
||
|
||
log "Запуск приложения..."
|
||
if [ "$NODE_TYPE" = "main" ]; then
|
||
# Для основной ноды запускаем все сервисы
|
||
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" up -d
|
||
log "🚀 Запущены все сервисы для основной ноды"
|
||
else
|
||
# Для обычной ноды запускаем только необходимые сервисы
|
||
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" up -d uploader-bot converter-builder watchtower 2>/dev/null || \
|
||
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" up -d
|
||
log "🚀 Запущены основные сервисы для обычной ноды"
|
||
fi
|
||
|
||
# Ожидание запуска
|
||
log "Ожидание запуска сервисов..."
|
||
sleep 15
|
||
|
||
# Проверка статуса
|
||
log "Проверка статуса контейнеров..."
|
||
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" ps
|
||
|
||
log_success "Приложение запущено"
|
||
log_progress "docker_build"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo -e "${WHITE} 🏁 ШАГ 12: ФИНАЛЬНАЯ ПРОВЕРКА${NC}"
|
||
echo -e "${BLUE}==========================================${NC}"
|
||
echo ""
|
||
|
||
if ! check_and_skip "final_check" "Финальная проверка"; then
|
||
MAIN_PROJECT_DIR="$PROJECT_DIR/uploader-bot"
|
||
|
||
log "Проверка доступности сервисов..."
|
||
|
||
# Проверка локального API
|
||
if curl -f -s http://localhost:15100/health > /dev/null; then
|
||
log_success "Локальный API доступен"
|
||
else
|
||
log_error "Локальный API недоступен"
|
||
fi
|
||
|
||
# Проверка web2-client только для основной ноды
|
||
if [ "$NODE_TYPE" = "main" ]; then
|
||
if curl -f -s http://localhost:3000/health > /dev/null 2>&1; then
|
||
log_success "Web2-client доступен"
|
||
else
|
||
log_warning "Web2-client может быть недоступен"
|
||
fi
|
||
fi
|
||
|
||
# Проверка HTTPS API
|
||
if curl -f -s "https://$DOMAIN/health" > /dev/null; then
|
||
log_success "HTTPS API доступен"
|
||
else
|
||
log_warning "HTTPS API пока недоступен (может потребоваться время)"
|
||
fi
|
||
|
||
# Создание управляющих скриптов
|
||
log "Создание управляющих скриптов..."
|
||
|
||
# Определяем какой compose file использовать
|
||
if [ -f "$MAIN_PROJECT_DIR/docker-compose.production.yml" ]; then
|
||
COMPOSE_FILE="docker-compose.production.yml"
|
||
else
|
||
COMPOSE_FILE="docker-compose.yml"
|
||
fi
|
||
|
||
cat > "$MAIN_PROJECT_DIR/start.sh" << EOF
|
||
#!/bin/bash
|
||
cd $MAIN_PROJECT_DIR
|
||
if [ "$NODE_TYPE" = "main" ]; then
|
||
docker-compose -f $COMPOSE_FILE up -d
|
||
else
|
||
docker-compose -f $COMPOSE_FILE up -d uploader-bot converter-builder watchtower 2>/dev/null || docker-compose -f $COMPOSE_FILE up -d
|
||
fi
|
||
echo "✅ MY Uploader Bot запущен (тип ноды: $NODE_TYPE)"
|
||
docker-compose -f $COMPOSE_FILE ps
|
||
EOF
|
||
|
||
cat > "$MAIN_PROJECT_DIR/stop.sh" << EOF
|
||
#!/bin/bash
|
||
cd $MAIN_PROJECT_DIR
|
||
docker-compose -f $COMPOSE_FILE down
|
||
echo "🛑 MY Uploader Bot остановлен"
|
||
EOF
|
||
|
||
cat > "$MAIN_PROJECT_DIR/status.sh" << EOF
|
||
#!/bin/bash
|
||
cd $MAIN_PROJECT_DIR
|
||
echo "📊 Статус MY Uploader Bot (тип ноды: $NODE_TYPE):"
|
||
docker-compose -f $COMPOSE_FILE ps
|
||
echo ""
|
||
echo "🌐 API статус:"
|
||
curl -s http://localhost:15100/health | jq . 2>/dev/null || echo "API недоступен"
|
||
echo ""
|
||
echo "🔒 SSL сертификат:"
|
||
sudo certbot certificates | grep -A2 -B2 $DOMAIN || echo "Нет SSL сертификата"
|
||
echo ""
|
||
echo "📈 Прогресс установки:"
|
||
cat $PROGRESS_FILE 2>/dev/null || echo "Файл прогресса не найден"
|
||
EOF
|
||
|
||
cat > "$MAIN_PROJECT_DIR/logs.sh" << EOF
|
||
#!/bin/bash
|
||
cd $MAIN_PROJECT_DIR
|
||
docker-compose -f $COMPOSE_FILE logs -f
|
||
EOF
|
||
|
||
cat > "$MAIN_PROJECT_DIR/rebuild.sh" << EOF
|
||
#!/bin/bash
|
||
cd $MAIN_PROJECT_DIR
|
||
echo "🔄 Пересборка и перезапуск..."
|
||
docker-compose -f $COMPOSE_FILE down
|
||
docker-compose -f $COMPOSE_FILE build
|
||
if [ "$NODE_TYPE" = "main" ]; then
|
||
docker-compose -f $COMPOSE_FILE up -d
|
||
else
|
||
docker-compose -f $COMPOSE_FILE up -d uploader-bot converter-builder watchtower 2>/dev/null || docker-compose -f $COMPOSE_FILE up -d
|
||
fi
|
||
echo "✅ Пересборка завершена"
|
||
docker-compose -f $COMPOSE_FILE ps
|
||
EOF
|
||
|
||
chmod +x "$MAIN_PROJECT_DIR"/{start,stop,status,logs,rebuild}.sh
|
||
chown "$SERVICE_USER:$SERVICE_USER" "$MAIN_PROJECT_DIR"/{start,stop,status,logs,rebuild}.sh
|
||
|
||
# Настройка автозапуска
|
||
log "Настройка автозапуска..."
|
||
|
||
if [ "$NODE_TYPE" = "main" ]; then
|
||
SERVICE_EXEC_START="/usr/local/bin/docker-compose -f $COMPOSE_FILE up -d"
|
||
SERVICE_DESCRIPTION="MY Uploader Bot (Main Node)"
|
||
else
|
||
SERVICE_EXEC_START="/usr/local/bin/docker-compose -f $COMPOSE_FILE up -d uploader-bot converter-builder watchtower"
|
||
SERVICE_DESCRIPTION="MY Uploader Bot (Regular Node)"
|
||
fi
|
||
|
||
cat > /etc/systemd/system/my-uploader-bot.service << EOF
|
||
[Unit]
|
||
Description=$SERVICE_DESCRIPTION
|
||
Requires=docker.service
|
||
After=docker.service
|
||
|
||
[Service]
|
||
Type=oneshot
|
||
RemainAfterExit=yes
|
||
User=$SERVICE_USER
|
||
Group=$SERVICE_USER
|
||
WorkingDirectory=$MAIN_PROJECT_DIR
|
||
ExecStart=$SERVICE_EXEC_START
|
||
ExecStop=/usr/local/bin/docker-compose -f $COMPOSE_FILE down
|
||
TimeoutStartSec=300
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
EOF
|
||
|
||
systemctl daemon-reload
|
||
systemctl enable my-uploader-bot.service
|
||
|
||
log_success "Автозапуск настроен"
|
||
log_progress "final_check"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${GREEN}================================================${NC}"
|
||
echo -e "${WHITE}🎉 УСТАНОВКА ЗАВЕРШЕНА УСПЕШНО!${NC}"
|
||
echo -e "${GREEN}================================================${NC}"
|
||
echo ""
|
||
echo -e "${CYAN}🌐 Домен:${NC} https://$DOMAIN"
|
||
echo -e "${CYAN}🏷️ Тип ноды:${NC} $NODE_TYPE"
|
||
echo -e "${CYAN}📁 Проект:${NC} $MAIN_PROJECT_DIR"
|
||
echo -e "${CYAN}👤 Пользователь:${NC} $SERVICE_USER"
|
||
echo -e "${CYAN}📊 Прогресс:${NC} $PROGRESS_FILE"
|
||
echo ""
|
||
echo -e "${YELLOW}📦 Установленные модули:${NC}"
|
||
echo -e " 🚀 Uploader Bot: $PROJECT_DIR/uploader-bot"
|
||
echo -e " 🔄 Converter: $PROJECT_DIR/converter-module"
|
||
if [ "$NODE_TYPE" = "main" ]; then
|
||
echo -e " 🌐 Web2 Client: $PROJECT_DIR/web2-client"
|
||
else
|
||
echo -e " 🌐 Web2 Client: ${YELLOW}пропущен (обычная нода)${NC}"
|
||
fi
|
||
echo ""
|
||
|
||
# Показываем разные endpoints в зависимости от типа ноды
|
||
if [ "$NODE_TYPE" = "main" ]; then
|
||
echo -e "${YELLOW}🌍 Доступные endpoints (основная нода):${NC}"
|
||
echo -e " 🌐 Web Interface: https://$DOMAIN/"
|
||
echo -e " 📊 API: https://$DOMAIN/api/"
|
||
echo -e " 📤 Upload: https://$DOMAIN/api/upload"
|
||
echo -e " 🔄 Converter API: https://$DOMAIN/converter/"
|
||
echo -e " ❤️ Health Check: https://$DOMAIN/health"
|
||
else
|
||
echo -e "${YELLOW}🌍 Доступные endpoints (обычная нода):${NC}"
|
||
echo -e " 📊 API: https://$DOMAIN/"
|
||
echo -e " 📤 Upload: https://$DOMAIN/upload"
|
||
echo -e " 🔄 Converter API: https://$DOMAIN/converter/"
|
||
echo -e " ❤️ Health Check: https://$DOMAIN/health"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${YELLOW}📋 Полезные команды:${NC}"
|
||
echo -e "${WHITE}# Статус системы${NC}"
|
||
echo "sudo $MAIN_PROJECT_DIR/status.sh"
|
||
echo ""
|
||
echo -e "${WHITE}# Просмотр логов${NC}"
|
||
echo "sudo $MAIN_PROJECT_DIR/logs.sh"
|
||
echo ""
|
||
echo -e "${WHITE}# Перезапуск системы${NC}"
|
||
echo "sudo $MAIN_PROJECT_DIR/stop.sh && sudo $MAIN_PROJECT_DIR/start.sh"
|
||
echo ""
|
||
echo -e "${WHITE}# Пересборка и перезапуск${NC}"
|
||
echo "sudo $MAIN_PROJECT_DIR/rebuild.sh"
|
||
echo ""
|
||
echo -e "${WHITE}# Проверка SSL сертификата${NC}"
|
||
echo "sudo certbot certificates"
|
||
echo ""
|
||
echo -e "${YELLOW}🔧 Управление сервисом:${NC}"
|
||
echo "sudo systemctl start my-uploader-bot"
|
||
echo "sudo systemctl stop my-uploader-bot"
|
||
echo "sudo systemctl status my-uploader-bot"
|
||
echo ""
|
||
echo -e "${YELLOW}🔄 Повторная установка:${NC}"
|
||
echo "# Для полной переустановки:"
|
||
echo "sudo rm -f $PROGRESS_FILE"
|
||
echo "# Для сброса определенного этапа отредактируйте:"
|
||
echo "sudo nano $PROGRESS_FILE"
|
||
echo ""
|
||
echo -e "${YELLOW}🌍 MY Network API:${NC}"
|
||
echo "curl https://$DOMAIN/api/my/bootstrap/config"
|
||
echo ""
|
||
echo -e "${YELLOW}📊 Мониторинг:${NC}"
|
||
echo "docker stats"
|
||
echo "sudo docker-compose -f $MAIN_PROJECT_DIR/$COMPOSE_FILE logs -f"
|
||
echo ""
|
||
echo -e "${GREEN}✅ MY Uploader Bot готов к работе!${NC}"
|
||
echo ""
|
||
|
||
# Отметка полной установки
|
||
log_progress "installation_complete"
|
||
|
||
# Финальная проверка доступности
|
||
log "Финальная проверка через 5 секунд..."
|
||
sleep 5
|
||
|
||
if curl -f -s "https://$DOMAIN/health" > /dev/null; then
|
||
log_success "Система полностью функциональна: https://$DOMAIN"
|
||
else
|
||
log_warning "Система запущена, но требует времени для полной готовности"
|
||
log_info "Проверьте статус через несколько минут: https://$DOMAIN/health"
|
||
fi
|
||
|
||
echo -e "${GREEN}🚀 Добро пожаловать в MY Network!${NC}" |