Add gitignore, proxy-aware base URL and concise README
build-and-push / build (push) Canceled after 0s

This commit is contained in:
Repinoid
2026-10-23 19:14:52 +03:00
parent a1dc34a90c
commit f4a390b5fe
4 changed files with 97 additions and 194 deletions
+19 -190
View File
@@ -1,206 +1,35 @@
# liberta — HTTP-сервис конвертации документов Word
# liberta — конвертер документов Word
Сервис принимает устаревшие документы Microsoft Word (`.doc`, а также `.rtf` и `.odt`)
и возвращает современный `.docx`. Конвертация выполняется **LibreOffice в headless-режиме**
внутри контейнера; исходный файл на диске не сохраняется.
HTTP-сервис: принимает `.doc`, `.rtf`, `.odt` и возвращает `.docx`.
Конвертация — LibreOffice headless в контейнере; входные файлы не сохраняются.
- **Публичный адрес (текущий стенд):** `https://liberta.containerk8s.dev.nubes.ru/`
- **Имя образа:** `gitea.services.ngcloud.ru/nail/contractor-convert:latest`
- **Репозиторий:** `https://gitea.services.ngcloud.ru/forcloud/liberta.git`
- **Версия приложения:** `1.0.0` (константа `VERSION` в `app.py`)
Стек: Python 3.12, Flask, LibreOffice.
## Содержание
## Запуск
- [Состав репозитория](#состав-репозитория)
- [Как работает](#как-работает)
- [API](#api)
- [Запуск локально](#запуск-локально)
- [Сборка и запуск в Docker](#сборка-и-запуск-в-docker)
- [Развёртывание на платформе (Штурвал)](#развёртывание-на-платформе-штурвал)
- [Использование из Obfuscator / DrHider](#использование-из-obfuscator--drhider)
- [Ограничения и известные особенности](#ограничения-и-известные-особенности)
- [Проверка работоспособности](#проверка-работоспособности)
## Состав репозитория
```text
.
├── app.py Flask-приложение: UI, /convert, /health, /static/logo.svg
├── Dockerfile python:3.12 + libreoffice, EXPOSE 5000, CMD python3 app.py
└── requirements.txt flask
```bash
docker build -t liberta:1.0.2 .
docker run --rm -p 5000:5000 liberta:1.0.2
curl -s http://localhost:5000/health
# {"ok":true,"version":"1.0.2"}
```
Никаких других зависимостей, конфигов, БД или внешних сервисов нет.
## Как работает
1. Клиент отправляет `POST /convert` с файлом в поле `file` (`multipart/form-data`).
2. Сервер сохраняет входной файл во временный файл с суффиксом `.doc`.
3. Вызывается LibreOffice:
```text
libreoffice --headless --convert-to docx --outdir <tmpdir> <input>
```
Таймаут вызова — **120 секунд**. `check=True`: ненулевой код выхода считается ошибкой.
4. Из временного каталога берётся первый `.docx`; если его нет — ошибка `conversion produced no .docx`.
5. Файл отдаётся клиенту как вложение (`Content-Disposition: attachment`) с MIME
`application/vnd.openxmlformats-officedocument.wordprocessingml.document`.
6. В `finally` удаляются входной файл, временный каталог и всё его содержимое —
**документы не сохраняются на сервере**.
Дополнительно на `GET /` отдаётся встроенная HTML-страница с формой конвертации
(тот же `/convert` вызывается из браузера через `fetch`).
Без Docker: `pip install -r requirements.txt && python3 app.py`
(нужен LibreOffice в `PATH`).
## API
### `POST /convert`
Конвертирует один файл в `.docx`.
| Параметр | Значение |
| --- | --- |
| Метод | `POST` |
| Content-Type | `multipart/form-data` |
| Поле файла | `file` |
| Поддерживаемые входы | `.doc`, `.rtf`, `.odt` (фактически — всё, что умеет LibreOffice) |
| Успех | `200`, тело — бинарный `.docx`, `Content-Disposition: attachment` |
| Ошибка | `400`/`500`, JSON `{"ok": false, "error": "..."}` |
Коды ошибок:
| Ситуация | Статус | Тело |
| Метод | Путь | Назначение |
| --- | --- | --- |
| Нет поля `file` | `400` | `{"ok": false, "error": "no file"}` |
| Пустое имя файла | `400` | `{"ok": false, "error": "empty filename"}` |
| Превышен таймаут LibreOffice (120 с) | `500` | `{"ok": false, "error": "conversion timeout"}` |
| LibreOffice завершился с ошибкой | `500` | `{"ok": false, "error": "libreoffice: <stderr>"}` |
| `.docx` не создан | `500` | `{"ok": false, "error": "conversion produced no .docx"}` |
| `POST` | `/convert` | `multipart/form-data`, поле `file` → бинарный `.docx` |
| `GET` | `/health` | `{"ok": true, "version": "..."}` |
| `GET` | `/` | HTML-форма конвертации |
Пример:
Ошибки — `400`/`500` с JSON `{"ok": false, "error": "..."}`.
```bash
curl -X POST -F "file=@документ.doc" \
https://liberta.containerk8s.dev.nubes.ru/convert \
-o результат.docx
http://localhost:5000/convert -o результат.docx
```
### `GET /health`
Проверка живости и версии:
```json
{"ok": true, "version": "1.0.0"}
```
Используется платформой и внешними клиентами для liveness/readiness-проверок.
### `GET /`
HTML-интерфейс: логотип, форма загрузки `.doc/.rtf/.odt`, кнопка «Конвертировать»,
краткая справка по API и пример `curl`.
### `GET /static/logo.svg`
Отдаёт встроенный SVG-логотип (data URI в коде страницы, отдельный файл не требуется).
## Запуск локально
Требования:
- Python 3.12+ и Flask (`pip install -r requirements.txt`);
- установленный **LibreOffice** в `PATH` (без него конвертация завершится ошибкой).
```bash
cd liberta
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python3 app.py
```
Приложение слушает `0.0.0.0:5000`.
## Сборка и запуск в Docker
`Dockerfile` ставит LibreOffice и Flask; других слоёв нет.
```bash
docker build -t contractor-convert:latest .
docker run --rm -p 5000:5000 contractor-convert:latest
```
Проверка:
```bash
curl -s http://localhost:5000/health
# {"ok":true,"version":"1.0.0"}
```
## Развёртывание на платформе (Штурвал)
Текущий стенд: имя экземпляра `liberta`, реалм `iot-naeel`, домен `liberta`,
образ `gitea.services.ngcloud.ru/nail/contractor-convert:latest`.
Параметры, которые нужно учесть при переносе на другой стенд:
1. **Порт жёстко задан `5000`** (`app.run(host="0.0.0.0", port=5000)`).
Переменная окружения `PORT` приложением **не читается** — стенд должен
использовать/пробросить порт `5000` либо потребуется правка кода.
2. **Запускать только как скрипт** — `python3 app.py` (так задано в `CMD`).
`io` импортируется внутри блока `if __name__ == "__main__"`, а `/convert`
использует `io.BytesIO`. При запуске через `gunicorn`/`flask run` (импорт модуля)
`/convert` упадёт с `NameError`.
3. **Ресурсы.** LibreOffice требователен к CPU и памяти: в текущем стенде
`CPU=500m`, `memory=1024 MiB`. Меньшие лимиты или параллельные запросы
приводят к таймаутам/падениям. Запросы к сервису следует выполнять
последовательно (Obfuscator так и делает).
4. **`/tmp`** должен быть доступен для записи и иметь достаточный размер
(входной `.doc`, временный каталог, `.docx`).
5. **Сеть/DNS/TLS** — домен, сертификат и ingress настраиваются в стенде,
в образе их нет.
6. **Тег `latest` мутабелен** — для воспроизводимости фиксируйте digest
или версионированный тег.
7. **Healthcheck** в `Dockerfile` отсутствует, но есть `GET /health` —
probe настраивается на стенде.
## Использование из Obfuscator / DrHider
`drhider/extractor.py::doc_to_markdown()` отправляет `.doc` на этот сервис:
- URL берётся из переменной окружения `CONVERT_SERVICE_URL`
(дефолт в коде — `https://liberta.containerk8s.dev.nubes.ru/convert`);
- запрос: `POST`, `multipart/form-data`, поле `file`, имя файла `input.doc`;
- ожидается ответ `200` с телом `.docx`; далее документ обрабатывается штатным
`docx_to_markdown()`;
- вызовы выполняются **последовательно**: сервис не рассчитан на конкуренцию.
⚠️ Дефолт `CONVERT_SERVICE_URL` указывает на dev-домен
(`containerk8s.dev.nubes.ru`). В продакшене переменную нужно задать явно,
иначе прод обратится к dev-сервису.
## Ограничения и известные особенности
- **Нет аутентификации и rate-limit** — любой, кто знает URL, может конвертировать
файлы и нагружать CPU. Для публичного использования нужен внешний контроль
(ingress-auth / сетевые ограничения).
- **Нет CORS-заголовков** — вызовы из браузера с другого origin заблокируются
(Obfuscator вызывает сервис с бэкенда, CORS не нужен).
- **Один файл за запрос** — пакетная конвертация не поддерживается.
- **Нет кэширования** — каждый запрос запускает новый процесс LibreOffice.
- **Временные файлы** создаются в системном `/tmp`; при аварийном завершении
процесса теоретически могут остаться (штатный путь удаляет их в `finally`).
- **Версия в UI/health** берётся из константы `VERSION` в `app.py` — при изменении
кода поднимайте её.
## Проверка работоспособности
Проверено 2026-10-23 на `https://liberta.containerk8s.dev.nubes.ru`:
| Запрос | Ответ |
| --- | --- |
| `GET /` | `200`, HTML «Конвертер документов — Nubes» |
| `OPTIONS /convert` | `200`, `Allow: POST, OPTIONS` |
| `GET /health` | `200`, `{"ok":true,"version":"1.0.0"}` |
Версия `1.0.0` совпадает с константой в `app.py`, что подтверждает соответствие
развёрнутого сервиса данному репозиторию.
Один файл за запрос; таймаут — 120 с; порт фиксирован (`5000`).