Документация: README.md, ARCHITECTURE.md, DEPLOY.md, .github/copilot-instructions.md
Deploy drhider / validate (push) Waiting to run

This commit is contained in:
2026-07-12 08:37:51 +04:00
parent 7a005f7b06
commit 0707d53b37
4 changed files with 591 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
# ⛔ ПРАВИЛА ДЛЯ COPILOT — ПРОЕКТ DRHIDER
Читать перед ЛЮБЫМ действием. Нарушение = зря потраченное время.
---
## ⛔⛔⛔ ЗОЛОТОЕ ПРАВИЛО
**НИКОГДА НИЧЕГО НЕ ДЕЛАТЬ БЕЗ ЯВНОЙ КОМАНДЫ «ДЕЛАЙ».**
Любой вопрос, обсуждение, анализ — НЕ повод менять код.
Только после явного: «делай», «делайте», «давай», «приступай», «пушить», «commit», «push».
---
## ⛔⛔⛔ НЕ МЕНЯТЬ КОД БЕЗ «ДЕЛАЙ»
Даже если ошибка очевидна. Даже если «исправление в одну строку».
**Показать проблему → описать решение → ЖДАТЬ «делай».**
---
## ⛔⛔⛔ НЕ СМОТРЕТЬ ЛОКАЛЬНЫЕ ПАПКИ CONTRACTS
Весь старый код drhider — ТОЛЬКО на ВМ (`5.172.178.213` через SSH).
**НЕ лезть** в `/home/naeel/nubes/contracts/`, `contracts-flask/` и т.д.
Единственное исключение: `/home/naeel/nubes/loadtest/` (образец managed Flask).
---
## ⛔⛔⛔ НЕ СПЕШИТЬ
Сначала ВСЁ обдумать. Проверить. Перепроверить. Только потом отвечать.
Спешка → ошибки → проблемы.
---
## ⛔⛔⛔ НЕ ПРЕДПОЛАГАТЬ, НЕ ДОГАДЫВАТЬСЯ
Есть сомнения — **ОСТАНОВИТЬСЯ И СПРОСИТЬ**.
Не надо самостоятельно «улучшать» рабочий код без прямого разрешения.
---
## ⛔⛔⛔ КОМАНДЫ В ТЕРМИНАЛЕ — С ТАЙМАУТАМИ
- `curl`: `--max-time N`
- `ssh`: `-o ConnectTimeout=N`
- `grep` на больших файлах: `timeout N grep ...`
---
## ✅ ПОСЛЕ КАЖДОЙ ПРАВКИ
1. **Проверить синтаксис:**
```bash
python3 -c "import py_compile; py_compile.compile('файл.py', doraise=True)"
```
2. **Проверить импорт работает:**
```bash
cd /home/naeel/nubes/drhider && python3 -c "from drhider import obfuscate_files, LLMClient"
```
3. **Проверить соседние функции не затёрты** (после replace_string_in_file)
4. **СРАЗУ commit + push** с подробным сообщением
---
## ✅ КОММИТИТЬ + ПУШИТЬ ПОСЛЕ КАЖДОЙ ПРАВКИ
Без исключений. Никаких «потом», никаких накоплений.
```bash
git add -A
git commit -m "подробное описание что сделано"
git push
```
---
## ✅ ДОКУМЕНТИРОВАТЬ ВСЁ В History/
Каждое изменение, план, ошибку — в `/home/naeel/nubes/drhider/History/`:
- Отдельный `.md` файл на каждую сессию/задачу
- Что планировалось → что сделано → в чём ошибся
---
## 📋 ПРОЕКТ DRHIDER — КЛЮЧЕВЫЕ ФАКТЫ
| Факт | Значение |
|---|---|
| Платформа | **Штурвал** (Managed Flask на pythonk8s) |
| URL | https://drhider.pythonk8s.dev.nubes.ru/ |
| Репозиторий | https://gitea.services.ngcloud.ru/Nail/drhider |
| Точка входа | `site/app.py` → `app = create_app()` |
| БД | **НЕТ** (всё в памяти) |
| Dockerfile | **НЕТ** (Штурвал сам запускает Flask) |
| gunicorn | **НЕТ** (Штурвал сам) |
| Зависимости | `flask`, `python-docx`, `pdfplumber`, `httpx` |
| ВМ (legacy) | `5.172.178.213`, ssh-ключ `~/.ssh/naeel_vm_id_ed25519` |
### Переменные окружения
| Переменная | Значение |
|---|---|
| `LLM_API_KEY` | `sk-ucI5YvOticoOQ9Kuj5K9mQ` |
| `LLM_URL` | `https://api.aillm.ru/v1/chat/completions` |
| `LLM_MODEL` | `gpt-oss-120b` |
---
## ⛔ ЗАПРЕЩЕНО
1. Менять код без «делай»
2. Смотреть локальные папки contracts/
3. Действовать по догадкам («я бы сделал так», «можно попробовать»)
4. Удалять файлы/папки без разрешения
5. Использовать sed
6. Откладывать commit/push «на потом»
7. Игнорировать проверку синтаксиса перед push
+191
View File
@@ -0,0 +1,191 @@
# Архитектура DrHider
## Общая схема
```
Пользователь (браузер)
https://drhider.pythonk8s.dev.nubes.ru/
Штурвал (Managed Flask на pythonk8s)
site/app.py ──→ Flask (create_app)
├── GET / → main_bp → templates/index.html
├── GET /health → health_bp → {"ok": true}
└── POST /api/drhider → api_bp → drhider.obfuscate_files()
TwoPassObfuscator
┌────────────┼────────────┐
│ │ │
extractor scanner replacer
(текст) (regex+LLM) (замена)
│ │ │
└────────────┼────────────┘
builder
(ZIP+CSV)
application/zip
```
---
## Двухпроходная обфускация
### Проход 1: сбор сущностей
```
Файлы (.docx, .pdf, .txt, .zip)
extractor.expand_zips() ← распаковать ZIP
extractor.convert_pdfs_to_docx() ← PDF → DOCX
extractor.extract_text() ← извлечь текст из каждого файла
├──→ scanner.scan_regex() ← regex: телефоны, email, ИНН, ОГРН, КПП,
│ БИК, счета, паспорта, компании, ФИО
└──→ scanner.scan_llm_ner() ← LLM: имена, адреса, паспорта
(только если llm_client передан)
mapping = {оригинал → фиктивное} ← глобальный словарь замен
```
### Проход 2: замена
```
mapping + sorted_keys (по убыванию длины)
Для каждого файла:
├── .docx → replacer.replace_in_docx() ← склеить runs → заменить → сохранить
├── .doc → без изменений (бинарный)
└── .txt/.pdf → replacer.replace_in_text() ← простая строковая замена
builder.build_mapping_csv() ← mapping.csv
builder.build_zip() ← ZIP со всеми файлами + mapping.csv
```
---
## Модули
### `drhider/config.py`
Константы уровня всего пакета. НЕ содержит логики — только данные.
- `ENTITY_PATTERNS` — 10 regex-паттернов
- `COMPANY_PATTERN`, `PERSON_PATTERN` — скомпилированные regex
- Словари: `RU_SURNAMES`, `RU_NAMES`, `RU_PATRONYMICS`, `RU_CITIES`, `RU_STREETS`, `FAKE_DOMAINS`
### `drhider/checksum.py`
Чистые функции для расчёта контрольных сумм. Без побочных эффектов.
- `checksum_inn10(inn)` → 1 цифра
- `checksum_inn12(inn)` → 2 цифры
- `checksum_ogrn(ogrn)` → 1 цифра
### `drhider/random_utils.py`
Утилиты генерации случайных строк.
- `random_digits(n)` → строка из n цифр
- `random_letters(n)` → строка из n строчных латинских букв
### `drhider/generators/`
Каждый генератор — независимая функция с сигнатурой `generate_xxx(original: str) -> str`.
Параметр `original` нужен для извлечения префикса (например, «ИНН » из «ИНН 1234567890»).
Генераторы НЕ общаются друг с другом — только импортируют из `config`, `checksum`, `random_utils`.
`__init__.py` содержит `ENTITY_GENERATORS` — словарь, связывающий `entity_type` из `ENTITY_PATTERNS` с функцией-генератором. Используется в `scanner.scan_regex()`.
### `drhider/llm_client.py`
HTTP-клиент к OpenAI-совместимому API. Читает переменные окружения.
Интерфейс: `.complete(prompt: str) -> str`.
### `drhider/extractor.py`
Три независимые функции (не класс):
- `extract_text(fname, content, ctype)``(text, docx_document_or_None)`
- `expand_zips(files)` → распакованный список
- `convert_pdfs_to_docx(files)` → PDF заменены на DOCX
### `drhider/scanner.py`
Две функции (не класс), мутируют переданный `mapping: Dict[str, str]`:
- `scan_regex(text, mapping)` — regex-поиск
- `scan_llm_ner(all_texts, mapping, llm_client)` — LLM NER
### `drhider/replacer.py`
Три чистые функции:
- `apply_replacements(text, mapping, sorted_keys)` → строка с заменами
- `replace_in_docx(doc, mapping, sorted_keys)` → bytes (изменённый DOCX)
- `replace_in_text(text, fname, mapping, sorted_keys)` → bytes
### `drhider/builder.py`
Две функции сборки результата:
- `build_zip(files, mapping_csv)` → bytes (ZIP-архив)
- `build_mapping_csv(mapping)` → str (CSV)
### `drhider/obfuscator.py`
`TwoPassObfuscator` — оркестратор. Единственный класс, который знает о顺序е вызовов.
Метод `.obfuscate(files)` вызывает модули в правильном порядке.
`obfuscate_files()` — удобная функция-обёртка.
### `site/app.py`
Точка входа для Штурвала. `create_app()` создаёт Flask, регистрирует blueprint'ы.
`app = create_app()` — глобальный экземпляр.
### `site/routes/`
Blueprint'ы — каждый в своём файле:
- `main_bp` — только `GET /`
- `health_bp` — только `GET /health`
- `api_bp` — только `POST /api/drhider`
`__init__.py` содержит `register_routes(app)` — единая точка регистрации.
---
## Поток данных
```
HTTP POST /api/drhider (multipart/form-data)
api_bp.drhider()
│ request.files.getlist("files")
│ → [(filename, bytes, mimetype), ...]
obfuscate_files(files, llm_client=LLMClient())
TwoPassObfuscator.obfuscate(files)
├── expand_zips → convert_pdfs_to_docx → extract_text
├── scan_regex + scan_llm_ner → mapping
├── replace_in_docx / replace_in_text → обфусцированные файлы
└── build_mapping_csv + build_zip → (zip_bytes, csv_str)
send_file(BytesIO(zip_data), mimetype="application/zip")
HTTP 200 + ZIP-архив
```
---
## Принципы
1. **Модули — чистые функции.** Где возможно — без классов, без состояния.
2. **Единственный класс — TwoPassObfuscator.** Только он управляет состоянием (mapping, sorted_keys).
3. **Генераторы — изолированы.** Каждый в своём файле, не зависят друг от друга.
4. **Flask — тонкая прослойка.** Только принимает запрос, вызывает `obfuscate_files()`, отдаёт ответ.
5. **Без БД.** Всё в памяти. Никаких внешних зависимостей кроме LLM API.
+139
View File
@@ -0,0 +1,139 @@
# Деплой, ВМ и переменные окружения
---
## Платформа
**Штурвал** — Managed Flask на pythonk8s (Kubernetes).
- Запускает Flask **сам** — без Dockerfile, без gunicorn
- Точка входа: `site/app.py` → глобальная переменная `app`
- Деплой: `git push origin master` → авто-деплой через UI Штурвала
---
## Деплой
```bash
cd /home/naeel/nubes/drhider
git add -A
git commit -m "описание изменений"
git push origin master
```
После пуша — зайти в UI Штурвала и нажать ** Redeploy** для `drhider`.
---
## Переменные окружения (настраиваются в UI Штурвала)
| Переменная | Значение | Для чего |
|---|---|---|
| `LLM_API_KEY` | `sk-ucI5YvOticoOQ9Kuj5K9mQ` | Ключ доступа к LLM API |
| `LLM_URL` | `https://api.aillm.ru/v1/chat/completions` | URL LLM API |
| `LLM_MODEL` | `gpt-oss-120b` | Модель для NER |
Без `LLM_API_KEY` LLM-сканирование не работает (только regex).
---
## ВМ contracts (legacy)
Старый сервер contracts.kube5s.ru. DrHider там — standalone HTTP-сервер на порту 8767.
### SSH
```bash
ssh -i ~/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213
```
### Где лежит старый drhider
```
/home/naeel/contracts/drhider/
├── drhider.py # Ядро обфускации (отсюда скопировано в этот проект)
├── drhider_server.py # HTTP-сервер :8767 (НЕ ИСПОЛЬЗУЕТСЯ в новом проекте)
└── __init__.py
```
### Сервисы на ВМ
| Сервис | Порт | systemd unit |
|---|---|---|
| drhider | 8767 | `contracts-drhider` |
| convert_server | 8766 | `contracts` |
| Flask UI | 5001 | — (start.sh) |
| nginx | 443 | `nginx` |
| PostgreSQL | 5432 | — |
### Управление drhider на ВМ
```bash
# Статус
systemctl status contracts-drhider
# Логи
journalctl -u contracts-drhider -f
# Перезапуск
sudo systemctl restart contracts-drhider
```
---
## Локальная разработка
```bash
cd /home/naeel/nubes/drhider
# Установка зависимостей
pip install -r requirements.txt
# Запуск Flask (dev-сервер)
cd site && python3 app.py
# Проверка синтаксиса ВСЕХ файлов
python3 -c "
import py_compile, os
for root, dirs, files in os.walk('.'):
for f in files:
if f.endswith('.py'):
path = os.path.join(root, f)
py_compile.compile(path, doraise=True)
print(f'OK: {path}')
"
# Проверка импорта
python3 -c "from drhider import obfuscate_files, LLMClient; print('OK')"
```
---
## Как править код
1. Понять что меняем → **описать план**
2. Получить **«делай»**
3. Внести изменения
4. `python3 -c "import py_compile; py_compile.compile('файл.py', doraise=True)"` — КАЖДЫЙ изменённый .py
5. `python3 -c "from drhider import obfuscate_files, LLMClient"` — проверка импорта
6. `git add -A && git commit -m "подробно что и зачем" && git push`
7. Записать в `History/` что сделано
8. Зайти в UI Штурвала → Redeploy
---
## Как добавить новый генератор
1. Создать `drhider/generators/новый_тип.py` с функцией `generate_xxx(original: str) -> str`
2. Добавить regex-паттерн в `drhider/config.py``ENTITY_PATTERNS`
3. Добавить импорт и маппинг в `drhider/generators/__init__.py``ENTITY_GENERATORS`
4. Проверить синтаксис → commit → push → Redeploy
---
## История версий
| Версия | Дата | Что |
|---|---|---|
| 2.0.0 | 2026-07-12 | Модульный рефакторинг (22 модуля вместо монолита) |
| 1.0.0 | 2026-07-11 | Начальная версия (monolith drhider.py + Flask) |
+141
View File
@@ -0,0 +1,141 @@
# DrHider — обфускация документов (Managed Flask)
**URL:** https://drhider.pythonk8s.dev.nubes.ru/
**Репозиторий:** https://gitea.services.ngcloud.ru/Nail/drhider
**Платформа:** Штурвал (Managed Flask на pythonk8s)
**Дизайн фронтенда:** `/home/naeel/nubes/design/`
---
## Что делает
Загружаешь документы (.docx, .pdf, .txt, .zip) — получаешь ZIP с обфусцированными копиями.
Все персональные данные, реквизиты, телефоны, email заменяются на фиктивные.
В архиве — mapping.csv с таблицей замен (оригинал → фиктивное).
Обфускация двухпроходная:
1. **Проход 1 (сбор):** regex + LLM находят все сущности во всех файлах → глобальный словарь замен
2. **Проход 2 (замена):** применяем замены ко всем файлам → ZIP
Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение.
---
## ⛔ ПРАВИЛА ДЛЯ АГЕНТОВ (читать перед ЛЮБЫМ действием)
### 1. НЕ смотреть локальные папки contracts/contracts-flask
Весь старый код drhider — ТОЛЬКО на ВМ (`5.172.178.213`). НЕ локально.
Единственное исключение: `/home/naeel/nubes/loadtest/` (образец структуры managed Flask).
### 2. Штурвал запускает Flask САМ
- БЕЗ Dockerfile (удалён)
- БЕЗ gunicorn (нет в requirements.txt)
- Точка входа: `site/app.py``app = create_app()`
- Деплой: `git push origin master` → авто-деплой в UI Штурвала
### 3. НЕ менять код без «делай»
Даже если ошибка очевидна. Показать → описать → ЖДАТЬ.
### 4. После ЛЮБОЙ правки:
```bash
python3 -c "import py_compile; py_compile.compile('файл.py', doraise=True)"
git add -A && git commit -m "подробное описание" && git push
```
### 5. Документировать ВСЁ в History/
Каждое изменение, план, ошибку — в отдельный .md файл.
---
## Структура проекта
### `drhider/` — пакет ядра обфускации
| Файл | Назначение | Строк |
|---|---|---|
| `__init__.py` | re-export: `obfuscate_files`, `LLMClient`, `TwoPassObfuscator` | 10 |
| `config.py` | Константы: regex-паттерны (ENTITY_PATTERNS, COMPANY_PATTERN, PERSON_PATTERN), словари имён/городов/улиц | 80 |
| `checksum.py` | Контрольные суммы: ИНН10, ИНН12, ОГРН | 60 |
| `random_utils.py` | Утилиты: `random_digits`, `random_letters` | 20 |
| `llm_client.py` | LLMClient — HTTP-клиент к LLM API (aillm.ru) | 50 |
| `extractor.py` | Извлечение текста из .docx/.pdf/.txt + `expand_zips` + `convert_pdfs_to_docx` | 180 |
| `scanner.py` | Проход 1: `scan_regex` (regex) + `scan_llm_ner` (LLM NER) | 110 |
| `replacer.py` | Проход 2: `apply_replacements`, `replace_in_docx`, `replace_in_text` | 100 |
| `builder.py` | Сборка: `build_zip`, `build_mapping_csv` | 65 |
| `obfuscator.py` | `TwoPassObfuscator` — оркестратор + `obfuscate_files()` | 100 |
### `drhider/generators/` — генераторы фиктивных значений
Каждый файл — один генератор (10–40 строк). Интерфейс: `generate_xxx(original: str) -> str`.
| Файл | Что генерирует |
|---|---|
| `phone.py` | +7 (XXX) XXX-XX-XX |
| `email.py` | user@fake-domain |
| `inn.py` | ИНН 10 и 12 знаков (с контрольной суммой) |
| `ogrn.py` | ОГРН 13 знаков |
| `kpp.py` | КПП 9 знаков |
| `bik.py` | БИК 9 знаков (04XXXXXXX) |
| `accounts.py` | Расчётный счёт (40702...) + корр. счёт (30101...) |
| `passport.py` | Паспорт (XX XX XXXXXX) |
| `company.py` | ООО/ЗАО/АО «СлучайноеНазвание» |
| `person.py` | Фамилия И.О. |
| `address.py` | Город, ул. Улица, д. N |
`__init__.py` содержит `ENTITY_GENERATORS` — маппинг `entity_type → генератор`.
### `site/` — Flask-приложение
| Файл | Назначение |
|---|---|
| `app.py` | `create_app()` — создание Flask, регистрация blueprint'ов, VERSION |
| `routes/__init__.py` | `register_routes(app)` |
| `routes/main_bp.py` | `GET /` — HTML-интерфейс |
| `routes/health_bp.py` | `GET /health``{"ok": true, "version": "..."}` |
| `routes/api_bp.py` | `POST /api/drhider` — multipart files → ZIP |
| `templates/index.html` | UI (выбор файлов, прогресс, скачивание) |
| `static/favicon.svg` | Иконка |
### Корневые файлы
| Файл | Назначение |
|---|---|
| `requirements.txt` | `flask`, `python-docx`, `pdfplumber`, `httpx` |
| `.gitignore` | `__pycache__/`, `*.pyc`, `.env` |
| `.gitea/workflows/deploy.yaml` | CI: валидация Python |
| `History/` | Документация всех изменений |
| `README.md` | Этот файл |
| `ARCHITECTURE.md` | Архитектура |
| `DEPLOY.md` | Деплой, ВМ, переменные окружения |
---
## API
| Метод | Путь | Что делает |
|---|---|---|
| `GET` | `/` | HTML-интерфейс |
| `GET` | `/health` | `{"ok": true, "version": "2.0.0"}` |
| `POST` | `/api/drhider` | multipart/form-data (поле `files`) → `application/zip` |
---
## Переменные окружения (Штурвал)
| Переменная | Значение |
|---|---|
| `LLM_API_KEY` | `sk-ucI5YvOticoOQ9Kuj5K9mQ` |
| `LLM_URL` | `https://api.aillm.ru/v1/chat/completions` |
| `LLM_MODEL` | `gpt-oss-120b` |
---
## Связь с ВМ contracts (legacy)
На ВМ `5.172.178.213` (contracts.kube5s.ru) есть старая версия drhider:
- `drhider_server.py` на порту 8767 — standalone HTTP-сервер
- `drhider.py` — та же логика, что и в этом проекте (скопирована оттуда)
Этот проект (`drhider.pythonk8s.dev.nubes.ru`) — **независимый managed Flask**, заменяет ВМ-версию.
SSH к ВМ: `ssh -i ~/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213`