diff --git a/api.md b/api.md new file mode 100644 index 0000000..357abae --- /dev/null +++ b/api.md @@ -0,0 +1,878 @@ +# API документация 3dp-manager + +**Версия API:** v1 +**Базовый URL:** `http://:3100/api` +**Префикс:** Все endpoints начинаются с `/api`, кроме публичных подписок (`/bus/:uuid`) + +--- + +## Содержание + +1. [Аутентификация](#аутентификация) +2. [Подписки](#подписки) +3. [Домены](#домены) +4. [Туннели (перенаправление)](#туннели-перенаправление) +5. [Настройки](#настройки) +6. [Ротация](#ротация) +7. [Клиентские endpoints (публичные)](#клиентские-endpoints-публичные) + +--- + +## Аутентификация + +Все endpoints требуют JWT-аутентификации, кроме помеченных как **Public**. + +### POST `/api/auth/login` + +Получение JWT-токена. + +**Body:** +```json +{ + "login": "admin", + "password": "your_password" +} +``` + +**Response:** +```json +{ + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +--- + +### POST `/api/auth/change-password` + +Смена пароля администратора. + +**Headers:** `Authorization: Bearer ` + +**Body:** +```json +{ + "password": "newSecurePassword" +} +``` + +**Response:** +```json +{ + "success": true +} +``` + +--- + +### POST `/api/auth/update-profile` + +Обновление профиля администратора (логин + опционально пароль). + +**Headers:** `Authorization: Bearer ` + +**Body:** +```json +{ + "login": "newAdmin", + "password": "newPassword" +} +``` + +**Response:** +```json +{ + "success": true +} +``` + +--- + +## Подписки + +### GET `/api/subscriptions` + +Получить все подписки. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +[ + { + "id": 1, + "uuid": "abc-123-def", + "name": "My Subscription", + "isEnabled": true, + "isAutoRotationEnabled": true, + "inbounds": [...], + "createdAt": "2024-01-01T00:00:00Z", + "updatedAt": "2024-01-01T00:00:00Z" + } +] +``` + +--- + +### POST `/api/subscriptions` + +Создать новую подписку. + +**Headers:** `Authorization: Bearer ` + +**Body:** +```json +{ + "name": "My Subscription", + "inboundsConfig": [ + { + "type": "vless", + "port": 443, + "sni": "example.com" + }, + { + "type": "vmess", + "port": 8443, + "transport": "websocket" + } + ], + "isAutoRotationEnabled": true +} +``` + +**Response:** +```json +{ + "id": 1, + "uuid": "abc-123-def", + "name": "My Subscription", + ... +} +``` + +--- + +### PUT `/api/subscriptions/:id` + +Обновить подписку. + +**Headers:** `Authorization: Bearer ` + +**Body:** +```json +{ + "name": "Updated Name", + "isEnabled": false, + "isAutoRotationEnabled": true +} +``` + +**Response:** +```json +{ + "id": 1, + "name": "Updated Name", + ... +} +``` + +--- + +### PUT `/api/subscriptions/bulk-auto-rotation` + +Массовое включение/отключение авто-ротации. + +**Headers:** `Authorization: Bearer ` + +**Body:** +```json +{ + "subscriptionIds": ["1", "2", "3"], + "enabled": true +} +``` + +**Response:** +```json +{ + "success": true, + "message": "Обновлено 3 подписок", + "updatedCount": 3, + "notFound": [] +} +``` + +--- + +### DELETE `/api/subscriptions/:id` + +Удалить подписку. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "success": true +} +``` + +--- + +## Домены + +### GET `/api/domains/all` + +Получить все домены без пагинации. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +[ + { "id": 1, "name": "ya.ru" }, + { "id": 2, "name": "vk.com" } +] +``` + +--- + +### GET `/api/domains?page=1&limit=10` + +Получить домены с пагинацией. + +**Headers:** `Authorization: Bearer ` + +**Query Parameters:** +- `page` (default: 1) +- `limit` (default: 10) + +**Response:** +```json +{ + "data": [ + { "id": 1, "name": "ya.ru" } + ], + "total": 100 +} +``` + +--- + +### GET `/api/domains/:id` + +Получить домен по ID. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "id": 1, + "name": "ya.ru" +} +``` + +--- + +### POST `/api/domains` + +Создать один домен. + +**Headers:** `Authorization: Bearer ` + +**Body:** +```json +{ + "name": "example.com" +} +``` + +**Response:** +```json +{ + "id": 1, + "name": "example.com" +} +``` + +--- + +### POST `/api/domains/upload` + +Загрузить несколько доменов сразу. + +**Headers:** `Authorization: Bearer ` + +**Body:** +```json +{ + "domains": ["ya.ru", "vk.com", "example.com"] +} +``` + +**Response:** +```json +{ + "count": 3 +} +``` + +--- + +### DELETE `/api/domains/:id` + +Удалить домен по ID. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "success": true +} +``` + +--- + +### DELETE `/api/domains/all` + +Удалить все домены. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "success": true +} +``` + +--- + +### GET `/api/domains/scan/capabilities` + +Получить возможности сканера доменов (наличие бинарника, timeout). + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "scannerAvailable": true, + "scannerPath": "/usr/bin/RealiTLScanner-linux-64", + "timeoutAvailable": true, + "timeoutPath": "/usr/bin/timeout" +} +``` + +--- + +### GET `/api/domains/scan/status` + +Получить статус текущего сканирования. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "running": false, + "runId": null, + "lastRunId": "scan-123" +} +``` + +--- + +### GET `/api/domains/scan/last-result` + +Получить результат последнего сканирования. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "runId": "scan-123", + "foundCount": 10, + "domains": ["ya.ru", "vk.com", ...] +} +``` + +--- + +### POST `/api/domains/scan/start` + +Запустить сканирование IP на наличие SNI. + +**Headers:** `Authorization: Bearer ` + +**Body:** +```json +{ + "addr": "192.168.1.1", + "scanSeconds": 60, + "thread": 100, + "timeout": 30 +} +``` + +**Response:** +```json +{ + "success": true, + "runId": "scan-456" +} +``` + +--- + +## Туннели (перенаправление) + +### GET `/api/tunnels` + +Получить все туннели. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +[ + { + "id": 1, + "name": "Relay Server 1", + "ip": "192.168.1.100", + "sshPort": 22, + "username": "root", + "domain": "relay.example.com", + "isInstalled": true + } +] +``` + +--- + +### POST `/api/tunnels` + +Создать новый туннель. + +**Headers:** `Authorization: Bearer ` + +**Body:** +```json +{ + "name": "Relay Server 1", + "ip": "192.168.1.100", + "sshPort": 22, + "username": "root", + "password": "ssh_password", + "domain": "relay.example.com" +} +``` + +**Response:** +```json +{ + "id": 1, + "name": "Relay Server 1", + ... +} +``` + +--- + +### POST `/api/tunnels/:id/install` + +Установить скрипт перенаправления на удалённый сервер. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "success": true, + "script": "#!/bin/bash ...", + "message": "Скрипт сгенерирован. Выполните его на целевом сервере." +} +``` + +--- + +### DELETE `/api/tunnels/:id` + +Удалить туннель. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "success": true +} +``` + +--- + +## Настройки + +### GET `/api/settings` + +Получить все настройки. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "xui_url": "http://192.168.1.1:54321", + "xui_login": "admin", + "rotationIntervalMinutes": "1440", + "xui_geo_country": "Germany", + "xui_geo_flag": "🇩🇪" +} +``` + +--- + +### POST `/api/settings` + +Обновить настройки. + +**Headers:** `Authorization: Bearer ` + +**Body:** +```json +{ + "xui_url": "http://192.168.1.1:54321", + "xui_login": "admin", + "xui_password": "password", + "rotationIntervalMinutes": "1440" +} +``` + +**Response:** +```json +{ + "success": true +} +``` + +*Автоматически извлекает host, IP, определяет страну по GeoIP.* + +--- + +### POST `/api/settings/check` + +Проверить подключение к 3x-ui панели. + +**Headers:** `Authorization: Bearer ` + +**Body:** +```json +{ + "xui_url": "http://192.168.1.1:54321", + "xui_login": "admin", + "xui_password": "password" +} +``` + +**Response:** +```json +{ + "success": true +} +``` + +--- + +## Ротация + +### POST `/api/rotation/rotate-all` + +Выполнить ротацию всех подписок. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "success": true, + "rotatedCount": 5 +} +``` + +--- + +### POST `/api/rotation/rotate-one/:id` + +Выполнить ротацию одной подписки. + +**Headers:** `Authorization: Bearer ` + +**Response:** +```json +{ + "success": true, + "subscriptionId": "1" +} +``` + +--- + +## Клиентские endpoints (публичные) + +Эти endpoints **не требуют аутентификации**. + +### GET `/bus/:uuid` + +Получить подписку в формате Base64 или HTML-страницу с QR-кодом. + +**Public** — без токена. + +**URL:** +``` +GET /bus/abc-123-def +``` + +**Headers:** +- `User-Agent: Mozilla/...` → возвращает HTML с QR +- `User-Agent: v2rayN/...` → возвращает plain text Base64 + +**Response (Base64):** +``` +vless://abc123... +vmess://xyz789... +... +``` + +**Response (HTML):** +```html + + + +

