uploader-bot/setup_production_server.sh

1228 lines
45 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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"
MAIN_PROJECT_DIR="$PROJECT_DIR/uploader-bot"
# Репозитории для клонирования
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 создан"
# Создание папок и симлинков для модулей
log "Создание папок и симлинков для модулей..."
sudo -u "$SERVICE_USER" mkdir -p "$MAIN_PROJECT_DIR/modules"
sudo -u "$SERVICE_USER" mkdir -p "$MAIN_PROJECT_DIR/scripts"
# Создание симлинков для модулей
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
# Создание init-db.sql если его нет
if [ ! -f "$MAIN_PROJECT_DIR/scripts/init-db.sql" ]; then
log "Создание init-db.sql..."
cat > "$MAIN_PROJECT_DIR/scripts/init-db.sql" << 'EOF'
-- PostgreSQL Database Initialization Script for MY Uploader Bot
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
CREATE EXTENSION IF NOT EXISTS "btree_gin";
-- Create schemas for better organization
CREATE SCHEMA IF NOT EXISTS my_network;
CREATE SCHEMA IF NOT EXISTS content;
CREATE SCHEMA IF NOT EXISTS users;
CREATE SCHEMA IF NOT EXISTS analytics;
-- Grant permissions on schemas
GRANT USAGE, CREATE ON SCHEMA my_network TO my_user;
GRANT USAGE, CREATE ON SCHEMA content TO my_user;
GRANT USAGE, CREATE ON SCHEMA users TO my_user;
GRANT USAGE, CREATE ON SCHEMA analytics TO my_user;
-- Set default search path
ALTER DATABASE my_uploader_db SET search_path TO public, my_network, content, users, analytics;
-- Create health check table
CREATE TABLE IF NOT EXISTS health_check (
id SERIAL PRIMARY KEY,
status VARCHAR(50) NOT NULL DEFAULT 'ok',
timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
details JSONB
);
-- Insert initial health check record
INSERT INTO health_check (status, details)
VALUES ('initialized', '{"message": "Database initialized successfully", "version": "1.0.0"}')
ON CONFLICT DO NOTHING;
-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_health_check_timestamp ON health_check(timestamp);
CREATE INDEX IF NOT EXISTS idx_health_check_status ON health_check(status);
COMMENT ON DATABASE my_uploader_db IS 'MY Uploader Bot - Main database for content management and MY Network protocol';
EOF
chown "$SERVICE_USER:$SERVICE_USER" "$MAIN_PROJECT_DIR/scripts/init-db.sql"
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
# Определяем MAIN_PROJECT_DIR если не определена
MAIN_PROJECT_DIR="$PROJECT_DIR/uploader-bot"
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) - только HTTP сначала
cat > "/etc/nginx/sites-available/$DOMAIN" << EOF
server {
listen 80;
server_name $DOMAIN;
# File upload limits
client_max_body_size 100M;
client_body_timeout 600s;
proxy_read_timeout 600s;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
# Hide server version
server_tokens off;
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# 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;
proxy_buffering off;
proxy_request_buffering off;
}
# 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;
proxy_set_header Host \$host;
access_log off;
}
# Static files
location /static/ {
alias $MAIN_PROJECT_DIR/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
EOF
else
# Конфигурация для обычной ноды (только API) - только HTTP сначала
cat > "/etc/nginx/sites-available/$DOMAIN" << EOF
server {
listen 80;
server_name $DOMAIN;
# File upload limits
client_max_body_size 100M;
client_body_timeout 600s;
proxy_read_timeout 600s;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
# Hide server version
server_tokens off;
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# 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;
proxy_buffering off;
proxy_request_buffering off;
}
# 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;
proxy_set_header Host \$host;
access_log off;
}
# Static files
location /static/ {
alias $MAIN_PROJECT_DIR/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
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 "Сборка приложения для типа ноды: $NODE_TYPE..."
if [ "$NODE_TYPE" = "main" ]; then
# Для основной ноды пытаемся собрать с профилем main-node (включая web2-client)
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
if sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" --profile main-node build 2>/dev/null; then
log_success "Сборка основной ноды (с web2-client) завершена"
else
log_warning "Сборка с web2-client не удалась, собираем без него..."
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" build app postgres redis converter 2>/dev/null || \
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" build app postgres redis
fi
else
# Для обычного docker-compose.yml собираем все доступные сервисы
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" build
fi
else
# Для обычной ноды собираем только основные сервисы
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" build app postgres redis converter
else
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" build app postgres redis indexer ton_daemon license_index convert_process
fi
fi
# Сборка 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
# Для основной ноды пытаемся запустить с профилем main-node
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
if sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" --profile main-node up -d 2>/dev/null; then
log "🚀 Запущены все сервисы основной ноды (включая web2-client)"
else
log_warning "Запуск с web2-client не удался, запускаем основные сервисы..."
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" up -d app postgres redis
log "🚀 Запущены основные сервисы (web2-client пропущен)"
fi
else
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" up -d
log "🚀 Запущены все доступные сервисы для основной ноды"
fi
else
# Для обычной ноды запускаем только основные сервисы
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" up -d app postgres redis
else
sudo -u "$SERVICE_USER" docker-compose -f "$COMPOSE_FILE" up -d app postgres redis indexer ton_daemon license_index convert_process
fi
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
echo "🚀 Запуск MY Uploader Bot (тип ноды: $NODE_TYPE)..."
if [ "$NODE_TYPE" = "main" ]; then
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
# Пытаемся запустить с профилем main-node (включая web2-client)
if docker-compose -f $COMPOSE_FILE --profile main-node up -d 2>/dev/null; then
echo "✅ Запущены все сервисы основной ноды (включая web2-client)"
else
echo "⚠️ Web2-client недоступен, запускаем основные сервисы..."
docker-compose -f $COMPOSE_FILE up -d app postgres redis
echo "✅ Запущены основные сервисы (web2-client пропущен)"
fi
else
docker-compose -f $COMPOSE_FILE up -d
echo "✅ Запущены все доступные сервисы для основной ноды"
fi
else
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
docker-compose -f $COMPOSE_FILE up -d app postgres redis
else
docker-compose -f $COMPOSE_FILE up -d app postgres redis indexer ton_daemon license_index convert_process
fi
echo "✅ Запущены основные сервисы для обычной ноды"
fi
echo ""
echo "📊 Статус контейнеров:"
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 "🗄️ PostgreSQL статус:"
docker-compose -f $COMPOSE_FILE exec postgres pg_isready -U my_user -d my_uploader_db 2>/dev/null || echo "PostgreSQL недоступен"
echo ""
echo "🔴 Redis статус:"
docker-compose -f $COMPOSE_FILE exec redis redis-cli ping 2>/dev/null || echo "Redis недоступен"
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
echo "🔧 Пересборка образов для типа ноды: $NODE_TYPE..."
if [ "$NODE_TYPE" = "main" ]; then
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
# Пытаемся собрать с профилем main-node
if docker-compose -f $COMPOSE_FILE --profile main-node build 2>/dev/null; then
echo "✅ Сборка основной ноды (с web2-client) завершена"
else
echo "⚠️ Сборка с web2-client не удалась, собираем основные сервисы..."
docker-compose -f $COMPOSE_FILE build app postgres redis converter 2>/dev/null || \
docker-compose -f $COMPOSE_FILE build app postgres redis
fi
else
docker-compose -f $COMPOSE_FILE build
fi
else
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
docker-compose -f $COMPOSE_FILE build app postgres redis converter
else
docker-compose -f $COMPOSE_FILE build app postgres redis indexer ton_daemon license_index convert_process
fi
fi
echo "🚀 Запуск сервисов..."
if [ "$NODE_TYPE" = "main" ]; then
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
if docker-compose -f $COMPOSE_FILE --profile main-node up -d 2>/dev/null; then
echo "✅ Запущены все сервисы основной ноды (включая web2-client)"
else
echo "⚠️ Web2-client недоступен, запускаем основные сервисы..."
docker-compose -f $COMPOSE_FILE up -d app postgres redis
echo "✅ Запущены основные сервисы (web2-client пропущен)"
fi
else
docker-compose -f $COMPOSE_FILE up -d
fi
else
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
docker-compose -f $COMPOSE_FILE up -d app postgres redis
else
docker-compose -f $COMPOSE_FILE up -d app postgres redis indexer ton_daemon license_index convert_process
fi
fi
echo "✅ Пересборка и перезапуск завершены"
echo ""
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 "Настройка автозапуска..."
# Создаем обертку скрипта для systemd service (учитывает профили Docker Compose)
cat > "$MAIN_PROJECT_DIR/systemd_start.sh" << EOF
#!/bin/bash
cd $MAIN_PROJECT_DIR
if [ "$NODE_TYPE" = "main" ]; then
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
# Пытаемся запустить с профилем main-node
if docker-compose -f $COMPOSE_FILE --profile main-node up -d 2>/dev/null; then
echo "✅ Запущены все сервисы основной ноды (включая web2-client)"
else
echo "⚠️ Web2-client недоступен, запускаем основные сервисы..."
docker-compose -f $COMPOSE_FILE up -d app postgres redis
fi
else
docker-compose -f $COMPOSE_FILE up -d
fi
else
if [ "$COMPOSE_FILE" = "docker-compose.production.yml" ]; then
docker-compose -f $COMPOSE_FILE up -d app postgres redis
else
docker-compose -f $COMPOSE_FILE up -d app postgres redis indexer ton_daemon license_index convert_process
fi
fi
EOF
chmod +x "$MAIN_PROJECT_DIR/systemd_start.sh"
chown "$SERVICE_USER:$SERVICE_USER" "$MAIN_PROJECT_DIR/systemd_start.sh"
if [ "$NODE_TYPE" = "main" ]; then
SERVICE_DESCRIPTION="MY Uploader Bot (Main Node)"
else
SERVICE_DESCRIPTION="MY Uploader Bot (Regular Node)"
fi
SERVICE_EXEC_START="$MAIN_PROJECT_DIR/systemd_start.sh"
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}🗄️ PostgreSQL управление:${NC}"
echo "# Подключение к БД:"
echo "sudo docker-compose -f $MAIN_PROJECT_DIR/$COMPOSE_FILE exec postgres psql -U my_user -d my_uploader_db"
echo "# Бэкап БД:"
echo "sudo docker-compose -f $MAIN_PROJECT_DIR/$COMPOSE_FILE exec postgres pg_dump -U my_user my_uploader_db > backup_\$(date +%Y%m%d).sql"
echo "# Проверка статуса БД:"
echo "sudo docker-compose -f $MAIN_PROJECT_DIR/$COMPOSE_FILE exec postgres pg_isready -U my_user -d my_uploader_db"
echo ""
echo -e "${YELLOW}<EFBFBD> Повторная установка:${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 "# Логи PostgreSQL:"
echo "sudo docker-compose -f $MAIN_PROJECT_DIR/$COMPOSE_FILE logs postgres"
echo "# Логи Redis:"
echo "sudo docker-compose -f $MAIN_PROJECT_DIR/$COMPOSE_FILE logs redis"
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}"