Compare commits
2
Commits
7537e7ffe1
...
2a735e34f8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a735e34f8 | ||
|
|
7be1a1f649 |
@@ -0,0 +1,75 @@
|
|||||||
|
# ПЛАН: универсальный файловый сервис на ВМ (API)
|
||||||
|
|
||||||
|
_Дата: 2026-08-21. Статус: РЕАЛИЗОВАНО И ЗАДЕПЛОЕНО (v1.0.0), см. `README.md` сервиса `/home/naeel/nubes/vmfiles/` и `2026-08-21-vm-upload-implemented.md`._
|
||||||
|
|
||||||
|
## Цель
|
||||||
|
|
||||||
|
Один внутренний (НЕ публичный) сервис на ВМ `5.172.178.213` (`contracts.kube5s.ru`)
|
||||||
|
для приёма/временного хранения/выдачи файлов. Потребители: **drhider**, **contracts**,
|
||||||
|
**SQS IoT**. Заменяет сырой nginx-dav `/drhider-upload/` на задокументированный,
|
||||||
|
версионированный API, чтобы любому сервису было ясно, как им пользоваться.
|
||||||
|
|
||||||
|
## Потребители и режимы
|
||||||
|
- **drhider** — браузер (PUT файла) + сервер (pull). Нужен CORS для браузера.
|
||||||
|
- **contracts** — сервер-к-серверу, много файлов из локали.
|
||||||
|
- **SQS IoT** — сервер-к-серверу.
|
||||||
|
|
||||||
|
## Технология
|
||||||
|
**FastAPI + uvicorn** (Python уже есть на ВМ).
|
||||||
|
Почему FastAPI: автогенерация OpenAPI/Swagger (`/docs`) → «как юзать» видно из коробки;
|
||||||
|
нативный streaming для больших тел.
|
||||||
|
|
||||||
|
## API v1 (контракт)
|
||||||
|
|
||||||
|
| Метод | Путь | Вход | Ответ |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `POST` | `/api/v1/files` | тело=файл (raw или multipart `file`), header `X-App-Key` | `201 {"id","url","size","expires_at"}` |
|
||||||
|
| `GET` | `/api/v1/files/{id}` | header `X-App-Key` | байты файла |
|
||||||
|
| `DELETE` | `/api/v1/files/{id}` | header `X-App-Key` | `204` |
|
||||||
|
| `GET` | `/api/v1/health` | — | `{"ok":true,"version":...}` |
|
||||||
|
| `GET` | `/api/v1/docs` | — | Swagger UI (авто) |
|
||||||
|
|
||||||
|
Единые ошибки: `{"error": "...", "code": "unauthorized|not_found|too_large|expired|quota_exceeded"}`.
|
||||||
|
|
||||||
|
## Авторизация
|
||||||
|
Заголовок `X-App-Key`, по одному секрету на приложение. Ключи — в конфиге на ВМ
|
||||||
|
(файл `apps.yml`/`.env`): `drhider`, `contracts`, `sqs-iot`. Без валидного ключа — `401`.
|
||||||
|
|
||||||
|
## Хранилище и метаданные
|
||||||
|
- Файлы: `/var/lib/vmfiles/` (или `/var/www/vmfiles/`, владелец — пользователь сервиса).
|
||||||
|
- Метаданные: SQLite (`id, app, name, size, created_at, expires_at`).
|
||||||
|
- id — серверный UUID (или клиентский токен — см. «открытые вопросы»).
|
||||||
|
|
||||||
|
## Лимиты и TTL
|
||||||
|
- `max_file_size` (по умолчанию 1024m) и `quota` на приложение.
|
||||||
|
- TTL по умолчанию 30 мин (на приложение можно переопределить).
|
||||||
|
- Чистка — встроенная фоновая задача сервиса (вместо текущего cron-`find`).
|
||||||
|
|
||||||
|
## Деплой на ВМ
|
||||||
|
1. Код сервиса — в отдельной папке на ВМ (или отдельный репозиторий).
|
||||||
|
2. systemd-юнит `vmfiles.service` → `uvicorn app:app --host 127.0.0.1 --port 8769`.
|
||||||
|
3. nginx: новый `location /api/v1/ { proxy_pass http://127.0.0.1:8769; ... }`
|
||||||
|
+ `proxy_request_buffering off` (стримить большие тела) + `client_max_body_size 1024m`.
|
||||||
|
CORS — отдать сервису (FastAPI), не nginx.
|
||||||
|
4. Проверка: `nginx -t` → reload.
|
||||||
|
|
||||||
|
## Миграция drhider
|
||||||
|
- Пока сервис поднимается — `/drhider-upload/` оставить как есть (не ломать текущее).
|
||||||
|
- После готовности — перевести `uploadFiles()` и `/api/upload_refs` на `/api/v1/files`,
|
||||||
|
убрать dav-location и cron-`find`.
|
||||||
|
|
||||||
|
## Порядок работ
|
||||||
|
1. Скелет FastAPI + health + `/docs`.
|
||||||
|
2. `POST/GET/DELETE /files` + SQLite-метаданные + TTL-чистка.
|
||||||
|
3. `X-App-Key` авторизация + квоты.
|
||||||
|
4. systemd + nginx `proxy_pass`.
|
||||||
|
5. Тесты: curl (raw + multipart), большие файлы, expiry, quota.
|
||||||
|
6. Перевести drhider на новый API, выпилить старый dav.
|
||||||
|
7. Документ-спека (`README`/`API.md`) + примеры для contracts/SQS.
|
||||||
|
|
||||||
|
## Открытые вопросы (до «делай»)
|
||||||
|
1. Язык: FastAPI (рекомендую) или Flask?
|
||||||
|
2. id файла: сервер генерирует (POST) или клиент приносит токен (PUT)? Влияет на браузерный поток drhider.
|
||||||
|
3. Загрузка: только raw-тело или поддерживать multipart тоже?
|
||||||
|
4. Где живёт код сервиса — отдельный репозиторий на gitea или папка на ВМ?
|
||||||
|
5. Ключи приложений — придумать сейчас или сервис сгенерирует при первом старте?
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# ПЛАН: тест-режим «только загрузка» (для Флэша)
|
||||||
|
|
||||||
|
_Дата: 2026-08-23. Цель: при нажатии «Обфусцировать» — только загрузить файлы
|
||||||
|
(браузер→ВМ→Flask), НЕ запускать обработку (SSE/LLM/обфускацию)._
|
||||||
|
|
||||||
|
## Суть
|
||||||
|
|
||||||
|
Добавить флаг тест-режима в фронтенд. Когда он включён, `uploadFiles()` проходит
|
||||||
|
Фазу 1 полностью (PUT на ВМ + `POST /api/upload_refs` — pull в сессию), а потом
|
||||||
|
**останавливается до Фазы 2** (не открывает EventSource, не запускает обработку).
|
||||||
|
|
||||||
|
Плюс опциональный отладочный endpoint на бэке — проверить, что файлы реально
|
||||||
|
легли в сессию с корректными размерами.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Изменение 1 — флаг (site/templates/index.html)
|
||||||
|
|
||||||
|
В начало скрипта, рядом с `VM_UPLOAD_URL` (строка ~201), добавить:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Тест-режим: открыть страницу с ?upload-only=1 → только загрузка, без обработки
|
||||||
|
const TEST_UPLOAD_ONLY = new URLSearchParams(location.search).get('upload-only') === '1';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Изменение 2 — ранний выход после загрузки (site/templates/index.html)
|
||||||
|
|
||||||
|
В `uploadFiles()`, сразу ПОСЛЕ блока «Шаг 1b» (try/catch `POST /api/upload_refs`)
|
||||||
|
и ПЕРЕД комментарием `// Фаза 2: обработка (SSE — прогресс по каждому файлу)`.
|
||||||
|
|
||||||
|
При этом в шаге 1b зафиксировать число загруженных файлов. Сейчас там:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const data = await resp.json();
|
||||||
|
if (!data.ok) throw new Error(data.error || 'HTTP ' + resp.status);
|
||||||
|
currentSid = data.session;
|
||||||
|
```
|
||||||
|
|
||||||
|
Запомнить count:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const data = await resp.json();
|
||||||
|
if (!data.ok) throw new Error(data.error || 'HTTP ' + resp.status);
|
||||||
|
currentSid = data.session;
|
||||||
|
uploadedCount = data.count || refs.length; // сколько реально легло в сессию
|
||||||
|
```
|
||||||
|
|
||||||
|
(объявить `let uploadedCount = 0;` рядом с `const refs = [];` в начале Фазы 1).
|
||||||
|
|
||||||
|
Сразу после `catch` шага 1b вставить:
|
||||||
|
|
||||||
|
```js
|
||||||
|
if (TEST_UPLOAD_ONLY) {
|
||||||
|
st.className = 'status done';
|
||||||
|
st.textContent = '✅ Загружено ' + uploadedCount + ' файлов (тест: обработка пропущена). Сессия: ' + currentSid;
|
||||||
|
ub.disabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
То есть: показываем итог, снова включаем кнопку, выходим из функции. Фаза 2 не выполняется.
|
||||||
|
|
||||||
|
## Изменение 3 (опционально, рекомендуется) — debug-endpoint (site/routes/api_bp.py)
|
||||||
|
|
||||||
|
Чтобы проверить размеры файлов в сессии (не повредились ли при pull), добавить:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@api_bp.route("/session_files/<sid>", methods=["GET"])
|
||||||
|
def session_files(sid):
|
||||||
|
"""Отладка: список файлов сессии с размерами."""
|
||||||
|
files = get_files(sid)
|
||||||
|
if files is None:
|
||||||
|
return jsonify({"ok": False, "error": "Session not found"}), 404
|
||||||
|
return jsonify({
|
||||||
|
"ok": True, "count": len(files),
|
||||||
|
"files": [{"name": n, "size": len(b)} for n, b in files],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`get_files` уже импортирован в `api_bp.py`. Endpoint временный — убрать после теста.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Как тестировать
|
||||||
|
|
||||||
|
1. Поднять сервис (локально `cd site && python app.py` или redeploy на Штурвале).
|
||||||
|
2. Открыть `https://drhider.pythonk8s.dev.nubes.ru/?upload-only=1` (или `http://127.0.0.1:5000/?upload-only=1`).
|
||||||
|
3. Выбрать файлы (в т.ч. большой >1МБ), нажать «Обфусцировать».
|
||||||
|
4. Ожидать: прогресс «Загрузка на ВМ…» по каждому файлу → «Передача ссылок…» →
|
||||||
|
«✅ Загружено N файлов (тест: обработка пропущена)». SSE НЕ открывается.
|
||||||
|
5. (Если сделан п.3) в консоли/curl: `curl /api/session_files/<sid>` → сверить `size` с оригиналом.
|
||||||
|
6. Проверить на ВМ: после pull файлы удалены (`ls /var/www/drhider-upload/` пусто).
|
||||||
|
|
||||||
|
## Как вернуть обратно (после теста)
|
||||||
|
|
||||||
|
- Флаг `TEST_UPLOAD_ONLY` без `?upload-only=1` даёт обычное поведение — **ничего выпиливать не надо**,
|
||||||
|
режим включается только query-параметром.
|
||||||
|
- Debug-endpoint (п.3) — убрать, когда тест завершён (или оставить под комментарием «отладка»).
|
||||||
|
|
||||||
|
## Замечания
|
||||||
|
|
||||||
|
- НЕ трогать бэк-логику pull (`/api/upload_refs`) — она уже работает (v0.0.58).
|
||||||
|
- НЕ менять Фазу 2 (SSE) — только добавить ранний `return` до неё.
|
||||||
|
- После правки: `python3 -c "import py_compile; py_compile.compile('site/routes/api_bp.py', doraise=True)"`,
|
||||||
|
JS проверить `node --check` (извлечь блок `<script>` из index.html).
|
||||||
|
- Версию `VERSION` в `site/app.py` НЕ поднимать, пока это тестовый режим — либо поднять, если деплоим.
|
||||||
Reference in New Issue
Block a user