Subscription: My Subscription

+ + Скачать подписку + + +``` + +--- + +### GET `/bus/:uuid/:tunnelId` + +Получить подписку с заменой хоста на релей-сервер. + +**Public** — без токена. + +**Query Parameters:** +- `format` (опционально): `base64` | `html` + +**URL:** +``` +GET /bus/abc-123-def/1 +``` + +**Response:** +- Автоматически заменяет IP/домен в ссылках на `tunnel.domain` или `tunnel.ip` +- Возвращает Base64 или HTML с QR + +--- + +## Сущности + +### Subscription +```typescript +{ + id: number; + uuid: string; + name: string; + isEnabled: boolean; + isAutoRotationEnabled: boolean; + inbounds: Inbound[]; + createdAt: Date; + updatedAt: Date; +} +``` + +### Inbound +```typescript +{ + id: number; + type: 'vless' | 'vmess' | 'shadowsocks' | 'hysteria2' | 'trojan' | 'custom'; + port: number | string; + sni?: string; + link: string; + transport?: 'tcp' | 'websocket' | 'grpc' | 'xhttp'; +} +``` + +### Domain +```typescript +{ + id: number; + name: string; +} +``` + +### Tunnel +```typescript +{ + id: number; + name: string; + ip: string; + sshPort: number; + username: string; + domain?: string; + isInstalled: boolean; +} +``` + +### Setting +```typescript +{ + key: string; + value: string; +} +``` + +--- + +## Коды ошибок + +| Код | Описание | +|-----|----------| +| 200 | Успех | +| 400 | Неверный запрос | +| 401 | Неавторизован (нет токена или неверный) | +| 403 | Доступ запрещён | +| 404 | Ресурс не найден | +| 500 | Внутренняя ошибка сервера | + +--- + +## Примеры использования + +### JavaScript (Fetch API) + +```javascript +// Логин +const loginResponse = await fetch('http://localhost:3100/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ login: 'admin', password: 'admin' }) +}); +const { access_token } = await loginResponse.json(); + +// Получить все подписки +const subsResponse = await fetch('http://localhost:3100/api/subscriptions', { + headers: { 'Authorization': `Bearer ${access_token}` } +}); +const subscriptions = await subsResponse.json(); + +// Создать подписку +const createResponse = await fetch('http://localhost:3100/api/subscriptions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${access_token}` + }, + body: JSON.stringify({ + name: 'My Subscription', + inboundsConfig: [ + { type: 'vless', port: 443, sni: 'example.com' } + ], + isAutoRotationEnabled: true + }) +}); +const subscription = await createResponse.json(); +``` + +### Python (requests) + +```python +import requests + +# Логин +response = requests.post('http://localhost:3100/api/auth/login', json={ + 'login': 'admin', + 'password': 'admin' +}) +token = response.json()['access_token'] + +# Получить все домены +headers = {'Authorization': f'Bearer {token}'} +response = requests.get('http://localhost:3100/api/domains/all', headers=headers) +domains = response.json() + +# Запустить сканирование +response = requests.post('http://localhost:3100/api/domains/scan/start', + headers=headers, + json={ + 'addr': '192.168.1.1', + 'scanSeconds': 60, + 'thread': 100, + 'timeout': 30 + } +) +``` + +### cURL + +```bash +# Логин +curl -X POST http://localhost:3100/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"login":"admin","password":"admin"}' + +# Получить подписку (Base64) +curl http://localhost:3100/bus/abc-123-def + +# Получить подписку (HTML с QR) +curl -H "User-Agent: Mozilla/5.0" \ + http://localhost:3100/bus/abc-123-def +``` + +--- + +## Интеграция + +### Ссылка на подписку в клиенте + +Формат: +``` +http://:/bus/ +``` + +Пример для v2rayN, Clash, Surge: +``` +http://192.168.1.100:3100/bus/abc-123-def +``` + +### QR-код + +Откройте в браузере: +``` +http://:/bus/ +``` + +Сканируйте QR-код приложением. + +--- + +## Примечания + +- **JWT-токен** передаётся в заголовке `Authorization: Bearer ` +- **Публичные endpoints** (`/bus/*`) не требуют токена +- **Base64-кодировка** используется для подписок (стандарт v2ray) +- **Авто-ротация** выполняется по интервалу из настроек (`rotationIntervalMinutes`) +- **Scanner** (RealiTLScanner) — Go-бинарник, встраивается в backend-контейнер + +--- + +## Контакты + +- Telegram: [@denpiligrim_web](https://t.me/denpiligrim_web) +- YouTube: [DenPiligrim](https://www.youtube.com/@denpiligrim) +- GitHub: [denpiligrim/3dp-manager](https://github.com/denpiligrim/3dp-manager)