docs / script
This commit is contained in:
parent
5a158222d7
commit
47db638ea6
266
README.md
266
README.md
|
|
@ -1,170 +1,176 @@
|
|||
# MY Network - Универсальная установка
|
||||
# MY Network v3.0 - Единый установочный скрипт
|
||||
|
||||
🚀 **Универсальный установщик MY Network для любого сервера**
|
||||
**Автоматическая установка и запуск децентрализованной сети контента одной командой**
|
||||
|
||||
## ⚡ Быстрая установка
|
||||
## 🚀 Быстрая установка
|
||||
|
||||
Один скрипт для полной установки на любом сервере:
|
||||
### Развертывание на новом сервере одной командой:
|
||||
|
||||
```bash
|
||||
# Скачать проект
|
||||
git clone <repository_url>
|
||||
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 с автообновлением
|
||||
После установки создается отчет: `/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 - Проект с открытым исходным кодом
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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/<job_id>', 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/<job_id>', 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 архитектуру.
|
||||
|
|
@ -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 сервере.
|
||||
|
|
@ -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 # Обновление политик доступа
|
||||
```
|
||||
|
||||
Эта архитектура обеспечивает полную децентрализацию при сохранении гибкости и безопасности системы.
|
||||
|
|
@ -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 при сохранении безопасности и эффективности системы.
|
||||
|
|
@ -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*
|
||||
|
|
@ -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 # Статистика совместимости
|
||||
```
|
||||
|
||||
Эта система обеспечивает надежную работу сети при наличии нод с разными версиями, предотвращая проблемы совместимости и обеспечивая плавные обновления.
|
||||
Loading…
Reference in New Issue