This commit is contained in:
@@ -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
@@ -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) |
|
||||
@@ -0,0 +1,104 @@
|
||||
# Порядок действий при изменении кода
|
||||
|
||||
**Читать перед ЛЮБОЙ правкой.**
|
||||
|
||||
---
|
||||
|
||||
## ⛔ ЗАПРЕЩЕНО
|
||||
|
||||
1. Менять код без «делай»
|
||||
2. Действовать по догадкам («я бы сделал так», «можно попробовать»)
|
||||
3. Удалять файлы/папки без разрешения
|
||||
4. «Улучшать» рабочий код без прямого разрешения
|
||||
5. Использовать `sed`
|
||||
6. Откладывать commit/push «на потом»
|
||||
7. Игнорировать проверку синтаксиса перед push
|
||||
|
||||
---
|
||||
|
||||
## ✅ ПОРЯДОК ПРИ ЛЮБОМ ИЗМЕНЕНИИ
|
||||
|
||||
### Шаг 1 — План
|
||||
```
|
||||
Описать что меняем → получить «делай»
|
||||
```
|
||||
|
||||
### Шаг 2 — Правка
|
||||
Вносим изменения. Только то, что обговорено. Никакой самодеятельности.
|
||||
|
||||
### Шаг 3 — Проверка синтаксиса
|
||||
```bash
|
||||
python3 -c "import py_compile; py_compile.compile('путь/к/файлу.py', doraise=True)"
|
||||
```
|
||||
**КАЖДЫЙ изменённый .py файл.**
|
||||
|
||||
### Шаг 4 — Проверка импорта
|
||||
```bash
|
||||
cd /home/naeel/nubes/drhider && python3 -c "from drhider import obfuscate_files, LLMClient; print('OK')"
|
||||
```
|
||||
|
||||
### Шаг 5 — Проверить соседей
|
||||
После `replace_string_in_file` — прочитать соседние строки, убедиться что не затёр соседнюю функцию/класс.
|
||||
|
||||
### Шаг 6 — commit + push (СРАЗУ)
|
||||
```bash
|
||||
cd /home/naeel/nubes/drhider
|
||||
git add -A
|
||||
git commit -m "подробное описание что и зачем"
|
||||
git push
|
||||
```
|
||||
**Без исключений. Никаких накоплений.**
|
||||
|
||||
### Шаг 7 — Документировать
|
||||
Записать в `History/` что сделано:
|
||||
- Отдельный `.md` файл
|
||||
- Что планировалось → что сделано → в чём ошибся
|
||||
|
||||
### Шаг 8 — Redeploy
|
||||
Зайти в UI Штурвала → Redeploy `drhider`.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Команды в терминале — всегда с таймаутами
|
||||
|
||||
```bash
|
||||
curl --max-time 30 ...
|
||||
ssh -o ConnectTimeout=10 ...
|
||||
timeout 10 grep ... file.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Проверка ВСЕХ файлов разом
|
||||
|
||||
```bash
|
||||
cd /home/naeel/nubes/drhider
|
||||
python3 -c "
|
||||
import py_compile, os
|
||||
for root, dirs, files in os.walk('.'):
|
||||
for f in files:
|
||||
if f.endswith('.py'):
|
||||
py_compile.compile(os.path.join(root, f), doraise=True)
|
||||
print(f'OK: {os.path.join(root, f)}')
|
||||
"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ Версионирование
|
||||
|
||||
Версия в `site/app.py` (переменная `VERSION`).
|
||||
Менять при любом изменении кода, влияющем на поведение.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Пример хорошего коммита
|
||||
|
||||
```
|
||||
git commit -m "generators: добавил generate_snils — генератор фиктивных СНИЛС
|
||||
|
||||
- Новый файл drhider/generators/snils.py
|
||||
- Добавлен паттерн в config.py → ENTITY_PATTERNS
|
||||
- Добавлен в generators/__init__.py → ENTITY_GENERATORS
|
||||
- bump VERSION 2.0.0 → 2.0.1"
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
НЕ СПЕШИ ! не ошибайся. НЕ ПРЕДПОЛАГАЙ !
|
||||
не надо ДОГАДОК !
|
||||
не надо самостоятельно что либо "УЛуЧШАТЬ" - никаких изменений рабочего кода без прямого разрешения
|
||||
есть сомнения - лучше остановись и спроси
|
||||
ВСЁ записывай в хистори ! что планировал что сделал в чём ошибся и тд
|
||||
Reference in New Issue
Block a user