Initial commit

This commit is contained in:
tanexc
2026-06-12 23:53:39 +03:00
commit 67fd2bc5ac
32 changed files with 1107 additions and 0 deletions
+265
View File
@@ -0,0 +1,265 @@
# Model Update Server
Минимальный backend-сервис на FastAPI для загрузки `.pt` моделей, их stub-конвертации в `.ort`, хранения метаданных в SQLite и выдачи последней модели устройствам.
## Что умеет
- `POST /models` - загрузка новой `.pt` модели с версией
- `GET /models/latest` - метаданные последней модели
- `GET /models/latest/download` - скачивание последней `.ort` модели
## API
Все API-запросы требуют заголовок авторизации:
```http
X-API-Key: <server-issued-key>
```
Ключи выпускаются вручную на сервере и задаются через переменную окружения `API_KEYS`.
Можно указать один ключ или несколько ключей через запятую.
### `POST /models`
Загружает новую модель и регистрирует её как актуальную.
Принимает:
- `Content-Type: multipart/form-data`
- поле `file` - файл модели в формате `.pt`
- поле `version` - строковая версия модели, например `1.0.3`
Возвращает:
- `201 Created` и JSON:
```json
{
"version": "1.0.3",
"created_at": "2026-05-12T10:00:00Z",
"filename": "model_v1.0.3.ort",
"is_latest": true
}
```
Ошибки:
- `400` - пустая версия или файл не `.pt`
- `409` - такая версия уже существует
- `500` - ошибка конвертации
### `GET /models/latest`
Возвращает метаданные последней доступной модели.
Принимает:
- ничего
Возвращает:
- `200 OK` и JSON:
```json
{
"version": "1.0.3",
"created_at": "2026-05-12T10:00:00Z",
"filename": "model_v1.0.3.ort"
}
```
Ошибки:
- `404` - если моделей ещё нет
### `GET /models/latest/download`
Возвращает файл последней `.ort` модели.
Принимает:
- query-параметр `version`
- query-параметр `filename`
Возвращает:
- `200 OK`
- бинарный файл `.ort`
- заголовок `Content-Disposition` для скачивания
Ошибки:
- `404` - если моделей нет, файл отсутствует на диске или `version`/`filename` не совпадают с актуальной моделью
## Требования
- Python 3.11+
## Локальный запуск
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload
```
Сервис будет доступен на `http://127.0.0.1:8000`.
Пример запуска с API-ключом:
```bash
API_KEYS="local-dev-key" uvicorn app.main:app --reload
```
Сгенерировать 10 API-ключей и экспортировать их в текущую shell:
```bash
source scripts/generate_api_keys.sh
uvicorn app.main:app --reload
```
Сгенерировать 10 API-ключей и сохранить их в env-файл:
```bash
scripts/generate_api_keys.sh --env-file .env
set -a
source .env
set +a
uvicorn app.main:app --reload
```
## Переменные окружения
- `MODELS_DIR` - директория хранения итоговых `.ort` моделей
- `TMP_DIR` - директория для временно загруженных `.pt`
- `DATABASE_URL` - URL подключения к SQLite, например `sqlite:///./db/app.db`
- `API_KEYS` - один или несколько API-ключей через запятую, например `key-1,key-2`
По умолчанию используются:
- `storage/models`
- `tmp`
- `sqlite:///db/app.db`
- пустой список API-ключей, при котором API-запросы будут отклоняться с `401`
## Архитектура
Проект разложен по слоям clean architecture:
- `app/domain` - сущности, интерфейсы репозиториев и доменные исключения
- `app/application` - use cases и DTO
- `app/infrastructure` - SQLite, файловое хранилище, stub-конвертер, bootstrap
- `app/presentation` - FastAPI routes, зависимости и response-схемы
Точка входа приложения: [app/main.py](/Users/tanexc/PyCharmMiscProject/convert-and-update-model-server/app/main.py).
## Поведение stub-конвертера
Stub-конвертер уже подключен к `POST /models`, но реальную конвертацию пока не делает. В текущей версии он копирует загруженный `.pt` файл в хранилище с расширением `.ort`, чтобы можно было проверить сценарий загрузки и скачивания end-to-end.
Точка расширения находится в [app/infrastructure/ModelConverterImpl.py](/Users/tanexc/PyCharmMiscProject/convert-and-update-model-server/app/infrastructure/ModelConverterImpl.py).
## Примеры запросов
Загрузка модели:
```bash
curl -X POST "http://127.0.0.1:8000/models" \
-H "X-API-Key: local-dev-key" \
-F "version=1.0.3" \
-F "file=@./example.pt"
```
Получение метаданных:
```bash
curl -H "X-API-Key: local-dev-key" \
"http://127.0.0.1:8000/models/latest"
```
Скачивание последней модели:
```bash
curl -OJ -H "X-API-Key: local-dev-key" \
"http://127.0.0.1:8000/models/latest/download?version=1.0.3&filename=model_v1.0.3.ort"
```
## Docker
Сборка:
```bash
docker build -t model-update-server .
```
Сгенерировать ключи в `.env`:
```bash
scripts/generate_api_keys.sh --env-file .env
```
Запуск через env-файл:
```bash
docker run --rm -p 8000:8000 \
--env-file .env \
-v "$(pwd)/storage/models:/app/storage/models" \
-v "$(pwd)/db:/app/db" \
model-update-server
```
Файл `.env` содержит реальные ключи и не должен попадать в репозиторий. Шаблон без секретов находится в `.env.example`.
## systemd
Пример unit-файла находится в `deploy/systemd/model-update-server.service`.
Он читает переменные окружения из `/etc/model-update-server/model-update-server.env`.
Установка env-файла с ключами:
```bash
sudo mkdir -p /etc/model-update-server
scripts/generate_api_keys.sh --env-file /tmp/model-update-server.env
sudo install -m 600 /tmp/model-update-server.env /etc/model-update-server/model-update-server.env
rm /tmp/model-update-server.env
```
Установка unit-файла:
```bash
sudo cp deploy/systemd/model-update-server.service /etc/systemd/system/model-update-server.service
sudo systemctl daemon-reload
sudo systemctl enable --now model-update-server
```
## CI
В CI храните `API_KEYS` как secret/protected variable и передавайте её в окружение сборки или деплоя.
Не генерируйте новые ключи при каждом деплое, если старые клиенты должны продолжать работать.
Пример для GitHub Actions:
```yaml
env:
API_KEYS: ${{ secrets.API_KEYS }}
```
Пример для GitLab CI:
```yaml
deploy:
script:
- docker run --env API_KEYS="$API_KEYS" model-update-server
```
## Структура данных
Таблица `models`:
- `id`
- `version`
- `filename`
- `file_path`
- `created_at`
- `is_latest`