#!/bin/bash # MY Network v3.0 - Автоматическая установка и запуск ноды # Версия: 3.0.0 set -e # Цвета для вывода 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 # Переменные по умолчанию SCRIPT_VERSION="3.0.0" PROJECT_DIR="/opt/my-network" STORAGE_DIR="/opt/my-network/storage" CONFIG_DIR="/opt/my-network/config" LOGS_DIR="/opt/my-network/logs" # Параметры ноды NODE_TYPE="" NETWORK_MODE="" ALLOW_INCOMING="false" DOCKER_SOCK_PATH="/var/run/docker.sock" BOOTSTRAP_CONFIG="" TELEGRAM_API_KEY="" CLIENT_TELEGRAM_API_KEY="" NODE_VERSION="3.0.0" ENABLE_SSL="false" DOMAIN="" EMAIL="" ENABLE_WEB_CLIENT="true" # Функция логирования log() { echo -e "${WHITE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" } log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # Показать заставку show_banner() { clear echo -e "${PURPLE}" cat << "EOF" ╔══════════════════════════════════════════════════════════════╗ ║ MY Network v3.0 ║ ║ Децентрализованная сеть контента ║ ║ ║ ║ • Мгновенная трансляция без расшифровки ║ ║ • Блокчейн интеграция для uploader-bot ║ ║ • Полная децентрализация без консенсуса ║ ║ • Автоматическая конвертация через Docker ║ ╚══════════════════════════════════════════════════════════════╝ EOF echo -e "${NC}" echo "" log_info "Запуск автоматической установки MY Network v3.0..." echo "" } # Проверка прав root check_root() { if [[ $EUID -ne 0 ]]; then log_error "Этот скрипт должен запускаться с правами root (sudo)" echo "Использование: sudo bash start.sh" exit 1 fi } # Определение операционной системы detect_os() { if [ -f /etc/os-release ]; then . /etc/os-release OS=$NAME VER=$VERSION_ID OS_ID=$ID else log_error "Не удалось определить операционную систему" exit 1 fi log_info "Обнаружена ОС: $OS $VER" # Проверка поддерживаемых ОС case $OS_ID in ubuntu|debian|centos|rhel|fedora) log_success "Операционная система поддерживается" ;; *) log_warn "Операционная система может быть не полностью поддержана" if check_interactive; then echo -n "Продолжить установку? [y/N]: " >&2 read -r REPLY < /dev/tty if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi else log_info "Неинтерактивный режим: продолжаем с неподдерживаемой ОС" fi ;; esac } # Проверка существующей установки check_existing_installation() { log_info "🔍 Проверка существующей установки MY Network..." local existing_installation=false local services_running=false # Проверяем наличие папки проекта if [ -d "$PROJECT_DIR" ]; then log_info "Обнаружена папка проекта: $PROJECT_DIR" existing_installation=true fi # Проверяем systemd сервис if systemctl list-unit-files | grep -q "my-network.service"; then log_info "Обнаружен systemd сервис: my-network" existing_installation=true if systemctl is-active my-network >/dev/null 2>&1; then log_info "Сервис my-network активен" services_running=true fi fi # Проверяем Docker контейнеры if docker ps -a --format "table {{.Names}}" | grep -q "my-network"; then log_info "Обнаружены Docker контейнеры MY Network" existing_installation=true if docker ps --format "table {{.Names}}" | grep -q "my-network"; then log_info "Найдены запущенные контейнеры MY Network" services_running=true fi fi # Проверяем Docker образы if docker images --format "table {{.Repository}}" | grep -q "my-network"; then log_info "Обнаружены Docker образы MY Network" existing_installation=true fi if [ "$existing_installation" = true ]; then echo "" echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${WHITE} ОБНАРУЖЕНА СУЩЕСТВУЮЩАЯ УСТАНОВКА ${NC}" echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" if [ "$services_running" = true ]; then log_warn "Обнаружены запущенные сервисы MY Network" fi echo -e "${WHITE}Найдены компоненты предыдущей установки MY Network.${NC}" echo -e "${WHITE}Для корректного обновления необходимо выполнить очистку.${NC}" echo "" if check_interactive; then echo -n "Обновить существующую установку MY Network? [y/N]: " >&2 read -r update_choice < /dev/tty if [[ ! $update_choice =~ ^[Yy]$ ]]; then log_info "Обновление отменено пользователем" echo "" echo -e "${CYAN}Для ручного управления используйте:${NC}" echo -e "${BLUE}systemctl stop my-network${NC} # Остановка сервиса" echo -e "${BLUE}docker-compose -f $PROJECT_DIR/my-network/docker-compose.yml down${NC} # Остановка контейнеров" echo -e "${BLUE}sudo rm -rf $PROJECT_DIR${NC} # Удаление проекта" echo "" exit 0 fi else log_warn "Неинтерактивный режим: существующая установка будет автоматически обновлена" log_info "Для предотвращения обновления запустите скрипт локально" sleep 3 fi cleanup_existing_installation else log_success "Предыдущие установки не обнаружены. Выполняется чистая установка." fi } # Очистка существующей установки cleanup_existing_installation() { log_info "🧹 Очистка существующей установки..." # 1. Остановка systemd сервиса if systemctl is-active my-network >/dev/null 2>&1; then log_info "Остановка systemd сервиса my-network..." systemctl stop my-network || log_warn "Не удалось остановить сервис" systemctl disable my-network >/dev/null 2>&1 || true fi # 2. Остановка и удаление Docker контейнеров log_info "Остановка и удаление Docker контейнеров..." # Переходим в папку проекта если существует if [ -d "$PROJECT_DIR/my-network" ]; then cd "$PROJECT_DIR/my-network" docker-compose down --remove-orphans --volumes 2>/dev/null || true fi # Принудительная остановка всех контейнеров MY Network local containers=$(docker ps -a --filter "name=my-network" --format "{{.ID}}" 2>/dev/null || true) if [ -n "$containers" ]; then log_info "Удаление контейнеров MY Network..." echo "$containers" | xargs docker rm -f 2>/dev/null || true fi # 3. Удаление Docker образов log_info "Удаление Docker образов MY Network..." local images=$(docker images --filter "reference=my-network*" --format "{{.ID}}" 2>/dev/null || true) if [ -n "$images" ]; then echo "$images" | xargs docker rmi -f 2>/dev/null || true fi # Удаление converter образа docker rmi my-network-converter:latest 2>/dev/null || true # 4. Очистка Docker системы log_info "Очистка Docker кэша и неиспользуемых ресурсов..." docker system prune -f --volumes 2>/dev/null || true docker builder prune -f 2>/dev/null || true docker volume prune -f 2>/dev/null || true # 5. Удаление systemd сервиса if [ -f "/etc/systemd/system/my-network.service" ]; then log_info "Удаление systemd сервиса..." rm -f /etc/systemd/system/my-network.service systemctl daemon-reload fi # 6. Остановка nginx если настроен для MY Network if systemctl is-active nginx >/dev/null 2>&1; then if [ -f "/etc/nginx/sites-enabled/my-network" ]; then log_info "Удаление nginx конфигурации MY Network..." rm -f /etc/nginx/sites-enabled/my-network rm -f /etc/nginx/sites-available/my-network # Восстанавливаем дефолтную конфигурацию nginx если есть backup if [ -f /etc/nginx/nginx.conf.backup ]; then cp /etc/nginx/nginx.conf.backup /etc/nginx/nginx.conf fi # Перезапускаем nginx systemctl reload nginx 2>/dev/null || systemctl restart nginx 2>/dev/null || true fi fi # 7. Удаление веб-файлов if [ -d "/var/www/my-network-web" ]; then log_info "Удаление веб-файлов..." rm -rf /var/www/my-network-web fi # 8. Вопрос о базе данных local remove_database=false echo "" echo -e "${PURPLE}❓ База данных:${NC}" if check_interactive; then echo -n "Удалить существующую базу данных? [y/N]: " >&2 read -r db_choice < /dev/tty if [[ $db_choice =~ ^[Yy]$ ]]; then remove_database=true log_warn "База данных будет удалена" else log_info "База данных будет сохранена для миграции" fi else log_info "Неинтерактивный режим: база данных сохраняется для миграции" fi # 9. Удаление файлов проекта (кроме БД если сохраняем) if [ -d "$PROJECT_DIR" ]; then log_info "Удаление файлов проекта..." if [ "$remove_database" = true ]; then # Удаляем все включая БД rm -rf "$PROJECT_DIR" rm -rf "$STORAGE_DIR" 2>/dev/null || true rm -rf "$CONFIG_DIR" 2>/dev/null || true rm -rf "$LOGS_DIR" 2>/dev/null || true log_info "Проект полностью удален включая базу данных" else # Сохраняем только docker volumes с БД backup_dir="/tmp/my-network-db-backup-$(date +%s)" # Создаем резервную копию volumes перед удалением if docker volume ls | grep -q "my-network.*postgres"; then log_info "Создание резервной копии базы данных..." mkdir -p "$backup_dir" # Экспортируем данные PostgreSQL if docker run --rm -v my-network_postgres_data:/source -v "$backup_dir":/backup alpine tar czf /backup/postgres_data.tar.gz -C /source . 2>/dev/null; then log_success "Резервная копия создана: $backup_dir/postgres_data.tar.gz" else log_warn "Не удалось создать резервную копию БД" fi fi # Удаляем все файлы проекта rm -rf "$PROJECT_DIR" rm -rf "$STORAGE_DIR" 2>/dev/null || true rm -rf "$LOGS_DIR" 2>/dev/null || true # Сохраняем только папку config для ключей если есть if [ -d "$CONFIG_DIR" ]; then config_backup="$CONFIG_DIR.backup-$(date +%s)" mv "$CONFIG_DIR" "$config_backup" 2>/dev/null || true log_info "Конфигурация сохранена: $config_backup" fi log_info "Проект удален, база данных сохранена" fi fi # 10. Очистка Docker volumes (только если удаляем БД) if [ "$remove_database" = true ]; then log_info "Удаление Docker volumes..." docker volume ls | grep "my-network" | awk '{print $2}' | xargs -r docker volume rm 2>/dev/null || true fi log_success "Очистка завершена" echo "" } # Проверка доступности TTY для интерактивного ввода check_interactive() { if [ -t 0 ] && [ -t 1 ]; then return 0 # TTY доступен else return 1 # TTY недоступен fi } # Безопасное чтение ввода safe_read() { local prompt="$1" local default="$2" local var_name="$3" if check_interactive; then # Интерактивный режим - используем /dev/tty echo -n "$prompt" >&2 read -r input < /dev/tty if [ -n "$input" ]; then eval "$var_name='$input'" else eval "$var_name='$default'" fi else # Неинтерактивный режим - используем значения по умолчанию log_info "Неинтерактивный режим: $prompt -> используется значение по умолчанию: $default" eval "$var_name='$default'" fi } # Интерактивная настройка interactive_setup() { echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${WHITE} НАСТРОЙКА MY NETWORK v3.0 ${NC}" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" # Проверяем режим if ! check_interactive; then log_warn "Обнаружен неинтерактивный режим (curl | bash)" log_info "Используются настройки по умолчанию. Для интерактивной настройки скачайте и запустите скрипт локально:" log_info "wget https://git.projscale.dev/my-dev/uploader-bot/raw/branch/main/start.sh && chmod +x start.sh && sudo ./start.sh" echo "" fi # 1. Тип сети echo -e "${PURPLE}❓ Выберите режим работы сети:${NC}" echo " 1) Создать новую сеть (Bootstrap нода)" echo " 2) Подключиться к существующей сети" echo "" if check_interactive; then while true; do echo -n "Введите номер [1-2]: " >&2 read -r network_choice < /dev/tty case $network_choice in 1) NETWORK_MODE="bootstrap" NODE_TYPE="bootstrap" log_info "Режим: Создание новой сети (Bootstrap нода)" break ;; 2) NETWORK_MODE="existing" log_info "Режим: Подключение к существующей сети" break ;; *) log_error "Неверный выбор. Введите 1 или 2." ;; esac done else # Неинтерактивный режим - создаем новую сеть по умолчанию NETWORK_MODE="bootstrap" NODE_TYPE="bootstrap" log_info "Неинтерактивный режим: используется режим создания новой сети (Bootstrap нода)" fi # 2. Тип ноды (если подключаемся к существующей) if [ "$NETWORK_MODE" = "existing" ]; then echo "" echo -e "${PURPLE}❓ Выберите тип ноды:${NC}" echo " 1) Публичная нода (принимает входящие соединения)" echo " 2) Приватная нода (только исходящие соединения)" echo "" if check_interactive; then while true; do echo -n "Введите номер [1-2]: " >&2 read -r node_choice < /dev/tty case $node_choice in 1) NODE_TYPE="public" ALLOW_INCOMING="true" log_info "Тип ноды: Публичная (открытые порты)" break ;; 2) NODE_TYPE="private" ALLOW_INCOMING="false" log_info "Тип ноды: Приватная (закрытые порты)" break ;; *) log_error "Неверный выбор. Введите 1 или 2." ;; esac done else # Неинтерактивный режим - публичная нода по умолчанию NODE_TYPE="public" ALLOW_INCOMING="true" log_info "Неинтерактивный режим: используется публичная нода по умолчанию" fi else ALLOW_INCOMING="true" # Bootstrap нода всегда принимает подключения fi # 3. Bootstrap конфигурация echo "" echo -e "${PURPLE}❓ Конфигурация bootstrap узлов:${NC}" if [ "$NETWORK_MODE" = "bootstrap" ]; then log_info "Bootstrap нода будет создавать новую сеть" BOOTSTRAP_CONFIG="new" else if check_interactive; then echo -n "Путь до bootstrap.json [Enter для дефолтного]: " >&2 read -r custom_bootstrap < /dev/tty else custom_bootstrap="" log_info "Неинтерактивный режим: используется дефолтный bootstrap.json" fi if [ -n "$custom_bootstrap" ] && [ -f "$custom_bootstrap" ]; then BOOTSTRAP_CONFIG="$custom_bootstrap" log_success "Использован кастомный bootstrap.json: $custom_bootstrap" else BOOTSTRAP_CONFIG="default" log_info "Используется дефолтный bootstrap.json" fi fi # 4. Docker socket echo "" echo -e "${PURPLE}❓ Настройка Docker для конвертации:${NC}" if check_interactive; then echo -n "Путь до docker.sock [$DOCKER_SOCK_PATH]: " >&2 read -r custom_docker_sock < /dev/tty else custom_docker_sock="" log_info "Неинтерактивный режим: используется дефолтный путь $DOCKER_SOCK_PATH" fi if [ -n "$custom_docker_sock" ]; then DOCKER_SOCK_PATH="$custom_docker_sock" fi if [ -S "$DOCKER_SOCK_PATH" ]; then log_success "Docker socket найден: $DOCKER_SOCK_PATH" else log_warn "Docker socket не найден: $DOCKER_SOCK_PATH" log_info "Docker будет установлен автоматически" fi # 5. Настройка веб-клиента echo "" echo -e "${PURPLE}❓ Настройка веб-интерфейса:${NC}" if check_interactive; then echo -n "Развернуть веб-клиент для управления нодой? [Y/n]: " >&2 read -r web_choice < /dev/tty else web_choice="Y" log_info "Неинтерактивный режим: веб-клиент включен по умолчанию" fi if [[ ! $web_choice =~ ^[Nn]$ ]]; then ENABLE_WEB_CLIENT="true" log_success "Веб-клиент будет развернут" else ENABLE_WEB_CLIENT="false" log_info "Веб-клиент отключен" fi # 6. SSL и домен (только для публичных нод с веб-клиентом) if [ "$ALLOW_INCOMING" = "true" ] && [ "$ENABLE_WEB_CLIENT" = "true" ]; then echo "" echo -e "${PURPLE}❓ Настройка SSL сертификата:${NC}" if check_interactive; then echo -n "Настроить SSL сертификат? [y/N]: " >&2 read -r ssl_choice < /dev/tty else ssl_choice="N" log_info "Неинтерактивный режим: SSL отключен (требует ручной настройки)" fi if [[ $ssl_choice =~ ^[Yy]$ ]]; then echo -n "Доменное имя: " >&2 read -r DOMAIN < /dev/tty if [ -n "$DOMAIN" ]; then echo -n "Email для уведомлений SSL: " >&2 read -r EMAIL < /dev/tty if [ -n "$EMAIL" ]; then ENABLE_SSL="true" log_success "SSL будет настроен для домена: $DOMAIN" else log_error "Email не указан. SSL отключен" fi else log_error "Домен не указан. SSL отключен" fi fi else log_info "SSL недоступен для данной конфигурации" fi # 7. Telegram API ключи echo "" echo -e "${PURPLE}❓ Настройка Telegram ботов (необязательно):${NC}" if check_interactive; then echo -n "TELEGRAM_API_KEY (основной бот) [Enter для пропуска]: " >&2 read -r TELEGRAM_API_KEY < /dev/tty else TELEGRAM_API_KEY="" log_info "Неинтерактивный режим: Telegram боты отключены" fi if [ -n "$TELEGRAM_API_KEY" ]; then log_success "TELEGRAM_API_KEY настроен" if check_interactive; then echo -n "CLIENT_TELEGRAM_API_KEY (клиентский бот) [Enter для пропуска]: " >&2 read -r CLIENT_TELEGRAM_API_KEY < /dev/tty else CLIENT_TELEGRAM_API_KEY="" fi if [ -n "$CLIENT_TELEGRAM_API_KEY" ]; then log_success "CLIENT_TELEGRAM_API_KEY настроен" else log_info "Клиентский Telegram бот будет отключен" fi else log_info "Telegram боты будут отключены" fi # 6. Подтверждение настроек echo "" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${WHITE} ПОДТВЕРЖДЕНИЕ НАСТРОЕК ${NC}" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo -e "${WHITE}Режим сети:${NC} $NETWORK_MODE" echo -e "${WHITE}Тип ноды:${NC} $NODE_TYPE" echo -e "${WHITE}Входящие соединения:${NC} $ALLOW_INCOMING" echo -e "${WHITE}Docker socket:${NC} $DOCKER_SOCK_PATH" echo -e "${WHITE}Bootstrap config:${NC} $BOOTSTRAP_CONFIG" echo -e "${WHITE}Веб-клиент:${NC} $([ "$ENABLE_WEB_CLIENT" = "true" ] && echo "включен" || echo "отключен")" echo -e "${WHITE}SSL сертификат:${NC} $([ "$ENABLE_SSL" = "true" ] && echo "включен для $DOMAIN" || echo "отключен")" echo -e "${WHITE}Telegram основной:${NC} $([ -n "$TELEGRAM_API_KEY" ] && echo "настроен" || echo "отключен")" echo -e "${WHITE}Telegram клиентский:${NC} $([ -n "$CLIENT_TELEGRAM_API_KEY" ] && echo "настроен" || echo "отключен")" echo "" if check_interactive; then echo -n "Продолжить установку с этими настройками? [Y/n]: " >&2 read -r REPLY < /dev/tty if [[ $REPLY =~ ^[Nn]$ ]]; then log_info "Установка отменена пользователем" exit 0 fi else log_info "Неинтерактивный режим: продолжаем установку с настройками по умолчанию" sleep 3 fi } # Установка зависимостей install_dependencies() { log_info "📦 Установка системных зависимостей..." # Настройка неинтерактивного режима для apt export DEBIAN_FRONTEND=noninteractive export NEEDRESTART_MODE=a export NEEDRESTART_SUSPEND=1 # Обновление пакетов case $OS_ID in ubuntu|debian) # Настройка для автоматического согласия с дефолтными настройками echo 'DPkg::Options {"--force-confdef";"--force-confold";}' > /etc/apt/apt.conf.d/50unattended-upgrades-local apt-get update -qq apt-get upgrade -y -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" apt-get install -y -qq curl wget git unzip htop nano ufw fail2ban \ python3 python3-pip python3-venv build-essential \ postgresql-client jq netcat-openbsd ;; centos|rhel|fedora) if command -v dnf &> /dev/null; then dnf update -y -q dnf install -y -q curl wget git unzip htop nano firewalld \ python3 python3-pip python3-devel gcc gcc-c++ \ postgresql jq nc else yum update -y -q yum install -y -q curl wget git unzip htop nano firewalld \ python3 python3-pip python3-devel gcc gcc-c++ \ postgresql jq nc fi ;; esac # Очистка переменных окружения unset DEBIAN_FRONTEND unset NEEDRESTART_MODE unset NEEDRESTART_SUSPEND log_success "Системные зависимости установлены" } # Установка Docker и Docker Compose install_docker() { if command -v docker &> /dev/null; then log_info "Docker уже установлен: $(docker --version)" else log_info "🐳 Установка Docker..." # Установка Docker через официальный скрипт curl -fsSL https://get.docker.com -o get-docker.sh sh get-docker.sh rm get-docker.sh # Запуск и автозагрузка Docker systemctl start docker systemctl enable docker log_success "Docker установлен: $(docker --version)" fi # Проверка Docker Compose if command -v docker-compose &> /dev/null; then log_info "Docker Compose уже установлен: $(docker-compose --version)" else log_info "🐳 Установка Docker Compose..." # Установка последней версии Docker Compose COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r .tag_name) curl -L "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose log_success "Docker Compose установлен: $(docker-compose --version)" fi # Проверка доступности Docker socket if [ ! -S "$DOCKER_SOCK_PATH" ]; then log_error "Docker socket недоступен: $DOCKER_SOCK_PATH" log_info "Перезапуск Docker..." systemctl restart docker sleep 5 if [ ! -S "$DOCKER_SOCK_PATH" ]; then log_error "Не удалось настроить Docker socket" exit 1 fi fi log_success "Docker socket доступен: $DOCKER_SOCK_PATH" } # Создание директорий проекта create_directories() { log_info "📁 Создание структуры директорий..." # Основные директории mkdir -p "$PROJECT_DIR" mkdir -p "$STORAGE_DIR" mkdir -p "$CONFIG_DIR" mkdir -p "$LOGS_DIR" # Поддиректории для хранения mkdir -p "$STORAGE_DIR" # Права доступа chmod 755 "$PROJECT_DIR" chmod 755 "$STORAGE_DIR" chmod 700 "$CONFIG_DIR" chmod 755 "$LOGS_DIR" log_success "Структура директорий создана" log_info "Проект: $PROJECT_DIR" log_info "Хранилище: $STORAGE_DIR" log_info "Конфигурация: $CONFIG_DIR" log_info "Логи: $LOGS_DIR" } # Клонирование репозиториев clone_repositories() { log_info "📥 Клонирование репозиториев MY Network v3.0..." cd "$PROJECT_DIR" # Если проект уже существует, удаляем старую версию if [ -d "my-network" ]; then log_info "Удаление существующего проекта..." rm -rf my-network fi # Создаем структуру проекта mkdir -p my-network cd my-network # Клонирование всех репозиториев log_info "Клонирование uploader-bot..." if git clone https://git.projscale.dev/my-dev/uploader-bot.git .; then log_success "uploader-bot клонирован" else log_error "Ошибка клонирования uploader-bot" exit 1 fi log_info "Клонирование web2-client..." if git clone https://git.projscale.dev/my-dev/web2-client.git web2-client; then log_success "web2-client клонирован" else log_error "Ошибка клонирования web2-client" exit 1 fi log_info "Клонирование converter-module..." if git clone https://git.projscale.dev/my-dev/converter-module.git converter-module; then log_success "converter-module клонирован" else log_error "Ошибка клонирования converter-module" exit 1 fi log_success "Все репозитории клонированы в $PROJECT_DIR/my-network" } # Создание файлов проекта create_project_files() { log_info "📝 Создание файлов проекта..." cd "$PROJECT_DIR/my-network" # Создание docker-compose.yml cat > docker-compose.yml << 'EOF' services: app: build: . container_name: my-network-app restart: unless-stopped ports: - "15100:15100" volumes: - ${STORAGE_PATH:-./storage}:/app/storage - ${DOCKER_SOCK_PATH:-/var/run/docker.sock}:/var/run/docker.sock - ./logs:/app/logs - ./config/keys:/app/keys:ro environment: - DATABASE_URL=${DATABASE_URL} - REDIS_URL=${REDIS_URL} - NODE_ID=${NODE_ID} - NODE_TYPE=${NODE_TYPE} - NODE_VERSION=${NODE_VERSION} - NETWORK_MODE=${NETWORK_MODE} - ALLOW_INCOMING_CONNECTIONS=${ALLOW_INCOMING_CONNECTIONS} - SECRET_KEY=${SECRET_KEY} - JWT_SECRET_KEY=${JWT_SECRET_KEY} - ENCRYPTION_KEY=${ENCRYPTION_KEY} - STORAGE_PATH=/app/storage - API_HOST=${API_HOST} - API_PORT=${API_PORT} - DOCKER_SOCK_PATH=/var/run/docker.sock - NODE_PRIVATE_KEY_PATH=/app/keys/node_private_key - NODE_PUBLIC_KEY_PATH=/app/keys/node_public_key - NODE_PUBLIC_KEY_HEX=${NODE_PUBLIC_KEY_HEX} - TELEGRAM_API_KEY=${TELEGRAM_API_KEY} - CLIENT_TELEGRAM_API_KEY=${CLIENT_TELEGRAM_API_KEY} - LOG_LEVEL=${LOG_LEVEL} - LOG_PATH=/app/logs - BOOTSTRAP_CONFIG=${BOOTSTRAP_CONFIG} - MAX_PEER_CONNECTIONS=${MAX_PEER_CONNECTIONS} - SYNC_INTERVAL=${SYNC_INTERVAL} - CONVERT_MAX_PARALLEL=${CONVERT_MAX_PARALLEL} - CONVERT_TIMEOUT=${CONVERT_TIMEOUT} depends_on: - postgres - redis networks: - my-network postgres: image: postgres:15-alpine container_name: my-network-postgres restart: unless-stopped environment: - POSTGRES_DB=${POSTGRES_DB} - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data - ./init_db.sql:/docker-entrypoint-initdb.d/init_db.sql networks: - my-network redis: image: redis:7-alpine container_name: my-network-redis restart: unless-stopped command: redis-server --appendonly yes volumes: - redis_data:/data networks: - my-network volumes: postgres_data: redis_data: networks: my-network: driver: bridge EOF # Создание Dockerfile cat > Dockerfile << 'EOF' FROM python:3.11-slim WORKDIR /app # Установка системных зависимостей RUN apt-get update && apt-get install -y \ gcc \ g++ \ curl \ && rm -rf /var/lib/apt/lists/* # Копирование requirements и установка Python зависимостей COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Копирование кода приложения COPY app/ ./app/ COPY alembic/ ./alembic/ COPY alembic.ini . COPY bootstrap.json . # Создание директорий RUN mkdir -p /app/storage /app/logs # Права доступа RUN chmod +x /app/app/main.py # Переменные окружения для корректного запуска ENV UVICORN_HOST=0.0.0.0 ENV UVICORN_PORT=15100 ENV API_HOST=0.0.0.0 ENV API_PORT=15100 EXPOSE 15100 CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "15100"] EOF # Создание requirements.txt cat > requirements.txt << 'EOF' fastapi==0.104.1 uvicorn[standard]==0.24.0 pydantic==2.4.2 pydantic-settings==2.0.3 sqlalchemy==2.0.23 alembic==1.12.1 asyncpg==0.29.0 redis==5.0.1 aioredis==2.0.1 aiofiles==23.2.1 cryptography==41.0.7 python-jose[cryptography]==3.3.0 python-multipart==0.0.6 httpx==0.25.2 websockets==12.0 docker==6.1.3 base58==2.1.1 passlib[bcrypt]==1.7.4 python-telegram-bot==20.7 APScheduler==3.10.4 psutil==5.9.6 requests==2.31.0 PyYAML==6.0.1 python-dotenv==1.0.0 Pillow==10.1.0 ffmpeg-python==0.2.0 python-magic==0.4.27 jinja2==3.1.2 starlette==0.27.0 structlog==23.2.0 aiogram==3.3.0 sanic==23.12.1 PyJWT==2.8.0 cryptography==41.0.7 ed25519==1.5 EOF # Создание init_db.sql cat > init_db.sql << 'EOF' -- MY Network v3.0 Database Initialization -- Extension for UUID generation CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- Create enum types DO $$ BEGIN CREATE TYPE content_status AS ENUM ('pending', 'processing', 'completed', 'failed'); EXCEPTION WHEN duplicate_object THEN null; END $$; -- Create stored_content table (compatible with DEPRECATED-uploader-bot) CREATE TABLE IF NOT EXISTS stored_content ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), hash VARCHAR(255) UNIQUE NOT NULL, original_filename VARCHAR(255) NOT NULL, file_type VARCHAR(100) NOT NULL, file_size BIGINT NOT NULL, content_type VARCHAR(255), storage_path TEXT NOT NULL, decrypted_path TEXT, encrypted_path TEXT NOT NULL, thumbnail_path TEXT, converted_formats JSONB DEFAULT '{}', metadata JSONB DEFAULT '{}', encryption_key TEXT NOT NULL, upload_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(), last_accessed TIMESTAMP WITH TIME ZONE DEFAULT NOW(), access_count INTEGER DEFAULT 0, status content_status DEFAULT 'pending', uploader_id VARCHAR(255), tags TEXT[], description TEXT, is_public BOOLEAN DEFAULT false, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Create indexes for performance CREATE INDEX IF NOT EXISTS idx_stored_content_hash ON stored_content(hash); CREATE INDEX IF NOT EXISTS idx_stored_content_status ON stored_content(status); CREATE INDEX IF NOT EXISTS idx_stored_content_upload_date ON stored_content(upload_date); CREATE INDEX IF NOT EXISTS idx_stored_content_uploader_id ON stored_content(uploader_id); CREATE INDEX IF NOT EXISTS idx_stored_content_file_type ON stored_content(file_type); -- Create nodes table for network management CREATE TABLE IF NOT EXISTS nodes ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), node_id VARCHAR(255) UNIQUE NOT NULL, address INET NOT NULL, port INTEGER NOT NULL, public_key TEXT, node_type VARCHAR(50) NOT NULL, version VARCHAR(20) NOT NULL, last_seen TIMESTAMP WITH TIME ZONE DEFAULT NOW(), trust_score DECIMAL(3,2) DEFAULT 1.0, is_active BOOLEAN DEFAULT true, metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Create indexes for nodes CREATE INDEX IF NOT EXISTS idx_nodes_node_id ON nodes(node_id); CREATE INDEX IF NOT EXISTS idx_nodes_address ON nodes(address); CREATE INDEX IF NOT EXISTS idx_nodes_is_active ON nodes(is_active); CREATE INDEX IF NOT EXISTS idx_nodes_last_seen ON nodes(last_seen); -- Create content_sync table for decentralized synchronization CREATE TABLE IF NOT EXISTS content_sync ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), content_hash VARCHAR(255) NOT NULL, node_id VARCHAR(255) NOT NULL, sync_status VARCHAR(50) DEFAULT 'pending', attempts INTEGER DEFAULT 0, last_attempt TIMESTAMP WITH TIME ZONE, error_message TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Create indexes for content_sync CREATE INDEX IF NOT EXISTS idx_content_sync_hash ON content_sync(content_hash); CREATE INDEX IF NOT EXISTS idx_content_sync_node_id ON content_sync(node_id); CREATE INDEX IF NOT EXISTS idx_content_sync_status ON content_sync(sync_status); -- Create conversion_jobs table CREATE TABLE IF NOT EXISTS conversion_jobs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), content_id UUID REFERENCES stored_content(id), target_format VARCHAR(50) NOT NULL, status content_status DEFAULT 'pending', priority INTEGER DEFAULT 5, attempts INTEGER DEFAULT 0, max_attempts INTEGER DEFAULT 3, error_message TEXT, conversion_params JSONB DEFAULT '{}', output_path TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), started_at TIMESTAMP WITH TIME ZONE, completed_at TIMESTAMP WITH TIME ZONE ); -- Create indexes for conversion_jobs CREATE INDEX IF NOT EXISTS idx_conversion_jobs_content_id ON conversion_jobs(content_id); CREATE INDEX IF NOT EXISTS idx_conversion_jobs_status ON conversion_jobs(status); CREATE INDEX IF NOT EXISTS idx_conversion_jobs_priority ON conversion_jobs(priority); -- Update trigger for updated_at columns CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ language 'plpgsql'; -- Apply update triggers DROP TRIGGER IF EXISTS update_stored_content_updated_at ON stored_content; CREATE TRIGGER update_stored_content_updated_at BEFORE UPDATE ON stored_content FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_nodes_updated_at ON nodes; CREATE TRIGGER update_nodes_updated_at BEFORE UPDATE ON nodes FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_content_sync_updated_at ON content_sync; CREATE TRIGGER update_content_sync_updated_at BEFORE UPDATE ON content_sync FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_conversion_jobs_updated_at ON conversion_jobs; CREATE TRIGGER update_conversion_jobs_updated_at BEFORE UPDATE ON conversion_jobs FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); EOF # Создание alembic.ini cat > alembic.ini << 'EOF' [alembic] script_location = alembic prepend_sys_path = . version_path_separator = os sqlalchemy.url = [post_write_hooks] [loggers] keys = root,sqlalchemy,alembic [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console qualname = [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine [logger_alembic] level = INFO handlers = qualname = alembic [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(levelname)-5.5s [%(name)s] %(message)s datefmt = %H:%M:%S EOF log_success "Файлы проекта созданы" } # Загрузка и настройка проекта setup_project() { clone_repositories create_project_files cd "$PROJECT_DIR/my-network" log_success "Проект настроен в $PROJECT_DIR/my-network" } # Функция для проверки доступности Docker registry check_docker_registry() { log_info "Проверка доступности Docker registry..." # Проверяем доступность registry.docker.io if timeout 30 curl -s --connect-timeout 10 https://registry.docker.io/v2/ >/dev/null 2>&1; then log_success "Docker registry доступен" return 0 else log_warn "Docker registry недоступен или медленно отвечает" return 1 fi } # Функция для настройки Docker daemon timeout configure_docker_timeout() { log_info "Настройка Docker timeout для сетевых операций..." # Создаем или обновляем Docker daemon config local docker_config="/etc/docker/daemon.json" local temp_config="/tmp/daemon.json.tmp" if [ -f "$docker_config" ]; then # Читаем существующий конфиг cp "$docker_config" "$temp_config" else # Создаем новый конфиг echo '{}' > "$temp_config" fi # Добавляем настройки timeout с помощью jq если доступен if command -v jq >/dev/null 2>&1; then jq '. + { "registry-mirrors": [], "insecure-registries": [], "max-concurrent-downloads": 3, "max-concurrent-uploads": 3, "default-runtime": "runc" }' "$temp_config" > "${temp_config}.new" && mv "${temp_config}.new" "$temp_config" if sudo cp "$temp_config" "$docker_config" 2>/dev/null; then log_info "Docker daemon конфигурация обновлена" # Перезапускаем Docker только если это безопасно if ! docker ps >/dev/null 2>&1 || [ "$(docker ps -q | wc -l)" -eq 0 ]; then sudo systemctl reload docker 2>/dev/null || true fi fi fi rm -f "$temp_config" 2>/dev/null } # Сборка converter образа build_converter_image() { log_info "🔧 Сборка converter образа из converter-module..." cd "$PROJECT_DIR/my-network" # Проверяем наличие клонированного converter-module if [ ! -d "converter-module" ]; then log_error "converter-module не найден. Проверьте клонирование репозиториев." return 1 fi cd converter-module # Проверяем наличие Dockerfile в папке converter if [ ! -f "converter/Dockerfile" ]; then log_error "Dockerfile не найден в converter-module/converter/" return 1 fi # Переходим в папку converter для сборки cd converter # Настраиваем Docker timeout configure_docker_timeout # Проверяем доступность registry if ! check_docker_registry; then log_warn "Docker registry недоступен, пробуем продолжить с увеличенным timeout" fi # Сборка converter образа из оригинального репозитория с retry логикой log_info "Сборка Docker образа для converter..." # Попытки сборки с retry local max_attempts=3 local attempt=1 local success=false while [ $attempt -le $max_attempts ] && [ "$success" = false ]; do log_info "Попытка сборки $attempt из $max_attempts..." # Сборка с увеличенными таймаутами и дополнительными параметрами if docker build \ --network=host \ --build-arg BUILDKIT_PROGRESS=plain \ --build-arg HTTP_TIMEOUT=300 \ --build-arg HTTPS_TIMEOUT=300 \ -t my-network-converter:latest . ; then log_success "Converter образ собран: my-network-converter:latest" success=true else log_warn "Попытка $attempt неудачна" if [ $attempt -lt $max_attempts ]; then log_info "Ожидание 15 секунд перед следующей попыткой..." sleep 15 # Очистка Docker build cache и системы при неудачной попытке log_info "Очистка Docker cache..." docker builder prune -f >/dev/null 2>&1 || true docker system prune -f >/dev/null 2>&1 || true # Попытка сброса сетевых настроек Docker if [ $attempt -eq 2 ]; then log_info "Перезапуск Docker daemon для сброса сетевых настроек..." systemctl restart docker >/dev/null 2>&1 || true sleep 10 fi fi attempt=$((attempt + 1)) fi done if [ "$success" = false ]; then log_error "Не удалось собрать converter образ после $max_attempts попыток" log_warn "Возможные причины:" log_warn "1. Проблемы с подключением к Docker Hub" log_warn "2. Сетевые проблемы на сервере" log_warn "3. Временные проблемы Docker Registry" log_info "Установка продолжится без converter образа" log_info "Converter можно собрать позже командой:" log_info "cd $PROJECT_DIR/my-network/converter-module/converter && docker build -t my-network-converter:latest ." fi cd "$PROJECT_DIR/my-network" } # Установка и настройка nginx setup_nginx() { if [ "$ENABLE_WEB_CLIENT" = "true" ] || [ "$ENABLE_SSL" = "true" ]; then log_info "🌐 Установка и настройка nginx..." # Установка nginx case $OS_ID in ubuntu|debian) export DEBIAN_FRONTEND=noninteractive export NEEDRESTART_MODE=a export NEEDRESTART_SUSPEND=1 apt-get update -qq apt-get install -y -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" \ nginx certbot python3-certbot-nginx unset DEBIAN_FRONTEND unset NEEDRESTART_MODE unset NEEDRESTART_SUSPEND ;; centos|rhel|fedora) if command -v dnf &> /dev/null; then dnf install -y -q nginx certbot python3-certbot-nginx else yum install -y -q nginx certbot python3-certbot-nginx fi ;; esac # Развертывание web2-client if [ "$ENABLE_WEB_CLIENT" = "true" ]; then log_info "Развертывание web2-client из репозитория..." # Проверяем наличие клонированного web2-client if [ ! -d "$PROJECT_DIR/my-network/web2-client" ]; then log_error "web2-client не найден. Проверьте клонирование репозиториев." return 1 fi # Создаем директорию для nginx mkdir -p /var/www/my-network-web # Копируем файлы web2-client cd "$PROJECT_DIR/my-network/web2-client" # Если есть сборка (build process), выполняем её if [ -f "package.json" ]; then log_info "Установка зависимостей web2-client..." npm install || log_warn "Не удалось установить зависимости npm" # Если есть build скрипт, выполняем сборку if npm run build 2>/dev/null; then log_success "Сборка web2-client завершена" # Копируем собранные файлы if [ -d "build" ]; then cp -r build/* /var/www/my-network-web/ elif [ -d "dist" ]; then cp -r dist/* /var/www/my-network-web/ else cp -r . /var/www/my-network-web/ fi else log_warn "Сборка не требуется, копируем исходные файлы" cp -r . /var/www/my-network-web/ fi else # Копируем файлы как есть log_info "Копирование статических файлов web2-client..." cp -r . /var/www/my-network-web/ fi # Настройка прав доступа chown -R www-data:www-data /var/www/my-network-web/ chmod -R 755 /var/www/my-network-web/ log_success "Web2-client развернут в /var/www/my-network-web" fi # Полная очистка старых nginx конфигураций log_info "Очистка старых nginx конфигураций..." # Удаляем все существующие конфигурации sites rm -f /etc/nginx/sites-enabled/* 2>/dev/null || true rm -f /etc/nginx/sites-available/my-network* 2>/dev/null || true # Очистка конфигураций certbot в nginx.conf if [ -f /etc/nginx/nginx.conf.backup ]; then cp /etc/nginx/nginx.conf.backup /etc/nginx/nginx.conf else cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup fi # Удаляем все SSL включения из основного nginx.conf sed -i '/# managed by Certbot/d' /etc/nginx/nginx.conf 2>/dev/null || true sed -i '/ssl_certificate/d' /etc/nginx/nginx.conf 2>/dev/null || true sed -i '/ssl_certificate_key/d' /etc/nginx/nginx.conf 2>/dev/null || true sed -i '/ssl_dhparam/d' /etc/nginx/nginx.conf 2>/dev/null || true # Создание чистой HTTP конфигурации nginx log_info "Создание чистой HTTP конфигурации nginx..." cat > /etc/nginx/sites-available/my-network << EOF # MY Network v3.0 nginx configuration # Upstream для API upstream my_network_api { server 127.0.0.1:15100; } server { listen 80; server_name ${DOMAIN:-localhost}; # Максимальный размер для chunked uploads client_max_body_size 10G; client_body_timeout 300s; client_header_timeout 300s; # Proxy buffering для больших файлов proxy_buffering off; proxy_request_buffering off; proxy_max_temp_file_size 0; # Статический контент (веб-интерфейс) location / { $([ "$ENABLE_WEB_CLIENT" = "true" ] && echo " root /var/www/my-network-web;" || echo " return 404;") $([ "$ENABLE_WEB_CLIENT" = "true" ] && echo " index index.html;" || echo "") $([ "$ENABLE_WEB_CLIENT" = "true" ] && echo " try_files \$uri \$uri/ =404;" || echo "") } # API proxy location /api/ { proxy_pass http://my_network_api; 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; # Для chunked uploads proxy_http_version 1.1; proxy_set_header Connection ""; proxy_buffering off; proxy_cache off; # Таймауты для больших файлов proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; } # Health check location /health { proxy_pass http://my_network_api; 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 /monitor { proxy_pass http://my_network_api; 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 # Активация HTTP конфигурации if [ ! -L "/etc/nginx/sites-enabled/my-network" ]; then ln -s /etc/nginx/sites-available/my-network /etc/nginx/sites-enabled/ fi # Тест конфигурации nginx log_info "Тестирование HTTP конфигурации nginx..." if nginx -t; then log_success "HTTP конфигурация nginx корректна" else log_error "Ошибка в HTTP конфигурации nginx" log_info "Показать детали ошибки nginx:" nginx -t 2>&1 || true return 1 fi # Запуск nginx с HTTP конфигурацией systemctl enable nginx systemctl restart nginx # Проверяем, что nginx запустился успешно sleep 3 if systemctl is-active nginx >/dev/null 2>&1; then log_success "Nginx запущен с HTTP конфигурацией" else log_error "Nginx не запустился" systemctl status nginx || true return 1 fi # Настройка SSL если нужно if [ "$ENABLE_SSL" = "true" ] && [ -n "$DOMAIN" ] && [ -n "$EMAIL" ]; then install_ssl_certificates fi else log_info "Nginx пропущен (веб-клиент и SSL отключены)" fi } # Установка SSL сертификатов install_ssl_certificates() { log_info "🔒 Установка SSL сертификата для $DOMAIN..." # Проверка DNS записи if ! host "$DOMAIN" > /dev/null 2>&1; then log_warn "DNS запись для $DOMAIN не найдена" log_info "Убедитесь что домен указывает на этот сервер" if check_interactive; then echo -n "Продолжить установку SSL? [y/N]: " >&2 read -r ssl_continue < /dev/tty if [[ ! $ssl_continue =~ ^[Yy]$ ]]; then log_info "Установка SSL пропущена" return 0 fi else log_info "Неинтерактивный режим: пропускаем SSL (требует ручной настройки)" return 0 fi fi # Проверка и остановка запущенных процессов certbot log_info "Проверка запущенных процессов certbot..." if pgrep -f certbot > /dev/null; then log_warn "Обнаружен запущенный процесс certbot, завершаем..." pkill -f certbot 2>/dev/null || true sleep 5 fi # Очистка временных файлов certbot rm -rf /tmp/tmp*/log 2>/dev/null || true # Проверка существующих сертификатов и их очистка при конфликте log_info "Проверка существующих сертификатов..." if [ -d "/etc/letsencrypt/live/$DOMAIN" ]; then log_warn "Обнаружен существующий сертификат для $DOMAIN, удаляем для предотвращения конфликтов..." certbot delete --cert-name "$DOMAIN" --non-interactive 2>/dev/null || true sleep 2 fi # Убеждаемся что nginx работает с HTTP перед установкой SSL log_info "Проверка готовности nginx для SSL..." if ! systemctl is-active nginx >/dev/null 2>&1 || ! nginx -t 2>/dev/null; then log_error "Nginx не готов для SSL установки" log_info "Система продолжит работу без SSL" return 0 fi # Получение сертификата через certbot с явным указанием типа ключа log_info "Запуск certbot для получения SSL сертификата..." if certbot --nginx -d "$DOMAIN" --email "$EMAIL" --agree-tos --non-interactive --redirect --key-type rsa --cert-name "$DOMAIN"; then log_success "SSL сертификат установлен для $DOMAIN" # Настройка автообновления if ! crontab -l 2>/dev/null | grep -q "certbot renew"; then (crontab -l 2>/dev/null; echo "0 12 * * * /usr/bin/certbot renew --quiet") | crontab - log_success "Автообновление SSL настроено" fi # Обновление firewall для HTTPS case $OS_ID in ubuntu|debian) ufw allow 443/tcp 2>/dev/null || true ;; centos|rhel|fedora) firewall-cmd --permanent --add-service=https 2>/dev/null || true firewall-cmd --reload 2>/dev/null || true ;; esac log_success "HTTPS порт открыт в firewall" else log_error "Ошибка установки SSL сертификата" log_warn "Возможные причины:" log_warn "1. DNS запись $DOMAIN не указывает на этот сервер" log_warn "2. Порт 80 заблокирован или недоступен из интернета" log_warn "3. Nginx не запущен или неправильно настроен" log_warn "4. Достигнут лимит запросов Let's Encrypt" log_warn "5. Другой процесс certbot уже запущен" log_info "Система продолжит работу без SSL. SSL можно настроить позже вручную:" log_info "certbot --nginx -d $DOMAIN --email $EMAIL --agree-tos --non-interactive --redirect" # НЕ завершаем скрипт, SSL не критичен для работы системы fi } # Генерация конфигурации generate_config() { log_info "⚙️ Генерация конфигурации..." # Генерация ed25519 ключей для ноды log_info "Генерация ed25519 ключей для ноды..." # Создаем временную папку для ключей mkdir -p "$CONFIG_DIR/keys" # Генерируем приватный ключ ed25519 PRIVATE_KEY_FILE="$CONFIG_DIR/keys/node_private_key" PUBLIC_KEY_FILE="$CONFIG_DIR/keys/node_public_key" # Генерируем ключевую пару ed25519 openssl genpkey -algorithm ed25519 -out "$PRIVATE_KEY_FILE" openssl pkey -in "$PRIVATE_KEY_FILE" -pubout -out "$PUBLIC_KEY_FILE" # Извлекаем raw публичный ключ для генерации NODE_ID PUBLIC_KEY_HEX=$(openssl pkey -in "$PRIVATE_KEY_FILE" -pubout -outform DER | tail -c 32 | xxd -p -c 32) # Генерируем NODE_ID как base58 от публичного ключа # Сначала конвертируем hex в binary, затем в base58 PUBLIC_KEY_BINARY=$(echo "$PUBLIC_KEY_HEX" | xxd -r -p | base64 -w 0) # Создаем простой base58 ID (упрощенная версия) NODE_ID="node-$(echo "$PUBLIC_KEY_HEX" | cut -c1-16)" # Читаем приватный ключ в PEM формате для конфигурации PRIVATE_KEY_PEM=$(cat "$PRIVATE_KEY_FILE") PUBLIC_KEY_PEM=$(cat "$PUBLIC_KEY_FILE") log_success "Ed25519 ключи сгенерированы для ноды: $NODE_ID" # Генерация других ключей SECRET_KEY=$(openssl rand -hex 32) JWT_SECRET_KEY=$(openssl rand -hex 32) ENCRYPTION_KEY=$(openssl rand -hex 32) DB_PASSWORD=$(openssl rand -hex 16) # Создание .env файла cat > "$CONFIG_DIR/.env" << EOF # MY Network v3.0 Configuration # Generated: $(date) # Node Configuration NODE_ID=$NODE_ID NODE_TYPE=$NODE_TYPE NODE_VERSION=$NODE_VERSION NETWORK_MODE=$NETWORK_MODE ALLOW_INCOMING_CONNECTIONS=$ALLOW_INCOMING # Database Configuration DATABASE_URL=postgresql://myuser:$DB_PASSWORD@postgres:5432/mynetwork POSTGRES_DB=mynetwork POSTGRES_USER=myuser POSTGRES_PASSWORD=$DB_PASSWORD # Redis Configuration REDIS_URL=redis://redis:6379/0 # Security SECRET_KEY=$SECRET_KEY JWT_SECRET_KEY=$JWT_SECRET_KEY ENCRYPTION_KEY=$ENCRYPTION_KEY # Storage STORAGE_PATH=$STORAGE_DIR # API Configuration API_HOST=0.0.0.0 API_PORT=15100 UVICORN_HOST=0.0.0.0 UVICORN_PORT=15100 FASTAPI_HOST=0.0.0.0 FASTAPI_PORT=15100 # Docker Configuration DOCKER_SOCK_PATH=$DOCKER_SOCK_PATH # Node Cryptographic Keys NODE_PRIVATE_KEY_PATH=$CONFIG_DIR/keys/node_private_key NODE_PUBLIC_KEY_PATH=$CONFIG_DIR/keys/node_public_key NODE_PUBLIC_KEY_HEX=$PUBLIC_KEY_HEX # Telegram Bots TELEGRAM_API_KEY=$TELEGRAM_API_KEY CLIENT_TELEGRAM_API_KEY=$CLIENT_TELEGRAM_API_KEY # Logging LOG_LEVEL=INFO LOG_PATH=$LOGS_DIR # Network Configuration BOOTSTRAP_CONFIG=$BOOTSTRAP_CONFIG MAX_PEER_CONNECTIONS=50 SYNC_INTERVAL=300 # Converter Configuration CONVERT_MAX_PARALLEL=3 CONVERT_TIMEOUT=300 EOF # Создание bootstrap.json if [ "$BOOTSTRAP_CONFIG" = "new" ]; then cat > "$CONFIG_DIR/bootstrap.json" << EOF { "version": "$NODE_VERSION", "network_id": "my-network-$(date +%s)", "created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "bootstrap_nodes": [ { "id": "$NODE_ID", "node_id": "$NODE_ID", "address": "$(curl -s ifconfig.me || echo 'localhost')", "port": 15100, "public_key": "$PUBLIC_KEY_HEX", "trusted": true, "node_type": "bootstrap" } ], "network_settings": { "protocol_version": "3.0", "max_peers": 50, "sync_interval": 300, "individual_decisions": true, "no_consensus": true } } EOF log_success "Создан новый bootstrap.json для новой сети" elif [ "$BOOTSTRAP_CONFIG" != "default" ]; then cp "$BOOTSTRAP_CONFIG" "$CONFIG_DIR/bootstrap.json" log_success "Скопирован кастомный bootstrap.json" fi # Копирование конфигурации в проект cp "$CONFIG_DIR/.env" "$PROJECT_DIR/my-network/.env" if [ -f "$CONFIG_DIR/bootstrap.json" ]; then cp "$CONFIG_DIR/bootstrap.json" "$PROJECT_DIR/my-network/bootstrap.json" fi # Копирование ключей в проект mkdir -p "$PROJECT_DIR/my-network/config/keys" cp "$CONFIG_DIR/keys/node_private_key" "$PROJECT_DIR/my-network/config/keys/" cp "$CONFIG_DIR/keys/node_public_key" "$PROJECT_DIR/my-network/config/keys/" # Защита приватного ключа chmod 600 "$PROJECT_DIR/my-network/config/keys/node_private_key" chmod 644 "$PROJECT_DIR/my-network/config/keys/node_public_key" log_success "Конфигурация сгенерирована" } # Настройка firewall setup_firewall() { if [ "$ALLOW_INCOMING" = "true" ]; then log_info "🔥 Настройка firewall для публичной ноды..." case $OS_ID in ubuntu|debian) # UFW для Ubuntu/Debian ufw default deny incoming ufw default allow outgoing ufw allow ssh ufw allow 15100/tcp # API порт MY Network ufw --force enable ;; centos|rhel|fedora) # Firewalld для CentOS/RHEL/Fedora systemctl start firewalld systemctl enable firewalld firewall-cmd --permanent --add-service=ssh firewall-cmd --permanent --add-port=15100/tcp firewall-cmd --reload ;; esac log_success "Firewall настроен (порт 15100 открыт)" else log_info "Приватная нода - firewall настройка пропущена" fi } # Сборка и запуск контейнеров build_and_start() { log_info "🐳 Сборка и запуск MY Network v3.0..." cd "$PROJECT_DIR/my-network" # Остановка существующих контейнеров docker-compose down 2>/dev/null || true # Сборка образов log_info "Сборка Docker образов..." docker-compose build --no-cache # Запуск сервисов log_info "Запуск сервисов..." docker-compose up -d # Ожидание готовности сервисов log_info "Ожидание готовности сервисов..." sleep 30 # Проверка статуса контейнеров if docker-compose ps | grep -q "Up"; then log_success "Контейнеры запущены" else log_error "Ошибка запуска контейнеров" docker-compose logs exit 1 fi } # Инициализация базы данных init_database() { log_info "🗄️ Инициализация базы данных..." cd "$PROJECT_DIR/my-network" # Ожидание готовности PostgreSQL log_info "Ожидание готовности PostgreSQL..." for i in {1..30}; do if docker-compose exec -T postgres pg_isready -U myuser -d mynetwork > /dev/null 2>&1; then break fi echo -n "." sleep 2 done echo "" # Выполнение миграций отключено из-за несовместимости моделей # База данных инициализируется через init_db.sql в PostgreSQL контейнере log_info "База данных инициализируется автоматически через init_db.sql" # Проверяем что база данных готова log_info "Проверка готовности базы данных..." if docker-compose exec -T postgres psql -U myuser -d mynetwork -c "SELECT 1;" > /dev/null 2>&1; then log_success "База данных инициализирована" else log_error "Ошибка доступа к базе данных" return 1 fi } # Подключение к сети connect_to_network() { log_info "🌐 Подключение к MY Network..." cd "$PROJECT_DIR/my-network" # Ожидание готовности API log_info "Ожидание готовности API..." for i in {1..60}; do if curl -f "http://localhost:15100/health" > /dev/null 2>&1; then break fi echo -n "." sleep 2 done echo "" if ! curl -f "http://localhost:15100/health" > /dev/null 2>&1; then log_error "API недоступно" return 1 fi log_success "API готово: http://localhost:15100" # Статистика ноды log_info "Получение статистики ноды..." node_stats=$(curl -s "http://localhost:15100/api/v3/node/status" 2>/dev/null || echo "{}") echo "$node_stats" | jq '.' 2>/dev/null || echo "Статистика недоступна" # Подключение к bootstrap нодам (если не bootstrap) if [ "$NODE_TYPE" != "bootstrap" ]; then log_info "Попытка подключения к bootstrap нодам..." # Автообнаружение пиров curl -X POST "http://localhost:15100/api/v3/node/connect" \ -H "Content-Type: application/json" \ -d '{"auto_discover": true}' > /dev/null 2>&1 sleep 10 # Проверка подключений peers_response=$(curl -s "http://localhost:15100/api/v3/node/peers" 2>/dev/null || echo '{"count": 0}') peer_count=$(echo "$peers_response" | jq -r '.count // 0' 2>/dev/null || echo "0") if [ "$peer_count" -gt 0 ]; then log_success "Подключено к $peer_count пир(ам)" else log_warn "Пока не удалось подключиться к другим нодам" log_info "Нода будет продолжать попытки подключения в фоне" fi else log_info "Bootstrap нода готова принимать подключения" fi # Статистика сети network_stats=$(curl -s "http://localhost:15100/api/v3/network/stats" 2>/dev/null || echo '{}') log_info "Статистика сети:" echo "$network_stats" | jq '.' 2>/dev/null || echo "Статистика сети недоступна" } # Создание systemd сервиса create_systemd_service() { log_info "⚙️ Создание systemd сервиса..." cat > /etc/systemd/system/my-network.service << EOF [Unit] Description=MY Network v3.0 Node Requires=docker.service After=docker.service [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=$PROJECT_DIR/my-network ExecStart=/usr/local/bin/docker-compose up -d ExecStop=/usr/local/bin/docker-compose down TimeoutStartSec=300 User=root [Install] WantedBy=multi-user.target EOF # Активация сервиса systemctl daemon-reload systemctl enable my-network log_success "Systemd сервис создан и активирован" } # Финальный отчет final_report() { clear echo -e "${GREEN}" cat << "EOF" ╔══════════════════════════════════════════════════════════════╗ ║ УСТАНОВКА ЗАВЕРШЕНА! ║ ║ MY Network v3.0 ║ ╚══════════════════════════════════════════════════════════════╝ EOF echo -e "${NC}" echo "" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${WHITE} СТАТУС СИСТЕМЫ ${NC}" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" # Проверка сервисов cd "$PROJECT_DIR/my-network" echo "" echo -e "${WHITE}🐳 Docker контейнеры:${NC}" docker-compose ps --format "table {{.Name}}\t{{.State}}\t{{.Ports}}" echo "" echo -e "${WHITE}⚙️ Systemd сервис:${NC}" if systemctl is-active my-network >/dev/null 2>&1; then echo -e " ${GREEN}✅ my-network: активен${NC}" else echo -e " ${RED}❌ my-network: неактивен${NC}" fi echo "" echo -e "${WHITE}🌐 Сетевая конфигурация:${NC}" echo -e " Тип ноды: ${YELLOW}$NODE_TYPE${NC}" echo -e " Режим сети: ${YELLOW}$NETWORK_MODE${NC}" echo -e " Входящие соединения: ${YELLOW}$ALLOW_INCOMING${NC}" if [ "$ALLOW_INCOMING" = "true" ]; then PUBLIC_IP=$(curl -s ifconfig.me 2>/dev/null || echo "неизвестен") echo -e " Публичный IP: ${YELLOW}$PUBLIC_IP${NC}" if netstat -tlnp 2>/dev/null | grep -q ":15100 "; then echo -e " API порт 15100: ${GREEN}✅ открыт${NC}" else echo -e " API порт 15100: ${RED}❌ недоступен${NC}" fi else echo -e " Режим: ${YELLOW}Приватная нода (только исходящие)${NC}" fi echo "" echo -e "${WHITE}📡 API и интерфейсы:${NC}" if curl -f "http://localhost:15100/health" > /dev/null 2>&1; then echo -e " API: ${GREEN}✅ http://localhost:15100${NC}" echo -e " Health: ${GREEN}✅ http://localhost:15100/health${NC}" echo -e " Мониторинг: ${GREEN}✅ http://localhost:15100/api/my/monitor/${NC}" echo -e " Статус ноды: ${GREEN}✅ http://localhost:15100/api/v3/node/status${NC}" else echo -e " API: ${RED}❌ недоступно${NC}" fi # Веб-клиент и SSL информация if [ "$ENABLE_WEB_CLIENT" = "true" ]; then echo "" echo -e "${WHITE}🌐 Веб-интерфейс:${NC}" if systemctl is-active nginx >/dev/null 2>&1; then if [ "$ENABLE_SSL" = "true" ] && [ -n "$DOMAIN" ]; then echo -e " Веб-интерфейс: ${GREEN}✅ https://$DOMAIN${NC}" echo -e " SSL сертификат: ${GREEN}✅ активен для $DOMAIN${NC}" echo -e " HTTP redirect: ${GREEN}✅ автоматический переход на HTTPS${NC}" else if [ "$ALLOW_INCOMING" = "true" ]; then PUBLIC_IP=$(curl -s ifconfig.me 2>/dev/null || echo "localhost") echo -e " Веб-интерфейс: ${GREEN}✅ http://$PUBLIC_IP${NC}" else echo -e " Веб-интерфейс: ${GREEN}✅ http://localhost${NC}" fi echo -e " SSL: ${YELLOW}⚠ не настроен${NC}" fi echo -e " Nginx: ${GREEN}✅ работает${NC}" echo -e " Chunked upload: ${GREEN}✅ поддерживается (до 10GB)${NC}" else echo -e " Веб-интерфейс: ${RED}❌ Nginx не запущен${NC}" fi else echo -e " Веб-интерфейс: ${YELLOW}⚠ отключен${NC}" fi echo "" echo -e "${WHITE}🤖 Telegram боты:${NC}" if [ -n "$TELEGRAM_API_KEY" ]; then echo -e " Основной бот: ${GREEN}✅ настроен${NC}" else echo -e " Основной бот: ${YELLOW}⚠ отключен${NC}" fi if [ -n "$CLIENT_TELEGRAM_API_KEY" ]; then echo -e " Клиентский бот: ${GREEN}✅ настроен${NC}" else echo -e " Клиентский бот: ${YELLOW}⚠ отключен${NC}" fi echo "" echo -e "${WHITE}💾 Хранилище и конвертация:${NC}" echo -e " Путь хранения: ${YELLOW}$STORAGE_DIR${NC}" echo -e " Docker socket: ${YELLOW}$DOCKER_SOCK_PATH${NC}" if [ -S "$DOCKER_SOCK_PATH" ]; then echo -e " Converter: ${GREEN}✅ готов к работе${NC}" # Проверка наличия converter образа if docker images | grep -q "my-network-converter"; then echo -e " Converter образ: ${GREEN}✅ my-network-converter:latest${NC}" else echo -e " Converter образ: ${YELLOW}⚠ не найден${NC}" fi else echo -e " Converter: ${RED}❌ Docker socket недоступен${NC}" fi echo "" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${WHITE} КОМАНДЫ УПРАВЛЕНИЯ ${NC}" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo -e "${WHITE}🔧 Управление сервисом:${NC}" echo -e " ${BLUE}systemctl start my-network${NC} # Запуск" echo -e " ${BLUE}systemctl stop my-network${NC} # Остановка" echo -e " ${BLUE}systemctl restart my-network${NC} # Перезапуск" echo -e " ${BLUE}systemctl status my-network${NC} # Статус" echo "" echo -e "${WHITE}📊 Мониторинг:${NC}" echo -e " ${BLUE}docker-compose -f $PROJECT_DIR/my-network/docker-compose.yml logs -f${NC}" echo -e " ${BLUE}curl http://localhost:15100/api/v3/node/status | jq${NC}" echo -e " ${BLUE}curl http://localhost:15100/api/v3/network/stats | jq${NC}" echo "" echo -e "${WHITE}📁 Важные файлы:${NC}" echo -e " Конфигурация: ${YELLOW}$CONFIG_DIR/.env${NC}" echo -e " Bootstrap: ${YELLOW}$CONFIG_DIR/bootstrap.json${NC}" echo -e " Логи: ${YELLOW}$LOGS_DIR/${NC}" echo -e " Проект: ${YELLOW}$PROJECT_DIR/my-network/${NC}" echo "" echo -e "${GREEN}🎉 MY Network v3.0 успешно установлен и запущен!${NC}" echo "" echo -e "${WHITE}🔐 Криптографическая безопасность:${NC}" echo -e " Node ID: ${YELLOW}$NODE_ID${NC}" echo -e " Приватный ключ: ${YELLOW}$CONFIG_DIR/keys/node_private_key${NC}" echo -e " Публичный ключ: ${YELLOW}$CONFIG_DIR/keys/node_public_key${NC}" echo -e " Ed25519 ключ: ${GREEN}✅ сгенерирован и защищен${NC}" echo -e " Подписи: ${GREEN}✅ все соединения подписываются ed25519${NC}" echo "" echo -e "${WHITE}Особенности v3.0:${NC}" echo -e " ✅ Полная децентрализация без консенсуса" echo -e " ✅ Мгновенная трансляция контента" echo -e " ✅ Автоматическая конвертация через Docker" echo -e " ✅ Блокчейн интеграция для uploader-bot" echo -e " ✅ Поддержка приватных и публичных нод" echo -e " ✅ Ed25519 криптографическая идентификация" echo -e " ✅ Подписанные и проверенные соединения" echo "" # Сохранение отчета cat > "$PROJECT_DIR/installation-report.txt" << EOF MY Network v3.0 Installation Report Generated: $(date) Node Configuration: - Node ID: $NODE_ID - Node Type: $NODE_TYPE - Network Mode: $NETWORK_MODE - Version: $NODE_VERSION - Allow Incoming: $ALLOW_INCOMING Paths: - Project: $PROJECT_DIR/my-network - Storage: $STORAGE_DIR - Config: $CONFIG_DIR - Logs: $LOGS_DIR - Docker Socket: $DOCKER_SOCK_PATH API Endpoints: - Health: http://localhost:15100/health - Node Status: http://localhost:15100/api/v3/node/status - Network Stats: http://localhost:15100/api/v3/network/stats - Monitoring: http://localhost:15100/api/my/monitor/ Management Commands: - Start: systemctl start my-network - Stop: systemctl stop my-network - Status: systemctl status my-network - Logs: docker-compose -f $PROJECT_DIR/my-network/docker-compose.yml logs -f Telegram Bots: - Main Bot: $([ -n "$TELEGRAM_API_KEY" ] && echo "enabled" || echo "disabled") - Client Bot: $([ -n "$CLIENT_TELEGRAM_API_KEY" ] && echo "enabled" || echo "disabled") EOF log_success "Отчет об установке сохранен: $PROJECT_DIR/installation-report.txt" } # Основная функция main() { show_banner check_root detect_os check_existing_installation interactive_setup install_dependencies install_docker create_directories setup_project build_converter_image setup_nginx generate_config setup_firewall build_and_start init_database connect_to_network create_systemd_service final_report } # Обработка ошибок error_handler() { log_error "Ошибка на строке $1. Код выхода: $2" log_error "Установка прервана" # Показать логи для диагностики if [ -d "$PROJECT_DIR/my-network" ]; then log_info "Логи для диагностики:" cd "$PROJECT_DIR/my-network" docker-compose logs --tail=50 fi exit $2 } trap 'error_handler $LINENO $?' ERR # Запуск установки main "$@"