diff --git a/README.md b/README.md index 83aaed3..aeee1be 100644 --- a/README.md +++ b/README.md @@ -1,170 +1,176 @@ -# MY Network - Универсальная установка +# MY Network v3.0 - Единый установочный скрипт -🚀 **Универсальный установщик MY Network для любого сервера** +**Автоматическая установка и запуск децентрализованной сети контента одной командой** -## ⚡ Быстрая установка +## 🚀 Быстрая установка -Один скрипт для полной установки на любом сервере: +### Развертывание на новом сервере одной командой: ```bash -# Скачать проект -git clone -cd uploader-bot # или my-uploader-bot, или название вашего проекта - -# Запустить установку -chmod +x universal_installer.sh -sudo ./universal_installer.sh +curl -fsSL https://git.projscale.dev/my-dev/uploader-bot/raw/branch/main/start.sh | sudo bash ``` -## 🌐 Настройка домена и HTTPS - -После установки настройте домен и SSL сертификат: +### Или скачать и запустить локально: ```bash -# 1. Исправить nginx (если есть ошибки) -chmod +x fix_nginx_now.sh -sudo ./fix_nginx_now.sh - -# 2. Настроить домен и SSL -chmod +x scripts/setup_domain_ssl.sh -sudo ./scripts/setup_domain_ssl.sh +wget https://git.projscale.dev/my-dev/uploader-bot/raw/branch/main/start.sh +chmod +x start.sh +sudo ./start.sh ``` -**Требования для SSL:** -- Домен должен быть направлен на IP сервера (A-запись в DNS) -- Порты 80 и 443 должны быть открыты -- Сервер должен быть доступен из интернета +## 📋 Что устанавливается -## 📁 Структура проекта +Скрипт `start.sh` автоматически: -``` -my-uploader-bot/ -├── universal_installer.sh # 🚀 Основной установщик -├── fix_nginx_now.sh # 🔧 Быстрое исправление nginx -├── app/ # 💻 Код приложения -├── scripts/ # 🛠️ Утилиты и скрипты -│ ├── setup_domain_ssl.sh # 🌐 Настройка домена и SSL -│ ├── diagnose.sh # 🔍 Диагностика проблем -│ └── ... -├── deployment/ # 📦 Файлы развертывания -├── docs/ # 📚 Документация -└── ... -``` +1. **Клонирует все репозитории:** + - `uploader-bot` - основное приложение + - `web2-client` - веб-интерфейс управления нодой + - `converter-module` - модуль конвертации медиа + - `contracts` - блокчейн контракты -### 📦 deployment/ -- `docker-compose.production.yml` - Основной compose файл -- `Dockerfile` - Образ приложения -- `requirements.txt` - Python зависимости -- `env.example` - Пример переменных окружения +2. **Устанавливает зависимости:** + - Docker и Docker Compose + - Python 3.11+ и системные библиотеки + - Nginx (при включении веб-клиента) + - Certbot (при включении SSL) -### 🛠️ scripts/ -- `setup_domain_ssl.sh` - Настройка домена и SSL -- `diagnose.sh` - Диагностика проблем -- `quick_check.sh` - Быстрая проверка статуса -- `setup_*.sh` - Скрипты настройки компонентов +3. **Настраивает инфраструктуру:** + - PostgreSQL база данных с миграциями + - Redis для кеширования + - Nginx с поддержкой chunked uploads до 10GB + - SSL сертификаты через Let's Encrypt (опционально) -### 📚 docs/ -- Документация проекта -- `archive/` - Устаревшие файлы +4. **Создает файлы проекта:** + - `docker-compose.yml` с полной конфигурацией + - `Dockerfile` для сборки приложения + - `requirements.txt` со всеми зависимостями + - `init_db.sql` с настройкой базы данных + - `alembic.ini` для миграций -## 🔧 Что делает universal_installer.sh +## 🔧 Интерактивная настройка -1. **🔍 Автопоиск проекта** - находит папку проекта в любой директории -2. **📦 Установка зависимостей** - Docker, Docker Compose, Nginx, UFW -3. **🔒 Настройка безопасности** - файрвол, fail2ban -4. **🗑️ Очистка** - удаляет старые контейнеры -5. **⚙️ Конфигурация** - создает .env, настраивает nginx -6. **🚀 Запуск** - запускает все сервисы -7. **🔧 SystemD service** - автозапуск при перезагрузке -8. **✅ Тестирование** - проверяет работу API +При запуске скрипт предложит настроить: + +### Сетевые настройки: +- **Режим сети:** Создать новую сеть (Bootstrap) или подключиться к существующей +- **Тип ноды:** Публичная (с входящими соединениями) или приватная +- **Bootstrap конфигурация:** Использовать дефолтную или кастомную + +### Веб-интерфейс: +- **Веб-клиент:** Развертывание интерфейса управления нодой +- **SSL сертификат:** Автоматическое получение и настройка HTTPS +- **Домен и email:** Для SSL сертификата + +### Дополнительные опции: +- **Docker socket:** Путь к docker.sock для конвертации +- **Telegram боты:** API ключи для основного и клиентского ботов ## 🌐 После установки -### HTTP доступ: -- `http://YOUR_SERVER_IP/api/health` - API health check -- `http://YOUR_SERVER_IP/health` - Альтернативный health check - -### HTTPS доступ (после настройки домена): -- `https://your-domain.com/api/health` - API health check -- `https://your-domain.com/health` - Альтернативный health check - -## 🛠️ Управление +### Доступ к ноде: +- **API:** `http://localhost:15100` или `https://your-domain.com` +- **Веб-интерфейс:** `http://localhost` или `https://your-domain.com` +- **Health check:** `/health` +- **Статус ноды:** `/api/v3/node/status` +### Управление сервисом: ```bash +# Запуск/остановка +systemctl start my-network +systemctl stop my-network +systemctl restart my-network + # Статус -sudo systemctl status mynetwork +systemctl status my-network -# Перезапуск -sudo systemctl restart mynetwork - -# Логи приложения -docker logs $(docker ps --format "{{.Names}}" | grep app | head -1) - -# Логи nginx -sudo journalctl -u nginx -f +# Логи +docker-compose -f /opt/my-network/my-network/docker-compose.yml logs -f ``` -## 🔍 Диагностика - -Если что-то не работает: - +### Мониторинг: ```bash -# Быстрая диагностика -./scripts/diagnose.sh +# Статус ноды +curl http://localhost:15100/api/v3/node/status | jq -# Полная диагностика -./scripts/full_diagnosis.sh +# Статистика сети +curl http://localhost:15100/api/v3/network/stats | jq -# Исправление nginx -sudo ./fix_nginx_now.sh +# Список пиров +curl http://localhost:15100/api/v3/node/peers | jq ``` -## 🔒 SSL и безопасность +## 🏗️ Архитектура v3.0 -После настройки SSL: -- Автоматическое перенаправление HTTP → HTTPS -- Автообновление сертификатов Let's Encrypt -- Безопасные заголовки HTTP -- Современные SSL протоколы +### Ключевые особенности: +- ✅ **Полная децентрализация** - без консенсуса и центральных узлов +- ✅ **Мгновенная трансляция** - контент доступен без расшифровки +- ✅ **Автоматическая конвертация** - через Docker контейнеры +- ✅ **Блокчейн интеграция** - совместимость с uploader-bot +- ✅ **Chunked uploads** - поддержка файлов до 10GB +- ✅ **SSL автоматизация** - Let's Encrypt интеграция -```bash -# Проверка сертификата -sudo certbot certificates +### Компоненты системы: +- **API Server** - FastAPI приложение на порту 15100 +- **База данных** - PostgreSQL с автомиграциями +- **Кеширование** - Redis для быстрого доступа +- **Веб-интерфейс** - Nginx + статические файлы +- **Конвертер** - Docker контейнер для медиа-обработки -# Тест обновления -sudo certbot renew --dry-run +## 🔐 Безопасность + +- **Шифрование** - AES-256 для контента в сети +- **JWT токены** - для API аутентификации +- **SSL/TLS** - автоматические сертификаты +- **Firewall** - автоматическая настройка портов +- **Fail2ban** - защита от брутфорса + +## 📁 Структура проекта + +После установки создается: ``` - -## 📋 Поддерживаемые системы - -- ✅ Ubuntu 20.04+ -- ✅ Debian 11+ -- ✅ CentOS 8+ (адаптация) - -## 🚨 Решение проблем - -### Nginx ошибки: -```bash -sudo ./fix_nginx_now.sh +/opt/my-network/ +├── my-network/ # Основной проект +│ ├── uploader-bot/ # Основное приложение +│ ├── web2-client/ # Веб-интерфейс +│ ├── converter-module/ # Модуль конвертации +│ ├── contracts/ # Блокчейн контракты +│ ├── docker-compose.yml +│ ├── Dockerfile +│ ├── requirements.txt +│ └── init_db.sql +├── storage/ # Хранилище контента +├── config/ # Конфигурация (.env, bootstrap.json) +└── logs/ # Логи системы ``` -### Контейнеры перезапускаются: -```bash -docker logs $(docker ps --format "{{.Names}}" | grep app | head -1) -./scripts/diagnose.sh -``` - -### SSL не работает: -- Проверьте DNS записи -- Убедитесь что порты 80/443 открыты -- Проверьте файрвол: `sudo ufw status` - ## 🆘 Поддержка -Скрипт автоматически: -- Находит проект в любой директории -- Адаптируется к разным именам контейнеров -- Работает с разными compose файлами -- Создает резервные копии конфигураций -- Показывает детальную диагностику при ошибках -- Настраивает SSL с автообновлением \ No newline at end of file +После установки создается отчет: `/opt/my-network/installation-report.txt` + +### Проблемы и решения: + +**Ошибка клонирования репозиториев:** +```bash +# Проверьте доступность git.projscale.dev +ping git.projscale.dev +``` + +**Контейнеры не запускаются:** +```bash +# Проверьте логи +cd /opt/my-network/my-network +docker-compose logs +``` + +**SSL не работает:** +```bash +# Проверьте DNS записи +nslookup your-domain.com +# Проверьте nginx +nginx -t +systemctl status nginx +``` + +## 📝 Лицензия + +MY Network v3.0 - Проект с открытым исходным кодом \ No newline at end of file diff --git a/deploy_main_bootstrap_node.sh b/deploy_main_bootstrap_node.sh index 2fec8aa..0982689 100755 --- a/deploy_main_bootstrap_node.sh +++ b/deploy_main_bootstrap_node.sh @@ -238,7 +238,7 @@ cat > $INSTALL_DIR/bootstrap_main.json << EOF } EOF -# Docker Compose for Main Bootstrap +# Docker Compose for Main Bootstrap (только MY Network) cat > $INSTALL_DIR/docker-compose.yml << EOF version: '3.8' @@ -260,34 +260,6 @@ services: - BOOTSTRAP_NODE=true networks: - mynetwork - depends_on: - - web2-client - - converter - - # Web2 Client - web2-client: - build: ./web2-client - container_name: web2-client-main - restart: unless-stopped - ports: - - "$WEB_CLIENT_PORT:3000" - environment: - - NEXT_PUBLIC_API_URL=https://$DOMAIN - - NEXT_PUBLIC_WS_URL=wss://$DOMAIN - networks: - - mynetwork - - # Converter Module - converter: - build: ./converter-module - container_name: converter-main - restart: unless-stopped - ports: - - "$CONVERTER_PORT:8080" - volumes: - - ./data/converter:/app/data - networks: - - mynetwork networks: mynetwork: @@ -312,8 +284,6 @@ ufw allow 22/tcp # SSH ufw allow 80/tcp # HTTP ufw allow 443/tcp # HTTPS ufw allow $MY_NETWORK_PORT/tcp # MY Network -ufw allow $WEB_CLIENT_PORT/tcp # Web Client -ufw allow $CONVERTER_PORT/tcp # Converter ufw --force enable @@ -349,9 +319,9 @@ server { add_header X-XSS-Protection "1; mode=block"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - # Main web interface (Web2 Client) + # Default location - MY Network API location / { - proxy_pass http://localhost:$WEB_CLIENT_PORT; + proxy_pass http://localhost:$MY_NETWORK_PORT/; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; @@ -389,15 +359,6 @@ server { proxy_set_header Connection "upgrade"; proxy_set_header Host \$host; } - - # Converter API - location /convert/ { - proxy_pass http://localhost:$CONVERTER_PORT/; - 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 @@ -499,7 +460,6 @@ endpoints=( "https://$DOMAIN/health" "https://$DOMAIN/api/bootstrap" "https://$DOMAIN/monitor/" - "https://$DOMAIN/" ) for endpoint in "${endpoints[@]}"; do @@ -519,7 +479,6 @@ echo "🎉 MY NETWORK v2.0 MAIN BOOTSTRAP NODE DEPLOYMENT COMPLETE!" echo "=============================================================" echo "" echo "🌐 Main Access Points:" -echo " • Web Interface: https://$DOMAIN/" echo " • Matrix Dashboard: https://$DOMAIN/monitor/" echo " • Health Check: https://$DOMAIN/health" echo " • Bootstrap API: https://$DOMAIN/api/bootstrap" @@ -528,8 +487,6 @@ echo " • API Docs: https://$DOMAIN/api/docs" echo "" echo "🔧 Service Ports:" echo " • MY Network: $MY_NETWORK_PORT" -echo " • Web Client: $WEB_CLIENT_PORT" -echo " • Converter: $CONVERTER_PORT" echo "" echo "🛠️ Management Commands:" echo " • View logs: docker-compose -f $INSTALL_DIR/docker-compose.yml logs -f" diff --git a/docs/CONVERTER_MODULE.md b/docs/CONVERTER_MODULE.md new file mode 100644 index 0000000..42e2451 --- /dev/null +++ b/docs/CONVERTER_MODULE.md @@ -0,0 +1,771 @@ +# MY Network v3.0 - Converter Module и Docker Integration + +## 🔄 Обзор Converter Module + +Converter Module представляет собой on-demand систему конвертации медиа файлов, работающую через Docker контейнеры. Модуль запускается только при необходимости обработки файлов и автоматически завершает работу после выполнения задачи. + +## 🐳 Архитектура Docker Integration + +### Принцип работы + +```mermaid +graph TB + API[MY Network API] --> CM[Converter Manager] + CM --> DS[Docker Socket] + DS --> DI[Docker Image: my-converter] + + subgraph "On-Demand Containers" + C1[Converter Container 1] + C2[Converter Container 2] + C3[Converter Container N] + end + + DI -.->|Creates| C1 + DI -.->|Creates| C2 + DI -.->|Creates| C3 + + C1 --> FS[Shared File System] + C2 --> FS + C3 --> FS + + C1 -.->|Auto-Remove| X1[❌] + C2 -.->|Auto-Remove| X2[❌] + C3 -.->|Auto-Remove| X3[❌] +``` + +### Docker Socket Integration + +```python +class DockerConverterManager: + """Управление converter контейнерами через Docker API""" + + def __init__(self, docker_sock_path: str = "/var/run/docker.sock"): + self.docker_sock_path = docker_sock_path + self.docker_client = None + self.converter_image = "my-converter:latest" + self.max_parallel_jobs = 3 + self.active_jobs = {} + + async def initialize(self): + """Инициализация Docker клиента""" + try: + # Проверка доступности Docker socket + if not os.path.exists(self.docker_sock_path): + raise DockerError(f"Docker socket not found: {self.docker_sock_path}") + + # Создание Docker клиента + self.docker_client = docker.DockerClient( + base_url=f"unix://{self.docker_sock_path}" + ) + + # Проверка связи с Docker + await self._test_docker_connection() + + # Проверка наличия converter образа + await self._ensure_converter_image() + + logger.info("Docker converter manager initialized successfully") + + except Exception as e: + logger.error(f"Failed to initialize Docker converter manager: {e}") + raise + + async def _test_docker_connection(self): + """Проверка подключения к Docker""" + try: + info = self.docker_client.info() + logger.info(f"Docker connected: {info['ServerVersion']}") + except Exception as e: + raise DockerError(f"Cannot connect to Docker: {e}") + + async def _ensure_converter_image(self): + """Проверка наличия converter образа""" + try: + image = self.docker_client.images.get(self.converter_image) + logger.info(f"Converter image found: {image.id[:12]}") + except docker.errors.ImageNotFound: + logger.error(f"Converter image not found: {self.converter_image}") + raise DockerError(f"Please build converter image: {self.converter_image}") + + async def convert_file(self, input_file: str, output_file: str, + conversion_type: str, options: dict = None) -> str: + """ + Запуск конвертации файла в новом контейнере + + Args: + input_file: Путь к входному файлу + output_file: Путь к выходному файлу + conversion_type: Тип конвертации (video_to_mp4, audio_to_mp3, etc.) + options: Дополнительные параметры конвертации + + Returns: + str: ID задачи конвертации + """ + + # Проверка лимита параллельных задач + if len(self.active_jobs) >= self.max_parallel_jobs: + raise ConverterError("Too many active conversion jobs") + + # Генерация ID задачи + job_id = str(uuid4()) + + # Подготовка окружения для контейнера + container_config = await self._prepare_container_config( + job_id, input_file, output_file, conversion_type, options + ) + + try: + # Создание и запуск контейнера + container = self.docker_client.containers.run( + image=self.converter_image, + command=container_config["command"], + volumes=container_config["volumes"], + environment=container_config["environment"], + name=f"converter-{job_id}", + detach=True, + auto_remove=True, # Автоматическое удаление после завершения + cpu_count=1, # Ограничение ресурсов + mem_limit="1g" + ) + + # Регистрация активной задачи + self.active_jobs[job_id] = { + "container": container, + "start_time": datetime.utcnow(), + "input_file": input_file, + "output_file": output_file, + "conversion_type": conversion_type, + "status": "running" + } + + logger.info(f"Conversion job {job_id} started in container {container.id[:12]}") + + # Запуск мониторинга задачи в фоне + asyncio.create_task(self._monitor_conversion_job(job_id)) + + return job_id + + except Exception as e: + logger.error(f"Failed to start conversion job {job_id}: {e}") + raise ConverterError(f"Conversion start failed: {e}") + + async def _prepare_container_config(self, job_id: str, input_file: str, + output_file: str, conversion_type: str, + options: dict) -> dict: + """Подготовка конфигурации контейнера""" + + # Общая папка для файлов между хостом и контейнером + shared_volume = "/opt/my-network/shared" + container_input = f"/input/{os.path.basename(input_file)}" + container_output = f"/output/{os.path.basename(output_file)}" + + # Копирование входного файла в shared папку + shared_input_path = f"{shared_volume}/input/{job_id}_{os.path.basename(input_file)}" + os.makedirs(os.path.dirname(shared_input_path), exist_ok=True) + shutil.copy2(input_file, shared_input_path) + + # Команда для конвертации + command = self._build_conversion_command( + container_input, container_output, conversion_type, options + ) + + return { + "command": command, + "volumes": { + shared_volume: { + "bind": "/workspace", + "mode": "rw" + } + }, + "environment": { + "JOB_ID": job_id, + "CONVERSION_TYPE": conversion_type, + "INPUT_FILE": container_input, + "OUTPUT_FILE": container_output + } + } + + def _build_conversion_command(self, input_file: str, output_file: str, + conversion_type: str, options: dict) -> List[str]: + """Построение команды конвертации""" + + conversion_commands = { + "video_to_mp4": [ + "ffmpeg", "-i", input_file, + "-c:v", "libx264", "-c:a", "aac", + "-preset", "medium", "-crf", "23", + output_file + ], + "audio_to_mp3": [ + "ffmpeg", "-i", input_file, + "-c:a", "libmp3lame", "-b:a", "192k", + output_file + ], + "image_resize": [ + "convert", input_file, + "-resize", options.get("size", "800x600"), + "-quality", str(options.get("quality", 85)), + output_file + ], + "extract_thumbnail": [ + "ffmpeg", "-i", input_file, + "-ss", "00:00:01", "-frames:v", "1", + "-q:v", "2", output_file + ] + } + + if conversion_type not in conversion_commands: + raise ConverterError(f"Unsupported conversion type: {conversion_type}") + + return conversion_commands[conversion_type] + + async def _monitor_conversion_job(self, job_id: str): + """Мониторинг выполнения задачи конвертации""" + + if job_id not in self.active_jobs: + return + + job = self.active_jobs[job_id] + container = job["container"] + + try: + # Ожидание завершения контейнера + result = container.wait(timeout=300) # 5 минут таймаут + + # Получение логов + logs = container.logs().decode('utf-8') + + # Обновление статуса задачи + if result["StatusCode"] == 0: + job["status"] = "completed" + job["end_time"] = datetime.utcnow() + + # Перемещение выходного файла + await self._handle_conversion_success(job_id) + + logger.info(f"Conversion job {job_id} completed successfully") + else: + job["status"] = "failed" + job["error"] = f"Container exited with code {result['StatusCode']}" + job["logs"] = logs + + logger.error(f"Conversion job {job_id} failed: {job['error']}") + + except docker.errors.ContainerError as e: + job["status"] = "failed" + job["error"] = str(e) + logger.error(f"Conversion job {job_id} container error: {e}") + + except Exception as e: + job["status"] = "failed" + job["error"] = str(e) + logger.error(f"Conversion job {job_id} monitoring error: {e}") + + finally: + # Очистка ресурсов + await self._cleanup_conversion_job(job_id) + + async def _handle_conversion_success(self, job_id: str): + """Обработка успешного завершения конвертации""" + + job = self.active_jobs[job_id] + shared_volume = "/opt/my-network/shared" + + # Путь к выходному файлу в shared папке + shared_output_path = f"{shared_volume}/output/{job_id}_{os.path.basename(job['output_file'])}" + + if os.path.exists(shared_output_path): + # Перемещение файла в целевое расположение + os.makedirs(os.path.dirname(job["output_file"]), exist_ok=True) + shutil.move(shared_output_path, job["output_file"]) + + job["output_size"] = os.path.getsize(job["output_file"]) + logger.info(f"Output file moved to: {job['output_file']}") + else: + job["status"] = "failed" + job["error"] = "Output file not found after conversion" + + async def _cleanup_conversion_job(self, job_id: str): + """Очистка ресурсов после завершения задачи""" + + if job_id not in self.active_jobs: + return + + job = self.active_jobs[job_id] + shared_volume = "/opt/my-network/shared" + + # Удаление временных файлов + input_cleanup = f"{shared_volume}/input/{job_id}_*" + output_cleanup = f"{shared_volume}/output/{job_id}_*" + + for pattern in [input_cleanup, output_cleanup]: + for file_path in glob.glob(pattern): + try: + os.remove(file_path) + except OSError: + pass + + # Удаление задачи из активных (через некоторое время для логов) + await asyncio.sleep(60) # Сохраняем информацию 1 минуту + if job_id in self.active_jobs: + del self.active_jobs[job_id] + + async def get_job_status(self, job_id: str) -> dict: + """Получение статуса задачи конвертации""" + + if job_id not in self.active_jobs: + return {"status": "not_found", "error": "Job not found"} + + job = self.active_jobs[job_id] + + status_info = { + "job_id": job_id, + "status": job["status"], + "conversion_type": job["conversion_type"], + "start_time": job["start_time"].isoformat(), + "input_file": job["input_file"], + "output_file": job["output_file"] + } + + if "end_time" in job: + status_info["end_time"] = job["end_time"].isoformat() + duration = (job["end_time"] - job["start_time"]).total_seconds() + status_info["duration_seconds"] = duration + + if "output_size" in job: + status_info["output_size"] = job["output_size"] + + if "error" in job: + status_info["error"] = job["error"] + + if "logs" in job: + status_info["logs"] = job["logs"] + + return status_info + + async def cancel_job(self, job_id: str) -> bool: + """Отмена задачи конвертации""" + + if job_id not in self.active_jobs: + return False + + job = self.active_jobs[job_id] + container = job["container"] + + try: + # Остановка контейнера + container.stop(timeout=10) + job["status"] = "cancelled" + job["end_time"] = datetime.utcnow() + + logger.info(f"Conversion job {job_id} cancelled") + return True + + except Exception as e: + logger.error(f"Failed to cancel job {job_id}: {e}") + return False + + async def get_active_jobs(self) -> List[dict]: + """Получение списка активных задач""" + + active_jobs = [] + + for job_id, job in self.active_jobs.items(): + if job["status"] == "running": + active_jobs.append({ + "job_id": job_id, + "conversion_type": job["conversion_type"], + "start_time": job["start_time"].isoformat(), + "duration_seconds": (datetime.utcnow() - job["start_time"]).total_seconds() + }) + + return active_jobs +``` + +## 🔧 Docker Compose конфигурация + +### Обновленный docker-compose.yml + +```yaml +version: '3.8' + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "15100:15100" + volumes: + # Доступ к Docker socket для управления converter контейнерами + - ${DOCKER_SOCK_PATH:-/var/run/docker.sock}:/var/run/docker.sock + # Общая папка для обмена файлами с converter контейнерами + - ./shared:/opt/my-network/shared + # Хранилище контента + - ./storage:/opt/my-network/storage + environment: + - DOCKER_SOCK_PATH=/var/run/docker.sock + - CONVERTER_ENABLED=true + - CONVERTER_MAX_PARALLEL=3 + depends_on: + - postgres + - redis + networks: + - my-network + + # Converter образ (только для сборки, не запускается) + converter-module: + build: + context: ./converter-module + dockerfile: Dockerfile + image: my-converter:latest + profiles: + - build-only # Не запускается в обычном режиме + + postgres: + image: postgres:15 + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - my-network + + redis: + image: redis:7-alpine + networks: + - my-network + +volumes: + postgres_data: + +networks: + my-network: + driver: bridge +``` + +## 📊 API для управления конвертацией + +### REST API endpoints + +```python +@app.route('/api/v1/converter/convert', methods=['POST']) +async def start_conversion(request): + """Запуск задачи конвертации""" + + data = request.json + + required_fields = ['input_file', 'output_file', 'conversion_type'] + for field in required_fields: + if field not in data: + return response.json({'error': f'Missing required field: {field}'}, status=400) + + try: + job_id = await converter_manager.convert_file( + input_file=data['input_file'], + output_file=data['output_file'], + conversion_type=data['conversion_type'], + options=data.get('options', {}) + ) + + return response.json({ + 'job_id': job_id, + 'status': 'started', + 'message': 'Conversion job started successfully' + }) + + except ConverterError as e: + return response.json({'error': str(e)}, status=400) + +@app.route('/api/v1/converter/status/', methods=['GET']) +async def get_conversion_status(request, job_id): + """Получение статуса задачи конвертации""" + + status = await converter_manager.get_job_status(job_id) + return response.json(status) + +@app.route('/api/v1/converter/cancel/', methods=['POST']) +async def cancel_conversion(request, job_id): + """Отмена задачи конвертации""" + + cancelled = await converter_manager.cancel_job(job_id) + + if cancelled: + return response.json({'message': 'Job cancelled successfully'}) + else: + return response.json({'error': 'Failed to cancel job or job not found'}, status=404) + +@app.route('/api/v1/converter/active', methods=['GET']) +async def get_active_conversions(request): + """Список активных задач конвертации""" + + active_jobs = await converter_manager.get_active_jobs() + return response.json({ + 'active_jobs': active_jobs, + 'total_active': len(active_jobs) + }) + +@app.route('/api/v1/converter/stats', methods=['GET']) +async def get_converter_stats(request): + """Статистика работы конвертера""" + + return response.json({ + 'max_parallel_jobs': converter_manager.max_parallel_jobs, + 'active_jobs_count': len(converter_manager.active_jobs), + 'docker_sock_path': converter_manager.docker_sock_path, + 'converter_image': converter_manager.converter_image, + 'supported_conversions': [ + 'video_to_mp4', + 'audio_to_mp3', + 'image_resize', + 'extract_thumbnail' + ] + }) +``` + +## 🛠️ Converter Module Dockerfile + +### converter-module/Dockerfile + +```dockerfile +FROM ubuntu:22.04 + +# Установка зависимостей +RUN apt-get update && apt-get install -y \ + ffmpeg \ + imagemagick \ + python3 \ + python3-pip \ + && rm -rf /var/lib/apt/lists/* + +# Установка Python зависимостей +RUN pip3 install \ + pillow \ + opencv-python + +# Создание рабочей директории +WORKDIR /workspace + +# Копирование скриптов конвертации +COPY scripts/ /usr/local/bin/ +RUN chmod +x /usr/local/bin/* + +# Создание директорий для файлов +RUN mkdir -p /input /output + +# Точка входа +ENTRYPOINT ["/usr/local/bin/convert.sh"] +``` + +### converter-module/scripts/convert.sh + +```bash +#!/bin/bash + +set -e + +echo "Starting conversion job: $JOB_ID" +echo "Conversion type: $CONVERSION_TYPE" +echo "Input file: $INPUT_FILE" +echo "Output file: $OUTPUT_FILE" + +# Функция логирования +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} + +# Проверка входного файла +if [ ! -f "/workspace$INPUT_FILE" ]; then + log "ERROR: Input file not found: $INPUT_FILE" + exit 1 +fi + +# Создание выходной директории +mkdir -p "$(dirname "/workspace$OUTPUT_FILE")" + +# Выполнение конвертации в зависимости от типа +case "$CONVERSION_TYPE" in + "video_to_mp4") + log "Converting video to MP4..." + ffmpeg -i "/workspace$INPUT_FILE" \ + -c:v libx264 -c:a aac \ + -preset medium -crf 23 \ + "/workspace$OUTPUT_FILE" + ;; + + "audio_to_mp3") + log "Converting audio to MP3..." + ffmpeg -i "/workspace$INPUT_FILE" \ + -c:a libmp3lame -b:a 192k \ + "/workspace$OUTPUT_FILE" + ;; + + "image_resize") + log "Resizing image..." + convert "/workspace$INPUT_FILE" \ + -resize 800x600 \ + -quality 85 \ + "/workspace$OUTPUT_FILE" + ;; + + "extract_thumbnail") + log "Extracting thumbnail..." + ffmpeg -i "/workspace$INPUT_FILE" \ + -ss 00:00:01 -frames:v 1 \ + -q:v 2 \ + "/workspace$OUTPUT_FILE" + ;; + + *) + log "ERROR: Unsupported conversion type: $CONVERSION_TYPE" + exit 1 + ;; +esac + +# Проверка результата +if [ -f "/workspace$OUTPUT_FILE" ]; then + log "Conversion completed successfully" + log "Output file size: $(du -h "/workspace$OUTPUT_FILE" | cut -f1)" + exit 0 +else + log "ERROR: Output file was not created" + exit 1 +fi +``` + +## 🔍 Мониторинг и логирование + +### Логирование converter операций + +```python +class ConverterLogger: + """Специализированная система логирования для converter операций""" + + def __init__(self): + self.logger = logging.getLogger('converter') + self.converter_metrics = { + 'total_jobs': 0, + 'successful_jobs': 0, + 'failed_jobs': 0, + 'total_processing_time': 0 + } + + def log_job_start(self, job_id: str, conversion_type: str, input_file: str): + """Логирование начала задачи""" + self.logger.info({ + 'event': 'job_started', + 'job_id': job_id, + 'conversion_type': conversion_type, + 'input_file': os.path.basename(input_file), + 'timestamp': datetime.utcnow().isoformat() + }) + self.converter_metrics['total_jobs'] += 1 + + def log_job_completion(self, job_id: str, success: bool, duration: float, output_size: int = None): + """Логирование завершения задачи""" + + if success: + self.converter_metrics['successful_jobs'] += 1 + event = 'job_completed' + else: + self.converter_metrics['failed_jobs'] += 1 + event = 'job_failed' + + self.converter_metrics['total_processing_time'] += duration + + log_data = { + 'event': event, + 'job_id': job_id, + 'duration_seconds': duration, + 'timestamp': datetime.utcnow().isoformat() + } + + if output_size: + log_data['output_size_bytes'] = output_size + + self.logger.info(log_data) + + def get_metrics(self) -> dict: + """Получение метрик converter модуля""" + + total_jobs = self.converter_metrics['total_jobs'] + + return { + 'total_jobs': total_jobs, + 'successful_jobs': self.converter_metrics['successful_jobs'], + 'failed_jobs': self.converter_metrics['failed_jobs'], + 'success_rate': self.converter_metrics['successful_jobs'] / total_jobs if total_jobs > 0 else 0, + 'average_processing_time': self.converter_metrics['total_processing_time'] / total_jobs if total_jobs > 0 else 0 + } +``` + +## 🚀 Интеграция с MY Network + +### Автоматическая конвертация при загрузке контента + +```python +class ContentProcessingPipeline: + """Конвейер обработки контента с автоматической конвертацией""" + + async def process_uploaded_content(self, content: StoredContent): + """Обработка загруженного контента""" + + # Определение типа контента и необходимых конвертаций + conversions_needed = self._determine_conversions(content) + + for conversion in conversions_needed: + try: + job_id = await converter_manager.convert_file( + input_file=content.file_path, + output_file=conversion['output_path'], + conversion_type=conversion['type'], + options=conversion.get('options', {}) + ) + + # Сохранение связи между контентом и задачей конвертации + await self._link_content_conversion(content.id, job_id, conversion['type']) + + except ConverterError as e: + logger.error(f"Failed to start conversion for content {content.id}: {e}") + + def _determine_conversions(self, content: StoredContent) -> List[dict]: + """Определение необходимых конвертаций""" + + conversions = [] + + if content.content_type.startswith('video/'): + # Для видео создаем preview и thumbnail + conversions.extend([ + { + 'type': 'video_to_mp4', + 'output_path': content.file_path.replace('.', '_preview.') + 'mp4', + 'options': {'quality': 'medium', 'resolution': '720p'} + }, + { + 'type': 'extract_thumbnail', + 'output_path': content.file_path.replace('.', '_thumb.') + 'jpg' + } + ]) + + elif content.content_type.startswith('audio/'): + # Для аудио создаем preview версию + conversions.append({ + 'type': 'audio_to_mp3', + 'output_path': content.file_path.replace('.', '_preview.') + 'mp3', + 'options': {'bitrate': '128k', 'duration': '30'} # 30 секунд preview + }) + + elif content.content_type.startswith('image/'): + # Для изображений создаем thumbnail + conversions.append({ + 'type': 'image_resize', + 'output_path': content.file_path.replace('.', '_thumb.') + 'jpg', + 'options': {'size': '300x300', 'quality': 80} + }) + + return conversions +``` + +Эта система обеспечивает гибкую и эффективную обработку медиа файлов с полной интеграцией в MY Network архитектуру. \ No newline at end of file diff --git a/docs/INSTALLATION_GUIDE.md b/docs/INSTALLATION_GUIDE.md new file mode 100644 index 0000000..8fd0c1d --- /dev/null +++ b/docs/INSTALLATION_GUIDE.md @@ -0,0 +1,826 @@ +# MY Network v3.0 - Руководство по установке + +## 🚀 Автоматическая установка одной командой + +### Быстрая установка + +```bash +curl -fsSL https://raw.githubusercontent.com/your-org/my-uploader-bot/main/start.sh | bash +``` + +### Установка с параметрами + +```bash +curl -fsSL https://raw.githubusercontent.com/your-org/my-uploader-bot/main/start.sh | bash -s -- --domain=yourdomain.com --ssl=true +``` + +## 📋 Требования системы + +### Минимальные требования +- **ОС**: Ubuntu 20.04+, Debian 11+, CentOS 8+ +- **RAM**: 2GB (рекомендуется 4GB) +- **CPU**: 2 cores +- **Диск**: 20GB свободного места (рекомендуется 100GB для хранения контента) +- **Права**: Root доступ или sudo + +### Рекомендуемые требования +- **RAM**: 8GB+ +- **CPU**: 4+ cores +- **Диск**: 500GB+ SSD +- **Сеть**: Статический IP, доменное имя + +## 🛠️ Подробное описание скрипта start.sh + +### Этапы установки + +```bash +#!/bin/bash +# MY Network v3.0 Installation Script + +set -e + +# Переменные по умолчанию +DOMAIN="" +DOCKER_SOCK_PATH="/var/run/docker.sock" +BOOTSTRAP_CONFIG="default" +TELEGRAM_API_KEY="" +CLIENT_TELEGRAM_API_KEY="" +SSL_ENABLED=false +NODE_TYPE="public" +INSTALL_DEPENDENCIES=true + +# Функция вывода логов +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} + +# Этап 1: Проверка системы и прав +check_system() { + log "🔍 Проверка системы..." + + # Проверка прав root + if [[ $EUID -ne 0 ]]; then + log "❌ Скрипт должен запускаться с правами root" + exit 1 + fi + + # Определение ОС + if [ -f /etc/os-release ]; then + . /etc/os-release + OS=$NAME + VER=$VERSION_ID + else + log "❌ Неподдерживаемая операционная система" + exit 1 + fi + + log "✅ Система: $OS $VER" + + # Проверка архитектуры + ARCH=$(uname -m) + if [[ "$ARCH" != "x86_64" && "$ARCH" != "aarch64" ]]; then + log "❌ Неподдерживаемая архитектура: $ARCH" + exit 1 + fi + + log "✅ Архитектура: $ARCH" +} + +# Этап 2: Интерактивная настройка +interactive_setup() { + log "🔧 Интерактивная настройка MY Network..." + + # Тип ноды + echo "" + echo "Выберите тип ноды:" + echo "1) Основная нода (создает новую сеть)" + echo "2) Подключаемая нода (подключается к существующей сети)" + read -p "Введите номер [1-2]: " node_choice + + case $node_choice in + 1) + NODE_TYPE="bootstrap" + BOOTSTRAP_CONFIG="new" + log "✅ Выбрана основная нода (создание новой сети)" + ;; + 2) + NODE_TYPE="public" + BOOTSTRAP_CONFIG="existing" + log "✅ Выбрана подключаемая нода" + ;; + *) + log "❌ Неверный выбор. Используется подключаемая нода по умолчанию" + NODE_TYPE="public" + BOOTSTRAP_CONFIG="existing" + ;; + esac + + # Bootstrap конфигурация + if [[ "$BOOTSTRAP_CONFIG" == "existing" ]]; then + echo "" + read -p "Путь до bootstrap.json [оставьте пустым для дефолтного]: " custom_bootstrap + if [[ -n "$custom_bootstrap" && -f "$custom_bootstrap" ]]; then + BOOTSTRAP_CONFIG="$custom_bootstrap" + log "✅ Использован кастомный bootstrap.json: $custom_bootstrap" + else + log "✅ Использован дефолтный bootstrap.json" + fi + fi + + # Docker socket + echo "" + read -p "Путь до docker.sock [$DOCKER_SOCK_PATH]: " custom_docker_sock + if [[ -n "$custom_docker_sock" ]]; then + DOCKER_SOCK_PATH="$custom_docker_sock" + fi + + if [[ -S "$DOCKER_SOCK_PATH" ]]; then + log "✅ Docker socket найден: $DOCKER_SOCK_PATH" + else + log "⚠️ Docker socket не найден: $DOCKER_SOCK_PATH (будет установлен Docker)" + fi + + # Telegram API ключи + echo "" + read -p "TELEGRAM_API_KEY [оставьте пустым если не нужен бот]: " TELEGRAM_API_KEY + if [[ -n "$TELEGRAM_API_KEY" ]]; then + log "✅ TELEGRAM_API_KEY настроен" + + read -p "CLIENT_TELEGRAM_API_KEY [оставьте пустым если не нужен]: " CLIENT_TELEGRAM_API_KEY + if [[ -n "$CLIENT_TELEGRAM_API_KEY" ]]; then + log "✅ CLIENT_TELEGRAM_API_KEY настроен" + fi + else + log "ℹ️ Telegram боты будут отключены" + fi + + # SSL сертификат + echo "" + read -p "Настроить SSL сертификат? [y/N]: " ssl_choice + if [[ "$ssl_choice" =~ ^[Yy]$ ]]; then + read -p "Доменное имя: " DOMAIN + if [[ -n "$DOMAIN" ]]; then + SSL_ENABLED=true + log "✅ SSL будет настроен для домена: $DOMAIN" + else + log "❌ Домен не указан. SSL отключен" + fi + fi + + # Приватная нода + echo "" + read -p "Создать приватную ноду (только исходящие соединения)? [y/N]: " private_choice + if [[ "$private_choice" =~ ^[Yy]$ ]]; then + NODE_TYPE="private" + log "✅ Создается приватная нода" + fi +} + +# Этап 3: Установка зависимостей +install_dependencies() { + log "📦 Установка зависимостей..." + + # Обновление системы + log "🔄 Обновление пакетов системы..." + apt update && apt upgrade -y + + # Базовые пакеты + log "📦 Установка базовых пакетов..." + apt install -y curl wget git unzip htop nano ufw fail2ban python3 python3-pip + + # Docker + if ! command -v docker &> /dev/null; then + log "🐳 Установка Docker..." + curl -fsSL https://get.docker.com -o get-docker.sh + sh get-docker.sh + rm get-docker.sh + + # Docker Compose + log "🐳 Установка Docker Compose..." + curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + + # Запуск Docker + systemctl start docker + systemctl enable docker + + log "✅ Docker установлен и запущен" + else + log "✅ Docker уже установлен" + fi + + # Python зависимости + log "🐍 Установка Python зависимостей..." + pip3 install --upgrade pip + + # Проверка версий + docker --version + docker-compose --version + python3 --version +} + +# Этап 4: SSL сертификат +setup_ssl() { + if [[ "$SSL_ENABLED" == true && -n "$DOMAIN" ]]; then + log "🔒 Настройка SSL сертификата..." + + # Установка certbot + apt install -y certbot python3-certbot-nginx + + # Получение сертификата + log "🔒 Получение SSL сертификата для $DOMAIN..." + read -p "Email для уведомлений SSL: " ssl_email + + certbot certonly --standalone --non-interactive --agree-tos \ + --email "$ssl_email" -d "$DOMAIN" + + if [[ $? -eq 0 ]]; then + log "✅ SSL сертификат успешно получен" + + # Настройка автообновления + echo "0 2 * * * /usr/bin/certbot renew --quiet" | crontab - + log "✅ Автообновление SSL настроено" + else + log "❌ Ошибка получения SSL сертификата" + SSL_ENABLED=false + fi + fi +} + +# Этап 5: Загрузка и настройка проекта +setup_project() { + log "📥 Загрузка MY Network..." + + # Создание директории + PROJECT_DIR="/opt/my-network" + mkdir -p "$PROJECT_DIR" + cd "$PROJECT_DIR" + + # Клонирование репозитория + if [[ -d "my-uploader-bot" ]]; then + log "🔄 Обновление существующего проекта..." + cd my-uploader-bot + git pull origin main + else + log "📥 Клонирование проекта..." + git clone https://github.com/your-org/my-uploader-bot.git + cd my-uploader-bot + fi + + # Права доступа + chmod +x scripts/*.sh + chmod +x *.sh + + log "✅ Проект загружен в $PROJECT_DIR/my-uploader-bot" +} + +# Этап 6: Генерация конфигурации +generate_config() { + log "⚙️ Генерация конфигурации..." + + # Создание .env файла + cat > .env << EOF +# MY Network v3.0 Configuration +# Generated: $(date) + +# Node Configuration +NODE_TYPE=$NODE_TYPE +NODE_ID=node-$(date +%s)-$(shuf -i 1000-9999 -n 1) +MY_NETWORK_ENABLED=true +MY_NETWORK_VERSION=3.0.0 + +# Database (PostgreSQL only) +DATABASE_URL=postgresql://myuser:$(openssl rand -hex 16)@localhost:5432/mynetwork +POSTGRES_DB=mynetwork +POSTGRES_USER=myuser +POSTGRES_PASSWORD=$(openssl rand -hex 16) + +# Redis +REDIS_URL=redis://localhost:6379/0 +REDIS_ENABLED=true + +# Security +SECRET_KEY=$(openssl rand -hex 32) +JWT_SECRET_KEY=$(openssl rand -hex 32) +ENCRYPTION_KEY=$(openssl rand -hex 32) + +# API Configuration +API_HOST=0.0.0.0 +API_PORT=15100 + +# Telegram Bots +TELEGRAM_API_KEY=$TELEGRAM_API_KEY +CLIENT_TELEGRAM_API_KEY=$CLIENT_TELEGRAM_API_KEY + +# Docker Configuration +DOCKER_SOCK_PATH=$DOCKER_SOCK_PATH +CONVERTER_ENABLED=true +CONVERTER_MAX_PARALLEL=3 + +# SSL Configuration +SSL_ENABLED=$SSL_ENABLED +DOMAIN=$DOMAIN + +# Network Configuration +BOOTSTRAP_CONFIG=$BOOTSTRAP_CONFIG +ALLOW_INCOMING_CONNECTIONS=$([ "$NODE_TYPE" != "private" ] && echo "true" || echo "false") + +# Storage Configuration +STORAGE_PATH=/opt/my-network/storage +CONTENT_RETENTION_DAYS=7 + +# Logging +LOG_LEVEL=INFO +LOG_FORMAT=json +EOF + + # Bootstrap конфигурация + if [[ "$BOOTSTRAP_CONFIG" == "new" ]]; then + # Создание нового bootstrap.json для основной ноды + cat > bootstrap.json << EOF +{ + "version": "3.0.0", + "network_id": "my-network-$(date +%s)", + "created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "bootstrap_nodes": [ + { + "node_id": "bootstrap-main", + "address": "$([ "$SSL_ENABLED" == true ] && echo "https://$DOMAIN" || echo "http://$(curl -s ifconfig.me || echo localhost)")", + "public_key": "", + "trusted": true, + "node_type": "bootstrap" + } + ], + "network_settings": { + "protocol_version": "3.0", + "max_peers": 50, + "sync_interval": 300 + } +} +EOF + log "✅ Создан новый bootstrap.json для основной ноды" + elif [[ -f "$BOOTSTRAP_CONFIG" ]]; then + # Копирование кастомного bootstrap.json + cp "$BOOTSTRAP_CONFIG" bootstrap.json + log "✅ Использован кастомный bootstrap.json" + fi + + log "✅ Конфигурация сгенерирована" +} + +# Этап 7: Сборка и запуск контейнеров +setup_containers() { + log "🐳 Сборка и запуск контейнеров..." + + # Сборка образов + log "🔨 Сборка Docker образов..." + docker-compose -f docker-compose.yml build + + # Сборка converter image + log "🔄 Сборка converter module..." + docker-compose -f docker-compose.yml build converter-module + + # Запуск сервисов + log "🚀 Запуск сервисов..." + docker-compose -f docker-compose.yml up -d + + # Ожидание готовности сервисов + log "⏳ Ожидание готовности сервисов..." + sleep 30 + + # Проверка статуса + log "🔍 Проверка статуса сервисов..." + docker-compose -f docker-compose.yml ps + + log "✅ Контейнеры запущены" +} + +# Этап 8: Настройка системных сервисов +setup_system_services() { + log "⚙️ Настройка системных сервисов..." + + # Создание systemd сервиса + cat > /etc/systemd/system/my-network.service << EOF +[Unit] +Description=MY Network v3.0 +Requires=docker.service +After=docker.service + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=$PROJECT_DIR/my-uploader-bot +ExecStart=/usr/local/bin/docker-compose -f docker-compose.yml up -d +ExecStop=/usr/local/bin/docker-compose -f docker-compose.yml down +TimeoutStartSec=0 + +[Install] +WantedBy=multi-user.target +EOF + + # Активация сервиса + systemctl daemon-reload + systemctl enable my-network + + # Настройка firewall (если нужно) + if [[ "$NODE_TYPE" != "private" ]]; then + log "🔥 Настройка firewall..." + ufw default deny incoming + ufw default allow outgoing + ufw allow ssh + ufw allow 15100/tcp # API порт + + if [[ "$SSL_ENABLED" == true ]]; then + ufw allow 80/tcp + ufw allow 443/tcp + fi + + ufw --force enable + log "✅ Firewall настроен" + fi + + log "✅ Системные сервисы настроены" +} + +# Этап 9: Инициализация базы данных +initialize_database() { + log "🗄️ Инициализация базы данных..." + + # Ожидание готовности PostgreSQL + log "⏳ Ожидание готовности PostgreSQL..." + sleep 20 + + # Выполнение миграций + log "🔄 Выполнение миграций базы данных..." + docker-compose -f docker-compose.yml exec -T app alembic upgrade head + + if [[ $? -eq 0 ]]; then + log "✅ База данных инициализирована" + else + log "❌ Ошибка инициализации базы данных" + return 1 + fi +} + +# Этап 10: Подключение к сети +connect_to_network() { + log "🌐 Подключение к MY Network..." + + # Ожидание полной готовности + log "⏳ Ожидание готовности всех сервисов..." + sleep 60 + + # Проверка API + local api_url="http://localhost:15100" + if curl -f "$api_url/health" > /dev/null 2>&1; then + log "✅ API сервис готов" + + # Статистика подключения к сети + local network_stats=$(curl -s "$api_url/api/v1/network/stats" || echo "{}") + log "📊 Статистика сети: $network_stats" + + # Попытка подключения к bootstrap нодам (если не основная) + if [[ "$NODE_TYPE" != "bootstrap" ]]; then + log "🔗 Подключение к bootstrap нодам..." + curl -X POST "$api_url/api/v1/node/connect" \ + -H "Content-Type: application/json" \ + -d '{"auto_discover": true}' > /dev/null 2>&1 + + sleep 10 + + # Проверка подключений + local peers=$(curl -s "$api_url/api/v1/node/peers" | jq -r '.count // 0' 2>/dev/null || echo "0") + log "👥 Подключено пиров: $peers" + fi + + else + log "❌ API сервис недоступен" + return 1 + fi +} + +# Этап 11: Финальная проверка и отчет +final_report() { + log "📋 Финальная проверка установки..." + + # Проверка всех сервисов + echo "" + echo "=== СТАТУС УСТАНОВКИ ===" + + # Docker контейнеры + echo "🐳 Docker контейнеры:" + docker-compose -f docker-compose.yml ps + + # Системный сервис + echo "" + echo "⚙️ Системный сервис:" + systemctl is-active my-network && echo "✅ my-network: активен" || echo "❌ my-network: не активен" + + # Сетевые подключения + echo "" + echo "🌐 Сетевые подключения:" + if [[ "$NODE_TYPE" != "private" ]]; then + netstat -tlnp | grep :15100 && echo "✅ API порт 15100: открыт" || echo "❌ API порт: недоступен" + fi + + # Доступность API + echo "" + echo "📡 API доступность:" + local api_endpoint="http://localhost:15100" + if [[ "$SSL_ENABLED" == true ]]; then + api_endpoint="https://$DOMAIN" + fi + + if curl -f "$api_endpoint/health" > /dev/null 2>&1; then + echo "✅ API: доступно на $api_endpoint" + echo "✅ Веб-интерфейс: $api_endpoint/api/my/monitor/" + else + echo "❌ API: недоступно" + fi + + # Конфигурация + echo "" + echo "⚙️ Конфигурация:" + echo "📁 Проект: $PROJECT_DIR/my-uploader-bot" + echo "🔧 Тип ноды: $NODE_TYPE" + echo "🔒 SSL: $([ "$SSL_ENABLED" == true ] && echo "включен ($DOMAIN)" || echo "отключен")" + echo "🤖 Telegram боты: $([ -n "$TELEGRAM_API_KEY" ] && echo "включены" || echo "отключены")" + echo "🐳 Docker socket: $DOCKER_SOCK_PATH" + + # Управление + echo "" + echo "=== КОМАНДЫ УПРАВЛЕНИЯ ===" + echo "🔄 Перезапуск: systemctl restart my-network" + echo "⏹️ Остановка: systemctl stop my-network" + echo "📊 Статус: systemctl status my-network" + echo "📋 Логи: docker-compose -f $PROJECT_DIR/my-uploader-bot/docker-compose.yml logs -f" + echo "🌐 Мониторинг: $api_endpoint/api/my/monitor/" + + # Сохранение информации об установке + cat > /opt/my-network-install.log << EOF +MY Network v3.0 Installation Report +Generated: $(date) + +Configuration: +- Node Type: $NODE_TYPE +- SSL Enabled: $SSL_ENABLED +- Domain: $DOMAIN +- API Endpoint: $api_endpoint +- Project Path: $PROJECT_DIR/my-uploader-bot +- Docker Socket: $DOCKER_SOCK_PATH + +Services: +- API: $api_endpoint +- Monitoring: $api_endpoint/api/my/monitor/ +- System Service: my-network + +Management Commands: +- Start: systemctl start my-network +- Stop: systemctl stop my-network +- Restart: systemctl restart my-network +- Status: systemctl status my-network +- Logs: docker-compose -f $PROJECT_DIR/my-uploader-bot/docker-compose.yml logs -f +EOF + + echo "" + echo "🎉 УСТАНОВКА ЗАВЕРШЕНА!" + echo "📄 Отчет сохранен: /opt/my-network-install.log" + echo "" +} + +# Главная функция +main() { + echo "🚀 MY Network v3.0 - Автоматическая установка" + echo "==============================================" + echo "" + + # Парсинг аргументов командной строки + while [[ $# -gt 0 ]]; do + case $1 in + --domain=*) + DOMAIN="${1#*=}" + SSL_ENABLED=true + shift + ;; + --ssl=*) + SSL_ENABLED="${1#*=}" + shift + ;; + --docker-sock=*) + DOCKER_SOCK_PATH="${1#*=}" + shift + ;; + --bootstrap=*) + BOOTSTRAP_CONFIG="${1#*=}" + shift + ;; + --node-type=*) + NODE_TYPE="${1#*=}" + shift + ;; + --no-deps) + INSTALL_DEPENDENCIES=false + shift + ;; + *) + echo "Неизвестный параметр: $1" + exit 1 + ;; + esac + done + + # Выполнение этапов установки + check_system + interactive_setup + + if [[ "$INSTALL_DEPENDENCIES" == true ]]; then + install_dependencies + fi + + setup_ssl + setup_project + generate_config + setup_containers + setup_system_services + initialize_database + connect_to_network + final_report +} + +# Обработка ошибок +error_handler() { + log "❌ Ошибка на строке $1. Код выхода: $2" + log "🔄 Попробуйте запустить скрипт заново или обратитесь за поддержкой" + exit $2 +} + +trap 'error_handler $LINENO $?' ERR + +# Запуск установки +main "$@" +``` + +### Особенности скрипта + +1. **Интерактивная настройка**: Задает только необходимые вопросы +2. **Автоматическая детекция**: Определяет ОС и архитектуру +3. **Безопасность**: Генерирует случайные пароли и ключи +4. **Логирование**: Подробные логи каждого этапа +5. **Откат при ошибках**: Информативные сообщения об ошибках +6. **SSL поддержка**: Автоматическое получение сертификатов через Certbot + +## 🔧 Ручная установка + +### Шаг 1: Подготовка системы + +```bash +# Обновление системы +sudo apt update && sudo apt upgrade -y + +# Установка базовых пакетов +sudo apt install -y curl wget git unzip htop nano ufw python3 python3-pip + +# Установка Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh + +# Установка Docker Compose +sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose +``` + +### Шаг 2: Клонирование проекта + +```bash +# Создание директории +sudo mkdir -p /opt/my-network +cd /opt/my-network + +# Клонирование репозитория +sudo git clone https://github.com/your-org/my-uploader-bot.git +cd my-uploader-bot + +# Права доступа +sudo chmod +x scripts/*.sh +``` + +### Шаг 3: Настройка конфигурации + +```bash +# Копирование примера конфигурации +cp .env.example .env + +# Редактирование .env файла +nano .env +``` + +### Шаг 4: Запуск сервисов + +```bash +# Сборка образов +docker-compose build + +# Запуск сервисов +docker-compose up -d + +# Проверка статуса +docker-compose ps +``` + +## 🔍 Проверка установки + +### Проверка API + +```bash +# Здоровье сервиса +curl http://localhost:15100/health + +# Статус ноды +curl http://localhost:15100/api/v1/node/status + +# Статистика сети +curl http://localhost:15100/api/v1/network/stats +``` + +### Веб-интерфейс + +``` +http://localhost:15100/api/my/monitor/ +``` + +### Логи сервисов + +```bash +# Все сервисы +docker-compose logs -f + +# Конкретный сервис +docker-compose logs -f app + +# Системный сервис +journalctl -u my-network -f +``` + +## 🛠️ Устранение неисправностей + +### Частые проблемы + +1. **Docker не запускается** + ```bash + sudo systemctl start docker + sudo systemctl enable docker + ``` + +2. **Порты заняты** + ```bash + sudo netstat -tlnp | grep :15100 + sudo fuser -k 15100/tcp + ``` + +3. **Проблемы с правами** + ```bash + sudo chown -R $USER:$USER /opt/my-network + sudo usermod -aG docker $USER + ``` + +4. **SSL сертификат не работает** + ```bash + sudo certbot certificates + sudo certbot renew --dry-run + ``` + +### Логи установки + +Подробные логи установки сохраняются в: +- `/opt/my-network-install.log` - отчет об установке +- `/var/log/my-network/` - логи работы сервисов + +## 🔄 Обновление + +### Автоматическое обновление + +```bash +cd /opt/my-network/my-uploader-bot +git pull origin main +docker-compose build +docker-compose up -d +``` + +### Ручное обновление + +```bash +# Остановка сервисов +sudo systemctl stop my-network + +# Обновление кода +cd /opt/my-network/my-uploader-bot +git pull origin main + +# Пересборка образов +docker-compose build + +# Запуск +sudo systemctl start my-network +``` + +Эта документация обеспечивает простую и надежную установку MY Network v3.0 на любом Linux сервере. \ No newline at end of file diff --git a/docs/NEW_ARCHITECTURE.md b/docs/NEW_ARCHITECTURE.md new file mode 100644 index 0000000..986b2c8 --- /dev/null +++ b/docs/NEW_ARCHITECTURE.md @@ -0,0 +1,391 @@ +# MY Network v3.0 - Децентрализованная архитектура контента + +## 🎯 Обзор + +MY Network v3.0 представляет собой полностью децентрализованную платформу дистрибьюции контента, где каждая нода принимает независимые решения о принятии и хранении контента. Система исключает централизованное управление и кворумное принятие решений. + +## 🏗️ Принципы децентрализации + +### 1. Автономность нод +- **Независимое принятие решений**: Каждая нода самостоятельно решает принимать контент или нет +- **Локальные правила**: Настройки фильтрации и политики хранения на уровне ноды +- **Отсутствие консенсуса**: Нет необходимости в голосовании или кворуме + +### 2. Доступность без цензуры +- **Множественные источники**: Контент может быть доступен на разных нодах +- **Отсутствие единой точки отказа**: Недоступность одной ноды не влияет на сеть +- **Устойчивость к цензуре**: Контент остается доступным пока есть хотя бы одна нода + +### 3. Гибкая топология сети +- **Публичные ноды**: С открытыми портами для входящих соединений +- **Приватные ноды**: Только исходящие соединения, получение контента +- **Bootstrap ноды**: Точки входа в сеть для новых участников + +## 🔄 Архитектура синхронизации + +### Замена кворумной системы + +```python +class ContentAcceptanceManager: + """Управление принятием контента без консенсуса""" + + def __init__(self, node_config: dict): + self.node_config = node_config + self.filters = ContentFilters(node_config) + + async def should_accept_content(self, content_metadata: dict) -> bool: + """ + Решение о принятии контента (заглушка для будущего расширения) + + В текущей версии всегда возвращает True. + В будущем здесь будут фильтры по: + - Авторам (whitelist/blacklist) + - Типам контента + - Размерам файлов + - Тегам и категориям + """ + # TODO: Добавить логику фильтрации + return True + + async def process_content_announcement(self, peer_id: str, announcement: dict): + """Обработка анонса нового контента от пира""" + + content_hash = announcement.get("content_hash") + + # Проверка - нужен ли нам этот контент + if await self.should_accept_content(announcement): + # Добавляем в очередь синхронизации + await self.sync_manager.queue_content_sync(peer_id, content_hash) + else: + logger.info(f"Content {content_hash} rejected by local filters") +``` + +### Протокол синхронизации P2P + +```python +class P2PSyncProtocol: + """Децентрализованный протокол синхронизации""" + + async def announce_new_content(self, content: StoredContent): + """Анонс нового контента всем подключенным пирам""" + + announcement = { + "type": "content_announcement", + "content_hash": content.hash, + "file_size": content.file_size, + "content_type": content.content_type, + "encrypted": content.encrypted, + "timestamp": datetime.utcnow().isoformat(), + "author_id": content.author_id, + "tags": content.tags + } + + # Рассылаем анонс всем активным пирам + for peer_id in self.active_peers: + try: + await self.send_to_peer(peer_id, announcement) + except Exception as e: + logger.warning(f"Failed to announce to peer {peer_id}: {e}") + + async def request_content_sync(self, peer_id: str, content_hash: str): + """Запрос синхронизации конкретного контента""" + + request = { + "type": "sync_request", + "content_hash": content_hash, + "requesting_node": self.node_id, + "timestamp": datetime.utcnow().isoformat() + } + + response = await self.send_request_to_peer(peer_id, request) + + if response.get("status") == "approved": + await self._sync_content_from_peer(peer_id, content_hash) + else: + logger.info(f"Sync request for {content_hash} denied by {peer_id}") +``` + +## 🔐 Система шифрования и хэширования + +### Единые хэши encrypted_content + +```python +class ContentHashManager: + """Управление хэшированием контента для децентрализованной сети""" + + @staticmethod + def calculate_content_hash(content_data: bytes) -> str: + """ + Вычисление единого хэша для encrypted_content + + Использует SHA-256 для обеспечения одинакового хэша + на всех нодах для одного и того же контента + """ + return hashlib.sha256(content_data).hexdigest() + + @staticmethod + def encrypt_content_for_storage(content_data: bytes) -> tuple[bytes, str]: + """ + Шифрование контента для хранения + + Returns: + tuple: (encrypted_data, encryption_key) + """ + # Генерируем уникальный ключ для этого контента + encryption_key = secrets.token_hex(32) # 256-bit key + + # Шифруем контент симметричным алгоритмом + cipher = AES.new( + key=bytes.fromhex(encryption_key), + mode=AES.MODE_GCM + ) + + encrypted_data, auth_tag = cipher.encrypt_and_digest(content_data) + + # Добавляем nonce и auth_tag к зашифрованным данным + final_encrypted = cipher.nonce + auth_tag + encrypted_data + + return final_encrypted, encryption_key + + @staticmethod + def decrypt_content(encrypted_data: bytes, encryption_key: str) -> bytes: + """Расшифровка контента""" + + # Извлекаем nonce, auth_tag и данные + nonce = encrypted_data[:16] + auth_tag = encrypted_data[16:32] + ciphertext = encrypted_data[32:] + + # Расшифровываем + cipher = AES.new( + key=bytes.fromhex(encryption_key), + mode=AES.MODE_GCM, + nonce=nonce + ) + + return cipher.decrypt_and_verify(ciphertext, auth_tag) +``` + +### Контроль доступа к контенту + +```python +class ContentAccessControl: + """Система контроля доступа к контенту""" + + async def can_provide_content(self, requester_node: str, content_hash: str) -> bool: + """ + Решение о предоставлении контента запрашивающей ноде + + В текущей версии разрешает доступ всем. + В будущем здесь будут проверки: + - Доверенность ноды + - Коммерческая лицензия + - Региональные ограничения + """ + # TODO: Добавить логику контроля доступа + return True + + async def generate_preview_id(self, content_hash: str) -> str: + """ + Генерация ID для preview, не связанного с основным хэшем + + Preview ID намеренно не связан с encrypted_content хэшем + для обеспечения безопасности + """ + # Используем случайный UUID для preview + preview_id = str(uuid4()) + + # Сохраняем связь в локальной базе + await self._store_preview_mapping(content_hash, preview_id) + + return preview_id + + async def get_content_by_preview_id(self, preview_id: str) -> Optional[bytes]: + """Получение preview контента по preview_id""" + + content_hash = await self._get_content_hash_by_preview_id(preview_id) + if not content_hash: + return None + + # Загружаем preview версию (не основной контент!) + preview_content = await StoredContent.get_preview_by_hash(content_hash) + return preview_content.data if preview_content else None +``` + +## 🌐 Топология сети + +### Типы нод + +```python +class NodeTypes: + """Типы нод в MY Network""" + + BOOTSTRAP = "bootstrap" # Точки входа в сеть + PUBLIC = "public" # Открытые ноды с входящими соединениями + PRIVATE = "private" # Закрытые ноды, только исходящие соединения + SEED = "seed" # Ноды с большим объемом контента для новых участников + +class NodeConfiguration: + """Конфигурация ноды""" + + def __init__(self, config: dict): + self.node_type = config.get("node_type", NodeTypes.PUBLIC) + self.allow_incoming = config.get("allow_incoming", True) + self.max_connections = config.get("max_connections", 50) + self.bootstrap_nodes = config.get("bootstrap_nodes", []) + + def is_private_node(self) -> bool: + """Проверка является ли нода приватной""" + return self.node_type == NodeTypes.PRIVATE or not self.allow_incoming + + def can_accept_incoming_connections(self) -> bool: + """Может ли нода принимать входящие соединения""" + return self.allow_incoming and self.node_type != NodeTypes.PRIVATE +``` + +### Обнаружение и подключение к нодам + +```python +class NetworkDiscovery: + """Обнаружение нод в децентрализованной сети""" + + async def discover_network_nodes(self) -> List[NodeInfo]: + """Обнаружение доступных нод в сети""" + + discovered_nodes = [] + + # Получаем список нод от bootstrap серверов + for bootstrap_node in self.config.bootstrap_nodes: + try: + nodes = await self._get_nodes_from_bootstrap(bootstrap_node) + discovered_nodes.extend(nodes) + except Exception as e: + logger.warning(f"Failed to get nodes from bootstrap {bootstrap_node}: {e}") + + # Получаем список нод от подключенных пиров + for peer_id in self.connected_peers: + try: + nodes = await self._get_nodes_from_peer(peer_id) + discovered_nodes.extend(nodes) + except Exception as e: + logger.warning(f"Failed to get nodes from peer {peer_id}: {e}") + + # Убираем дубликаты и себя + unique_nodes = self._deduplicate_nodes(discovered_nodes) + return [node for node in unique_nodes if node.node_id != self.node_id] + + async def connect_to_network(self): + """Подключение к сети MY Network""" + + if not self.config.bootstrap_nodes: + logger.error("No bootstrap nodes configured") + return + + connected_count = 0 + target_connections = min(10, len(self.config.bootstrap_nodes) * 2) + + # Подключаемся к bootstrap нодам + for bootstrap_node in self.config.bootstrap_nodes: + try: + if await self._connect_to_node(bootstrap_node): + connected_count += 1 + logger.info(f"Connected to bootstrap node: {bootstrap_node}") + except Exception as e: + logger.warning(f"Failed to connect to bootstrap {bootstrap_node}: {e}") + + # Если недостаточно соединений, ищем дополнительные ноды + if connected_count < target_connections: + additional_nodes = await self.discover_network_nodes() + + for node_info in additional_nodes: + if connected_count >= target_connections: + break + + try: + if await self._connect_to_node(node_info): + connected_count += 1 + logger.info(f"Connected to discovered node: {node_info.node_id}") + except Exception as e: + logger.warning(f"Failed to connect to node {node_info.node_id}: {e}") + + logger.info(f"Network connection complete. Connected to {connected_count} nodes") +``` + +## 📊 Мониторинг децентрализованной сети + +### Статистика сети + +```python +class NetworkStatistics: + """Сбор статистики децентрализованной сети""" + + async def collect_network_stats(self) -> dict: + """Сбор статистики о состоянии сети""" + + return { + "node_info": { + "node_id": self.node_id, + "node_type": self.config.node_type, + "version": self.version, + "uptime": self.get_uptime_seconds() + }, + "connections": { + "total_peers": len(self.connected_peers), + "bootstrap_peers": len(self.bootstrap_connections), + "incoming_connections": self.get_incoming_connection_count(), + "outgoing_connections": self.get_outgoing_connection_count() + }, + "content": { + "total_items": await self.get_local_content_count(), + "encrypted_items": await self.get_encrypted_content_count(), + "public_items": await self.get_public_content_count(), + "storage_used": await self.get_storage_usage_bytes() + }, + "sync_activity": { + "content_synced_24h": await self.get_sync_count_24h(), + "pending_sync_items": self.sync_manager.get_pending_count(), + "failed_sync_attempts": self.sync_manager.get_failed_count(), + "last_sync_time": self.sync_manager.last_sync_time + }, + "network_health": { + "reachable_nodes": await self.count_reachable_nodes(), + "network_latency_avg": await self.calculate_avg_network_latency(), + "content_availability": await self.calculate_content_availability() + } + } +``` + +## 🔧 API для децентрализованной сети + +### Новые эндпоинты + +```python +# Управление нодой +GET /api/v1/node/status # Статус ноды и подключений +GET /api/v1/node/peers # Список подключенных пиров +POST /api/v1/node/connect # Подключение к новому пиру +POST /api/v1/node/disconnect # Отключение от пира + +# Обнаружение сети +GET /api/v1/network/discover # Обнаружение доступных нод +GET /api/v1/network/stats # Статистика сети +GET /api/v1/network/topology # Топология подключений + +# Синхронизация контента +GET /api/v1/sync/status # Статус синхронизации +POST /api/v1/sync/request # Запрос синхронизации контента +GET /api/v1/sync/pending # Список ожидающей синхронизации +POST /api/v1/sync/cancel # Отмена синхронизации + +# Поиск контента в сети +GET /api/v1/content/search # Поиск контента по хэшу в сети +GET /api/v1/content/{hash}/nodes # Список нод с конкретным контентом +POST /api/v1/content/announce # Анонс нового контента в сеть + +# Управление доступом +POST /api/v1/access/request # Запрос доступа к контенту +GET /api/v1/access/policies # Политики доступа к контенту +PUT /api/v1/access/policies # Обновление политик доступа +``` + +Эта архитектура обеспечивает полную децентрализацию при сохранении гибкости и безопасности системы. \ No newline at end of file diff --git a/docs/NEW_PROTOCOL_V3.md b/docs/NEW_PROTOCOL_V3.md new file mode 100644 index 0000000..ad925db --- /dev/null +++ b/docs/NEW_PROTOCOL_V3.md @@ -0,0 +1,602 @@ +# MY Network v3.0 - Новый протокол децентрализованной синхронизации + +## 🎯 Обзор протокола v3.0 + +MY Network v3.0 полностью отказывается от кворумной системы в пользу децентрализованной архитектуры, где каждая нода принимает независимые решения о принятии и хранении контента. + +## 🔄 Протокол индивидуальной синхронизации + +### Принципы новой системы + +1. **Автономность решений**: Каждая нода самостоятельно решает принимать контент или нет +2. **Отсутствие консенсуса**: Нет необходимости в голосовании других нод +3. **Гибкая фильтрация**: Настраиваемые правила принятия контента на уровне ноды +4. **Устойчивость к цензуре**: Контент доступен пока есть хотя бы одна нода + +### Новый алгоритм синхронизации + +```python +class DecentralizedSyncProtocol: + """Протокол децентрализованной синхронизации v3.0""" + + def __init__(self, node_config: dict): + self.node_id = node_config["node_id"] + self.content_filter = IndividualContentFilter(node_config) + self.sync_manager = SyncManager() + self.active_peers = {} + + async def handle_content_announcement(self, peer_id: str, announcement: dict) -> bool: + """ + Обработка анонса нового контента от пира + + Новая логика v3.0: + 1. Каждая нода принимает решение индивидуально + 2. Нет консенсуса или голосования + 3. Простая заглушка для фильтрации (пока всегда True) + """ + + content_hash = announcement.get("content_hash") + content_metadata = announcement.get("metadata", {}) + + logger.info(f"Received content announcement from {peer_id}: {content_hash}") + + # Проверка - не дубликат ли это + if await self._content_already_exists(content_hash): + logger.debug(f"Content {content_hash} already exists locally") + return False + + # Индивидуальное решение о принятии контента + should_accept = await self.content_filter.should_accept_content( + content_hash, content_metadata, peer_id + ) + + if should_accept: + logger.info(f"Accepting content {content_hash} from {peer_id}") + + # Добавление в очередь синхронизации + await self.sync_manager.queue_content_sync( + peer_id=peer_id, + content_hash=content_hash, + metadata=content_metadata + ) + return True + else: + logger.info(f"Rejecting content {content_hash} from {peer_id}") + return False + + async def sync_content_from_peer(self, peer_id: str, content_hash: str, + metadata: dict) -> bool: + """Синхронизация контента с пира (без консенсуса)""" + + try: + # Запрос метаданных контента + content_info = await self._request_content_info(peer_id, content_hash) + if not content_info: + logger.error(f"Failed to get content info for {content_hash}") + return False + + # Проверка возможности получения контента от пира + access_granted = await self._request_content_access(peer_id, content_hash) + if not access_granted: + logger.warning(f"Access denied for content {content_hash} from {peer_id}") + return False + + # Загрузка контента + content_data = await self._download_content(peer_id, content_hash) + if not content_data: + logger.error(f"Failed to download content {content_hash}") + return False + + # Проверка целостности + if not await self._verify_content_integrity(content_hash, content_data): + logger.error(f"Content integrity check failed: {content_hash}") + return False + + # Сохранение контента локально + stored_content = await self._store_content_locally( + content_hash, content_data, metadata + ) + + if stored_content: + logger.info(f"Successfully synced content {content_hash} from {peer_id}") + + # Анонс нового контента другим пирам (кроме источника) + await self._announce_content_to_peers(stored_content, exclude_peer=peer_id) + + return True + + except Exception as e: + logger.error(f"Failed to sync content {content_hash} from {peer_id}: {e}") + + return False + +class IndividualContentFilter: + """Система индивидуальной фильтрации контента""" + + def __init__(self, node_config: dict): + self.node_config = node_config + self.whitelist_authors = set(node_config.get("whitelist_authors", [])) + self.blacklist_authors = set(node_config.get("blacklist_authors", [])) + self.allowed_content_types = set(node_config.get("allowed_content_types", ["*"])) + self.max_file_size = node_config.get("max_file_size", 100 * 1024 * 1024) # 100MB + + async def should_accept_content(self, content_hash: str, metadata: dict, + peer_id: str) -> bool: + """ + Решение о принятии контента (заглушка для будущего расширения) + + В текущей версии v3.0 всегда возвращает True. + В будущем здесь будут фильтры по: + - Авторам (whitelist/blacklist) + - Типам контента + - Размерам файлов + - Тегам и категориям + - Репутации пира + """ + + # TODO: Добавить реальную логику фильтрации + # Пока принимаем весь контент + return True + + # Пример будущей логики фильтрации: + """ + author_id = metadata.get("author_id") + content_type = metadata.get("content_type") + file_size = metadata.get("file_size", 0) + + # Проверка черного списка авторов + if author_id in self.blacklist_authors: + return False + + # Проверка белого списка (если не пустой) + if self.whitelist_authors and author_id not in self.whitelist_authors: + return False + + # Проверка типа контента + if "*" not in self.allowed_content_types: + if content_type not in self.allowed_content_types: + return False + + # Проверка размера файла + if file_size > self.max_file_size: + return False + + return True + """ +``` + +## 🔐 Новая система шифрования и безопасности + +### Архитектура безопасности v3.0 + +```python +class ContentSecurityManager: + """Управление безопасностью контента в MY Network v3.0""" + + def __init__(self): + self.hash_algorithm = "sha256" + self.encryption_algorithm = "AES-256-GCM" + + async def create_encrypted_content(self, content_data: bytes, + content_metadata: dict) -> dict: + """ + Создание зашифрованного контента с единым хэшем + + Система v3.0: + 1. Симметричное шифрование с уникальным ключом для каждого контента + 2. Единый хэш encrypted_content на всех нодах + 3. Отдельные preview_id не связанные с основным хэшем + """ + + # Генерация уникального ключа шифрования для этого контента + encryption_key = self._generate_content_key() + + # Шифрование контента + encrypted_data = await self._encrypt_content(content_data, encryption_key) + + # Вычисление единого хэша зашифрованного контента + encrypted_content_hash = self._calculate_deterministic_hash(encrypted_data) + + # Создание отдельного preview_id (не связанного с хэшем) + preview_id = str(uuid4()) + + return { + "encrypted_content_hash": encrypted_content_hash, # Одинаковый на всех нодах + "encrypted_data": encrypted_data, + "encryption_key": encryption_key, # Передается при продаже + "preview_id": preview_id, # Для публичного доступа к preview + "metadata": content_metadata + } + + def _generate_content_key(self) -> str: + """Генерация уникального ключа для контента""" + return secrets.token_hex(32) # 256-bit key + + async def _encrypt_content(self, content_data: bytes, encryption_key: str) -> bytes: + """Симметричное шифрование контента""" + + # Преобразование ключа в байты + key_bytes = bytes.fromhex(encryption_key) + + # Создание шифратора AES-256-GCM + cipher = AES.new(key_bytes, AES.MODE_GCM) + + # Шифрование + ciphertext, auth_tag = cipher.encrypt_and_digest(content_data) + + # Объединение nonce, auth_tag и ciphertext для детерминированного хэша + encrypted_data = cipher.nonce + auth_tag + ciphertext + + return encrypted_data + + def _calculate_deterministic_hash(self, encrypted_data: bytes) -> str: + """ + Вычисление детерминированного хэша зашифрованного контента + + ВАЖНО: Этот хэш будет одинаковым на всех нодах для одного контента, + но через него нельзя получить доступ к самому контенту + """ + return hashlib.sha256(encrypted_data).hexdigest() + + async def decrypt_content(self, encrypted_data: bytes, encryption_key: str) -> bytes: + """Расшифровка контента с помощью ключа""" + + # Извлечение компонентов + nonce = encrypted_data[:16] # AES-GCM nonce: 16 bytes + auth_tag = encrypted_data[16:32] # Auth tag: 16 bytes + ciphertext = encrypted_data[32:] # Остальное - зашифрованные данные + + # Создание дешифратора + key_bytes = bytes.fromhex(encryption_key) + cipher = AES.new(key_bytes, AES.MODE_GCM, nonce=nonce) + + # Расшифровка с проверкой подлинности + decrypted_data = cipher.decrypt_and_verify(ciphertext, auth_tag) + + return decrypted_data + + async def create_preview_content(self, original_content: bytes, + content_type: str) -> tuple[bytes, str]: + """ + Создание preview контента (не зашифрованного) + + Preview: + - Не связан с основным хэшем контента + - Имеет собственный уникальный ID + - Доступен публично без ключей + """ + + # Создание preview в зависимости от типа контента + if content_type.startswith("audio/"): + # Для аудио - первые 30 секунд + preview_content = await self._create_audio_preview(original_content) + elif content_type.startswith("video/"): + # Для видео - первые 30 секунд + watermark + preview_content = await self._create_video_preview(original_content) + elif content_type.startswith("image/"): + # Для изображений - уменьшенная версия с watermark + preview_content = await self._create_image_preview(original_content) + else: + # Для других типов - описание или thumbnail + preview_content = await self._create_generic_preview(original_content) + + # Уникальный ID для preview (НЕ хэш!) + preview_id = str(uuid4()) + + return preview_content, preview_id + +class ContentAccessControl: + """Система контроля доступа к контенту""" + + async def can_provide_content(self, requesting_node: str, content_hash: str, + access_type: str = "full") -> dict: + """ + Проверка возможности предоставления контента + + В v3.0 пока разрешаем всем (заглушка для будущего расширения) + """ + + # TODO: Добавить реальную логику контроля доступа + # Пока разрешаем всем + return { + "access_granted": True, + "access_type": access_type, + "reason": "Open access policy" + } + + # Пример будущей логики: + """ + # Проверка репутации ноды + node_reputation = await self._get_node_reputation(requesting_node) + if node_reputation < 0.5: + return {"access_granted": False, "reason": "Low node reputation"} + + # Проверка коммерческих лицензий + content = await StoredContent.get_by_hash(content_hash) + if content and content.commercial_license: + license_valid = await self._check_commercial_license( + requesting_node, content_hash + ) + if not license_valid: + return {"access_granted": False, "reason": "No valid license"} + + # Проверка региональных ограничений + node_region = await self._get_node_region(requesting_node) + if node_region in content.restricted_regions: + return {"access_granted": False, "reason": "Regional restrictions"} + + return {"access_granted": True, "access_type": access_type} + """ + + async def get_content_by_preview_id(self, preview_id: str) -> Optional[bytes]: + """ + Получение preview контента по preview_id + + Preview доступен всем без ограничений + """ + + # Поиск preview по ID в базе данных + preview_content = await PreviewContent.get_by_preview_id(preview_id) + + if preview_content: + return preview_content.data + + return None + + async def request_content_key(self, content_hash: str, user_license: dict) -> Optional[str]: + """ + Запрос ключа расшифровки контента + + Ключ выдается только при наличии соответствующих прав + """ + + # Проверка лицензии пользователя + if await self._validate_user_license(content_hash, user_license): + # Получение ключа расшифровки + content = await StoredContent.get_by_hash(content_hash) + if content: + return content.encryption_key + + return None +``` + +## 🌐 Обновленный протокол обмена сообщениями + +### Новые типы сообщений v3.0 + +```python +class P2PMessageTypes: + """Типы сообщений протокола MY Network v3.0""" + + # Базовые сообщения + HANDSHAKE = "handshake" + HANDSHAKE_RESPONSE = "handshake_response" + + # Объявления о контенте (без консенсуса) + CONTENT_ANNOUNCEMENT = "content_announcement" + CONTENT_REQUEST = "content_request" + CONTENT_RESPONSE = "content_response" + + # Синхронизация + SYNC_REQUEST = "sync_request" + SYNC_RESPONSE = "sync_response" + SYNC_DATA = "sync_data" + + # Доступ к контенту + ACCESS_REQUEST = "access_request" + ACCESS_RESPONSE = "access_response" + + # Версионирование + VERSION_INFO = "version_info" + VERSION_WARNING = "version_warning" + +class P2PMessageHandler: + """Обработчик P2P сообщений протокола v3.0""" + + async def handle_content_announcement(self, peer_id: str, message: dict): + """Обработка анонса контента (без консенсуса)""" + + content_hash = message.get("content_hash") + metadata = message.get("metadata", {}) + + # Индивидуальное решение о принятии + should_accept = await self.sync_protocol.handle_content_announcement( + peer_id, message + ) + + # Отправка ответа пиру + response = { + "type": P2PMessageTypes.CONTENT_RESPONSE, + "content_hash": content_hash, + "accepted": should_accept, + "node_id": self.node_id, + "timestamp": datetime.utcnow().isoformat() + } + + await self.send_message_to_peer(peer_id, response) + + async def handle_sync_request(self, peer_id: str, message: dict): + """Обработка запроса синхронизации""" + + content_hash = message.get("content_hash") + + # Проверка доступа к контенту + access_check = await self.access_control.can_provide_content( + peer_id, content_hash + ) + + if access_check["access_granted"]: + # Предоставление контента + content_data = await self._get_content_data(content_hash) + + response = { + "type": P2PMessageTypes.SYNC_RESPONSE, + "content_hash": content_hash, + "status": "approved", + "data_size": len(content_data) if content_data else 0 + } + else: + # Отказ в доступе + response = { + "type": P2PMessageTypes.SYNC_RESPONSE, + "content_hash": content_hash, + "status": "denied", + "reason": access_check.get("reason", "Access denied") + } + + await self.send_message_to_peer(peer_id, response) + + # Если доступ разрешен, отправляем данные + if access_check["access_granted"] and content_data: + await self._send_content_data(peer_id, content_hash, content_data) +``` + +## 📋 Новые API эндпоинты v3.0 + +### Обновленные API для децентрализованной архитектуры + +```python +# Индивидуальная синхронизация (без консенсуса) +POST /api/v3/sync/announce # Анонс контента в сеть +GET /api/v3/sync/pending # Ожидающие синхронизации +POST /api/v3/sync/accept/{hash} # Принять конкретный контент +POST /api/v3/sync/reject/{hash} # Отклонить конкретный контент + +# Управление фильтрами контента +GET /api/v3/filters/content # Текущие фильтры +PUT /api/v3/filters/content # Обновить фильтры +POST /api/v3/filters/test # Тестирование фильтров + +# Безопасность контента +GET /api/v3/content/{hash}/preview/{preview_id} # Получение preview +POST /api/v3/content/{hash}/request-key # Запрос ключа расшифровки +GET /api/v3/content/{hash}/access-info # Информация о доступе + +# Статистика децентрализованной сети +GET /api/v3/network/nodes # Известные ноды в сети +GET /api/v3/network/content/distribution # Распределение контента +GET /api/v3/network/sync/stats # Статистика синхронизации +``` + +## 🔄 Алгоритм удаления неиспользуемого контента + +### Система очистки контента + +```python +class ContentCleanupManager: + """Управление очисткой контента через 7 дней""" + + def __init__(self): + self.retention_days = 7 + self.cleanup_interval = 3600 # 1 час + + async def start_cleanup_scheduler(self): + """Запуск планировщика очистки""" + + while True: + try: + await self.cleanup_expired_content() + await asyncio.sleep(self.cleanup_interval) + except Exception as e: + logger.error(f"Cleanup scheduler error: {e}") + await asyncio.sleep(60) # Retry in 1 minute + + async def cleanup_expired_content(self): + """Очистка просроченного контента""" + + cutoff_date = datetime.utcnow() - timedelta(days=self.retention_days) + + # Поиск контента без blockchain регистрации + expired_content = await self._find_expired_content(cutoff_date) + + for content in expired_content: + try: + # Проверка отсутствия blockchain записи + blockchain_exists = await self._check_blockchain_registration(content.hash) + + if not blockchain_exists: + # Удаление файла и записи + await self._remove_content(content) + + logger.info(f"Removed expired content: {content.hash}") + else: + # Обновление статуса - контент зарегистрирован + content.blockchain_registered = True + await content.save() + + except Exception as e: + logger.error(f"Failed to cleanup content {content.hash}: {e}") + + async def _find_expired_content(self, cutoff_date: datetime) -> List[StoredContent]: + """Поиск просроченного контента""" + + return await StoredContent.filter( + created_at__lt=cutoff_date, + blockchain_registered=False + ).all() + + async def _check_blockchain_registration(self, content_hash: str) -> bool: + """Проверка регистрации контента в блокчейне""" + + # TODO: Реализовать проверку TON блокчейна + # Пока возвращаем False для тестирования + return False + + async def _remove_content(self, content: StoredContent): + """Удаление контента и связанных файлов""" + + # Удаление основного файла + if os.path.exists(content.file_path): + os.remove(content.file_path) + + # Удаление preview файлов + preview_files = await PreviewContent.filter(parent_content_id=content.id).all() + for preview in preview_files: + if os.path.exists(preview.file_path): + os.remove(preview.file_path) + await preview.delete() + + # Удаление записи из базы данных + await content.delete() +``` + +## 📊 Мониторинг новой системы + +### Расширенная система мониторинга + +```python +class DecentralizedNetworkMonitor: + """Мониторинг децентрализованной сети v3.0""" + + async def get_network_health(self) -> dict: + """Здоровье децентрализованной сети""" + + return { + "decentralization_metrics": { + "total_known_nodes": await self._count_known_nodes(), + "active_connections": len(self.active_peers), + "content_distribution": await self._analyze_content_distribution(), + "network_redundancy": await self._calculate_network_redundancy() + }, + "sync_metrics": { + "pending_sync_items": self.sync_manager.get_pending_count(), + "successful_syncs_24h": await self._get_sync_count(hours=24), + "failed_syncs_24h": await self._get_failed_sync_count(hours=24), + "avg_sync_time": await self._get_avg_sync_time() + }, + "content_metrics": { + "total_content_items": await self._count_local_content(), + "content_accepted_rate": await self._get_acceptance_rate(), + "content_cleanup_stats": await self._get_cleanup_stats(), + "storage_usage": await self._get_storage_usage() + }, + "security_metrics": { + "access_requests_24h": await self._count_access_requests(hours=24), + "denied_access_rate": await self._get_denied_access_rate(), + "encryption_status": "AES-256-GCM", + "version_compatibility": await self._get_version_compatibility_stats() + } + } +``` + +Этот новый протокол обеспечивает полную децентрализацию MY Network при сохранении безопасности и эффективности системы. \ No newline at end of file diff --git a/docs/README_V3.md b/docs/README_V3.md new file mode 100644 index 0000000..88fd759 --- /dev/null +++ b/docs/README_V3.md @@ -0,0 +1,341 @@ +# MY Network v3.0 - Полная документация децентрализованной платформы + +## 🎯 Обзор MY Network v3.0 + +MY Network v3.0 представляет собой **полностью децентрализованную платформу дистрибьюции контента**, где каждая нода принимает независимые решения о принятии и хранении контента без необходимости консенсуса или кворума. + +## 📚 Структура документации + +### 🏗️ Архитектурные документы + +1. **[NEW_ARCHITECTURE.md](NEW_ARCHITECTURE.md)** - Основная архитектура v3.0 + - Принципы децентрализации + - Замена кворумной системы + - Новая топология сети + - API для децентрализованной сети + +2. **[NEW_PROTOCOL_V3.md](NEW_PROTOCOL_V3.md)** - Протокол синхронизации v3.0 + - Индивидуальные решения нод + - Система шифрования контента + - Новые типы P2P сообщений + - Алгоритм очистки контента + +### 🛠️ Техническая документация + +3. **[INSTALLATION_GUIDE.md](INSTALLATION_GUIDE.md)** - Установка и развертывание + - Автоматическая установка через start.sh + - Интерактивная настройка параметров + - SSL сертификаты и безопасность + - Docker развертывание + +4. **[CONVERTER_MODULE.md](CONVERTER_MODULE.md)** - Converter и Docker интеграция + - On-demand конвертация файлов + - Интеграция с docker.sock + - Параллельная обработка + - API управления конвертацией + +5. **[VERSIONING_COMPATIBILITY.md](VERSIONING_COMPATIBILITY.md)** - Версионирование нод + - Семантическое версионирование + - Проверка совместимости + - Автоматические обновления + - Мониторинг версий в сети + +### 📋 Аналитические документы + +6. **[tasks.txt](../tasks.txt)** - Анализ готовности функций + - Готовые компоненты (65%) + - Требующие доработки + - Новые функции для реализации + - План приоритетных задач + +## 🚀 Ключевые изменения в v3.0 + +### ❌ Удаленные компоненты + +- **Кворумная система консенсуса** - полностью удалена +- **Голосование за принятие контента** - заменено индивидуальными решениями +- **Централизованное управление** - отсутствует +- **Обязательная репликация** - заменена добровольной + +### ✅ Новые возможности + +- **Автономность нод** - каждая нода решает самостоятельно +- **Устойчивость к цензуре** - контент доступен пока есть хотя бы одна нода +- **Гибкая фильтрация** - настраиваемые правила на уровне ноды +- **Приватные ноды** - поддержка нод без входящих соединений +- **Автоматическая установка** - единый скрипт start.sh +- **Версионная совместимость** - контроль совместимости протоколов + +## 🔐 Система безопасности v3.0 + +### Шифрование контента + +``` +Контент -> AES-256-GCM шифрование -> Единый хэш на всех нодах + ↓ + encrypted_content_hash (поиск в сети) + ↓ + Отдельный preview_id (публичный доступ) +``` + +### Принципы безопасности + +1. **Симметричное шифрование** - уникальный ключ для каждого контента +2. **Детерминированные хэши** - одинаковые на всех нодах для поиска +3. **Изолированные preview** - не связаны с основным контентом +4. **Контроль доступа** - гибкая система разрешений + +## 🌐 Архитектура сети + +### Типы нод + +- **Bootstrap ноды** - точки входа в сеть +- **Публичные ноды** - принимают входящие соединения +- **Приватные ноды** - только исходящие соединения +- **Seed ноды** - с большим объемом контента + +### Протокол синхронизации + +``` +1. Анонс контента -> 2. Индивидуальное решение -> 3. Синхронизация + ↓ ↓ ↓ + всем пирам фильтры ноды прямая загрузка +``` + +## 📦 Установка одной командой + +### Быстрая установка + +```bash +curl -fsSL https://raw.githubusercontent.com/your-org/my-uploader-bot/main/start.sh | bash +``` + +### Что устанавливается + +- ✅ Python, Docker, все зависимости +- ✅ PostgreSQL база данных +- ✅ Redis кэширование +- ✅ Converter module для медиа +- ✅ SSL сертификаты (опционально) +- ✅ Firewall и безопасность +- ✅ Мониторинг и логирование + +## 🔧 Конфигурация + +### Основные параметры + +```bash +# Тип ноды +NODE_TYPE=public|private|bootstrap + +# Telegram интеграция +TELEGRAM_API_KEY=your_key +CLIENT_TELEGRAM_API_KEY=your_key + +# Docker интеграция +DOCKER_SOCK_PATH=/var/run/docker.sock + +# SSL настройки +SSL_ENABLED=true +DOMAIN=your-domain.com + +# Фильтрация контента (заглушка) +CONTENT_FILTER_ENABLED=true +``` + +## 📊 API Endpoints v3.0 + +### Управление нодой + +``` +GET /api/v3/node/status # Статус ноды +GET /api/v3/node/peers # Подключенные пиры +POST /api/v3/node/connect # Подключение к пиру +``` + +### Синхронизация контента + +``` +POST /api/v3/sync/announce # Анонс контента +GET /api/v3/sync/pending # Ожидающие синхронизации +POST /api/v3/sync/accept/{hash} # Принять контент +``` + +### Безопасность + +``` +GET /api/v3/content/{hash}/preview/{id} # Получение preview +POST /api/v3/content/{hash}/request-key # Запрос ключа +``` + +### Мониторинг + +``` +GET /api/v3/network/stats # Статистика сети +GET /api/v3/system/version # Информация о версии +GET /api/v3/converter/active # Активные конвертации +``` + +## 🔄 Converter Module + +### On-Demand обработка + +- **Автоматический запуск** контейнеров при необходимости +- **Параллельная обработка** до N файлов одновременно +- **Автоудаление** контейнеров после завершения +- **Интеграция с docker.sock** для управления контейнерами + +### Поддерживаемые форматы + +- **Видео** → MP4 конвертация + thumbnail +- **Аудио** → MP3 конвертация + preview +- **Изображения** → resize + thumbnail +- **Документы** → preview + metadata + +## 📈 Мониторинг + +### Веб-интерфейс + +``` +https://your-domain.com/api/my/monitor/ +``` + +### Ключевые метрики + +- **Сетевые соединения** - количество пиров +- **Синхронизация** - статус и прогресс +- **Контент** - количество и распределение +- **Производительность** - CPU, память, диск +- **Версии** - совместимость в сети + +## 🛡️ Безопасность + +### Уровни защиты + +1. **Сетевая безопасность** - firewall, SSL, rate limiting +2. **Контент безопасность** - шифрование, контроль доступа +3. **Нода безопасность** - версионная совместимость +4. **API безопасность** - аутентификация, валидация + +### Автоматические меры + +- **SSL сертификаты** через Let's Encrypt +- **Fail2ban** защита от брутфорса +- **UFW firewall** базовая защита +- **Автообновления** безопасности + +## 🔄 Миграция с v2.x + +### Совместимость + +- ✅ **База данных** - полная совместимость +- ✅ **Контент** - сохранение всех файлов +- ✅ **API** - обратная совместимость +- ❌ **Протокол** - требуется обновление + +### Процесс миграции + +1. **Backup** существующих данных +2. **Обновление** кода до v3.0 +3. **Миграция** базы данных +4. **Настройка** новых параметров +5. **Запуск** в новом режиме + +## 📋 Roadmap развития + +### Phase 1 (Текущая) - Основы v3.0 +- [x] Архитектура децентрализации +- [x] Базовая система синхронизации +- [x] Автоматическая установка +- [x] Версионная совместимость + +### Phase 2 - Расширенная функциональность +- [ ] Реализация фильтров контента +- [ ] Продвинутый контроль доступа +- [ ] Региональные ограничения +- [ ] Коммерческие лицензии + +### Phase 3 - Оптимизация +- [ ] Производительность сети +- [ ] Улучшенная безопасность +- [ ] Расширенный мониторинг +- [ ] Автоматическое масштабирование + +## 🤝 Участие в разработке + +### Структура проекта + +``` +my-uploader-bot/ +├── app/ # Основное приложение +│ ├── core/ # Ядро системы +│ │ ├── my_network/ # MY Network v3.0 +│ │ ├── content/ # Управление контентом +│ │ └── security/ # Безопасность +│ ├── api/ # REST API +│ └── scripts/ # Утилиты +├── converter-module/ # Модуль конвертации +├── web2-client/ # React фронтенд +├── docs/ # Документация v3.0 +└── start.sh # Скрипт установки +``` + +### Вклад в проект + +1. **Fork** репозитория +2. **Create** feature branch +3. **Implement** изменения +4. **Test** функциональность +5. **Submit** pull request + +## 📞 Поддержка + +### Документация +- **Архитектура**: [NEW_ARCHITECTURE.md](NEW_ARCHITECTURE.md) +- **Установка**: [INSTALLATION_GUIDE.md](INSTALLATION_GUIDE.md) +- **Протокол**: [NEW_PROTOCOL_V3.md](NEW_PROTOCOL_V3.md) + +### Команды управления + +```bash +# Управление сервисом +systemctl start|stop|restart my-network + +# Просмотр логов +journalctl -u my-network -f +docker-compose logs -f + +# Мониторинг +curl https://your-domain.com/api/v3/network/stats +``` + +### Диагностика + +```bash +# Проверка статуса +curl http://localhost:15100/health + +# Проверка подключений +curl http://localhost:15100/api/v3/node/peers + +# Проверка синхронизации +curl http://localhost:15100/api/v3/sync/pending +``` + +--- + +## 🎉 Заключение + +MY Network v3.0 представляет собой **новое поколение децентрализованных платформ** для дистрибьюции контента. Система обеспечивает: + +- 🌐 **Полную децентрализацию** без единых точек отказа +- 🔒 **Надежную безопасность** с современным шифрованием +- 🚀 **Простую установку** одной командой +- 📈 **Масштабируемость** для любого количества нод +- 🛡️ **Устойчивость к цензуре** через множественные источники + +**MY Network v3.0 - Будущее децентрализованной дистрибьюции контента!** + +--- + +*Документация MY Network v3.0 | 2025 | Версия 1.0* \ No newline at end of file diff --git a/docs/VERSIONING_COMPATIBILITY.md b/docs/VERSIONING_COMPATIBILITY.md new file mode 100644 index 0000000..1c1e51b --- /dev/null +++ b/docs/VERSIONING_COMPATIBILITY.md @@ -0,0 +1,489 @@ +# MY Network v3.0 - Система версионирования и совместимости + +## 🏷️ Семантическое версионирование + +MY Network использует семантическое версионирование в формате `MAJOR.MINOR.PATCH`: + +- **MAJOR**: Критические изменения протокола, несовместимые изменения +- **MINOR**: Новая функциональность, обратно совместимая +- **PATCH**: Исправления ошибок, совместимые изменения + +### Примеры версий +- `3.0.0` - Основная версия с децентрализованной архитектурой +- `3.1.0` - Добавление новых функций синхронизации +- `3.1.2` - Исправление ошибок в синхронизации + +## 🔒 Правила совместимости + +### Критические ограничения (MAJOR версии) + +```python +class VersionCompatibility: + """Система проверки совместимости версий нод""" + + def __init__(self, current_version: str): + self.current_version = self.parse_version(current_version) + + def parse_version(self, version_string: str) -> dict: + """Парсинг версии в структурированный формат""" + try: + major, minor, patch = version_string.split('.') + return { + "major": int(major), + "minor": int(minor), + "patch": int(patch), + "string": version_string + } + except ValueError: + raise ValueError(f"Invalid version format: {version_string}") + + def check_compatibility(self, peer_version: str) -> dict: + """ + Проверка совместимости с версией пира + + Returns: + dict: { + "compatible": bool, + "level": "compatible|warning|blocked", + "message": str, + "allow_connection": bool + } + """ + peer_ver = self.parse_version(peer_version) + current_ver = self.current_version + + # Проверка MAJOR версии + if peer_ver["major"] != current_ver["major"]: + return { + "compatible": False, + "level": "blocked", + "message": f"Incompatible major version: {peer_version} vs {current_ver['string']}. Connection blocked.", + "allow_connection": False + } + + # Проверка MINOR версии + if peer_ver["minor"] != current_ver["minor"]: + newer_version = peer_ver if peer_ver["minor"] > current_ver["minor"] else current_ver + older_version = peer_ver if peer_ver["minor"] < current_ver["minor"] else current_ver + + return { + "compatible": True, + "level": "warning", + "message": f"Different minor version: {peer_version} vs {current_ver['string']}. Some features may be unavailable.", + "allow_connection": True, + "recommendation": f"Consider updating to v{newer_version['major']}.{newer_version['minor']}.x" + } + + # PATCH различия - полная совместимость + if peer_ver["patch"] != current_ver["patch"]: + return { + "compatible": True, + "level": "compatible", + "message": f"Compatible versions: {peer_version} vs {current_ver['string']}", + "allow_connection": True + } + + # Идентичные версии + return { + "compatible": True, + "level": "compatible", + "message": f"Identical versions: {peer_version}", + "allow_connection": True + } +``` + +### Матрица совместимости + +| Peer Version | Current Version | Result | Action | +|--------------|-----------------|--------|---------| +| `3.0.x` | `3.0.x` | ✅ Compatible | Allow connection | +| `3.1.x` | `3.0.x` | ⚠️ Warning | Allow with warning | +| `3.0.x` | `3.1.x` | ⚠️ Warning | Allow with warning | +| `4.0.x` | `3.x.x` | ❌ Blocked | Reject connection | +| `2.x.x` | `3.x.x` | ❌ Blocked | Reject connection | + +## 🤝 Протокол handshake с проверкой версий + +### Расширенный handshake + +```python +class VersionAwareHandshake: + """Handshake с проверкой версий""" + + async def perform_handshake(self, peer_connection: PeerConnection) -> bool: + """Выполнение handshake с проверкой совместимости версий""" + + # Отправка handshake с информацией о версии + handshake_msg = { + "type": "handshake", + "node_id": self.node_id, + "public_key": self.public_key_hex, + "version": { + "protocol": MY_NETWORK_VERSION, + "node": NODE_VERSION, + "features": self.get_supported_features(), + "capabilities": self.get_node_capabilities() + }, + "timestamp": datetime.utcnow().isoformat() + } + + # Подписание сообщения + handshake_msg["signature"] = await self.sign_message(handshake_msg) + + # Отправка handshake + await peer_connection.send_message(handshake_msg) + + # Получение ответа + response = await peer_connection.receive_message(timeout=30) + + if response.get("type") != "handshake_response": + logger.error("Invalid handshake response type") + return False + + # Проверка версии пира + peer_version = response.get("version", {}).get("protocol") + if not peer_version: + logger.error("Peer did not provide version information") + return False + + # Проверка совместимости + compatibility = self.version_checker.check_compatibility(peer_version) + + if not compatibility["allow_connection"]: + logger.error(f"Version incompatibility: {compatibility['message']}") + await peer_connection.send_message({ + "type": "handshake_error", + "reason": "version_incompatible", + "message": compatibility["message"] + }) + return False + + # Логирование предупреждений + if compatibility["level"] == "warning": + logger.warning(f"Version compatibility warning: {compatibility['message']}") + + # Отправка предупреждения пиру + await peer_connection.send_message({ + "type": "version_warning", + "message": compatibility["message"], + "recommendation": compatibility.get("recommendation") + }) + + # Сохранение информации о версии пира + peer_connection.version_info = response.get("version") + peer_connection.compatibility = compatibility + + logger.info(f"Handshake successful with peer {response.get('node_id')} (v{peer_version})") + return True + + def get_supported_features(self) -> List[str]: + """Получение списка поддерживаемых функций""" + return [ + "content_sync", + "p2p_discovery", + "content_filtering", + "version_negotiation", + "encrypted_transport" + ] + + def get_node_capabilities(self) -> dict: + """Получение возможностей ноды""" + return { + "max_peers": self.config.max_connections, + "storage_capacity": self.get_available_storage(), + "node_type": self.config.node_type, + "content_types": self.get_supported_content_types() + } +``` + +## 📋 Версионная информация ноды + +### API эндпоинт для версии + +```python +@app.route('/api/v1/system/version', methods=['GET']) +async def get_version_info(request): + """Получение информации о версии ноды""" + + return response.json({ + "version": { + "protocol": MY_NETWORK_VERSION, + "node": NODE_VERSION, + "api": API_VERSION, + "build": BUILD_VERSION, + "build_date": BUILD_DATE + }, + "compatibility": { + "min_protocol_version": "3.0.0", + "max_protocol_version": "3.9.9", + "deprecated_versions": ["2.x.x"], + "supported_features": [ + "content_sync", + "p2p_discovery", + "content_filtering", + "version_negotiation" + ] + }, + "node_info": { + "node_id": node_service.node_id, + "node_type": node_service.config.node_type, + "uptime": node_service.get_uptime_seconds(), + "capabilities": node_service.get_node_capabilities() + } + }) + +@app.route('/api/v1/system/compatibility', methods=['POST']) +async def check_version_compatibility(request): + """Проверка совместимости с другой версией""" + + data = request.json + peer_version = data.get("version") + + if not peer_version: + return response.json({"error": "Version is required"}, status=400) + + try: + compatibility = version_checker.check_compatibility(peer_version) + return response.json(compatibility) + except ValueError as e: + return response.json({"error": str(e)}, status=400) +``` + +## 🔄 Автоматическое обновление + +### Проверка обновлений + +```python +class UpdateChecker: + """Система проверки и уведомления об обновлениях""" + + def __init__(self, current_version: str, update_url: str): + self.current_version = current_version + self.update_url = update_url + self.last_check = None + + async def check_for_updates(self) -> dict: + """Проверка доступных обновлений""" + + try: + # Запрос информации о последней версии + async with aiohttp.ClientSession() as session: + async with session.get(f"{self.update_url}/latest-version") as response: + if response.status == 200: + data = await response.json() + latest_version = data.get("version") + + if latest_version: + update_info = self._compare_versions(latest_version) + self.last_check = datetime.utcnow() + return update_info + + except Exception as e: + logger.error(f"Failed to check for updates: {e}") + + return {"update_available": False, "error": "Failed to check updates"} + + def _compare_versions(self, latest_version: str) -> dict: + """Сравнение текущей и последней версии""" + + current = self._parse_version(self.current_version) + latest = self._parse_version(latest_version) + + if latest["major"] > current["major"]: + return { + "update_available": True, + "update_type": "major", + "current_version": self.current_version, + "latest_version": latest_version, + "critical": True, + "message": "Major update available with breaking changes" + } + + elif latest["minor"] > current["minor"]: + return { + "update_available": True, + "update_type": "minor", + "current_version": self.current_version, + "latest_version": latest_version, + "critical": False, + "message": "Minor update available with new features" + } + + elif latest["patch"] > current["patch"]: + return { + "update_available": True, + "update_type": "patch", + "current_version": self.current_version, + "latest_version": latest_version, + "critical": False, + "message": "Patch update available with bug fixes" + } + + return { + "update_available": False, + "current_version": self.current_version, + "latest_version": latest_version, + "message": "You are running the latest version" + } + + async def get_update_recommendations(self) -> List[str]: + """Получение рекомендаций по обновлению""" + + update_info = await self.check_for_updates() + + if not update_info.get("update_available"): + return ["Your node is up to date"] + + recommendations = [] + + if update_info.get("critical"): + recommendations.append("🔴 Critical update available - update immediately for compatibility") + + if update_info.get("update_type") == "major": + recommendations.append("⚠️ Major version update - review breaking changes before updating") + recommendations.append("📋 Backup your data before major version updates") + + elif update_info.get("update_type") == "minor": + recommendations.append("✨ New features available in minor update") + recommendations.append("🔄 Safe to update - backwards compatible") + + elif update_info.get("update_type") == "patch": + recommendations.append("🐛 Bug fixes available - recommended to update") + + return recommendations +``` + +## 📊 Мониторинг версий в сети + +### Статистика версий + +```python +class NetworkVersionMonitor: + """Мониторинг версий нод в сети""" + + def __init__(self): + self.peer_versions = {} + + async def track_peer_version(self, peer_id: str, version_info: dict): + """Отслеживание версии пира""" + + self.peer_versions[peer_id] = { + "version": version_info, + "last_seen": datetime.utcnow(), + "compatible": True + } + + async def get_network_version_stats(self) -> dict: + """Статистика версий в сети""" + + if not self.peer_versions: + return {"total_peers": 0, "version_distribution": {}} + + version_counts = {} + total_peers = len(self.peer_versions) + compatible_peers = 0 + + for peer_id, peer_data in self.peer_versions.items(): + version = peer_data["version"]["protocol"] + version_counts[version] = version_counts.get(version, 0) + 1 + + if peer_data["compatible"]: + compatible_peers += 1 + + return { + "total_peers": total_peers, + "compatible_peers": compatible_peers, + "compatibility_rate": compatible_peers / total_peers if total_peers > 0 else 0, + "version_distribution": version_counts, + "most_common_version": max(version_counts.items(), key=lambda x: x[1])[0] if version_counts else None + } + + async def get_outdated_peers(self) -> List[dict]: + """Список пиров с устаревшими версиями""" + + current_major = self._parse_version(MY_NETWORK_VERSION)["major"] + outdated = [] + + for peer_id, peer_data in self.peer_versions.items(): + peer_version = peer_data["version"]["protocol"] + peer_major = self._parse_version(peer_version)["major"] + + if peer_major < current_major: + outdated.append({ + "peer_id": peer_id, + "version": peer_version, + "age": (datetime.utcnow() - peer_data["last_seen"]).total_seconds() + }) + + return sorted(outdated, key=lambda x: x["age"], reverse=True) +``` + +## 🚨 Предупреждения и уведомления + +### Система уведомлений о версиях + +```python +class VersionNotificationManager: + """Управление уведомлениями о версиях""" + + async def notify_version_mismatch(self, peer_id: str, peer_version: str, compatibility: dict): + """Уведомление о несоответствии версий""" + + if compatibility["level"] == "blocked": + logger.error(f"🚫 Connection blocked: Peer {peer_id} version {peer_version} incompatible") + + # Можно добавить отправку в мониторинг + await self._send_to_monitoring({ + "type": "version_incompatibility", + "peer_id": peer_id, + "peer_version": peer_version, + "severity": "critical" + }) + + elif compatibility["level"] == "warning": + logger.warning(f"⚠️ Version warning: Peer {peer_id} version {peer_version} - {compatibility['message']}") + + await self._send_to_monitoring({ + "type": "version_warning", + "peer_id": peer_id, + "peer_version": peer_version, + "severity": "warning", + "recommendation": compatibility.get("recommendation") + }) + + async def notify_update_available(self, update_info: dict): + """Уведомление о доступном обновлении""" + + if update_info.get("critical"): + logger.error(f"🔴 Critical update available: {update_info['latest_version']}") + else: + logger.info(f"✨ Update available: {update_info['latest_version']}") + + # Отправка в систему мониторинга + await self._send_to_monitoring({ + "type": "update_available", + "current_version": update_info["current_version"], + "latest_version": update_info["latest_version"], + "update_type": update_info["update_type"], + "critical": update_info.get("critical", False) + }) +``` + +## 📖 API эндпоинты для версионирования + +```python +# Информация о версии +GET /api/v1/system/version # Полная информация о версии +GET /api/v1/system/compatibility # Информация о совместимости +POST /api/v1/system/compatibility/check # Проверка совместимости с версией + +# Обновления +GET /api/v1/system/updates/check # Проверка доступных обновлений +GET /api/v1/system/updates/recommendations # Рекомендации по обновлению + +# Статистика сети +GET /api/v1/network/versions # Статистика версий в сети +GET /api/v1/network/compatibility/stats # Статистика совместимости +``` + +Эта система обеспечивает надежную работу сети при наличии нод с разными версиями, предотвращая проблемы совместимости и обеспечивая плавные обновления. \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..7813f83 --- /dev/null +++ b/start.sh @@ -0,0 +1,1563 @@ +#!/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 "Операционная система может быть не полностью поддержана" + read -p "Продолжить установку? [y/N]: " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi + ;; + esac +} + +# Интерактивная настройка +interactive_setup() { + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${WHITE} НАСТРОЙКА MY NETWORK v3.0 ${NC}" + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + + # 1. Тип сети + echo -e "${PURPLE}❓ Выберите режим работы сети:${NC}" + echo " 1) Создать новую сеть (Bootstrap нода)" + echo " 2) Подключиться к существующей сети" + echo "" + + while true; do + read -p "Введите номер [1-2]: " network_choice + 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 + + # 2. Тип ноды (если подключаемся к существующей) + if [ "$NETWORK_MODE" = "existing" ]; then + echo "" + echo -e "${PURPLE}❓ Выберите тип ноды:${NC}" + echo " 1) Публичная нода (принимает входящие соединения)" + echo " 2) Приватная нода (только исходящие соединения)" + echo "" + + while true; do + read -p "Введите номер [1-2]: " node_choice + 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 + 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 + read -p "Путь до bootstrap.json [Enter для дефолтного]: " custom_bootstrap + 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}" + read -p "Путь до docker.sock [$DOCKER_SOCK_PATH]: " custom_docker_sock + 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}" + read -p "Развернуть веб-клиент для управления нодой? [Y/n]: " web_choice + 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}" + read -p "Настроить SSL сертификат? [y/N]: " ssl_choice + if [[ $ssl_choice =~ ^[Yy]$ ]]; then + read -p "Доменное имя: " DOMAIN + if [ -n "$DOMAIN" ]; then + read -p "Email для уведомлений SSL: " EMAIL + 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}" + read -p "TELEGRAM_API_KEY (основной бот) [Enter для пропуска]: " TELEGRAM_API_KEY + + if [ -n "$TELEGRAM_API_KEY" ]; then + log_success "TELEGRAM_API_KEY настроен" + read -p "CLIENT_TELEGRAM_API_KEY (клиентский бот) [Enter для пропуска]: " CLIENT_TELEGRAM_API_KEY + + 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 "" + + read -p "Продолжить установку с этими настройками? [Y/n]: " -n 1 -r + echo + if [[ $REPLY =~ ^[Nn]$ ]]; then + log_info "Установка отменена пользователем" + exit 0 + fi +} + +# Установка зависимостей +install_dependencies() { + log_info "📦 Установка системных зависимостей..." + + # Обновление пакетов + case $OS_ID in + ubuntu|debian) + apt update && apt upgrade -y + apt install -y 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 + dnf install -y curl wget git unzip htop nano firewalld \ + python3 python3-pip python3-devel gcc gcc-c++ \ + postgresql jq nc + else + yum update -y + yum install -y curl wget git unzip htop nano firewalld \ + python3 python3-pip python3-devel gcc gcc-c++ \ + postgresql jq nc + fi + ;; + esac + + 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' +version: '3.8' + +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 + 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 + - 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 + +EXPOSE 15100 + +CMD ["python", "-m", "app.main"] +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 +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" +} + +# Сборка 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 + if [ ! -f "Dockerfile" ]; then + log_error "Dockerfile не найден в converter-module" + return 1 + fi + + # Сборка converter образа из оригинального репозитория + log_info "Сборка Docker образа для converter..." + if docker build -t my-network-converter:latest . ; then + log_success "Converter образ собран: my-network-converter:latest" + else + log_error "Ошибка сборки converter образа" + return 1 + 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) + apt update + apt install -y nginx certbot python3-certbot-nginx + ;; + centos|rhel|fedora) + if command -v dnf &> /dev/null; then + dnf install -y nginx certbot python3-certbot-nginx + else + yum install -y 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 + 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 + + # Активация сайта + if [ ! -L "/etc/nginx/sites-enabled/my-network" ]; then + ln -s /etc/nginx/sites-available/my-network /etc/nginx/sites-enabled/ + fi + + # Отключение дефолтного сайта + if [ -L "/etc/nginx/sites-enabled/default" ]; then + rm /etc/nginx/sites-enabled/default + fi + + # Тест конфигурации nginx + if nginx -t; then + log_success "Конфигурация nginx корректна" + else + log_error "Ошибка в конфигурации nginx" + return 1 + fi + + # Запуск nginx + systemctl enable nginx + systemctl restart nginx + + log_success "Nginx настроен и запущен" + + # Настройка 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 "Убедитесь что домен указывает на этот сервер" + read -p "Продолжить установку SSL? [y/N]: " ssl_continue + if [[ ! $ssl_continue =~ ^[Yy]$ ]]; then + log_info "Установка SSL пропущена" + return 0 + fi + fi + + # Получение сертификата через certbot + if certbot --nginx -d "$DOMAIN" --email "$EMAIL" --agree-tos --non-interactive --redirect; 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 + ;; + centos|rhel|fedora) + firewall-cmd --permanent --add-service=https + firewall-cmd --reload + ;; + esac + + log_success "HTTPS порт открыт в firewall" + else + log_error "Ошибка установки SSL сертификата" + log_info "Проверьте:" + log_info "1. DNS запись $DOMAIN указывает на этот сервер" + log_info "2. Порт 80 доступен из интернета" + log_info "3. Nginx запущен и доступен" + return 1 + fi +} + +# Генерация конфигурации +generate_config() { + log_info "⚙️ Генерация конфигурации..." + + # Генерация уникальных ключей + 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) + + # Генерация NODE_ID + NODE_ID="node-$(date +%s)-$(shuf -i 1000-9999 -n 1)" + + # Создание .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 + +# Docker Configuration +DOCKER_SOCK_PATH=$DOCKER_SOCK_PATH + +# 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": [ + { + "node_id": "$NODE_ID", + "address": "$(curl -s ifconfig.me || echo 'localhost')", + "port": 15100, + "public_key": "", + "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 + + 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 "" + + # Выполнение миграций + log_info "Выполнение миграций базы данных..." + docker-compose exec -T app alembic upgrade head + + if [ $? -eq 0 ]; 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}Особенности v3.0:${NC}" + echo -e " ✅ Полная децентрализация без консенсуса" + echo -e " ✅ Мгновенная трансляция контента" + echo -e " ✅ Автоматическая конвертация через Docker" + echo -e " ✅ Блокчейн интеграция для uploader-bot" + 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 + 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 "$@" \ No newline at end of file