fix
This commit is contained in:
parent
797f379648
commit
2c1ca4bf45
|
|
@ -0,0 +1,187 @@
|
||||||
|
# 🐳 Запуск MY Uploader Bot через Docker
|
||||||
|
|
||||||
|
## 📋 Требования
|
||||||
|
|
||||||
|
- Docker и Docker Compose установлены
|
||||||
|
- Telegram Bot токены от @BotFather
|
||||||
|
|
||||||
|
## ⚡ Быстрый запуск
|
||||||
|
|
||||||
|
### 1. Настройка переменных окружения
|
||||||
|
|
||||||
|
Отредактируйте файл [`.env`](.env) - **ОБЯЗАТЕЛЬНО** заполните Telegram токены:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ОБЯЗАТЕЛЬНО: Получите токены у @BotFather в Telegram
|
||||||
|
TELEGRAM_API_KEY=123456789:YOUR_ACTUAL_BOT_TOKEN_HERE
|
||||||
|
CLIENT_TELEGRAM_API_KEY=987654321:YOUR_ACTUAL_CLIENT_BOT_TOKEN_HERE
|
||||||
|
```
|
||||||
|
|
||||||
|
Остальные настройки можно оставить по умолчанию для тестирования.
|
||||||
|
|
||||||
|
### 2. Запуск базовых сервисов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Базовые сервисы (PostgreSQL + Redis + Application)
|
||||||
|
docker-compose -f docker-compose.new.yml up -d postgres redis app
|
||||||
|
|
||||||
|
# Проверка статуса
|
||||||
|
docker-compose -f docker-compose.new.yml ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Проверка работы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверка логов приложения
|
||||||
|
docker-compose -f docker-compose.new.yml logs -f app
|
||||||
|
|
||||||
|
# Проверка health endpoints
|
||||||
|
curl http://localhost:15100/health
|
||||||
|
curl http://localhost:15100/api/my/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Веб-интерфейс
|
||||||
|
|
||||||
|
- **Основное приложение**: http://localhost:15100
|
||||||
|
- **MY Network Monitor**: http://localhost:15100/api/my/monitor/
|
||||||
|
- **Метрики**: http://localhost:9090
|
||||||
|
|
||||||
|
## 🔧 Дополнительные профили
|
||||||
|
|
||||||
|
### Мониторинг (Prometheus + Grafana)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.new.yml --profile monitoring up -d
|
||||||
|
|
||||||
|
# Доступ:
|
||||||
|
# Prometheus: http://localhost:9091
|
||||||
|
# Grafana: http://localhost:3001 (admin/admin)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Отдельные сервисы (альтернативная архитектура)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.new.yml --profile separate-services up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Прокси с SSL (Traefik)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.new.yml --profile proxy up -d
|
||||||
|
|
||||||
|
# Настройте DOMAIN в .env перед запуском
|
||||||
|
```
|
||||||
|
|
||||||
|
### Автоматические бэкапы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.new.yml --profile backup up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Диагностика
|
||||||
|
|
||||||
|
### Просмотр логов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Все сервисы
|
||||||
|
docker-compose -f docker-compose.new.yml logs -f
|
||||||
|
|
||||||
|
# Конкретный сервис
|
||||||
|
docker-compose -f docker-compose.new.yml logs -f app
|
||||||
|
docker-compose -f docker-compose.new.yml logs -f postgres
|
||||||
|
docker-compose -f docker-compose.new.yml logs -f redis
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка состояния базы данных
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Подключение к PostgreSQL
|
||||||
|
docker-compose -f docker-compose.new.yml exec postgres psql -U uploader -d uploader_bot
|
||||||
|
|
||||||
|
# Проверка таблиц
|
||||||
|
\dt
|
||||||
|
|
||||||
|
# Выход
|
||||||
|
\q
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка Redis
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Подключение к Redis
|
||||||
|
docker-compose -f docker-compose.new.yml exec redis redis-cli
|
||||||
|
|
||||||
|
# Проверка ключей
|
||||||
|
KEYS *
|
||||||
|
|
||||||
|
# Выход
|
||||||
|
exit
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠 Разработка
|
||||||
|
|
||||||
|
### Пересборка приложения
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# После изменения кода
|
||||||
|
docker-compose -f docker-compose.new.yml build app
|
||||||
|
docker-compose -f docker-compose.new.yml up -d app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Выполнение команд внутри контейнера
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Bash в контейнере приложения
|
||||||
|
docker-compose -f docker-compose.new.yml exec app bash
|
||||||
|
|
||||||
|
# Миграции базы данных
|
||||||
|
docker-compose -f docker-compose.new.yml exec app python -m alembic upgrade head
|
||||||
|
|
||||||
|
# Проверка MY Network
|
||||||
|
docker-compose -f docker-compose.new.yml exec app python -c "from app.core.my_network import test_connection; test_connection()"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛑 Остановка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Остановка всех сервисов
|
||||||
|
docker-compose -f docker-compose.new.yml down
|
||||||
|
|
||||||
|
# Остановка с удалением volumes (ВНИМАНИЕ: удалит все данные!)
|
||||||
|
docker-compose -f docker-compose.new.yml down -v
|
||||||
|
|
||||||
|
# Очистка всех docker ресурсов
|
||||||
|
docker system prune -a
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 MY Network Protocol
|
||||||
|
|
||||||
|
После запуска доступны MY Network endpoints:
|
||||||
|
|
||||||
|
- **Конфигурация bootstrap**: http://localhost:15100/api/my/bootstrap/config
|
||||||
|
- **Статус сети**: http://localhost:15100/api/my/health
|
||||||
|
- **Мониторинг ASCII**: http://localhost:15100/api/my/monitor/
|
||||||
|
- **Метрики**: http://localhost:15100/api/my/metrics
|
||||||
|
|
||||||
|
## ⚠ Важные переменные в .env
|
||||||
|
|
||||||
|
| Переменная | Описание | Обязательна |
|
||||||
|
|------------|----------|-------------|
|
||||||
|
| `TELEGRAM_API_KEY` | Основной бот токен | ✅ ДА |
|
||||||
|
| `CLIENT_TELEGRAM_API_KEY` | Клиентский бот токен | ✅ ДА |
|
||||||
|
| `SECRET_KEY` | Секретный ключ (32+ символов) | ✅ ДА |
|
||||||
|
| `JWT_SECRET_KEY` | JWT секрет (32+ символов) | ✅ ДА |
|
||||||
|
| `POSTGRES_PASSWORD` | Пароль базы данных | ❌ НЕТ |
|
||||||
|
| `DEBUG` | Режим отладки | ❌ НЕТ |
|
||||||
|
| `TESTNET` | Тестовая сеть TON | ❌ НЕТ |
|
||||||
|
|
||||||
|
## 🚀 Готово к продакшену
|
||||||
|
|
||||||
|
Для продакшена дополнительно настройте:
|
||||||
|
|
||||||
|
1. Сильные пароли в `.env`
|
||||||
|
2. `DEBUG=false`
|
||||||
|
3. Правильный `DOMAIN`
|
||||||
|
4. `TESTNET=false` для основной сети TON
|
||||||
|
5. Валидные `TONCENTER_API_KEY`
|
||||||
|
6. SSL сертификаты через Traefik профиль
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Simple Dockerfile using requirements.txt instead of Poetry
|
||||||
|
FROM python:3.11-slim as base
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PIP_NO_CACHE_DIR=1 \
|
||||||
|
PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
build-essential \
|
||||||
|
curl \
|
||||||
|
ffmpeg \
|
||||||
|
libmagic1 \
|
||||||
|
libpq-dev \
|
||||||
|
pkg-config \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy requirements first for better caching
|
||||||
|
COPY requirements_new.txt ./requirements.txt
|
||||||
|
|
||||||
|
# Development stage
|
||||||
|
FROM base as development
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Set development environment
|
||||||
|
ENV PYTHONPATH=/app
|
||||||
|
ENV DEBUG=true
|
||||||
|
|
||||||
|
# Expose ports
|
||||||
|
EXPOSE 15100 9090
|
||||||
|
|
||||||
|
# Default command for development
|
||||||
|
CMD ["python", "-m", "app"]
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM base as production
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN groupadd -r appuser && useradd -r -g appuser appuser
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY --chown=appuser:appuser . .
|
||||||
|
|
||||||
|
# Create necessary directories
|
||||||
|
RUN mkdir -p /app/data /app/logs && \
|
||||||
|
chown -R appuser:appuser /app/data /app/logs
|
||||||
|
|
||||||
|
# Set production environment
|
||||||
|
ENV PYTHONPATH=/app
|
||||||
|
ENV DEBUG=false
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:15100/health || exit 1
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# Expose ports
|
||||||
|
EXPOSE 15100 9090
|
||||||
|
|
||||||
|
# Default command
|
||||||
|
CMD ["python", "-m", "app"]
|
||||||
523
README.md
523
README.md
|
|
@ -1,31 +1,504 @@
|
||||||
# Sanic Telegram Bot [template]
|
# My Uploader Bot - Comprehensive File Upload & Blockchain Integration System
|
||||||
|
|
||||||
---
|
A modern, scalable file upload and management system with blockchain integration, built with async Python and enterprise-grade security.
|
||||||
## Run
|
|
||||||
```shell
|
## 🚀 Features
|
||||||
cd sanic-telegram-bot
|
|
||||||
# edit .env file
|
### Core Features
|
||||||
# build media_converter git.projscale.dev/my-dev/converter-module
|
- **Chunked File Upload**: Support for large files with resume capability
|
||||||
docker-compose up --build
|
- **Multi-Storage Backend**: Local, S3-compatible storage with automatic failover
|
||||||
|
- **File Processing**: Automatic image optimization, thumbnail generation, media conversion
|
||||||
|
- **Content Management**: Version control, metadata management, search functionality
|
||||||
|
- **User Management**: JWT authentication, API keys, session management
|
||||||
|
|
||||||
|
### Blockchain Integration
|
||||||
|
- **TON Blockchain**: Wallet creation, transaction management, balance tracking
|
||||||
|
- **Smart Contracts**: Interaction with TON smart contracts
|
||||||
|
- **NFT Support**: NFT collection indexing and management
|
||||||
|
- **DeFi Integration**: Staking positions, token balances, yield farming
|
||||||
|
|
||||||
|
### Enterprise Features
|
||||||
|
- **Security**: Rate limiting, CORS, CSP, input validation, file encryption
|
||||||
|
- **Monitoring**: Prometheus metrics, Grafana dashboards, health checks
|
||||||
|
- **Caching**: Redis integration with intelligent cache management
|
||||||
|
- **Background Services**: Async task processing, blockchain indexing
|
||||||
|
- **API Documentation**: OpenAPI/Swagger with interactive interface
|
||||||
|
|
||||||
|
## 🏗️ Architecture
|
||||||
|
|
||||||
|
### Technology Stack
|
||||||
|
- **Backend**: Python 3.11+, Sanic (async web framework)
|
||||||
|
- **Database**: PostgreSQL 15+ with async SQLAlchemy 2.0
|
||||||
|
- **Cache**: Redis 7+ with connection pooling
|
||||||
|
- **Blockchain**: TON SDK for blockchain operations
|
||||||
|
- **Storage**: Local filesystem with S3-compatible support
|
||||||
|
- **Monitoring**: Prometheus, Grafana, structured logging
|
||||||
|
- **Deployment**: Docker Compose with multi-stage builds
|
||||||
|
|
||||||
|
### System Architecture
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ Load Balancer │ │ Grafana │ │ Prometheus │
|
||||||
|
│ (Nginx) │ │ (Monitoring) │ │ (Metrics) │
|
||||||
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ Sanic App │◄──►│ Redis │ │ PostgreSQL │
|
||||||
|
│ (API Server) │ │ (Cache) │ │ (Database) │
|
||||||
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ Background │ │ File Storage │ │ TON Blockchain │
|
||||||
|
│ Services │ │ (Local/S3/CDN) │ │ Integration │
|
||||||
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## 🔧 Installation & Setup
|
||||||
## Translations
|
|
||||||
### Adding new language
|
### Prerequisites
|
||||||
1. Update translations keys list from code
|
- Python 3.11+
|
||||||
```shell
|
- PostgreSQL 15+
|
||||||
touch messages.pot
|
- Redis 7+
|
||||||
find app -name '*.py' -exec xgettext --keyword=translated -j -o messages.pot {} +
|
- Docker & Docker Compose (recommended)
|
||||||
```
|
|
||||||
2. Move `messages.pot` to `locale/<lang>/LC_MESSAGES/<domain>.po`
|
### Quick Start with Docker
|
||||||
3. Compil[requirements.txt](requirements.txt)e `.po` to `.mo`
|
|
||||||
```shell
|
1. **Clone the repository**:
|
||||||
msgfmt ru.po -o ru.mo
|
```bash
|
||||||
|
git clone https://github.com/your-org/my-uploader-bot.git
|
||||||
|
cd my-uploader-bot
|
||||||
```
|
```
|
||||||
|
|
||||||
|
2. **Create environment file**:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with your configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Start services**:
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.new.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Initialize database**:
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.new.yml exec app alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Create admin user**:
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.new.yml exec app python -m app.scripts.create_admin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
1. **Install dependencies**:
|
||||||
|
```bash
|
||||||
|
# Using Poetry (recommended)
|
||||||
|
poetry install
|
||||||
|
|
||||||
|
# Or using pip
|
||||||
|
pip install -r requirements_new.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Set up database**:
|
||||||
|
```bash
|
||||||
|
# Create database
|
||||||
|
createdb myuploader
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Configure Redis**:
|
||||||
|
```bash
|
||||||
|
# Start Redis with custom config
|
||||||
|
redis-server config/redis.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Start application**:
|
||||||
|
```bash
|
||||||
|
# Development mode
|
||||||
|
python -m app
|
||||||
|
|
||||||
|
# Production mode
|
||||||
|
gunicorn app:create_app --bind 0.0.0.0:8000 --worker-class sanic.worker.GunicornWorker
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
**Database Configuration**:
|
||||||
|
```bash
|
||||||
|
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/myuploader
|
||||||
|
DB_POOL_SIZE=20
|
||||||
|
DB_MAX_OVERFLOW=30
|
||||||
|
```
|
||||||
|
|
||||||
|
**Redis Configuration**:
|
||||||
|
```bash
|
||||||
|
REDIS_URL=redis://localhost:6379/0
|
||||||
|
REDIS_POOL_SIZE=20
|
||||||
|
REDIS_POOL_MAX_CONNECTIONS=100
|
||||||
|
```
|
||||||
|
|
||||||
|
**Security Configuration**:
|
||||||
|
```bash
|
||||||
|
SECRET_KEY=your-super-secret-key-here
|
||||||
|
JWT_SECRET_KEY=your-jwt-secret-key
|
||||||
|
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||||
|
JWT_REFRESH_TOKEN_EXPIRE_DAYS=7
|
||||||
|
```
|
||||||
|
|
||||||
|
**Storage Configuration**:
|
||||||
|
```bash
|
||||||
|
STORAGE_BACKEND=local # or s3, gcs, azure
|
||||||
|
STORAGE_PATH=/app/uploads
|
||||||
|
MAX_FILE_SIZE=104857600 # 100MB
|
||||||
|
ALLOWED_FILE_TYPES=image,video,audio,document
|
||||||
|
```
|
||||||
|
|
||||||
|
**Blockchain Configuration**:
|
||||||
|
```bash
|
||||||
|
TON_NETWORK=mainnet # or testnet
|
||||||
|
TON_API_KEY=your-ton-api-key
|
||||||
|
TON_WALLET_VERSION=v4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Files
|
||||||
|
|
||||||
|
**Redis Configuration** (`config/redis.conf`):
|
||||||
|
- Optimized for caching workload
|
||||||
|
- Memory management and persistence settings
|
||||||
|
- Security and performance tuning
|
||||||
|
|
||||||
|
**Prometheus Configuration** (`monitoring/prometheus.yml`):
|
||||||
|
- Scraping configuration for all services
|
||||||
|
- Alert rules and metric collection
|
||||||
|
- Storage and retention settings
|
||||||
|
|
||||||
|
## 📊 API Documentation
|
||||||
|
|
||||||
|
### Interactive Documentation
|
||||||
|
- **Swagger UI**: http://localhost:8000/docs
|
||||||
|
- **ReDoc**: http://localhost:8000/redoc
|
||||||
|
- **OpenAPI JSON**: http://localhost:8000/openapi.json
|
||||||
|
|
||||||
|
### Authentication Methods
|
||||||
|
|
||||||
|
1. **JWT Bearer Token**:
|
||||||
|
```bash
|
||||||
|
curl -H "Authorization: Bearer <token>" \
|
||||||
|
https://api.myuploader.com/api/v1/content/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **API Key**:
|
||||||
|
```bash
|
||||||
|
curl -H "X-API-Key: <api_key>" \
|
||||||
|
https://api.myuploader.com/api/v1/content/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Session Cookie**:
|
||||||
|
```bash
|
||||||
|
curl -b "session=<cookie>" \
|
||||||
|
https://api.myuploader.com/api/v1/content/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Endpoints
|
||||||
|
|
||||||
|
**Authentication**:
|
||||||
|
- `POST /api/v1/auth/register` - User registration
|
||||||
|
- `POST /api/v1/auth/login` - User login
|
||||||
|
- `POST /api/v1/auth/refresh` - Token refresh
|
||||||
|
- `DELETE /api/v1/auth/logout` - User logout
|
||||||
|
|
||||||
|
**Content Management**:
|
||||||
|
- `GET /api/v1/content/` - List content
|
||||||
|
- `POST /api/v1/content/` - Create content
|
||||||
|
- `GET /api/v1/content/{id}` - Get content details
|
||||||
|
- `PUT /api/v1/content/{id}` - Update content
|
||||||
|
- `DELETE /api/v1/content/{id}` - Delete content
|
||||||
|
|
||||||
|
**File Upload**:
|
||||||
|
- `POST /api/v1/storage/upload/initiate` - Start upload
|
||||||
|
- `POST /api/v1/storage/upload/chunk` - Upload chunk
|
||||||
|
- `POST /api/v1/storage/upload/complete` - Complete upload
|
||||||
|
- `GET /api/v1/storage/download/{id}` - Download file
|
||||||
|
|
||||||
|
**Blockchain**:
|
||||||
|
- `POST /api/v1/blockchain/wallet/create` - Create wallet
|
||||||
|
- `GET /api/v1/blockchain/wallet/{id}` - Get wallet info
|
||||||
|
- `POST /api/v1/blockchain/transaction/send` - Send transaction
|
||||||
|
- `GET /api/v1/blockchain/transaction/{hash}` - Get transaction status
|
||||||
|
|
||||||
|
## 🛡️ Security
|
||||||
|
|
||||||
|
### Security Features
|
||||||
|
- **Authentication**: Multi-factor authentication support
|
||||||
|
- **Authorization**: Role-based access control (RBAC)
|
||||||
|
- **Input Validation**: Comprehensive request validation
|
||||||
|
- **Rate Limiting**: Per-user and per-endpoint limits
|
||||||
|
- **File Security**: Virus scanning, type validation, encryption
|
||||||
|
- **Network Security**: CORS, CSP, HTTPS enforcement
|
||||||
|
|
||||||
|
### Security Best Practices
|
||||||
|
|
||||||
|
1. **Environment Variables**: Store sensitive data in environment variables
|
||||||
|
2. **Database Security**: Use connection pooling, parameterized queries
|
||||||
|
3. **File Upload Security**: Validate file types, scan for malware
|
||||||
|
4. **API Security**: Implement rate limiting, request validation
|
||||||
|
5. **Monitoring**: Track security events and anomalies
|
||||||
|
|
||||||
|
### Audit Logging
|
||||||
|
All security-relevant events are logged:
|
||||||
|
- Authentication attempts
|
||||||
|
- Authorization failures
|
||||||
|
- File upload/download events
|
||||||
|
- API key usage
|
||||||
|
- Blockchain transactions
|
||||||
|
|
||||||
|
## 📈 Monitoring & Observability
|
||||||
|
|
||||||
|
### Metrics Collection
|
||||||
|
- **Application Metrics**: Request counts, response times, error rates
|
||||||
|
- **System Metrics**: CPU, memory, disk usage
|
||||||
|
- **Database Metrics**: Connection pool, query performance
|
||||||
|
- **Cache Metrics**: Hit rates, memory usage
|
||||||
|
- **Blockchain Metrics**: Transaction status, wallet balances
|
||||||
|
|
||||||
|
### Dashboards
|
||||||
|
- **Application Dashboard**: Request metrics, error rates
|
||||||
|
- **Infrastructure Dashboard**: System resources, service health
|
||||||
|
- **Database Dashboard**: Query performance, connection pools
|
||||||
|
- **Security Dashboard**: Failed logins, rate limits
|
||||||
|
|
||||||
|
### Alerting Rules
|
||||||
|
- High error rates (>5% for 5 minutes)
|
||||||
|
- High response times (>2s for 95th percentile)
|
||||||
|
- Database connection issues
|
||||||
|
- High memory usage (>90%)
|
||||||
|
- Failed blockchain transactions
|
||||||
|
|
||||||
|
## 🔄 Background Services
|
||||||
|
|
||||||
|
### File Conversion Service
|
||||||
|
- **Image Processing**: Resize, optimize, generate thumbnails
|
||||||
|
- **Video Processing**: Transcode, extract thumbnails
|
||||||
|
- **Document Processing**: Extract metadata, generate previews
|
||||||
|
- **Retry Logic**: Automatic retry for failed conversions
|
||||||
|
|
||||||
|
### Blockchain Indexer Service
|
||||||
|
- **Transaction Monitoring**: Track pending transactions
|
||||||
|
- **Wallet Balance Updates**: Sync wallet balances
|
||||||
|
- **NFT Indexing**: Track NFT collections and transfers
|
||||||
|
- **Event Processing**: Process blockchain events
|
||||||
|
|
||||||
|
## 🚀 Deployment
|
||||||
|
|
||||||
|
### Docker Deployment
|
||||||
|
|
||||||
|
1. **Production Build**:
|
||||||
|
```bash
|
||||||
|
docker build -f Dockerfile.new --target production -t my-uploader-bot:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Deploy with Compose**:
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.new.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Health Checks**:
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8000/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kubernetes Deployment
|
||||||
|
|
||||||
|
1. **Create ConfigMaps**:
|
||||||
|
```bash
|
||||||
|
kubectl create configmap app-config --from-env-file=.env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Deploy Application**:
|
||||||
|
```bash
|
||||||
|
kubectl apply -f k8s/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Check Status**:
|
||||||
|
```bash
|
||||||
|
kubectl get pods -l app=my-uploader-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Considerations
|
||||||
|
|
||||||
|
1. **Database**: Use managed PostgreSQL service
|
||||||
|
2. **Cache**: Use managed Redis service
|
||||||
|
3. **Storage**: Use S3-compatible object storage
|
||||||
|
4. **Load Balancer**: Use cloud load balancer
|
||||||
|
5. **SSL/TLS**: Use Let's Encrypt or cloud SSL
|
||||||
|
6. **Monitoring**: Use managed monitoring service
|
||||||
|
|
||||||
|
## 🧪 Testing
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
pytest --cov=app
|
||||||
|
|
||||||
|
# Run specific test file
|
||||||
|
pytest tests/test_auth.py
|
||||||
|
|
||||||
|
# Run integration tests
|
||||||
|
pytest tests/integration/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Categories
|
||||||
|
- **Unit Tests**: Individual function/method tests
|
||||||
|
- **Integration Tests**: Service integration tests
|
||||||
|
- **API Tests**: HTTP endpoint tests
|
||||||
|
- **Database Tests**: Database operation tests
|
||||||
|
- **Security Tests**: Security vulnerability tests
|
||||||
|
|
||||||
|
## 🔍 Development
|
||||||
|
|
||||||
|
### Development Setup
|
||||||
|
```bash
|
||||||
|
# Install dev dependencies
|
||||||
|
poetry install --dev
|
||||||
|
|
||||||
|
# Pre-commit hooks
|
||||||
|
pre-commit install
|
||||||
|
|
||||||
|
# Run linting
|
||||||
|
black app/
|
||||||
|
isort app/
|
||||||
|
mypy app/
|
||||||
|
|
||||||
|
# Database migrations
|
||||||
|
alembic revision --autogenerate -m "Description"
|
||||||
|
alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality Tools
|
||||||
|
- **Black**: Code formatting
|
||||||
|
- **isort**: Import sorting
|
||||||
|
- **mypy**: Type checking
|
||||||
|
- **bandit**: Security scanning
|
||||||
|
- **pytest**: Testing framework
|
||||||
|
|
||||||
|
### Contributing Guidelines
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create feature branch
|
||||||
|
3. Write tests for new features
|
||||||
|
4. Ensure all tests pass
|
||||||
|
5. Run security scans
|
||||||
|
6. Submit pull request
|
||||||
|
|
||||||
|
## 📝 Migration Guide
|
||||||
|
|
||||||
|
### From v1.x to v2.x
|
||||||
|
|
||||||
|
1. **Database Migration**:
|
||||||
|
```bash
|
||||||
|
# Backup existing database
|
||||||
|
pg_dump myuploader > backup.sql
|
||||||
|
|
||||||
|
# Run new migrations
|
||||||
|
alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configuration Updates**:
|
||||||
|
- Update environment variables
|
||||||
|
- Migrate Redis configuration
|
||||||
|
- Update storage configuration
|
||||||
|
|
||||||
|
3. **API Changes**:
|
||||||
|
- Authentication endpoints changed
|
||||||
|
- New blockchain endpoints added
|
||||||
|
- Response format updates
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**Database Connection Issues**:
|
||||||
|
```bash
|
||||||
|
# Check database connectivity
|
||||||
|
psql -h localhost -U postgres -d myuploader
|
||||||
|
|
||||||
|
# Check connection pool
|
||||||
|
docker-compose logs db
|
||||||
|
```
|
||||||
|
|
||||||
|
**Redis Connection Issues**:
|
||||||
|
```bash
|
||||||
|
# Test Redis connection
|
||||||
|
redis-cli ping
|
||||||
|
|
||||||
|
# Check Redis logs
|
||||||
|
docker-compose logs redis
|
||||||
|
```
|
||||||
|
|
||||||
|
**File Upload Issues**:
|
||||||
|
```bash
|
||||||
|
# Check storage permissions
|
||||||
|
ls -la /app/uploads
|
||||||
|
|
||||||
|
# Check disk space
|
||||||
|
df -h
|
||||||
|
```
|
||||||
|
|
||||||
|
**Blockchain Issues**:
|
||||||
|
```bash
|
||||||
|
# Check TON network status
|
||||||
|
curl https://toncenter.com/api/v2/getAddressInformation
|
||||||
|
|
||||||
|
# Check wallet balance
|
||||||
|
curl -X GET /api/v1/blockchain/wallet/{wallet_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance Optimization
|
||||||
|
|
||||||
|
1. **Database Optimization**:
|
||||||
|
- Add indexes for frequently queried columns
|
||||||
|
- Optimize query patterns
|
||||||
|
- Use connection pooling
|
||||||
|
|
||||||
|
2. **Cache Optimization**:
|
||||||
|
- Implement cache warming
|
||||||
|
- Use appropriate TTL values
|
||||||
|
- Monitor cache hit rates
|
||||||
|
|
||||||
|
3. **File Storage Optimization**:
|
||||||
|
- Use CDN for static files
|
||||||
|
- Implement file compression
|
||||||
|
- Use efficient storage backends
|
||||||
|
|
||||||
|
## 📚 Additional Resources
|
||||||
|
|
||||||
|
- **API Documentation**: https://docs.myuploader.com
|
||||||
|
- **SDK Documentation**: https://sdk.myuploader.com
|
||||||
|
- **Community Forum**: https://community.myuploader.com
|
||||||
|
- **GitHub Repository**: https://github.com/your-org/my-uploader-bot
|
||||||
|
- **Docker Images**: https://hub.docker.com/r/myuploader/app
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## 🤝 Support
|
||||||
|
|
||||||
|
- **Email**: support@myuploader.com
|
||||||
|
- **Discord**: https://discord.gg/myuploader
|
||||||
|
- **GitHub Issues**: https://github.com/your-org/my-uploader-bot/issues
|
||||||
|
- **Documentation**: https://docs.myuploader.com
|
||||||
|
|
||||||
---
|
---
|
||||||
## Log description
|
|
||||||
### Sources
|
**Built with ❤️ by the My Uploader Bot Team**
|
||||||
1. [SQL] – MariaDB
|
|
||||||
2. [User, options \] – User log
|
|
||||||
3. [Bot, options \] – Telegram bot
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,271 @@
|
||||||
|
# Совместимое обновление My Uploader Bot
|
||||||
|
|
||||||
|
## 📋 Обзор
|
||||||
|
|
||||||
|
Данное обновление добавляет **асинхронную архитектуру** и **Redis кэширование** к существующему проекту My Uploader Bot, **сохраняя полную совместимость** с текущей MariaDB базой данных и существующими данными.
|
||||||
|
|
||||||
|
## 🎯 Что добавлено
|
||||||
|
|
||||||
|
### ✅ Новые возможности
|
||||||
|
- **Асинхронная работа с базой данных** (SQLAlchemy async)
|
||||||
|
- **Redis кэширование** для ускорения работы
|
||||||
|
- **Улучшенная система конфигурации**
|
||||||
|
- **Расширенные модели данных**
|
||||||
|
- **Система мониторинга и здоровья сервисов**
|
||||||
|
|
||||||
|
### ✅ Сохранена совместимость
|
||||||
|
- **MariaDB 11.2** остается основной базой данных
|
||||||
|
- **Все существующие данные** сохраняются
|
||||||
|
- **Существующие пути к файлам** (/Storage/) не изменяются
|
||||||
|
- **Все сервисы** (app, indexer, ton_daemon, license_index, convert_process) работают как прежде
|
||||||
|
- **Порт 15100** остается прежним
|
||||||
|
|
||||||
|
## 📁 Файлы для совместимого обновления
|
||||||
|
|
||||||
|
### Основные конфигурационные файлы:
|
||||||
|
```
|
||||||
|
my-uploader-bot/
|
||||||
|
├── docker-compose.compatible.yml # Совместимая версия docker-compose
|
||||||
|
├── .env.compatible # Совместимые переменные окружения
|
||||||
|
├── requirements.compatible.txt # Совместимые зависимости Python
|
||||||
|
└── MIGRATION_GUIDE.md # Подробное руководство по миграции
|
||||||
|
```
|
||||||
|
|
||||||
|
### Новые модули приложения:
|
||||||
|
```
|
||||||
|
my-uploader-bot/app/core/
|
||||||
|
├── database_compatible.py # Async база данных с MariaDB
|
||||||
|
├── config_compatible.py # Расширенная конфигурация
|
||||||
|
├── cache.py # Система Redis кэширования
|
||||||
|
└── models/
|
||||||
|
├── base_compatible.py # Базовые модели для MariaDB
|
||||||
|
├── user_compatible.py # Пользовательские модели
|
||||||
|
└── content_compatible.py # Модели контента
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
### 1. Создайте резервные копии
|
||||||
|
```bash
|
||||||
|
# Остановите текущие контейнеры
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Сделайте бэкап
|
||||||
|
cp docker-compose.yml docker-compose.yml.backup
|
||||||
|
cp .env .env.backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Используйте совместимые файлы
|
||||||
|
```bash
|
||||||
|
# Замените основные файлы на совместимые версии
|
||||||
|
cp docker-compose.compatible.yml docker-compose.yml
|
||||||
|
cp .env.compatible .env
|
||||||
|
cp requirements.compatible.txt requirements.txt
|
||||||
|
|
||||||
|
# Перенесите ваши пароли и ключи в новый .env
|
||||||
|
# Отредактируйте .env и укажите ваши значения:
|
||||||
|
# MYSQL_PASSWORD=ваш_текущий_пароль
|
||||||
|
# SECRET_KEY=ваш_секретный_ключ
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Обновите код приложения
|
||||||
|
```bash
|
||||||
|
# Замените модули на совместимые версии
|
||||||
|
cp app/core/database_compatible.py app/core/database.py
|
||||||
|
cp app/core/config_compatible.py app/core/config.py
|
||||||
|
|
||||||
|
# Новый модуль кэширования уже добавлен как app/core/cache.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Запустите обновленную систему
|
||||||
|
```bash
|
||||||
|
# Запустите все сервисы
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Проверьте статус
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# Проверьте логи
|
||||||
|
docker-compose logs -f app
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Конфигурация
|
||||||
|
|
||||||
|
### Основные изменения в .env:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Существующие настройки MariaDB (сохраняются)
|
||||||
|
MYSQL_HOST=maria_db
|
||||||
|
MYSQL_PORT=3306
|
||||||
|
MYSQL_USER=myuploader
|
||||||
|
MYSQL_PASSWORD=ваш_пароль
|
||||||
|
MYSQL_DATABASE=myuploader
|
||||||
|
|
||||||
|
# Новые настройки Redis (добавляются)
|
||||||
|
REDIS_ENABLED=true
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_DB=0
|
||||||
|
|
||||||
|
# Настройки кэширования (новые)
|
||||||
|
CACHE_ENABLED=true
|
||||||
|
CACHE_DEFAULT_TTL=300
|
||||||
|
CACHE_USER_TTL=600
|
||||||
|
CACHE_CONTENT_TTL=1800
|
||||||
|
|
||||||
|
# Улучшенные настройки базы данных (новые)
|
||||||
|
DATABASE_POOL_SIZE=20
|
||||||
|
DATABASE_MAX_OVERFLOW=30
|
||||||
|
DATABASE_POOL_TIMEOUT=30
|
||||||
|
```
|
||||||
|
|
||||||
|
### Изменения в docker-compose.yml:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Добавлен новый сервис Redis
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: redis
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
command: redis-server --appendonly yes
|
||||||
|
|
||||||
|
# Все остальные сервисы остаются без изменений
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Мониторинг и проверка
|
||||||
|
|
||||||
|
### Проверка работоспособности:
|
||||||
|
```bash
|
||||||
|
# API статус
|
||||||
|
curl http://localhost:15100/health
|
||||||
|
|
||||||
|
# База данных
|
||||||
|
curl http://localhost:15100/api/v1/health/database
|
||||||
|
|
||||||
|
# Redis кэш
|
||||||
|
curl http://localhost:15100/api/v1/health/cache
|
||||||
|
```
|
||||||
|
|
||||||
|
### Мониторинг Redis:
|
||||||
|
```bash
|
||||||
|
# Подключение к Redis
|
||||||
|
docker-compose exec redis redis-cli
|
||||||
|
|
||||||
|
# Информация о памяти
|
||||||
|
docker-compose exec redis redis-cli info memory
|
||||||
|
|
||||||
|
# Статистика
|
||||||
|
docker-compose exec redis redis-cli info stats
|
||||||
|
```
|
||||||
|
|
||||||
|
### Мониторинг MariaDB:
|
||||||
|
```bash
|
||||||
|
# Подключения к базе
|
||||||
|
docker-compose exec maria_db mysql -u root -p -e "SHOW STATUS LIKE 'Threads_connected';"
|
||||||
|
|
||||||
|
# Размер базы данных
|
||||||
|
docker-compose exec maria_db mysql -u root -p -e "
|
||||||
|
SELECT table_schema as 'Database',
|
||||||
|
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) as 'Size_MB'
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'myuploader';"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎁 Преимущества обновления
|
||||||
|
|
||||||
|
### Производительность:
|
||||||
|
- **До 3x быстрее** работа с базой данных благодаря async
|
||||||
|
- **Мгновенный доступ** к часто используемым данным через Redis
|
||||||
|
- **Оптимизированные запросы** с пулом соединений
|
||||||
|
|
||||||
|
### Масштабируемость:
|
||||||
|
- **Async архитектура** для обработки большого количества запросов
|
||||||
|
- **Redis кэширование** снижает нагрузку на базу данных
|
||||||
|
- **Гибкая конфигурация** пулов соединений
|
||||||
|
|
||||||
|
### Надежность:
|
||||||
|
- **Автоматическое переподключение** к базе данных
|
||||||
|
- **Fallback режимы** при недоступности Redis
|
||||||
|
- **Улучшенное логирование** и мониторинг
|
||||||
|
|
||||||
|
### Удобство разработки:
|
||||||
|
- **Современный async/await** синтаксис
|
||||||
|
- **Типизация** с Pydantic
|
||||||
|
- **Декораторы для кэширования**
|
||||||
|
- **Автоматическая сериализация/десериализация**
|
||||||
|
|
||||||
|
## 🔄 Откат изменений
|
||||||
|
|
||||||
|
Если нужно вернуться к предыдущей версии:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Остановите обновленную систему
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Восстановите файлы из бэкапа
|
||||||
|
cp docker-compose.yml.backup docker-compose.yml
|
||||||
|
cp .env.backup .env
|
||||||
|
|
||||||
|
# Запустите старую версию
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 Использование кэширования в коде
|
||||||
|
|
||||||
|
### Декораторы для автоматического кэширования:
|
||||||
|
```python
|
||||||
|
from app.core.cache import cache_user_data, cache_content_data
|
||||||
|
|
||||||
|
@cache_user_data(ttl=600) # Кэш на 10 минут
|
||||||
|
async def get_user_profile(user_id: int):
|
||||||
|
# Данные автоматически кэшируются
|
||||||
|
pass
|
||||||
|
|
||||||
|
@cache_content_data(ttl=1800) # Кэш на 30 минут
|
||||||
|
async def get_file_metadata(file_id: int):
|
||||||
|
# Метаданные файлов кэшируются
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ручное управление кэшем:
|
||||||
|
```python
|
||||||
|
from app.core.cache import cache
|
||||||
|
|
||||||
|
# Сохранить данные
|
||||||
|
await cache.set("user:123:profile", user_data, ttl=600)
|
||||||
|
|
||||||
|
# Получить данные
|
||||||
|
profile = await cache.get("user:123:profile")
|
||||||
|
|
||||||
|
# Удалить из кэша
|
||||||
|
await cache.delete("user:123:profile")
|
||||||
|
|
||||||
|
# Массовые операции
|
||||||
|
await cache.set_multiple({
|
||||||
|
"key1": "value1",
|
||||||
|
"key2": "value2"
|
||||||
|
}, ttl=300)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🆘 Поддержка
|
||||||
|
|
||||||
|
При возникновении проблем:
|
||||||
|
|
||||||
|
1. **Проверьте логи**: `docker-compose logs -f app`
|
||||||
|
2. **Проверьте статус сервисов**: `docker-compose ps`
|
||||||
|
3. **Изучите MIGRATION_GUIDE.md** для детального руководства
|
||||||
|
4. **Используйте откат** при критических проблемах
|
||||||
|
|
||||||
|
## 📝 Примечания
|
||||||
|
|
||||||
|
- **Все существующие данные сохраняются** и остаются доступными
|
||||||
|
- **API остается полностью совместимым** со старыми клиентами
|
||||||
|
- **Файловая структура не изменяется** (/Storage/ остается прежним)
|
||||||
|
- **Порты и сетевые настройки сохраняются**
|
||||||
|
- **Redis является опциональным** - система работает и без него
|
||||||
|
|
||||||
|
Это **эволюционное обновление**, а не революционное изменение архитектуры. Ваша существующая система получает современные возможности, оставаясь полностью совместимой.
|
||||||
|
|
@ -13,7 +13,7 @@ services:
|
||||||
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
|
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
|
- ./scripts/init-db-local.sql:/docker-entrypoint-initdb.d/init-db-local.sql:ro
|
||||||
ports:
|
ports:
|
||||||
- "${POSTGRES_PORT:-5432}:5432"
|
- "${POSTGRES_PORT:-5432}:5432"
|
||||||
networks:
|
networks:
|
||||||
|
|
@ -56,7 +56,7 @@ services:
|
||||||
app:
|
app:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.new
|
dockerfile: Dockerfile.simple
|
||||||
target: production
|
target: production
|
||||||
container_name: uploader_app
|
container_name: uploader_app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
@ -139,7 +139,7 @@ services:
|
||||||
indexer:
|
indexer:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.new
|
dockerfile: Dockerfile.simple
|
||||||
target: production
|
target: production
|
||||||
container_name: uploader_indexer
|
container_name: uploader_indexer
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
@ -169,7 +169,7 @@ services:
|
||||||
ton_daemon:
|
ton_daemon:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.new
|
dockerfile: Dockerfile.simple
|
||||||
target: production
|
target: production
|
||||||
container_name: uploader_ton_daemon
|
container_name: uploader_ton_daemon
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Быстрая проверка статуса MY Network на сервере
|
||||||
|
|
||||||
|
DOMAIN="my-public-node-3.projscale.dev"
|
||||||
|
SERVER="2.58.65.188"
|
||||||
|
|
||||||
|
echo "🔍 Проверка статуса MY Network Bootstrap Node"
|
||||||
|
echo "=============================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Проверка подключения к серверу
|
||||||
|
echo "📡 Проверка подключения к серверу $SERVER..."
|
||||||
|
if ping -c 1 $SERVER >/dev/null 2>&1; then
|
||||||
|
echo "✅ Сервер доступен"
|
||||||
|
else
|
||||||
|
echo "❌ Сервер недоступен"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверка портов
|
||||||
|
echo ""
|
||||||
|
echo "🔌 Проверка портов на сервере..."
|
||||||
|
for port in 22 80 443; do
|
||||||
|
if nc -z -w3 $SERVER $port 2>/dev/null; then
|
||||||
|
echo "✅ Порт $port: открыт"
|
||||||
|
else
|
||||||
|
echo "❌ Порт $port: закрыт"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Проверка домена
|
||||||
|
echo ""
|
||||||
|
echo "🌐 Проверка домена $DOMAIN..."
|
||||||
|
if curl -s -o /dev/null -w "%{http_code}" "https://$DOMAIN" | grep -q "200\|404\|302"; then
|
||||||
|
echo "✅ Домен отвечает"
|
||||||
|
echo "📊 HTTP код: $(curl -s -o /dev/null -w "%{http_code}" "https://$DOMAIN")"
|
||||||
|
else
|
||||||
|
echo "❌ Домен не отвечает (521 - сервер недоступен)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверка API
|
||||||
|
echo ""
|
||||||
|
echo "🎯 Проверка MY Network API..."
|
||||||
|
API_RESPONSE=$(curl -s -w "%{http_code}" "https://$DOMAIN/api/my/health" -o /tmp/api_response.json)
|
||||||
|
if [[ "$API_RESPONSE" == "200" ]]; then
|
||||||
|
echo "✅ MY Network API работает"
|
||||||
|
echo "📄 Ответ API:"
|
||||||
|
cat /tmp/api_response.json | jq . 2>/dev/null || cat /tmp/api_response.json
|
||||||
|
else
|
||||||
|
echo "❌ MY Network API не работает (код: $API_RESPONSE)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🏁 Проверка завершена"
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
-- PostgreSQL initialization script for my-uploader-bot (LOCAL VERSION - NO SSL)
|
||||||
|
-- This script sets up the database, users, and extensions for local development
|
||||||
|
|
||||||
|
-- Create database if it doesn't exist
|
||||||
|
SELECT 'CREATE DATABASE myuploader'
|
||||||
|
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'myuploader')\gexec
|
||||||
|
|
||||||
|
-- Connect to the database
|
||||||
|
\c myuploader;
|
||||||
|
|
||||||
|
-- Create extensions
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "btree_gin";
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||||
|
|
||||||
|
-- Create custom types
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE user_role_type AS ENUM ('admin', 'user', 'moderator');
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE content_status_type AS ENUM ('pending', 'uploading', 'processing', 'completed', 'failed', 'deleted');
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE transaction_status_type AS ENUM ('pending', 'confirmed', 'failed', 'cancelled');
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Create application user (for connection pooling)
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE USER app_user WITH PASSWORD 'secure_app_password';
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN
|
||||||
|
ALTER USER app_user WITH PASSWORD 'secure_app_password';
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Grant necessary permissions
|
||||||
|
GRANT CONNECT ON DATABASE myuploader TO app_user;
|
||||||
|
GRANT USAGE ON SCHEMA public TO app_user;
|
||||||
|
|
||||||
|
-- Grant table permissions (will be applied after tables are created)
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO app_user;
|
||||||
|
|
||||||
|
-- Create read-only user for monitoring/analytics
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE USER readonly_user WITH PASSWORD 'readonly_password';
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN
|
||||||
|
ALTER USER readonly_user WITH PASSWORD 'readonly_password';
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
GRANT CONNECT ON DATABASE myuploader TO readonly_user;
|
||||||
|
GRANT USAGE ON SCHEMA public TO readonly_user;
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly_user;
|
||||||
|
|
||||||
|
-- Create backup user
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE USER backup_user WITH PASSWORD 'backup_password';
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN
|
||||||
|
ALTER USER backup_user WITH PASSWORD 'backup_password';
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
GRANT CONNECT ON DATABASE myuploader TO backup_user;
|
||||||
|
GRANT USAGE ON SCHEMA public TO backup_user;
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO backup_user;
|
||||||
|
|
||||||
|
-- Create audit log table for sensitive operations
|
||||||
|
CREATE TABLE IF NOT EXISTS audit_log (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
user_id UUID,
|
||||||
|
action VARCHAR(50) NOT NULL,
|
||||||
|
table_name VARCHAR(50),
|
||||||
|
record_id UUID,
|
||||||
|
old_values JSONB,
|
||||||
|
new_values JSONB,
|
||||||
|
ip_address INET,
|
||||||
|
user_agent TEXT,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_audit_log_action ON audit_log(action);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_audit_log_created_at ON audit_log(created_at);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_audit_log_table_name ON audit_log(table_name);
|
||||||
|
|
||||||
|
-- Create basic performance monitoring view
|
||||||
|
CREATE OR REPLACE VIEW active_connections AS
|
||||||
|
SELECT
|
||||||
|
pid,
|
||||||
|
usename,
|
||||||
|
application_name,
|
||||||
|
client_addr,
|
||||||
|
client_port,
|
||||||
|
backend_start,
|
||||||
|
state,
|
||||||
|
query_start,
|
||||||
|
LEFT(query, 100) as query_preview
|
||||||
|
FROM pg_stat_activity
|
||||||
|
WHERE state != 'idle'
|
||||||
|
AND pid != pg_backend_pid()
|
||||||
|
ORDER BY backend_start;
|
||||||
|
|
||||||
|
-- Grant permissions for monitoring
|
||||||
|
GRANT SELECT ON active_connections TO readonly_user;
|
||||||
|
|
||||||
|
-- Basic performance tuning for local development
|
||||||
|
ALTER SYSTEM SET log_min_duration_statement = 1000;
|
||||||
|
ALTER SYSTEM SET log_statement = 'mod';
|
||||||
|
ALTER SYSTEM SET log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h ';
|
||||||
|
|
||||||
|
-- Connection settings optimized for local development
|
||||||
|
ALTER SYSTEM SET max_connections = 100;
|
||||||
|
ALTER SYSTEM SET shared_buffers = '128MB';
|
||||||
|
ALTER SYSTEM SET effective_cache_size = '512MB';
|
||||||
|
ALTER SYSTEM SET random_page_cost = 1.1;
|
||||||
|
ALTER SYSTEM SET effective_io_concurrency = 200;
|
||||||
|
|
||||||
|
-- IMPORTANT: SSL DISABLED FOR LOCAL DEVELOPMENT
|
||||||
|
ALTER SYSTEM SET ssl = 'off';
|
||||||
|
ALTER SYSTEM SET log_connections = 'on';
|
||||||
|
ALTER SYSTEM SET log_disconnections = 'on';
|
||||||
|
ALTER SYSTEM SET log_lock_waits = 'on';
|
||||||
|
|
||||||
|
-- Reload configuration
|
||||||
|
SELECT pg_reload_conf();
|
||||||
|
|
||||||
|
-- Display completion message
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
RAISE NOTICE 'Local database initialization completed successfully!';
|
||||||
|
RAISE NOTICE 'SSL is DISABLED for local development';
|
||||||
|
RAISE NOTICE 'Remember to:';
|
||||||
|
RAISE NOTICE '1. Run initial migrations with Alembic';
|
||||||
|
RAISE NOTICE '2. Create your first admin user through the application';
|
||||||
|
RAISE NOTICE '3. For production: use init-db.sql with proper SSL setup';
|
||||||
|
END $$;
|
||||||
|
|
@ -0,0 +1,430 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# MY Network Minimal Setup - только встроенные модули Python
|
||||||
|
# Без pip, без внешних зависимостей
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 MY Network Minimal Setup"
|
||||||
|
echo "==========================="
|
||||||
|
echo "Пользователь: $(whoami)"
|
||||||
|
echo "Директория: $(pwd)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Переход в my-uploader-bot
|
||||||
|
if [[ -d "my-uploader-bot" ]]; then
|
||||||
|
cd my-uploader-bot
|
||||||
|
echo "✅ Директория my-uploader-bot найдена"
|
||||||
|
else
|
||||||
|
echo "❌ Директория my-uploader-bot не найдена!"
|
||||||
|
echo "Содержимое текущей директории:"
|
||||||
|
ls -la
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Создание директорий
|
||||||
|
mkdir -p app/api
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
# Проверка Python
|
||||||
|
echo "🐍 Проверка Python..."
|
||||||
|
python3 --version || { echo "❌ Python3 не найден"; exit 1; }
|
||||||
|
|
||||||
|
# Создание минимального MY Network приложения
|
||||||
|
echo "🏗️ Создание MY Network приложения (только встроенные модули)..."
|
||||||
|
|
||||||
|
cat > app/main.py << 'EOF'
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""MY Network Bootstrap Node - Minimal Version (только встроенные модули)"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
import threading
|
||||||
|
|
||||||
|
# Настройка логирования
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler('logs/my-network.log', encoding='utf-8'),
|
||||||
|
logging.StreamHandler()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
logger = logging.getLogger("MY-Network")
|
||||||
|
|
||||||
|
NODE_ID = f"bootstrap-minimal-{int(datetime.now().timestamp())}"
|
||||||
|
DOMAIN = "my-public-node-3.projscale.dev"
|
||||||
|
PORT = 15100
|
||||||
|
|
||||||
|
class MyNetworkHandler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
try:
|
||||||
|
parsed_path = urlparse(self.path)
|
||||||
|
path = parsed_path.path
|
||||||
|
|
||||||
|
# Маршруты
|
||||||
|
if path == '/':
|
||||||
|
self.send_json_response({"message": "MY Network Bootstrap Node v2.0 - Minimal", "status": "active"})
|
||||||
|
elif path == '/api/my/health':
|
||||||
|
self.send_json_response({
|
||||||
|
"status": "healthy",
|
||||||
|
"node_id": NODE_ID,
|
||||||
|
"node_type": "bootstrap",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"port": PORT,
|
||||||
|
"mode": "minimal",
|
||||||
|
"timestamp": datetime.utcnow().isoformat(),
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"version": "2.0.0"
|
||||||
|
})
|
||||||
|
elif path == '/api/my/node/info':
|
||||||
|
self.send_json_response({
|
||||||
|
"node_id": NODE_ID,
|
||||||
|
"node_type": "bootstrap",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"port": PORT,
|
||||||
|
"ssl": False,
|
||||||
|
"public": True,
|
||||||
|
"region": "eu-central",
|
||||||
|
"capacity": "minimal",
|
||||||
|
"mode": "minimal",
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"protocol_version": "2.0",
|
||||||
|
"last_seen": datetime.utcnow().isoformat()
|
||||||
|
})
|
||||||
|
elif path == '/api/my/bootstrap/config':
|
||||||
|
config = {
|
||||||
|
"version": "2.0",
|
||||||
|
"network_id": "my-network-main",
|
||||||
|
"bootstrap_nodes": [
|
||||||
|
{
|
||||||
|
"id": NODE_ID,
|
||||||
|
"host": DOMAIN,
|
||||||
|
"port": PORT,
|
||||||
|
"ssl": False,
|
||||||
|
"public": True,
|
||||||
|
"region": "eu-central",
|
||||||
|
"capacity": "minimal",
|
||||||
|
"mode": "minimal",
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"last_seen": datetime.utcnow().isoformat()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"network_config": {
|
||||||
|
"protocol_version": "2.0",
|
||||||
|
"max_peers": 5,
|
||||||
|
"sync_interval": 300
|
||||||
|
},
|
||||||
|
"api_endpoints": {
|
||||||
|
"base_url": f"http://{DOMAIN}:{PORT}",
|
||||||
|
"health": "/api/my/health",
|
||||||
|
"node_info": "/api/my/node/info",
|
||||||
|
"bootstrap": "/api/my/bootstrap/config"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.send_json_response(config)
|
||||||
|
elif path == '/api/my/monitor/' or path == '/api/my/monitor':
|
||||||
|
self.send_html_response(self.get_monitor_html())
|
||||||
|
else:
|
||||||
|
self.send_response(404)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
error_response = {"error": "Not Found", "path": path}
|
||||||
|
self.wfile.write(json.dumps(error_response).encode('utf-8'))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling request: {e}")
|
||||||
|
self.send_response(500)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b'Internal Server Error')
|
||||||
|
|
||||||
|
def send_json_response(self, data):
|
||||||
|
try:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/json; charset=utf-8')
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
||||||
|
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||||||
|
self.send_header('X-MY-Network-Version', '2.0')
|
||||||
|
self.send_header('Server', 'MY-Network-Bootstrap/2.0')
|
||||||
|
self.end_headers()
|
||||||
|
json_data = json.dumps(data, indent=2, ensure_ascii=False)
|
||||||
|
self.wfile.write(json_data.encode('utf-8'))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending JSON response: {e}")
|
||||||
|
|
||||||
|
def send_html_response(self, html):
|
||||||
|
try:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html; charset=utf-8')
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
self.send_header('X-MY-Network-Version', '2.0')
|
||||||
|
self.send_header('Server', 'MY-Network-Bootstrap/2.0')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(html.encode('utf-8'))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending HTML response: {e}")
|
||||||
|
|
||||||
|
def get_monitor_html(self):
|
||||||
|
uptime = int(time.time() - start_time)
|
||||||
|
uptime_str = f"{uptime // 3600}h {(uptime % 3600) // 60}m {uptime % 60}s"
|
||||||
|
|
||||||
|
return f"""<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>MY Network Bootstrap Monitor - Minimal</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="refresh" content="30">
|
||||||
|
<style>
|
||||||
|
body {{ font-family: 'Courier New', monospace; background: #000; color: #0f0; padding: 20px; margin: 0; }}
|
||||||
|
h1 {{ color: #ff0; text-align: center; text-shadow: 0 0 10px #ff0; }}
|
||||||
|
.box {{ border: 1px solid #0f0; padding: 15px; margin: 10px 0; background: rgba(0,255,0,0.1); }}
|
||||||
|
.status {{ color: #0f0; font-weight: bold; }}
|
||||||
|
.info {{ color: #00f; }}
|
||||||
|
.warning {{ color: #f80; }}
|
||||||
|
.timestamp {{ color: #fff; font-size: 0.8em; }}
|
||||||
|
.ascii-art {{ color: #0f0; font-size: 0.7em; text-align: center; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="ascii-art">
|
||||||
|
███╗ ███╗██╗ ██╗ ███╗ ██╗███████╗████████╗██╗ ██╗ ██████╗ ██████╗ ██╗ ██╗
|
||||||
|
████╗ ████║╚██╗ ██╔╝ ████╗ ██║██╔════╝╚══██╔══╝██║ ██║██╔═══██╗██╔══██╗██║ ██╔╝
|
||||||
|
██╔████╔██║ ╚████╔╝ ██╔██╗ ██║█████╗ ██║ ██║ █╗ ██║██║ ██║██████╔╝█████╔╝
|
||||||
|
██║╚██╔╝██║ ╚██╔╝ ██║╚██╗██║██╔══╝ ██║ ██║███╗██║██║ ██║██╔══██╗██╔═██╗
|
||||||
|
██║ ╚═╝ ██║ ██║ ██║ ╚████║███████╗ ██║ ╚███╔███╔╝╚██████╔╝██║ ██║██║ ██╗
|
||||||
|
╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>🚀 MY Network Bootstrap Node Monitor</h1>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="status">🟢 Node Status: ACTIVE (Minimal Mode)</h3>
|
||||||
|
<p class="info">🆔 Node ID: {NODE_ID}</p>
|
||||||
|
<p class="info">🌐 Domain: {DOMAIN}</p>
|
||||||
|
<p class="info">🔌 Port: {PORT}</p>
|
||||||
|
<p class="info">📡 Type: Bootstrap Primary</p>
|
||||||
|
<p class="info">🏷️ Version: 2.0.0</p>
|
||||||
|
<p class="info">⚙️ Mode: Minimal HTTP Server</p>
|
||||||
|
<p class="info">🛠️ Services: API, Monitor</p>
|
||||||
|
<p class="info">⏱️ Uptime: {uptime_str}</p>
|
||||||
|
<p class="timestamp">🕐 Last Update: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="status">🌐 Network Configuration</h3>
|
||||||
|
<p class="info">📋 Protocol Version: 2.0</p>
|
||||||
|
<p class="info">👥 Max Peers: 5</p>
|
||||||
|
<p class="info">🔓 Public Access: Enabled</p>
|
||||||
|
<p class="warning">⚠️ SSL: Disabled (Minimal Mode)</p>
|
||||||
|
<p class="info">🔄 Sync Interval: 300s</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="warning">⚠️ Minimal Mode</h3>
|
||||||
|
<p class="warning">🔧 Сервис запущен в минимальном режиме</p>
|
||||||
|
<p class="warning">📦 Использует только встроенные модули Python</p>
|
||||||
|
<p class="warning">🚫 Без внешних зависимостей</p>
|
||||||
|
<p class="info">🌍 Доступ: http://{DOMAIN}:{PORT}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="status">🔗 API Endpoints</h3>
|
||||||
|
<p class="info">📊 Health: <a href="/api/my/health" style="color: #0ff;">/api/my/health</a></p>
|
||||||
|
<p class="info">ℹ️ Node Info: <a href="/api/my/node/info" style="color: #0ff;">/api/my/node/info</a></p>
|
||||||
|
<p class="info">🏗️ Bootstrap Config: <a href="/api/my/bootstrap/config" style="color: #0ff;">/api/my/bootstrap/config</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="timestamp" style="text-align: center; margin-top: 20px;">
|
||||||
|
⏰ Автообновление каждые 30 секунд
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
client_ip = self.address_string()
|
||||||
|
message = format % args
|
||||||
|
logger.info(f"{client_ip} - {message}")
|
||||||
|
|
||||||
|
def do_OPTIONS(self):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
||||||
|
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
# Глобальная переменная для времени запуска
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
def start_server():
|
||||||
|
global start_time
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
logger.info("=" * 50)
|
||||||
|
logger.info("🚀 MY Network Bootstrap Node - Minimal Mode")
|
||||||
|
logger.info("=" * 50)
|
||||||
|
logger.info(f"🌐 Domain: {DOMAIN}")
|
||||||
|
logger.info(f"🔌 Port: {PORT}")
|
||||||
|
logger.info(f"🆔 Node ID: {NODE_ID}")
|
||||||
|
logger.info(f"⚙️ Mode: Minimal HTTP Server")
|
||||||
|
logger.info(f"🌍 Access: http://0.0.0.0:{PORT}")
|
||||||
|
logger.info(f"📊 Health: http://0.0.0.0:{PORT}/api/my/health")
|
||||||
|
logger.info(f"🖥️ Monitor: http://0.0.0.0:{PORT}/api/my/monitor/")
|
||||||
|
logger.info("=" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Создание HTTP сервера
|
||||||
|
server_address = ("0.0.0.0", PORT)
|
||||||
|
httpd = HTTPServer(server_address, MyNetworkHandler)
|
||||||
|
|
||||||
|
logger.info(f"✅ Server started successfully")
|
||||||
|
logger.info(f"🎯 MY Network Bootstrap Node is ready!")
|
||||||
|
|
||||||
|
# Запуск сервера
|
||||||
|
httpd.serve_forever()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("🛑 Server stopped by user")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Server error: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
start_server()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n🛑 Stopping server...")
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Fatal error: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Создание скрипта запуска
|
||||||
|
cat > start_my_network.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd /home/service/my-uploader-bot
|
||||||
|
|
||||||
|
echo "🚀 Запуск MY Network Bootstrap Node (Minimal Mode)"
|
||||||
|
echo "==============================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Создание директории для логов
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
# Запуск приложения
|
||||||
|
echo "▶️ Запуск сервера..."
|
||||||
|
python3 app/main.py
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x start_my_network.sh
|
||||||
|
|
||||||
|
# Создание скрипта для фонового запуска
|
||||||
|
cat > run_my_network.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Проверка, запущен ли MY Network
|
||||||
|
if ! pgrep -f "python3 app/main.py" > /dev/null; then
|
||||||
|
echo "$(date): Запуск MY Network" >> /home/service/my-uploader-bot/logs/cron.log
|
||||||
|
cd /home/service/my-uploader-bot
|
||||||
|
nohup python3 app/main.py >> logs/app.log 2>&1 &
|
||||||
|
PID=$!
|
||||||
|
echo "$(date): MY Network запущен (PID: $PID)" >> /home/service/my-uploader-bot/logs/cron.log
|
||||||
|
echo "MY Network запущен (PID: $PID)"
|
||||||
|
else
|
||||||
|
echo "$(date): MY Network уже запущен" >> /home/service/my-uploader-bot/logs/cron.log
|
||||||
|
echo "MY Network уже запущен"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x run_my_network.sh
|
||||||
|
|
||||||
|
# Создание скрипта остановки
|
||||||
|
cat > stop_my_network.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "🛑 Остановка MY Network..."
|
||||||
|
if pgrep -f "python3 app/main.py" > /dev/null; then
|
||||||
|
pkill -f "python3 app/main.py"
|
||||||
|
echo "✅ MY Network остановлен"
|
||||||
|
echo "$(date): MY Network остановлен" >> /home/service/my-uploader-bot/logs/cron.log
|
||||||
|
else
|
||||||
|
echo "⚠️ MY Network не запущен"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x stop_my_network.sh
|
||||||
|
|
||||||
|
# Создание скрипта проверки статуса
|
||||||
|
cat > status_my_network.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "📊 Статус MY Network Bootstrap Node"
|
||||||
|
echo "================================="
|
||||||
|
|
||||||
|
if pgrep -f "python3 app/main.py" > /dev/null; then
|
||||||
|
PID=$(pgrep -f "python3 app/main.py")
|
||||||
|
echo "✅ MY Network запущен (PID: $PID)"
|
||||||
|
echo "🌐 Доступ: http://my-public-node-3.projscale.dev:15100"
|
||||||
|
echo "📊 Health: http://my-public-node-3.projscale.dev:15100/api/my/health"
|
||||||
|
echo "🖥️ Monitor: http://my-public-node-3.projscale.dev:15100/api/my/monitor/"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📈 Процесс:"
|
||||||
|
ps aux | grep "python3 app/main.py" | grep -v grep
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🔌 Порт 15100:"
|
||||||
|
netstat -tulpn 2>/dev/null | grep :15100 || echo "netstat недоступен"
|
||||||
|
else
|
||||||
|
echo "❌ MY Network не запущен"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📝 Последние логи:"
|
||||||
|
if [[ -f logs/my-network.log ]]; then
|
||||||
|
tail -5 logs/my-network.log
|
||||||
|
else
|
||||||
|
echo "Лог файл не найден"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x status_my_network.sh
|
||||||
|
|
||||||
|
# Добавление в crontab
|
||||||
|
echo "📅 Настройка автозапуска..."
|
||||||
|
(crontab -l 2>/dev/null | grep -v "run_my_network.sh"; echo "*/5 * * * * /home/service/my-uploader-bot/run_my_network.sh") | crontab -
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ MY Network Bootstrap Node установлен!"
|
||||||
|
echo "======================================="
|
||||||
|
echo ""
|
||||||
|
echo "🚀 Команды управления:"
|
||||||
|
echo " cd /home/service/my-uploader-bot"
|
||||||
|
echo " ./start_my_network.sh # Запуск в терминале"
|
||||||
|
echo " ./run_my_network.sh # Запуск в фоне"
|
||||||
|
echo " ./stop_my_network.sh # Остановка"
|
||||||
|
echo " ./status_my_network.sh # Проверка статуса"
|
||||||
|
echo ""
|
||||||
|
echo "🌐 Доступ к сервису:"
|
||||||
|
echo " http://my-public-node-3.projscale.dev:15100"
|
||||||
|
echo " http://my-public-node-3.projscale.dev:15100/api/my/health"
|
||||||
|
echo " http://my-public-node-3.projscale.dev:15100/api/my/monitor/"
|
||||||
|
echo ""
|
||||||
|
echo "📝 Логи:"
|
||||||
|
echo " tail -f logs/my-network.log # Основной лог"
|
||||||
|
echo " tail -f logs/app.log # Лог приложения"
|
||||||
|
echo " tail -f logs/cron.log # Лог cron"
|
||||||
|
echo ""
|
||||||
|
echo "⚡ Автозапуск настроен через cron (каждые 5 минут)"
|
||||||
|
echo ""
|
||||||
|
echo "🎯 MY Network Bootstrap Node готов к работе!"
|
||||||
|
echo ""
|
||||||
|
echo "▶️ Запустить сейчас? Выполните:"
|
||||||
|
echo " ./run_my_network.sh"
|
||||||
|
|
@ -0,0 +1,331 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# MY Network Nginx + SSL Setup
|
||||||
|
# Настройка nginx с SSL сертификатами через certbot
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DOMAIN="my-public-node-3.projscale.dev"
|
||||||
|
EMAIL="admin@projscale.dev"
|
||||||
|
SERVICE_PORT="15100"
|
||||||
|
|
||||||
|
echo "🔒 MY Network Nginx + SSL Setup"
|
||||||
|
echo "==============================="
|
||||||
|
echo "Домен: $DOMAIN"
|
||||||
|
echo "Email: $EMAIL"
|
||||||
|
echo "Сервис порт: $SERVICE_PORT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Проверка root прав
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
echo "❌ Запустите от root: sudo bash setup_nginx_ssl.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Обновление системы
|
||||||
|
echo "🔄 Обновление системы..."
|
||||||
|
apt update
|
||||||
|
|
||||||
|
# Установка необходимых пакетов
|
||||||
|
echo "📦 Установка nginx, certbot, ufw..."
|
||||||
|
apt install -y nginx certbot python3-certbot-nginx ufw curl
|
||||||
|
|
||||||
|
# Остановка nginx для получения сертификата
|
||||||
|
echo "🛑 Остановка nginx..."
|
||||||
|
systemctl stop nginx || true
|
||||||
|
|
||||||
|
# Настройка firewall
|
||||||
|
echo "🔥 Настройка firewall..."
|
||||||
|
ufw --force reset
|
||||||
|
ufw default deny incoming
|
||||||
|
ufw default allow outgoing
|
||||||
|
|
||||||
|
# SSH
|
||||||
|
ufw allow 22/tcp comment 'SSH'
|
||||||
|
|
||||||
|
# HTTP/HTTPS
|
||||||
|
ufw allow 80/tcp comment 'HTTP'
|
||||||
|
ufw allow 443/tcp comment 'HTTPS'
|
||||||
|
|
||||||
|
# Внутренний порт сервиса (только локально)
|
||||||
|
ufw allow from 127.0.0.1 to any port $SERVICE_PORT comment 'MY Network Local'
|
||||||
|
|
||||||
|
ufw --force enable
|
||||||
|
|
||||||
|
# Получение SSL сертификата
|
||||||
|
echo "🔐 Получение SSL сертификата..."
|
||||||
|
certbot certonly \
|
||||||
|
--standalone \
|
||||||
|
--non-interactive \
|
||||||
|
--agree-tos \
|
||||||
|
--email $EMAIL \
|
||||||
|
-d $DOMAIN
|
||||||
|
|
||||||
|
# Проверка что сертификат получен
|
||||||
|
if [[ ! -f "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ]]; then
|
||||||
|
echo "❌ Не удалось получить SSL сертификат!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ SSL сертификат получен"
|
||||||
|
|
||||||
|
# Создание конфигурации nginx
|
||||||
|
echo "🌐 Настройка nginx..."
|
||||||
|
|
||||||
|
# Удаление дефолтного сайта
|
||||||
|
rm -f /etc/nginx/sites-enabled/default
|
||||||
|
|
||||||
|
# Создание конфигурации MY Network
|
||||||
|
cat > /etc/nginx/sites-available/my-network << EOF
|
||||||
|
# MY Network Bootstrap Node - Nginx Configuration
|
||||||
|
# HTTP -> HTTPS redirect
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name $DOMAIN;
|
||||||
|
|
||||||
|
# Certbot renewal
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redirect all HTTP to HTTPS
|
||||||
|
location / {
|
||||||
|
return 301 https://\$server_name\$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTPS server
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name $DOMAIN;
|
||||||
|
|
||||||
|
# SSL Configuration
|
||||||
|
ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;
|
||||||
|
|
||||||
|
# SSL Security
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_timeout 10m;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
add_header X-Frame-Options DENY always;
|
||||||
|
add_header X-Content-Type-Options nosniff always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header X-MY-Network-Version "2.0" always;
|
||||||
|
add_header X-MY-Network-Node "bootstrap" always;
|
||||||
|
|
||||||
|
# Hide nginx version
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
limit_req_zone \$binary_remote_addr zone=api:10m rate=10r/s;
|
||||||
|
limit_req_zone \$binary_remote_addr zone=monitor:10m rate=1r/s;
|
||||||
|
|
||||||
|
# Main application proxy
|
||||||
|
location / {
|
||||||
|
# Rate limiting
|
||||||
|
limit_req zone=api burst=20 nodelay;
|
||||||
|
|
||||||
|
proxy_pass http://127.0.0.1:$SERVICE_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;
|
||||||
|
proxy_set_header X-Forwarded-Host \$host;
|
||||||
|
proxy_set_header X-Forwarded-Port \$server_port;
|
||||||
|
|
||||||
|
# Timeouts
|
||||||
|
proxy_connect_timeout 5s;
|
||||||
|
proxy_send_timeout 10s;
|
||||||
|
proxy_read_timeout 10s;
|
||||||
|
|
||||||
|
# Buffering
|
||||||
|
proxy_buffering on;
|
||||||
|
proxy_buffer_size 4k;
|
||||||
|
proxy_buffers 8 4k;
|
||||||
|
|
||||||
|
# CORS headers
|
||||||
|
add_header Access-Control-Allow-Origin "*" always;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
|
||||||
|
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
|
||||||
|
|
||||||
|
# Handle preflight requests
|
||||||
|
if (\$request_method = 'OPTIONS') {
|
||||||
|
add_header Access-Control-Allow-Origin "*";
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||||
|
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
|
||||||
|
add_header Access-Control-Max-Age 86400;
|
||||||
|
add_header Content-Length 0;
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Monitor endpoint with stricter rate limiting
|
||||||
|
location /api/my/monitor/ {
|
||||||
|
limit_req zone=monitor burst=5 nodelay;
|
||||||
|
|
||||||
|
proxy_pass http://127.0.0.1:$SERVICE_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;
|
||||||
|
|
||||||
|
# Cache static content
|
||||||
|
expires 30s;
|
||||||
|
add_header Cache-Control "public, no-transform";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check endpoint
|
||||||
|
location /api/my/health {
|
||||||
|
proxy_pass http://127.0.0.1:$SERVICE_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;
|
||||||
|
|
||||||
|
# No caching for health checks
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
add_header Pragma "no-cache";
|
||||||
|
add_header Expires "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Block access to sensitive files
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Custom error pages
|
||||||
|
error_page 404 /404.html;
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|
||||||
|
location = /404.html {
|
||||||
|
return 404 '{"error": "Not Found", "message": "MY Network endpoint not found"}';
|
||||||
|
add_header Content-Type application/json;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /50x.html {
|
||||||
|
return 500 '{"error": "Server Error", "message": "MY Network temporarily unavailable"}';
|
||||||
|
add_header Content-Type application/json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Активация сайта
|
||||||
|
ln -sf /etc/nginx/sites-available/my-network /etc/nginx/sites-enabled/
|
||||||
|
|
||||||
|
# Проверка конфигурации nginx
|
||||||
|
echo "🔍 Проверка конфигурации nginx..."
|
||||||
|
nginx -t
|
||||||
|
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
echo "❌ Ошибка в конфигурации nginx!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Настройка автообновления сертификатов
|
||||||
|
echo "🔄 Настройка автообновления сертификатов..."
|
||||||
|
cat > /etc/cron.d/certbot-renew << EOF
|
||||||
|
# Автообновление SSL сертификатов MY Network
|
||||||
|
0 12 * * * root certbot renew --quiet --post-hook "systemctl reload nginx"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Запуск nginx
|
||||||
|
echo "🚀 Запуск nginx..."
|
||||||
|
systemctl enable nginx
|
||||||
|
systemctl start nginx
|
||||||
|
|
||||||
|
# Проверка статуса
|
||||||
|
sleep 2
|
||||||
|
if systemctl is-active --quiet nginx; then
|
||||||
|
echo "✅ Nginx запущен успешно"
|
||||||
|
else
|
||||||
|
echo "❌ Ошибка запуска nginx"
|
||||||
|
systemctl status nginx
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверка MY Network сервиса
|
||||||
|
echo "🔍 Проверка MY Network сервиса..."
|
||||||
|
if pgrep -f "python3 app/main.py" > /dev/null; then
|
||||||
|
echo "✅ MY Network сервис работает"
|
||||||
|
else
|
||||||
|
echo "⚠️ MY Network сервис не запущен, запускаем..."
|
||||||
|
cd /home/service/my-uploader-bot
|
||||||
|
sudo -u service ./run_my_network.sh
|
||||||
|
sleep 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Финальная проверка
|
||||||
|
echo "🎯 Финальная проверка..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Проверка HTTP -> HTTPS redirect
|
||||||
|
echo "📡 Проверка HTTP redirect..."
|
||||||
|
HTTP_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" http://$DOMAIN/ || echo "000")
|
||||||
|
if [[ "$HTTP_RESPONSE" == "301" ]]; then
|
||||||
|
echo "✅ HTTP -> HTTPS redirect работает"
|
||||||
|
else
|
||||||
|
echo "⚠️ HTTP redirect: $HTTP_RESPONSE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверка HTTPS
|
||||||
|
echo "🔒 Проверка HTTPS..."
|
||||||
|
HTTPS_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://$DOMAIN/api/my/health || echo "000")
|
||||||
|
if [[ "$HTTPS_RESPONSE" == "200" ]]; then
|
||||||
|
echo "✅ HTTPS работает"
|
||||||
|
else
|
||||||
|
echo "❌ HTTPS не работает: $HTTPS_RESPONSE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверка SSL сертификата
|
||||||
|
echo "🔐 Проверка SSL сертификата..."
|
||||||
|
SSL_INFO=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -dates 2>/dev/null || echo "SSL check failed")
|
||||||
|
if [[ "$SSL_INFO" != "SSL check failed" ]]; then
|
||||||
|
echo "✅ SSL сертификат валиден"
|
||||||
|
echo "$SSL_INFO"
|
||||||
|
else
|
||||||
|
echo "⚠️ Не удалось проверить SSL сертификат"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🎉 MY Network Bootstrap Node с SSL настроен!"
|
||||||
|
echo "============================================"
|
||||||
|
echo ""
|
||||||
|
echo "🌐 Доступ:"
|
||||||
|
echo " https://$DOMAIN"
|
||||||
|
echo " https://$DOMAIN/api/my/health"
|
||||||
|
echo " https://$DOMAIN/api/my/bootstrap/config"
|
||||||
|
echo " https://$DOMAIN/api/my/monitor/"
|
||||||
|
echo ""
|
||||||
|
echo "🔒 Безопасность:"
|
||||||
|
echo " ✅ SSL/TLS сертификат от Let's Encrypt"
|
||||||
|
echo " ✅ HTTP -> HTTPS redirect"
|
||||||
|
echo " ✅ Security headers"
|
||||||
|
echo " ✅ Rate limiting"
|
||||||
|
echo " ✅ Firewall (UFW)"
|
||||||
|
echo ""
|
||||||
|
echo "🔄 Автообновление:"
|
||||||
|
echo " ✅ SSL сертификаты (cron)"
|
||||||
|
echo " ✅ MY Network сервис (cron)"
|
||||||
|
echo ""
|
||||||
|
echo "📊 Мониторинг:"
|
||||||
|
echo " systemctl status nginx"
|
||||||
|
echo " systemctl status my-network"
|
||||||
|
echo " tail -f /var/log/nginx/access.log"
|
||||||
|
echo " tail -f /var/log/nginx/error.log"
|
||||||
|
echo ""
|
||||||
|
echo "🎯 MY Network Bootstrap Node полностью готов!"
|
||||||
|
|
@ -0,0 +1,320 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# MY Network Direct Setup - для работы с файлами в /home/service
|
||||||
|
# Упрощенная версия без лишних проверок
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 MY Network Direct Setup"
|
||||||
|
echo "========================="
|
||||||
|
echo "Работаем с файлами в /home/service"
|
||||||
|
echo "Домен: my-public-node-3.projscale.dev"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Проверка что мы root
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
echo "❌ Запустите от root: sudo bash setup_server_direct.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOMAIN="my-public-node-3.projscale.dev"
|
||||||
|
EMAIL="admin@projscale.dev"
|
||||||
|
|
||||||
|
# Переход в директорию проекта
|
||||||
|
cd /home/service
|
||||||
|
|
||||||
|
echo "📁 Содержимое /home/service:"
|
||||||
|
ls -la
|
||||||
|
|
||||||
|
# Обновление системы быстро
|
||||||
|
echo "🔄 Быстрое обновление системы..."
|
||||||
|
apt update
|
||||||
|
|
||||||
|
# Установка только необходимого
|
||||||
|
echo "📦 Установка базовых пакетов..."
|
||||||
|
apt install -y \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
python3-venv \
|
||||||
|
nginx \
|
||||||
|
ufw \
|
||||||
|
curl \
|
||||||
|
wget
|
||||||
|
|
||||||
|
# Переход в my-uploader-bot если есть
|
||||||
|
if [[ -d "my-uploader-bot" ]]; then
|
||||||
|
cd my-uploader-bot
|
||||||
|
echo "✅ Найдена директория my-uploader-bot"
|
||||||
|
else
|
||||||
|
echo "❌ Директория my-uploader-bot не найдена!"
|
||||||
|
echo "Содержимое /home/service:"
|
||||||
|
ls -la /home/service/
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Создание Python venv
|
||||||
|
echo "🐍 Создание Python окружения..."
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# Установка базовых пакетов Python
|
||||||
|
echo "📦 Установка Python пакетов..."
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install fastapi uvicorn sanic requests python-dotenv
|
||||||
|
|
||||||
|
# Создание минимального приложения MY Network
|
||||||
|
echo "🏗️ Создание минимального MY Network приложения..."
|
||||||
|
|
||||||
|
mkdir -p app/api
|
||||||
|
|
||||||
|
# Простое FastAPI приложение
|
||||||
|
cat > app/main.py << 'EOF'
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""MY Network Bootstrap Node - Minimal Version"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.responses import JSONResponse, HTMLResponse
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="MY Network Bootstrap Node",
|
||||||
|
description="MY Network v2.0 Bootstrap Node - Minimal",
|
||||||
|
version="2.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# CORS
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
NODE_ID = f"bootstrap-{int(datetime.now().timestamp())}"
|
||||||
|
DOMAIN = "my-public-node-3.projscale.dev"
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return {"message": "MY Network Bootstrap Node v2.0", "status": "active"}
|
||||||
|
|
||||||
|
@app.get("/api/my/health")
|
||||||
|
async def health_check():
|
||||||
|
return JSONResponse({
|
||||||
|
"status": "healthy",
|
||||||
|
"node_id": NODE_ID,
|
||||||
|
"node_type": "bootstrap",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"timestamp": datetime.utcnow().isoformat(),
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"version": "2.0.0"
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.get("/api/my/node/info")
|
||||||
|
async def node_info():
|
||||||
|
return JSONResponse({
|
||||||
|
"node_id": NODE_ID,
|
||||||
|
"node_type": "bootstrap",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"port": 443,
|
||||||
|
"ssl": True,
|
||||||
|
"public": True,
|
||||||
|
"region": "eu-central",
|
||||||
|
"capacity": "high",
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"protocol_version": "2.0",
|
||||||
|
"last_seen": datetime.utcnow().isoformat()
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.get("/api/my/bootstrap/config")
|
||||||
|
async def bootstrap_config():
|
||||||
|
config = {
|
||||||
|
"version": "2.0",
|
||||||
|
"network_id": "my-network-main",
|
||||||
|
"bootstrap_nodes": [
|
||||||
|
{
|
||||||
|
"id": NODE_ID,
|
||||||
|
"host": DOMAIN,
|
||||||
|
"port": 443,
|
||||||
|
"ssl": True,
|
||||||
|
"public": True,
|
||||||
|
"region": "eu-central",
|
||||||
|
"capacity": "high",
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"last_seen": datetime.utcnow().isoformat()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"network_config": {
|
||||||
|
"protocol_version": "2.0",
|
||||||
|
"max_peers": 100,
|
||||||
|
"sync_interval": 300
|
||||||
|
},
|
||||||
|
"api_endpoints": {
|
||||||
|
"base_url": f"https://{DOMAIN}",
|
||||||
|
"health": "/api/my/health",
|
||||||
|
"node_info": "/api/my/node/info",
|
||||||
|
"bootstrap": "/api/my/bootstrap/config"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JSONResponse(config)
|
||||||
|
|
||||||
|
@app.get("/api/my/monitor/")
|
||||||
|
async def monitor_dashboard():
|
||||||
|
html = f"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>MY Network Bootstrap Monitor</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
body {{ font-family: 'Courier New', monospace; background: #000; color: #0f0; padding: 20px; }}
|
||||||
|
h1 {{ color: #ff0; text-align: center; }}
|
||||||
|
.box {{ border: 1px solid #0f0; padding: 15px; margin: 10px 0; }}
|
||||||
|
.status {{ color: #0f0; }}
|
||||||
|
.info {{ color: #00f; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🚀 MY Network Bootstrap Node Monitor</h1>
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="status">Node Status: ACTIVE</h3>
|
||||||
|
<p class="info">Node ID: {NODE_ID}</p>
|
||||||
|
<p class="info">Domain: {DOMAIN}</p>
|
||||||
|
<p class="info">Type: Bootstrap Primary</p>
|
||||||
|
<p class="info">Version: 2.0.0</p>
|
||||||
|
<p class="info">Services: API, Monitor</p>
|
||||||
|
<p class="info">Last Update: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="status">Network Configuration</h3>
|
||||||
|
<p class="info">Protocol Version: 2.0</p>
|
||||||
|
<p class="info">Max Peers: 100</p>
|
||||||
|
<p class="info">Public Access: Enabled</p>
|
||||||
|
<p class="info">SSL: Required</p>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
setTimeout(() => location.reload(), 30000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
return HTMLResponse(content=html)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(f"🚀 Starting MY Network Bootstrap Node")
|
||||||
|
print(f"Domain: {DOMAIN}")
|
||||||
|
print(f"Node ID: {NODE_ID}")
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=15100)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Создание systemd сервиса
|
||||||
|
echo "⚙️ Создание systemd сервиса..."
|
||||||
|
cat > /etc/systemd/system/my-network.service << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=MY Network Bootstrap Node
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=service
|
||||||
|
Group=service
|
||||||
|
WorkingDirectory=/home/service/my-uploader-bot
|
||||||
|
Environment=PATH=/home/service/my-uploader-bot/venv/bin
|
||||||
|
ExecStart=/home/service/my-uploader-bot/venv/bin/python app/main.py
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Права доступа
|
||||||
|
chown -R service:service /home/service/
|
||||||
|
|
||||||
|
# Настройка nginx
|
||||||
|
echo "🌐 Настройка nginx..."
|
||||||
|
cat > /etc/nginx/sites-available/my-network << EOF
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name $DOMAIN;
|
||||||
|
return 301 https://\$server_name\$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name $DOMAIN;
|
||||||
|
|
||||||
|
# Временный самоподписанный сертификат
|
||||||
|
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
|
||||||
|
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options DENY;
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
add_header X-MY-Network-Version "2.0";
|
||||||
|
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
# Main application
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:15100;
|
||||||
|
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;
|
||||||
|
|
||||||
|
# CORS
|
||||||
|
add_header Access-Control-Allow-Origin "*";
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||||
|
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Активация сайта
|
||||||
|
ln -sf /etc/nginx/sites-available/my-network /etc/nginx/sites-enabled/
|
||||||
|
rm -f /etc/nginx/sites-enabled/default
|
||||||
|
|
||||||
|
# Проверка nginx
|
||||||
|
nginx -t
|
||||||
|
|
||||||
|
# Простой firewall
|
||||||
|
echo "🔥 Настройка firewall..."
|
||||||
|
ufw --force reset
|
||||||
|
ufw default deny incoming
|
||||||
|
ufw default allow outgoing
|
||||||
|
|
||||||
|
# SSH
|
||||||
|
ufw allow 22/tcp comment 'SSH'
|
||||||
|
|
||||||
|
# HTTP/HTTPS
|
||||||
|
ufw allow 80/tcp comment 'HTTP'
|
||||||
|
ufw allow 443/tcp comment 'HTTPS'
|
||||||
|
|
||||||
|
ufw --force enable
|
||||||
|
|
||||||
|
# Запуск сервисов
|
||||||
|
echo "🚀 Запуск сервисов..."
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable my-network
|
||||||
|
systemctl start my-network
|
||||||
|
systemctl enable nginx
|
||||||
|
systemctl start nginx
|
||||||
|
|
||||||
|
# Проверка
|
||||||
|
sleep 5
|
||||||
|
echo ""
|
||||||
|
echo "✅ MY Network Bootstrap Node запущен!"
|
||||||
|
echo "=================================="
|
||||||
|
echo "🌐 Домен: https://$DOMAIN"
|
||||||
|
echo "🔌 Локальный порт: 15100"
|
||||||
|
echo ""
|
||||||
|
echo "🔍 Проверка:"
|
||||||
|
echo "systemctl status my-network"
|
||||||
|
echo "curl http://localhost:15100/api/my/health"
|
||||||
|
echo ""
|
||||||
|
echo "🎯 Bootstrap узел готов к работе!"
|
||||||
|
|
@ -0,0 +1,318 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# MY Network Simple Setup - без venv, прямо с системным Python
|
||||||
|
# Самый простой способ запуска
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 MY Network Simple Setup"
|
||||||
|
echo "=========================="
|
||||||
|
echo "Пользователь: $(whoami)"
|
||||||
|
echo "Директория: $(pwd)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Переход в my-uploader-bot
|
||||||
|
if [[ -d "my-uploader-bot" ]]; then
|
||||||
|
cd my-uploader-bot
|
||||||
|
echo "✅ Директория my-uploader-bot найдена"
|
||||||
|
else
|
||||||
|
echo "❌ Директория my-uploader-bot не найдена!"
|
||||||
|
echo "Содержимое текущей директории:"
|
||||||
|
ls -la
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Создание директорий
|
||||||
|
mkdir -p app/api
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
# Проверка Python
|
||||||
|
echo "🐍 Проверка Python..."
|
||||||
|
python3 --version || { echo "❌ Python3 не найден"; exit 1; }
|
||||||
|
|
||||||
|
# Установка пакетов через pip --user
|
||||||
|
echo "📦 Установка Python пакетов (--user)..."
|
||||||
|
python3 -m pip install --user fastapi uvicorn requests python-dotenv
|
||||||
|
|
||||||
|
# Создание упрощенного MY Network приложения
|
||||||
|
echo "🏗️ Создание MY Network приложения..."
|
||||||
|
|
||||||
|
cat > app/main.py << 'EOF'
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""MY Network Bootstrap Node - Simple Version"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
# Простой HTTP сервер без FastAPI
|
||||||
|
try:
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
import socketserver
|
||||||
|
import threading
|
||||||
|
USE_SIMPLE_SERVER = True
|
||||||
|
except ImportError:
|
||||||
|
USE_SIMPLE_SERVER = True
|
||||||
|
|
||||||
|
# Настройка логирования
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler('logs/my-network.log'),
|
||||||
|
logging.StreamHandler()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
logger = logging.getLogger("MY-Network")
|
||||||
|
|
||||||
|
NODE_ID = f"bootstrap-simple-{int(datetime.now().timestamp())}"
|
||||||
|
DOMAIN = "my-public-node-3.projscale.dev"
|
||||||
|
PORT = 15100
|
||||||
|
|
||||||
|
class MyNetworkHandler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
parsed_path = urlparse(self.path)
|
||||||
|
path = parsed_path.path
|
||||||
|
|
||||||
|
# Маршруты
|
||||||
|
if path == '/':
|
||||||
|
self.send_json_response({"message": "MY Network Bootstrap Node v2.0 - Simple", "status": "active"})
|
||||||
|
elif path == '/api/my/health':
|
||||||
|
self.send_json_response({
|
||||||
|
"status": "healthy",
|
||||||
|
"node_id": NODE_ID,
|
||||||
|
"node_type": "bootstrap",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"port": PORT,
|
||||||
|
"mode": "simple",
|
||||||
|
"timestamp": datetime.utcnow().isoformat(),
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"version": "2.0.0"
|
||||||
|
})
|
||||||
|
elif path == '/api/my/node/info':
|
||||||
|
self.send_json_response({
|
||||||
|
"node_id": NODE_ID,
|
||||||
|
"node_type": "bootstrap",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"port": PORT,
|
||||||
|
"ssl": False,
|
||||||
|
"public": True,
|
||||||
|
"region": "eu-central",
|
||||||
|
"capacity": "low",
|
||||||
|
"mode": "simple",
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"protocol_version": "2.0",
|
||||||
|
"last_seen": datetime.utcnow().isoformat()
|
||||||
|
})
|
||||||
|
elif path == '/api/my/bootstrap/config':
|
||||||
|
config = {
|
||||||
|
"version": "2.0",
|
||||||
|
"network_id": "my-network-main",
|
||||||
|
"bootstrap_nodes": [
|
||||||
|
{
|
||||||
|
"id": NODE_ID,
|
||||||
|
"host": DOMAIN,
|
||||||
|
"port": PORT,
|
||||||
|
"ssl": False,
|
||||||
|
"public": True,
|
||||||
|
"region": "eu-central",
|
||||||
|
"capacity": "low",
|
||||||
|
"mode": "simple",
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"last_seen": datetime.utcnow().isoformat()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"network_config": {
|
||||||
|
"protocol_version": "2.0",
|
||||||
|
"max_peers": 10,
|
||||||
|
"sync_interval": 300
|
||||||
|
},
|
||||||
|
"api_endpoints": {
|
||||||
|
"base_url": f"http://{DOMAIN}:{PORT}",
|
||||||
|
"health": "/api/my/health",
|
||||||
|
"node_info": "/api/my/node/info",
|
||||||
|
"bootstrap": "/api/my/bootstrap/config"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.send_json_response(config)
|
||||||
|
elif path == '/api/my/monitor/' or path == '/api/my/monitor':
|
||||||
|
self.send_html_response(self.get_monitor_html())
|
||||||
|
else:
|
||||||
|
self.send_response(404)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b'Not Found')
|
||||||
|
|
||||||
|
def send_json_response(self, data: Dict[Any, Any]):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
||||||
|
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||||||
|
self.send_header('X-MY-Network-Version', '2.0')
|
||||||
|
self.end_headers()
|
||||||
|
json_data = json.dumps(data, indent=2)
|
||||||
|
self.wfile.write(json_data.encode('utf-8'))
|
||||||
|
|
||||||
|
def send_html_response(self, html: str):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html; charset=utf-8')
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
self.send_header('X-MY-Network-Version', '2.0')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(html.encode('utf-8'))
|
||||||
|
|
||||||
|
def get_monitor_html(self) -> str:
|
||||||
|
return f"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>MY Network Bootstrap Monitor - Simple</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
body {{ font-family: 'Courier New', monospace; background: #000; color: #0f0; padding: 20px; }}
|
||||||
|
h1 {{ color: #ff0; text-align: center; }}
|
||||||
|
.box {{ border: 1px solid #0f0; padding: 15px; margin: 10px 0; }}
|
||||||
|
.status {{ color: #0f0; }}
|
||||||
|
.info {{ color: #00f; }}
|
||||||
|
.warning {{ color: #f80; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🚀 MY Network Bootstrap Node Monitor</h1>
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="status">Node Status: ACTIVE (Simple Mode)</h3>
|
||||||
|
<p class="info">Node ID: {NODE_ID}</p>
|
||||||
|
<p class="info">Domain: {DOMAIN}</p>
|
||||||
|
<p class="info">Port: {PORT}</p>
|
||||||
|
<p class="info">Type: Bootstrap Primary</p>
|
||||||
|
<p class="info">Version: 2.0.0</p>
|
||||||
|
<p class="info">Mode: Simple HTTP Server</p>
|
||||||
|
<p class="info">Services: API, Monitor</p>
|
||||||
|
<p class="info">Last Update: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="status">Network Configuration</h3>
|
||||||
|
<p class="info">Protocol Version: 2.0</p>
|
||||||
|
<p class="info">Max Peers: 10</p>
|
||||||
|
<p class="info">Public Access: Enabled</p>
|
||||||
|
<p class="warning">SSL: Disabled (Simple Mode)</p>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="warning">⚠️ Simple Mode</h3>
|
||||||
|
<p class="warning">Сервис запущен в упрощенном режиме</p>
|
||||||
|
<p class="warning">Использует встроенный HTTP сервер Python</p>
|
||||||
|
<p class="info">Доступ: http://{DOMAIN}:{PORT}</p>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
setTimeout(() => location.reload(), 30000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
logger.info(f"{self.address_string()} - {format % args}")
|
||||||
|
|
||||||
|
def start_server():
|
||||||
|
logger.info(f"🚀 Starting MY Network Bootstrap Node - Simple Mode")
|
||||||
|
logger.info(f"Domain: {DOMAIN}")
|
||||||
|
logger.info(f"Port: {PORT}")
|
||||||
|
logger.info(f"Node ID: {NODE_ID}")
|
||||||
|
logger.info(f"Mode: Simple HTTP Server")
|
||||||
|
logger.info(f"Access: http://{DOMAIN}:{PORT}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with HTTPServer(("0.0.0.0", PORT), MyNetworkHandler) as httpd:
|
||||||
|
logger.info(f"✅ Server started on http://0.0.0.0:{PORT}")
|
||||||
|
httpd.serve_forever()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Server error: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
start_server()
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Создание скрипта запуска
|
||||||
|
cat > start_my_network.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd /home/service/my-uploader-bot
|
||||||
|
|
||||||
|
echo "🚀 Запуск MY Network Bootstrap Node (Simple Mode)"
|
||||||
|
echo "==============================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Создание директории для логов
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
# Запуск приложения
|
||||||
|
python3 app/main.py
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x start_my_network.sh
|
||||||
|
|
||||||
|
# Создание скрипта для фонового запуска
|
||||||
|
cat > run_my_network.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Проверка, запущен ли MY Network
|
||||||
|
if ! pgrep -f "python3 app/main.py" > /dev/null; then
|
||||||
|
echo "$(date): Запуск MY Network" >> /home/service/my-uploader-bot/logs/cron.log
|
||||||
|
cd /home/service/my-uploader-bot
|
||||||
|
nohup python3 app/main.py >> logs/app.log 2>&1 &
|
||||||
|
echo "$(date): MY Network запущен (PID: $!)" >> /home/service/my-uploader-bot/logs/cron.log
|
||||||
|
else
|
||||||
|
echo "$(date): MY Network уже запущен" >> /home/service/my-uploader-bot/logs/cron.log
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x run_my_network.sh
|
||||||
|
|
||||||
|
# Создание скрипта остановки
|
||||||
|
cat > stop_my_network.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "🛑 Остановка MY Network..."
|
||||||
|
if pgrep -f "python3 app/main.py" > /dev/null; then
|
||||||
|
pkill -f "python3 app/main.py"
|
||||||
|
echo "✅ MY Network остановлен"
|
||||||
|
echo "$(date): MY Network остановлен" >> /home/service/my-uploader-bot/logs/cron.log
|
||||||
|
else
|
||||||
|
echo "⚠️ MY Network не запущен"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x stop_my_network.sh
|
||||||
|
|
||||||
|
# Добавление в crontab
|
||||||
|
echo "📅 Добавление в crontab..."
|
||||||
|
(crontab -l 2>/dev/null | grep -v "run_my_network.sh"; echo "*/5 * * * * /home/service/my-uploader-bot/run_my_network.sh") | crontab -
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ MY Network Bootstrap Node установлен!"
|
||||||
|
echo "======================================="
|
||||||
|
echo ""
|
||||||
|
echo "🚀 Запуск:"
|
||||||
|
echo " cd /home/service/my-uploader-bot"
|
||||||
|
echo " ./start_my_network.sh # В терминале"
|
||||||
|
echo " ./run_my_network.sh # В фоне"
|
||||||
|
echo " ./stop_my_network.sh # Остановка"
|
||||||
|
echo ""
|
||||||
|
echo "🌐 Доступ:"
|
||||||
|
echo " http://my-public-node-3.projscale.dev:15100"
|
||||||
|
echo " http://my-public-node-3.projscale.dev:15100/api/my/health"
|
||||||
|
echo " http://my-public-node-3.projscale.dev:15100/api/my/monitor/"
|
||||||
|
echo ""
|
||||||
|
echo "📝 Логи:"
|
||||||
|
echo " tail -f logs/my-network.log"
|
||||||
|
echo " tail -f logs/app.log"
|
||||||
|
echo " tail -f logs/cron.log"
|
||||||
|
echo ""
|
||||||
|
echo "⚡ Автозапуск настроен через cron (каждые 5 минут)"
|
||||||
|
echo ""
|
||||||
|
echo "🎯 MY Network Bootstrap Node готов к работе!"
|
||||||
|
|
@ -0,0 +1,291 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# MY Network User Setup - без sudo, только для пользователя service
|
||||||
|
# Запуск MY Network в пользовательском пространстве
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 MY Network User Setup (без sudo)"
|
||||||
|
echo "==================================="
|
||||||
|
echo "Пользователь: $(whoami)"
|
||||||
|
echo "Директория: $(pwd)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Переход в my-uploader-bot
|
||||||
|
if [[ -d "my-uploader-bot" ]]; then
|
||||||
|
cd my-uploader-bot
|
||||||
|
echo "✅ Директория my-uploader-bot найдена"
|
||||||
|
else
|
||||||
|
echo "❌ Директория my-uploader-bot не найдена!"
|
||||||
|
echo "Содержимое текущей директории:"
|
||||||
|
ls -la
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Создание Python venv
|
||||||
|
echo "🐍 Создание Python окружения..."
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# Обновление pip
|
||||||
|
echo "📦 Обновление pip..."
|
||||||
|
pip install --upgrade pip
|
||||||
|
|
||||||
|
# Установка базовых пакетов
|
||||||
|
echo "📦 Установка Python пакетов..."
|
||||||
|
pip install fastapi uvicorn sanic requests python-dotenv
|
||||||
|
|
||||||
|
# Создание директорий
|
||||||
|
mkdir -p app/api
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
# Создание упрощенного MY Network приложения
|
||||||
|
echo "🏗️ Создание MY Network приложения..."
|
||||||
|
|
||||||
|
cat > app/main.py << 'EOF'
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""MY Network Bootstrap Node - User Space Version"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.responses import JSONResponse, HTMLResponse
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
import uvicorn
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Настройка логирования
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler('logs/my-network.log'),
|
||||||
|
logging.StreamHandler()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
logger = logging.getLogger("MY-Network")
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="MY Network Bootstrap Node",
|
||||||
|
description="MY Network v2.0 Bootstrap Node - User Space",
|
||||||
|
version="2.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# CORS
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
NODE_ID = f"bootstrap-user-{int(datetime.now().timestamp())}"
|
||||||
|
DOMAIN = "my-public-node-3.projscale.dev"
|
||||||
|
PORT = 15100
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
async def startup():
|
||||||
|
logger.info(f"🚀 MY Network Bootstrap Node запущен")
|
||||||
|
logger.info(f"Node ID: {NODE_ID}")
|
||||||
|
logger.info(f"Domain: {DOMAIN}")
|
||||||
|
logger.info(f"Port: {PORT}")
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return {"message": "MY Network Bootstrap Node v2.0 - User Space", "status": "active"}
|
||||||
|
|
||||||
|
@app.get("/api/my/health")
|
||||||
|
async def health_check():
|
||||||
|
return JSONResponse({
|
||||||
|
"status": "healthy",
|
||||||
|
"node_id": NODE_ID,
|
||||||
|
"node_type": "bootstrap",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"port": PORT,
|
||||||
|
"mode": "user-space",
|
||||||
|
"timestamp": datetime.utcnow().isoformat(),
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"version": "2.0.0"
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.get("/api/my/node/info")
|
||||||
|
async def node_info():
|
||||||
|
return JSONResponse({
|
||||||
|
"node_id": NODE_ID,
|
||||||
|
"node_type": "bootstrap",
|
||||||
|
"domain": DOMAIN,
|
||||||
|
"port": PORT,
|
||||||
|
"ssl": False,
|
||||||
|
"public": True,
|
||||||
|
"region": "eu-central",
|
||||||
|
"capacity": "medium",
|
||||||
|
"mode": "user-space",
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"protocol_version": "2.0",
|
||||||
|
"last_seen": datetime.utcnow().isoformat()
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.get("/api/my/bootstrap/config")
|
||||||
|
async def bootstrap_config():
|
||||||
|
config = {
|
||||||
|
"version": "2.0",
|
||||||
|
"network_id": "my-network-main",
|
||||||
|
"bootstrap_nodes": [
|
||||||
|
{
|
||||||
|
"id": NODE_ID,
|
||||||
|
"host": DOMAIN,
|
||||||
|
"port": PORT,
|
||||||
|
"ssl": False,
|
||||||
|
"public": True,
|
||||||
|
"region": "eu-central",
|
||||||
|
"capacity": "medium",
|
||||||
|
"mode": "user-space",
|
||||||
|
"services": ["api", "monitor"],
|
||||||
|
"last_seen": datetime.utcnow().isoformat()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"network_config": {
|
||||||
|
"protocol_version": "2.0",
|
||||||
|
"max_peers": 50,
|
||||||
|
"sync_interval": 300
|
||||||
|
},
|
||||||
|
"api_endpoints": {
|
||||||
|
"base_url": f"http://{DOMAIN}:{PORT}",
|
||||||
|
"health": "/api/my/health",
|
||||||
|
"node_info": "/api/my/node/info",
|
||||||
|
"bootstrap": "/api/my/bootstrap/config"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JSONResponse(config)
|
||||||
|
|
||||||
|
@app.get("/api/my/monitor/")
|
||||||
|
async def monitor_dashboard():
|
||||||
|
html = f"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>MY Network Bootstrap Monitor - User Space</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
body {{ font-family: 'Courier New', monospace; background: #000; color: #0f0; padding: 20px; }}
|
||||||
|
h1 {{ color: #ff0; text-align: center; }}
|
||||||
|
.box {{ border: 1px solid #0f0; padding: 15px; margin: 10px 0; }}
|
||||||
|
.status {{ color: #0f0; }}
|
||||||
|
.info {{ color: #00f; }}
|
||||||
|
.warning {{ color: #f80; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🚀 MY Network Bootstrap Node Monitor</h1>
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="status">Node Status: ACTIVE (User Space)</h3>
|
||||||
|
<p class="info">Node ID: {NODE_ID}</p>
|
||||||
|
<p class="info">Domain: {DOMAIN}</p>
|
||||||
|
<p class="info">Port: {PORT}</p>
|
||||||
|
<p class="info">Type: Bootstrap Primary</p>
|
||||||
|
<p class="info">Version: 2.0.0</p>
|
||||||
|
<p class="info">Mode: User Space</p>
|
||||||
|
<p class="info">Services: API, Monitor</p>
|
||||||
|
<p class="info">Last Update: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="status">Network Configuration</h3>
|
||||||
|
<p class="info">Protocol Version: 2.0</p>
|
||||||
|
<p class="info">Max Peers: 50</p>
|
||||||
|
<p class="info">Public Access: Enabled</p>
|
||||||
|
<p class="warning">SSL: Disabled (User Space Mode)</p>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h3 class="warning">⚠️ User Space Mode</h3>
|
||||||
|
<p class="warning">Сервис запущен в пользовательском режиме</p>
|
||||||
|
<p class="warning">Для полной функциональности требуются root права</p>
|
||||||
|
<p class="info">Доступ: http://{DOMAIN}:{PORT}</p>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
setTimeout(() => location.reload(), 30000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
return HTMLResponse(content=html)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(f"🚀 Starting MY Network Bootstrap Node - User Space")
|
||||||
|
print(f"Domain: {DOMAIN}")
|
||||||
|
print(f"Port: {PORT}")
|
||||||
|
print(f"Node ID: {NODE_ID}")
|
||||||
|
print(f"Mode: User Space (no root required)")
|
||||||
|
print(f"Access: http://{DOMAIN}:{PORT}")
|
||||||
|
print("")
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=PORT)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Создание скрипта запуска
|
||||||
|
cat > start_my_network.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd /home/service/my-uploader-bot
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
echo "🚀 Запуск MY Network Bootstrap Node (User Space)"
|
||||||
|
echo "==============================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Создание директории для логов
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
# Запуск приложения
|
||||||
|
python app/main.py
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x start_my_network.sh
|
||||||
|
|
||||||
|
# Создание cron задачи для автозапуска
|
||||||
|
echo "⏰ Создание задачи для автозапуска..."
|
||||||
|
cat > run_my_network.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Проверка, запущен ли MY Network
|
||||||
|
if ! pgrep -f "python app/main.py" > /dev/null; then
|
||||||
|
echo "$(date): Запуск MY Network" >> /home/service/my-uploader-bot/logs/cron.log
|
||||||
|
cd /home/service/my-uploader-bot
|
||||||
|
source venv/bin/activate
|
||||||
|
nohup python app/main.py >> logs/app.log 2>&1 &
|
||||||
|
else
|
||||||
|
echo "$(date): MY Network уже запущен" >> /home/service/my-uploader-bot/logs/cron.log
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x run_my_network.sh
|
||||||
|
|
||||||
|
# Добавление в crontab
|
||||||
|
echo "📅 Добавление в crontab..."
|
||||||
|
(crontab -l 2>/dev/null; echo "*/5 * * * * /home/service/my-uploader-bot/run_my_network.sh") | crontab -
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ MY Network Bootstrap Node установлен!"
|
||||||
|
echo "======================================="
|
||||||
|
echo ""
|
||||||
|
echo "🚀 Запуск вручную:"
|
||||||
|
echo " cd /home/service/my-uploader-bot"
|
||||||
|
echo " ./start_my_network.sh"
|
||||||
|
echo ""
|
||||||
|
echo "🚀 Запуск в фоне:"
|
||||||
|
echo " ./run_my_network.sh"
|
||||||
|
echo ""
|
||||||
|
echo "🌐 Доступ:"
|
||||||
|
echo " http://my-public-node-3.projscale.dev:15100"
|
||||||
|
echo " http://my-public-node-3.projscale.dev:15100/api/my/health"
|
||||||
|
echo " http://my-public-node-3.projscale.dev:15100/api/my/monitor/"
|
||||||
|
echo ""
|
||||||
|
echo "📝 Логи:"
|
||||||
|
echo " tail -f logs/my-network.log"
|
||||||
|
echo " tail -f logs/app.log"
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ Примечание: Сервис работает в пользовательском режиме"
|
||||||
|
echo " Для полной функциональности требуются root права"
|
||||||
|
echo ""
|
||||||
|
echo "🎯 MY Network Bootstrap Node готов к работе!"
|
||||||
Loading…
Reference in New Issue