diff --git a/.github/CODERULES.md b/.github/CODERULES.md deleted file mode 100644 index a68c3c0..0000000 --- a/.github/CODERULES.md +++ /dev/null @@ -1,24 +0,0 @@ -# ⛔ ЖЁСТКИЕ ПРАВИЛА -- НЕЛЬЗЯ менять код без «делай» — показать план → ждать -- НЕЛЬЗЯ менять ЧТО-ЛИБО при вопросе (?, почему, как, где, что) — только ответ словами -- НЕЛЬЗЯ спешить — обдумать, проверить, потом отвечать -- НЕЛЬЗЯ лезть в /home/naeel/nubes/contracts/ (кроме loadtest) -- НИКОГДА не руководствоваться догадками. Если что-либо неизвестно точно или недоступно — немедленно остановиться и сообщить об этом; не продолжать действие и не выдавать предположение за факт. -- НЕЛЬЗЯ грузить внешние CDN/библиотеки на фронтенд — всё в коде - -# ✅ ПОРЯДОК ПРАВКИ -1. План → «делай» -2. Правим → проверяем syntax → проверяем import -3. git add -A && git commit -m "..." && git push -4. Bump VERSION в site/app.py перед push - -# 📋 ДОКИ -- Порядок правок: docs/DEVELOPMENT.md -- Архитектура: docs/ARCHITECTURE.md -- Деплой: docs/DEPLOY.md -- История: History/ - -# 📋 ПРОЕКТ -Штурвал Managed Flask, без Dockerfile/gunicorn, без БД -URL: https://drhider.pythonk8s.dev.nubes.ru/ -ВМ legacy: ssh -i ~/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 81f5aca..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,7 +0,0 @@ -# ⛔⛔⛔ STOP: без «делай» ничего не делать -# ⛔⛔⛔ ВОПРОС (?, почему, как, где, что) — ТОЛЬКО ОТВЕТ. Читать/узнавать МОЖНО, менять НЕЛЬЗЯ. -# ⛔ Не спешить. Не лезть в contracts/. Не гадать — спросить. -НИКОГДА не действовать по догадкам !!! -ВСЕГДА всё перепроверять перед действием или ответом !!! -ВСЕГДА КРАТКО без воды отвечать, чётко по делу, без лишних слов. -# 📋 Правила: .github/CODERULES.md diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c9ebf2d..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python-envs.defaultEnvManager": "ms-python.python:system" -} \ No newline at end of file diff --git a/0144-03-2023_отчет об оценке.pdf b/0144-03-2023_отчет об оценке.pdf deleted file mode 100755 index 178d48a..0000000 Binary files a/0144-03-2023_отчет об оценке.pdf and /dev/null differ diff --git a/History/README.md b/History/README.md deleted file mode 100644 index d26e0d2..0000000 --- a/History/README.md +++ /dev/null @@ -1,158 +0,0 @@ -# History — журнал проекта DrHider - -Хронологический журнал: планы, вопросы/ответы ревьюеров (Sonnet, Opus), -реализации фич (по версиям), диагностика, тесты и бенчмарки. - -**Правило:** любая находка/решение/факт записывается сюда сразу, в момент обнаружения. - -## Структура (папки по темам) - -| Папка | Что внутри | -|-------|-----------| -| [base/](base/) | Ранняя документация и первоначальная настройка проекта | -| [plans/](plans/) | Планы и проектирование (аудит, архитектуры v3/v3.1, планы реализации) | -| [sonnet/](sonnet/) | Все вопросы к Sonnet и его ответы (обсуждения, ревью, Q&A) | -| [opus/](opus/) | Запросы к Opus и его ответы (валидация MSS-clamping, ревью документации) | -| [llm/](llm/) | Всё про LLM-часть: метрики, статистика в UI, чанкинг для полного покрытия | -| [sse/](sse/) | SSE-события: отлов отключения клиента, hop-by-hop заголовки | -| [infra/](infra/) | Инфраструктура: кластер, ingress, RST-диагностика, HTTP/2, ВМ-буфер | -| [ux-frontend/](ux-frontend/) | Фичи фронтенда: таймеры, имена файлов, ZIP-раскрытие, лимиты, прерывание/ETA | -| [upload/](upload/) | Загрузка файлов: диагностика, лимиты, стресс-тесты | -| [upload-integration/](upload-integration/) | Интеграция переиспользуемого модуля upload-platform | -| [code-fixes/](code-fixes/) | Исправления кода по версиям (баги, оптимизация, безопасность, логирование) | -| [tests/](tests/) | Тесты и бенчмарки: замеры UI, обфускация, нагрузка | - ---- - -## base/ — первоначальная настройка - -- **2026-07-11-initial-setup.md** — создание проекта DrHider на платформе Штурвал (Managed Flask, URL, входные точки). -- **2026-07-12-documentation.md** — создание полной документации после рефакторинга (список документируемых файлов). - -## plans/ — планы и проектирование - -- **2026-07-12-audit-and-plan.md** — полный аудит проекта и план рефакторинга под Managed Flask. -- **2026-07-12-v3-plan.md** — план v3: универсальная архитектура (убрать жёсткий список типов сущностей). -- **2026-07-12-v3-done.md** — итоги v3: переход на Markdown-пайплайн + универсальное LLM-обнаружение. -- **2026-07-12-v3.1-plan.md** — план v3.1: пофайловая обработка + прогресс в таблице (решение таймаута). -- **2026-08-24-implementation-plan-for-flash.md** — план для агента-кодера (Flash): TTL-фикс, трекинг файлов, прерывание, ETA, UI. -- **2026-08-24-folder-select-plan.md** — план для Flash: кнопка «Выбрать папку» (webkitdirectory, рекурсивно, относительный путь сохраняется). - -## sonnet/ — обсуждения с Sonnet (вопросы и ответы) - -**12–13 июля — проектирование:** -- **2026-07-12-sonnet-query.md** + **2026-07-12-sonnet-response.md** — вопрос/ответ: универсальная архитектура DrHider (6 вопросов → 6 ответов → карта изменений). -- **2026-07-12-sonnet-query-timeout.md** + **2026-07-12-sonnet-response-timeout.md** — диагностика ERR_TIMED_OUT в новом окне браузера (dev-сервер: 6 потоков заняты keep-alive). -- **2026-07-12-sonnet-query-v2.md** + **2026-07-12-sonnet-response-v2.md** — улучшение LLM-промпта (двухфазная инструкция). -- **2026-07-12-sonnet-query-v3-numbering.md** + **2026-07-12-sonnet-response-v3-numbering.md** — нумерация вместо генерации фейков (прозрачный аудит). -- **2026-07-12-sonnet-query-v4-hybrid.md** + **2026-07-12-sonnet-response-v4-hybrid.md** — гибридный формат замен «шаблон + номер» (вариант В). -- **2026-07-13-sonnet-query-architecture.md** + **2026-07-13-sonnet-response-architecture.md** — архитектура пофайловой обработки с одним ZIP (вариант А + блокирующий /process). -- **2026-07-13-sonnet-query-obfuscation-tokens.md** — запрос: анализ обфускации. -- **2026-07-13-sonnet-response-obfuscation-analysis.md** — анализ обфускации (TMP-файлы vs код). -- **2026-07-13-sonnet-response-obfuscation-bugs.md** — анализ багов обфускации и их фикс. - -**20 августа — ревью скорости и сбоев:** -- **2026-08-20-sonnet-query-review-speed.md** — промпт: код-ревью drhider (скорость + объяснение сбоев, ограничение файлов). -- **2026-08-20-sonnet-query-review-speed-followup.md** — follow-up вопросы по спорным пунктам ревью (В1–В4). -- **2026-08-20-sonnet-review-qa.md** — Q&A: вопросы → ответы Sonnet → решения по ревью. - -**20 августа — LLM-чанкинг:** -- **2026-08-20-sonnet-query-llm-pattern.md** — вопрос: как лучше организовать LLM-покрытие всех документов. -- **2026-08-20-sonnet-query-llm-followup.md** — 6 уточнений перед внедрением (чанкинг, дедуп, верификация, промпт). -- **2026-08-20-sonnet-llm-qa.md** — сводный Q&A по LLM-чанкингу (схема + ответы У.1–У.6 + решение). - -**20 августа — ускорение:** -- **2026-08-20-sonnet-query-honest-speed.md** — честный вопрос: можно ли ускорить без риска устойчивости. - -## opus/ — обсуждения с Opus - -- **2026-07-13-opus-query-mss-clamping.md** — запрос: валидация плана MSS Clamping. -- **2026-07-13-opus-response-mss-clamping.md** — ответ: валидация плана MSS Clamping. -- **2026-07-13-opus-response-document-review.md** — проверка PROBLEM-AND-SOLUTION.md на ошибки. -- **2026-07-13-opus-response-final-review.md** — финальная проверка PROBLEM-AND-SOLUTION.md. - -## llm/ — LLM-часть - -- **2026-08-19-llm-metrics.md** — v0.0.34: вывод токенов и времени LLM. -- **2026-08-19-llm-stats-block.md** — v0.0.37: крупный блок статистики LLM в UI. -- **2026-08-20-llm-full-chunking.md** — v0.0.57: LLM обрабатывает ВСЕ данные (пофайловый чанкинг 6000/overlap 500, параллельность 4, дедуп + верификация). - -## sse/ — SSE-события - -- **2026-07-14-sse-generator-exit.md** — v0.0.18: отлов отключения клиента SSE (GeneratorExit/BrokenPipe). -- **2026-07-14-hop-by-hop-connection.md** — v0.0.21→0.0.22: Waitress запрещает hop-by-hop заголовок `Connection: close`. - -## infra/ — инфраструктура - -- **2026-07-14-browser-rst-debug.md** — диагностика ERR_CONNECTION_RESET в браузере. -- **2026-07-15-cluster-dump.md** — снимок кластера (4 ноды, taints, состояние). -- **2026-07-15-rst-root-cause.md** — итоговая диагностика ERR_CONNECTION_RESET (v0.0.29). -- **2026-08-19-remove-process-names-http2.md** — v0.0.46: удалён process_names + проблема git push через HTTP/2. -- **2026-08-21-pattern-vm-buffer-pull-plan.md** — план: перенос паттерна «ВМ-буфер + pull» в drhider. -- **2026-08-21-vm-files-api-plan.md** — план: универсальный файловый сервис на ВМ (API). -- **2026-08-21-vm-upload-implemented.md** — v0.0.58: ВМ-загрузка реализована (паттерн «ВМ-буфер + pull»). -- **2026-08-24-two-clusters-deploy.md** — деплой на двух кластерах: iot-naeel (актуальный, DNS → .151) и naeel-test-3 (не погашен). - -## ux-frontend/ — фичи фронтенда - -- **2026-08-19-live-timer-llm.md** — v0.0.38: живой таймер обработки + индикация ИИ. -- **2026-08-19-live-filename.md** — v0.0.40: имя файла в live-блоке + сброс итогов. -- **2026-08-19-file-time.md** — v0.0.41: время на конкретный файл в колонке «Статус». -- **2026-08-19-zip-expand-frontend.md** — v0.0.33: раскрытие ZIP в таблицу (фронтенд). -- **2026-08-19-zip-filter-docs.md** — v0.0.35: фильтр документов при раскрытии ZIP. -- **2026-08-19-ux-fixes.md** — v0.0.49: индикатор распаковки, разделение фаз (1/2, 2/2), таймауты SSE. -- **2026-08-20-upload-limits.md** — v0.0.50: лимиты загрузки (>100МБ/файл, >1ГБ сумма) + красная подсветка в таблице. -- **2026-08-24-interrupt-eta-design.md** — дизайн: TTL-фикс + прерывание с сохранением + оценка времени + UI. -- **2026-08-24-interrupt-eta-implemented.md** — v0.0.63: реализовано прерывание с сохранением + ETA + UI-таблица. -- **2026-08-24-time-tickers-estimates.md** — v0.0.64–0.0.65: тикер текущего файла на всех этапах, мгновенные оценки времени по файлам и суммарно. -- **2026-08-24-folder-select-implemented.md** — v0.0.68: кнопка «Выбрать папку» (webkitdirectory, рекурсивно, относительный путь). -- **2026-08-24-folder-zip-not-lost.md** — v0.0.69: архивы из папки без документов не теряются (добавляются как есть); раскрытие zip с документами; склонение счётчика. -- **2026-08-24-v075-small-fixes.md** — v0.0.75: кнопка при 0 файлов, фильтр zip (idx-мисматч), README-риски; замечен закоммиченный LLM_API_KEY. -- **2026-08-24-v074-cancel-extract.md** — v0.0.74: отмена в extract-фазе (не ждём все .doc). -- **2026-08-24-v073-security-fixes.md** — v0.0.73: 6 фиксов по ревью Соннета (SSRF, path traversal, слабый SID, proc_error, zip-бомба, self-XSS); 1 отклонено (cleanup), 5 отложено. -- **2026-08-24-pull-retry-limit-counter.md** — v0.0.72: ретраи pull из ВМ, корректная ошибка лимита сессии, счётчик дедупа. -- **2026-08-24-code-review-sonnet.md** — код-ревью Соннета: 15 багов (SSRF, path traversal, слабый SID, zip-бомба и др.), промпт в docs/code-review-sonnet.md. -- **2026-08-24-upload-logic-schema.md** — подробная схема логики загрузки + найденные баги/несоответствия. -- **2026-08-24-help-button-limits-text.md** — v0.0.70: заметная кнопка HELP (вместо «?») + пояснение статуса «пропущен» в ограничениях. -- **2026-08-24-ui-locks-session-freeze.md** — v0.0.66: блокировки UI (выбор/удаление) на время работы, заморозка сессии + кнопка «Новая сессия». - -## upload/ — загрузка файлов - -- **2026-08-19-upload-delay.md** — диагностика медленной/нестабильной загрузки + v0.0.43 (таймаут 300с). -- **2026-08-23-test-upload-only-plan.md** — план: тест-режим «только загрузка» (для Флаша). -- **2026-08-23-upload-stress-test.md** — тесты загрузки и стресс (v0.0.59–0.0.60, кластер naeel-test-3). -- **2026-08-24-upload-limits.md** — v0.0.62: лимиты 50 МБ на файл, 500 МБ на сессию. - -## upload-integration/ — интеграция платформы загрузки - -- [README.md](upload-integration/README.md) — стандарт и журнал процесса интеграции `upload-platform`. -- [2026-09-15-upload-platform-integration-plan.md](upload-integration/2026-09-15-upload-platform-integration-plan.md) — детальный архитектурный план и этапы интеграции версии v0.2.2 в drhider. - -## code-fixes/ — исправления кода (по версиям) - -- **2026-08-31-vm-sync-readme.md** — документирован `rsync` текущего проекта на ВМ - в `/home/naeel/drhider`; legacy-каталог исключён. -- **2026-08-31-api-bp-syntax-error.md** — v0.0.77: удалена случайная строка из `site/routes/api_bp.py`, - вызывавшая `SyntaxError` при импорте приложения; задокументирован ответ Fable по архитектуре. -- **2026-08-18-fix-review-bugs.md** — v0.0.32: фиксы по ревью обфускатора. -- **2026-08-19-replacer-optimization.md** — v0.0.39: оптимизация apply_replacements (однопроходная замена, один regex). -- **2026-08-20-logging.md** — v0.0.51: максимальное логирование (LOG_LEVEL env) + детальные логи SSE/воркера. -- **2026-08-20-logging-file.md** — v0.0.52: файловый лог /tmp/drhider.log (для kubectl exec). -- **2026-08-20-broken-files.md** — v0.0.53: устойчивость к битым файлам (воркер не падает, битые пропускаются). -- **2026-08-20-v1-v2-implemented.md** — v0.0.54: В2 (фикс кракозябр имён zip) + В1 (фильтр таблиц, эффект≈0). -- **2026-08-20-honest-answer-and-A-implemented.md** — v0.0.55: честный ответ Sonnet + реализация А (threading .doc, кэш regex). -- **2026-08-20-csv-out-of-zip.md** — v0.0.56: КРИТИЧНО — mapping.csv (ключ расшифровки) убран из ZIP. -- **2026-08-24-zip-name-cyrillic-frontend.md** — v0.0.67: фикс кириллических имён из ZIP на фронте (UTF-8 без флага → CP866-мусор), согласовано с бэком. - -## tests/ — тесты и бенчмарки - -- **2026-08-19-ui-5runs-benchmark.md** — v0.0.49: бенчмарк UI, 5 прогонов DownLoads.zip (18 файлов, замеры времени). -- **2026-08-23-obfuscation-tests.md** — тесты реальной обфускации и фикс .doc (v0.0.60–0.0.61, корпус /mnt/y/T). -- **2026-08-24-tests-upload-and-obfuscation.md** — тесты загрузки и обфускации (v0.0.62, лимиты, ВМ-буфер). -- **2026-08-24-test-batch-v067.md** — тесты v0.0.67: загрузка, лимит 50МБ, ZIP-кириллица, ETA, прерывание с частичным сохранением. - ---- - -## Соглашение об именах -`ГГГГ-ММ-ДД-тема.md` (иногда с версией). В начале каждого файла — дата и версия/статус. -При сортировке файл относится в папку по главной теме (имя файла/содержание), не по дате. diff --git a/History/base/2026-07-11-initial-setup.md b/History/base/2026-07-11-initial-setup.md deleted file mode 100644 index 21bafb6..0000000 --- a/History/base/2026-07-11-initial-setup.md +++ /dev/null @@ -1,43 +0,0 @@ -# DrHider — Managed Flask на pythonk8s - -**Дата:** 2026-07-11 -**URL:** https://drhider.pythonk8s.dev.nubes.ru/ -**Репозиторий:** https://gitea.services.ngcloud.ru/Nail/drhider - -## Структура проекта - -``` -drhider/ -├── Dockerfile # python:3.12-slim, gunicorn + gevent :5000 -├── requirements.txt # flask, gunicorn, gevent, python-docx, pdfplumber, httpx -├── .gitignore -├── .gitea/workflows/deploy.yaml # CI (linux_amd64) -├── drhider.py # ядро обфускации (скопировано с ВМ contracts) -├── drhider_server.py # HTTP-сервер (скопировано с ВМ contracts) -├── site/ -│ ├── app.py # Flask: GET /, POST /api/drhider, /health -│ ├── templates/index.html -│ └── static/favicon.svg -└── History/ -``` - -## API - -| Метод | Путь | Описание | -|---|---|---| -| GET | / | Веб-интерфейс | -| GET | /health | `{"ok": true, "version": "..."}` | -| POST | /api/drhider | multipart files → ZIP с обфусцированными документами | - -## Переменные окружения - -| Переменная | Назначение | По умолчанию | -|---|---|---| -| `LLM_API_KEY` / `LLM_KEY` | Ключ API для LLM | — | -| `LLM_URL` | URL LLM API | `https://api.aillm.ru/v1/chat/completions` | -| `LLM_MODEL` | Модель | `gpt-oss-120b` | - -## Коммиты - -- `a455f9d` — Начальная структура managed Flask + drhider -- `a89df86` — Managed Flask: app.py, index.html, CI workflow diff --git a/History/base/2026-07-12-documentation.md b/History/base/2026-07-12-documentation.md deleted file mode 100644 index 0388603..0000000 --- a/History/base/2026-07-12-documentation.md +++ /dev/null @@ -1,24 +0,0 @@ -# Документация — 2026-07-12 - -**После рефакторинга** создана полная документация: - -### Созданные файлы - -| Файл | Назначение | -|---|---| -| `README.md` | Обзор проекта, правила для агентов, структура, API, env vars | -| `ARCHITECTURE.md` | Архитектура: схема, двухпроходная обфускация, поток данных, описание каждого модуля | -| `DEPLOY.md` | Деплой, ВМ, переменные окружения, локальная разработка, как добавить генератор | -| `.github/copilot-instructions.md` | Правила для Copilot (не менять без «делай», не смотреть локальные папки, коммитить после каждой правки) | - -### Ключевые факты зафиксированы - -- Платформа: Штурвал (Managed Flask), без Dockerfile/gunicorn -- URL: https://drhider.pythonk8s.dev.nubes.ru/ -- Дизайн фронтенда: `/home/naeel/nubes/design/` -- ВМ (legacy): `5.172.178.213`, ssh-ключ `~/.ssh/naeel_vm_id_ed25519` -- Переменные окружения: LLM_API_KEY, LLM_URL, LLM_MODEL - -### Коммит - -`0707d53` — Документация: README.md, ARCHITECTURE.md, DEPLOY.md, .github/copilot-instructions.md diff --git a/History/code-fixes/2026-08-18-fix-review-bugs.md b/History/code-fixes/2026-08-18-fix-review-bugs.md deleted file mode 100644 index 62d37b1..0000000 --- a/History/code-fixes/2026-08-18-fix-review-bugs.md +++ /dev/null @@ -1,84 +0,0 @@ -# v0.0.32 — фиксы по ревью обфускатора — 2026-08-18 - -**Дата:** 2026-08-18 -**Версия:** 0.0.31 → 0.0.32 -**Ветка:** master (коммит `94e588a`, после rebase над `c3c6e5b`) -**Источник:** ревью `History/2026-08-18-review-sonnet-bugs.md` - ---- - -## Внесённые изменения - -### 1. Единый обфускатор на все файлы (ТОП-1 — согласованность токенов) -- **Было:** `api_bp.py` обрабатывал каждый файл отдельным `obfuscate_files()` → - новая инстанс `TwoPassObfuscator` на файл → одна сущность получала РАЗНЫЕ токены - в разных файлах, `all_mapping` (ручной разбор CSV) был неверен. -- **Стало:** все файлы сессии → один `obfuscate_files(...)` с общим mapping. - Проверено: «ООО Ромашка» в двух файлах → один токен «Сфера_0001». -- **Побочно:** убран хрупкий ручной разбор CSV `split(",", 2)` (ломавшийся на - запятых в значениях) — больше не нужен при едином вызове. - -### 2. Рекурсивный `expand_zips` (ТОП-2) -- `extractor.py:expand_zips` переписан: очередь + распаковка вложенных ZIP. - ZIP внутри ZIP теперь раскрывается. Проверено: вложенный `doc1.txt` извлекается. - -### 3. SSE-дисконнект (ТОП-3) -- `api_bp.py:generate()`: ловится `(GeneratorExit, BrokenPipeError, - ConnectionResetError)` вместо одного `GeneratorExit`. -- `obfuscate_files` выполняется в отдельном потоке; прогресс через очередь; - флаг отмены (`threading.Event`) при разрыве соединения. -- Коллбек прогресса `(phase, idx, total, fname)` где phase ∈ {start, done} — - согласуется с фронт-протоколом (`start`/`done` по `idx`). - -### 4. Кириллица 1С (CP437→CP866) -- `extractor.py._decode_name`: если имя не-UTF-8 (флаг 0x800 снят) и содержит - не-ASCII → `name.encode("cp437").decode("cp866")`. Проверено изолированно. - -### 5. Дедупликация имён -- `builder.py:build_zip`: одинаковое имя+контент → пропуск; имя+разный контент → - суффикс `_2`, `_3`... Проверено: `['a.md', 'a_2.md']`. -- `obfuscator.py:_dedupe_file_names`: уникализация имён на входе до прохода 1 - (защита `all_texts[fname]` от перезаписи). - -### 6. upload(): цикл по всем файлам -- `api_bp.py:upload()`: обрабатывает все `request.files.getlist("files")` - (поддержка загрузки целых папок через `webkitdirectory`). -- Сохранено поведение ошибок: файл без имени → «No filename» (совместимо с тестом). - -### 7. Лимит объёма сессии -- `session.py`: `MAX_SESSION_BYTES = 500 MB`; в `add_file` — скип при превышении. - -### 8. Защита от ZIP-бомб -- `extractor.py`: проверка `total + file_size > LIMIT` ДО `zf.read`; скип архива - при превышении ratio/лимита/числа файлов; лимиты глобально на вызов. - ---- - -## Тесты - -| Набор | Результат | -|-------|-----------| -| test_builder | 20/20 OK | -| test_zip | 28/28 OK | -| test_replacer | 10/10 OK | -| test_scanner | 20/20 OK | -| test_upload | 13/13 OK | -| test_extractor | **14/15** — падает `.doc` (внешний сервис liberta) | - -`test_extractor` `.doc`: отправляется мусорный OLE2-магик -`b"\xD0\xCF\x11\xE0\x00"*10`, сервис `liberta.containerk8s.dev.nubes.ru/convert` -возвращает «conversion produced no .docx». НЕ регрессия от v0.0.32 — ветка -`.doc`/`doc_to_markdown` не менялась. Вероятно, сервис изменил поведение или -не принимает невалидный контент. Требует отдельного разбора. - ---- - -## Git - -- Remote был впереди (посторонний коммит `c3c6e5b` про магистратуру) → `git pull --rebase`. -- Push успешен: `c3c6e5b..94e588a`. -- Ветка-сохранение документации: `save-docs-2026-08-18` (`de072d8`). - -## TODO / открытые вопросы -- [ ] Разобраться с тестом `.doc` (возможно, обновить тест на актуальный сервис). -- [ ] Визуально проверить SSE-прогресс во фронте после рефакторинга. diff --git a/History/code-fixes/2026-08-19-replacer-optimization.md b/History/code-fixes/2026-08-19-replacer-optimization.md deleted file mode 100644 index 2f9f193..0000000 --- a/History/code-fixes/2026-08-19-replacer-optimization.md +++ /dev/null @@ -1,45 +0,0 @@ -# v0.0.39 — оптимизация apply_replacements (однопроходная замена) — 2026-08-19 - -**Дата:** 2026-08-19 -**Версия:** 0.0.38 → 0.0.39 -**Файл:** `drhider/replacer.py`, `site/app.py` (версия) - ---- - -## Суть - -Обфускация PDF 19 МБ шла 132.7с, из них ИИ (LLM) всего 7с — остальное (~125с) -тратил основной код. Замер выявил узкое место: `apply_replacements`. - -## Причина - -`apply_replacements` для **каждой** сущности из mapping делала **2 полных -прохода `re.sub` по всему тексту** (основной + фоллбэк `_md_tolerant_pattern`). -Сложность O(N_сущностей × размер_текста × 2) — квадратичная по числу сущностей. -При 300+ сущностей и тексте в несколько МБ — десятки секунд. - -Замер (0.9 МБ, 304 сущности): **17.3 с**. - -## Решение - -Однопроходная замена: -- Все ключи собираются в **один** regex-паттерн через альтернацию `(?:...|...)` - (по убыванию длины, с `re.escape`, границы `(?` (кнопка «Скачать CSV»), как написано на странице. - -## Тесты (tests/test_builder.py) -- Блок «ZIP с mapping.csv» переписан: теперь проверяет, что mapping.csv НЕ в архиве. -- Интеграционный блок: ZIP содержит только doc.md, CSV проверяется отдельной строкой. -- Все 106 тестов OK. - -## Проверка -- python3 tests/test_builder.py — 20/20 OK (включая новый сценарий «mapping не в zip»). -- Остальные тесты — без изменений поведения, OK. -- VERSION 0.0.56. diff --git a/History/code-fixes/2026-08-20-honest-answer-and-A-implemented.md b/History/code-fixes/2026-08-20-honest-answer-and-A-implemented.md deleted file mode 100644 index 2878b1d..0000000 --- a/History/code-fixes/2026-08-20-honest-answer-and-A-implemented.md +++ /dev/null @@ -1,44 +0,0 @@ -# 2026-08-20 — Честный ответ Sonnet об ускорении + реализация А (v0.0.55) - -## Честный ответ Sonnet (History/2026-08-20-sonnet-query-honest-speed.md) -Вопрос: можно ли значительно ускорить обработку, не рискуя устойчивостью/таблицами? - -Ответ Sonnet (принято, согласуется с замерами): -- **Безопасно ускорить extract_text() на pdfplumber НЕЛЬЗЯ** — pdfminer pure Python, - CPU-bound, GIL полностью блокирует threading. (Подтверждено: extract_text 64.4с на Spartan10.) -- A) Безопасно-просто: - - A1. Threading для .doc (liberta, IO-bound) — N×~120с → ~120с при нескольких .doc. - - A2. Кэш compiled regex между файлами (проход 2). -- B) Заметный выигрыш, но с рисками (ОТЛОЖЕНО): - - B1. ProcessPool по файлам (spawn) — 3 PDF × 25с → ~30с (только 2+ PDF, CPU=2). - - B2. ProcessPool по страницам (spawn) — Spartan10 64с → ~35с (сложно: content, порядок). -- C) Не стоит: - - fork ProcessPool при session в памяти (COW-раздутие, 4Gi на пределе) — только spawn. - - Threading по страницам/файлам PDF — GIL, ноль. -- Итог Sonnet: безопасного значительного ускорения ОДНОГО PDF нет. Для набора 2+ PDF — - B1. При CPU=2 потолок 2x. - -## Решение пользователя -«Делай по А, безопасно» — реализованы ТОЛЬКО A1 и A2. B — НЕ отложено (не делаем). - -## Реализация (v0.0.55) - -### A1 — threading для .doc (obfuscator.py, проход 1) -- .doc файлы (конвертация через HTTP liberta, IO-bound) запускаются в - ThreadPoolExecutor(max_workers=min(4, N_doc)). -- Основной цикл сохраняет порядок: для .doc берёт future.result(), остальные — - последовательно. progress_cb/порядок start/done не меняются. -- Выигрыш: HTTP-конвертация .doc перекрывается с CPU-обработкой PDF/docx. - -### A2 — кэш compiled regex (replacer.py + obfuscator.py) -- replacer.py: вынесен `_build_combined_re(sorted_keys)`, `apply_replacements` получил - параметр `compiled_re=None` (если None — компилирует сам). -- obfuscator.py: после формирования `self._sorted_keys` компилируется один раз - `self._compiled_re`, передаётся во все вызовы apply_replacements (проход 2). -- __init__/finally: _compiled_re инициализируется/очищается. - -## Проверка -- Все 106 тестов OK (test_replacer 10/10 — apply_replacements с compiled_re). -- Интеграция: flat/ (10 файлов) → 5 обработано (EB.md, EB1.md, Spartan10, TKM, mini_78b), - битые пропущены, .doc через liberta в потоках. 78.3с (в основном Spartan10 64с extract_text). -- VERSION 0.0.55. diff --git a/History/code-fixes/2026-08-20-logging-file.md b/History/code-fixes/2026-08-20-logging-file.md deleted file mode 100644 index 8e0fa6d..0000000 --- a/History/code-fixes/2026-08-20-logging-file.md +++ /dev/null @@ -1,24 +0,0 @@ -# 2026-08-20 — Файловый лог в поде (v0.0.52) - -## Проблема -В v0.0.51 добавили логирование в stderr, но на платформе Штурвал -`kubectl logs` НЕ показывает stdout/stderr приложения (fd 1/2 python -> pipe, -который читает супервизор платформы, а не kubelet). Логи приложения недоступны. - -## Решение -`setup_logging()` в site/app.py: -- логи в stderr (для локального запуска) -- + `FileHandler` в `/tmp/drhider.log` (путь через env `LOG_FILE`) -- уровень через env `LOG_LEVEL` (DEBUG/INFO/WARNING) -- root.handlers.clear() + свои handler'ы (не полагаемся на basicConfig) -- если файл не открылся — не падаем, пишем в stderr - -## Как читать логи в поде -``` -kubectl exec -n 20a75175-a58c-49cb-b8fa-e86367b1a8dc -c app -- cat /tmp/drhider.log -``` -(или tail -f). Версия 0.0.52. - -## Проверено локально -Файл /tmp/drhider.log создаётся, пишет "Logging configured, version=0.0.52" -и сообщения логгеров. diff --git a/History/code-fixes/2026-08-20-logging.md b/History/code-fixes/2026-08-20-logging.md deleted file mode 100644 index a12106b..0000000 --- a/History/code-fixes/2026-08-20-logging.md +++ /dev/null @@ -1,32 +0,0 @@ -# 2026-08-20 — Максимальное логирование (v0.0.51) - -## Проблема -SSE рвётся на проде (пару минут), обработка при этом продолжается (воркер жив). -Логов в поде не было — приложение не писало в stdout, нельзя было диагностировать -обрыв (внешний шлюз vs генератор). - -## Решение -1. `site/app.py` — `setup_logging()`: - - уровень из env `LOG_LEVEL` (DEBUG/INFO/WARNING), default INFO - - `logging.basicConfig(..., stream=sys.stderr, force=True)` — в stdout/stderr пода - - формат с таймстампом; уровни для логгеров drhider/app/routes/session - - `LOG_LEVEL` задаётся через env-переменные приложения на платформе -2. `site/routes/api_bp.py` — подробные логи: - - upload: каждый файл (имя, размер), итог, ошибки - - process_stream: start, worker start/done (время, токены, zip_len), - каждое событие progress (start/done) с idx/name/elapsed, - disconnect на heartbeat/progress/complete/error (с причиной), - result/complete, error event -3. VERSION поднята до 0.0.51 - -## Проверка (локально, DEBUG) -Upload 1 файла + SSE: видны все события от upload до complete. -`LLM NER failed: Illegal header value b'Bearer '` — ожидаемо без ключа (локально). - -## Как читать логи пода после деплоя -``` -kubectl logs -n 20a75175-a58c-49cb-b8fa-e86367b1a8dc --tail=500 --timestamps -``` -- Если `process_stream: disconnect on progress` — клиент/шлюз оборвал. -- Если worker дошёл до `complete`, а клиент не получил — рвёт шлюз/браузер. -- Если `worker: exception` — ошибка обработки. diff --git a/History/code-fixes/2026-08-20-v1-v2-implemented.md b/History/code-fixes/2026-08-20-v1-v2-implemented.md deleted file mode 100644 index 63e5b69..0000000 --- a/History/code-fixes/2026-08-20-v1-v2-implemented.md +++ /dev/null @@ -1,37 +0,0 @@ -# 2026-08-20 — В1+В2 из ревью Sonnet (v0.0.54) - -## В2 — фикс кракозябр имён zip (extractor.py `_decode_name`) -Добавлена попытка decode("utf-8") ПЕРЕД decode("cp866"), с валидацией диапазона: -- результат UTF-8 принимается, только если все символы — ASCII или кириллица (U+0400–U+04FF) - (отсекает случайную коллизию CP866→UTF-8, напр. «Т»+«г» = U+04A3); -- при ошибке или выходе из диапазона — фоллбэк decode("cp866") (реальные 1С). - -Проверено: -- CP866-зип (zip_subfolders_cp866.zip) → имена корректны (1_Металлургия.pdf…) -- UTF-8 с флагом (zip_subfolders_utf8.zip) → корректны -- test_zip 28/28 OK - -## В1 — фильтр перед extract_tables() (extractor.py `pdf_to_markdown`) -Добавлен: `if page.lines or page.curves or page.rects:` перед `extract_tables()`. -Задумывалось как ускорение сканов (extract_tables на страницах без линий впустую). - -### ⚠️ ФАКТ замера (опровергает гипотезу Sonnet) -На Spartan10Manual.pdf (14.8 МБ, 619 стр): -- extract_text суммарно 64.4с (104мс/стр) — УЗКОЕ МЕСТО -- extract_tables суммарно 0.2с (0мс/стр) — ничтожно -- на 272 страницах без линий extract_tables суммарно 0.0с — почти мгновенно - -ВЫВОД: фильтр В1 НЕ даёт ускорения (extract_tables и так дешёвый). Реальное узкое -место — extract_text (pdfminer). Фильтр оставлен как безопасная защита (таблицы -не теряет: на 0144-03-2023_отчет об оценке.pdf 94=94 таблицы, потеряно страниц 0), -но эффект ускорения ≈ 0. - -### Реальный путь ускорения (отложено) -Узкое место — extract_text (104мс/стр × 619 стр = 64с). Ускорение только через: -- распараллеливание extract_text по страницам/файлам (ProcessPool) — отложено, - требует замера рисков (fork/память, CPU=2); -- или смену извлечения текста без потери таблиц (риск: таблицы критичны). - -## Тесты -Все 106 тестов OK (test_zip 28, test_extractor 15, test_builder 20, test_replacer 10, -test_scanner 20, test_upload 13). VERSION 0.0.54. diff --git a/History/code-fixes/2026-08-24-pull-retry-limit-counter.md b/History/code-fixes/2026-08-24-pull-retry-limit-counter.md deleted file mode 100644 index bbf7088..0000000 --- a/History/code-fixes/2026-08-24-pull-retry-limit-counter.md +++ /dev/null @@ -1,26 +0,0 @@ -# v0.0.72 — Ретраи pull, корректная ошибка лимита, счётчик дедупа (2026-08-24) - -_code-fixes. По итогам код-ревью (см. `infra/2026-08-24-upload-logic-schema.md`)._ - -## 1. Ретраи pull (site/routes/api_bp.py, `upload_refs`) -- `PULL_RETRIES = 3`, `PULL_RETRY_DELAY = 2` (сек). -- Pull из ВМ-буфера теперь в цикле: при любой ошибке (DNS `gaierror -5`, сеть) — до 3 попыток - с паузой 2с. После исчерпания — проброс исходной ошибки (502 «Pull failed»). -- Закрывает инцидент 18:42 (разовый DNS-сбой ронял всю загрузку). - -## 2. Корректная ошибка лимита сессии (`upload_refs`) -- `add_file` возвращает `False` и для «сессия исчезла», и для «превышен лимит 500МБ». -- Теперь: при `False` — если `get_files(sid) is None` → 404 «Session not found»; - иначе (лимит) → файл пропускается (`delete` с ВМ) и загрузка продолжается. - Раньше оба случая давали ложное «Session not found». - -## 3. Счётчик «Добавлено из папки» (site/templates/index.html) -- `addFileWithDedup` теперь возвращает `true` (файл добавлен) / `false` (дедуп). -- `added++` только при `true` — дубли больше не завышают счётчик. -- Проверено: папка [Договор.txt, Договор.txt (дубль), Акт.txt] → «Добавлено из папки: 2 файла». - -## Проверка -- `node --check` — OK, `py_compile` (app.py, api_bp.py) — OK. -- Локально: счётчик дедупа работает (2 файла при одном дубле). -- Ретраи/лимит — на проде после деплоя. -- Версия 0.0.71 → 0.0.72. diff --git a/History/code-fixes/2026-08-24-v073-security-fixes.md b/History/code-fixes/2026-08-24-v073-security-fixes.md deleted file mode 100644 index 4b93070..0000000 --- a/History/code-fixes/2026-08-24-v073-security-fixes.md +++ /dev/null @@ -1,46 +0,0 @@ -# v0.0.73 — фиксы по код-ревью Соннета (безопасность + явные баги) (2026-08-24) - -_code-fixes. По ревью `History/sonnet/2026-08-24-code-review-sonnet.md` (промпт `docs/code-review-sonnet.md`)._ -_Критично перепроверены в коде; спорные пункты — отклонены/отложены (см. ниже)._ - -## Исправлено (6 фиксов) - -1. **SSRF** (`api_bp.py::upload_refs`) — добавлена валидация `url.startswith(VM_UPLOAD_PREFIX)`; - недоверенный URL пропускается. Константа `VM_UPLOAD_PREFIX = "https://contracts.kube5s.ru/drhider-upload/"`. -2. **Path traversal / zip slip** (`api_bp.py`) — функция `_safe_name()`: нормализует слэши, - отбрасывает `..` и абсолютные пути, сохраняя подпапки (`Подпапка/Акт.txt` → как есть, - `../../evil.pdf` → ""). Применена в `upload` и `upload_refs`. - Unit-тест 8 кейсов — все OK. -3. **Слабый SID** (`session.py`) — `uuid.uuid4().hex[:12]` → `uuid.uuid4().hex` (128 бит). -4. **Серверная ошибка не отображалась** — серверное событие `event: error` (конфликт с встроенным - EventSource) → `event: proc_error`; добавлен клиентский `addEventListener('proc_error', …)` - с показом сообщения. -5. **ZIP-бомба: обход через поддельный `file_size`** (`extractor.py`) — добавлена проверка - `total_uncompressed > MAX_UNCOMPRESSED` ПОСЛЕ `zf.read()` с `break` (ранний выход). -6. **Self-XSS через имя файла** (`index.html`) — добавлена `esc()` и применена к `f.name` - в `rr()` и `procRow()`. - -## Отклонено (критично к Соннету) -- **«cleanup(sid) после /download»** — НЕ сделано: ZIP и CSV скачиваются РАЗДЕЛЬНЫМИ запросами; - удаление сессии после отдачи ZIP сломало бы скачивание CSV. Сессия и так чистится по TTL (30 мин). - -## Отложено (требуют решения/риск) -- idx-мисматч при `expand_zips` (фильтр расширений на бэке) — связано с фичей v0.0.69 - (zip без документов «как есть»), требует решения по поведению. -- Отмена не проверяется в extract-фазе (.doc liberta 120с) — отдельный фикс. -- LLM-таймаут тихо обнуляет чанк — логирование/статус. -- Debug-эндпоинт `session_files` — ограничить/закрыть. -- LLM prompt injection — задокументировать (класс риска, не фикс кода). - -## Проверка -- `py_compile` (app.py, api_bp.py, session.py, extractor.py) — OK; `node --check` — OK. -- `_safe_name` unit-тест — 8/8 OK. -- `create_app()` стартует (VERSION 0.0.73). -- Версия 0.0.72 → 0.0.73. - -## Проверка на проде (редеплой 20:00, v0.0.73) -- SSRF: `POST /api/upload_refs` с `url=http://169.254.169.254/...` → `{"count":0,"ok":true}` (URL отклонён). -- Path traversal: `name="../../evil.txt"` → `{"count":0,"ok":true}` (имя отклонено). -- SID теперь полный 32 hex (128 бит) — виден в ответе. -- Регресс: папка с подпапкой + кириллицей → «Обработано 2 файлов: 4.3с», - в ZIP `Подпапка/Договор_1.md` + `Акт.md` (пути сохранены, `_safe_name` не сломал подпапки). diff --git a/History/code-fixes/2026-08-24-v074-cancel-extract.md b/History/code-fixes/2026-08-24-v074-cancel-extract.md deleted file mode 100644 index 42314df..0000000 --- a/History/code-fixes/2026-08-24-v074-cancel-extract.md +++ /dev/null @@ -1,30 +0,0 @@ -# v0.0.74 — отмена в extract-фазе (2026-08-24) - -_code-fixes. Доработка по код-ревью Соннета (пункт «отмена не работает в extract-фазе»)._ - -## Что сделано (`drhider/obfuscator.py`) -- В проходе 1 (извлечение) добавлена проверка `cancel_event.is_set()` между файлами: - при отмене цикл извлечения прерывается (`break`), LLM-сканирование пропускается, - результат помечается `cancelled` (processed=0, пустой ZIP + mapping.csv только заголовок). -- Раньше отмена ждала завершения ВСЕХ извлечений, включая медленные `.doc` (liberta, до 120с/файл). - -## Проверка -- `py_compile` — OK, `import` drhider — OK. -- Unit-тест: `cancel_event` установлен до старта → `meta={'cancelled': True, 'processed': 0, 'total': 2}`, - пустой ZIP (22 Б), CSV только заголовок. - -## Критично к Соннету (по итогам проверки) -- **«LLM-таймаут тихо обнуляет чанк»** — ОТКЛОНЕНО: `_call_llm` уже логирует - (`log.warning("LLM NER failed: %s", e)`) в `except Exception`. Таймаут не «тихий». -- **«cleanup после /download»** — ОТКЛОНЕНО ранее (ZIP и CSV скачиваются раздельно, см. v0.0.73). - -## Осталось (не трогаем, задокументировано) -- idx-мисматч `expand_zips` (фильтр расширений) — краевой случай, спорное поведение. -- LLM prompt injection — класс риска, не фикс кода. -- debug-эндпоинт `session_files` — мелочь, может сломать тесты. - -Версия 0.0.73 → 0.0.74. - -## Проверка на проде (редеплой 20:07, v0.0.74) -- Версия v0.0.74 — на проде. -- Регресс: обычный цикл «Обработано 2 файлов: 1.9с» — фикс отмены не сломал обработку. diff --git a/History/code-fixes/2026-08-24-v075-small-fixes.md b/History/code-fixes/2026-08-24-v075-small-fixes.md deleted file mode 100644 index 057ce6f..0000000 --- a/History/code-fixes/2026-08-24-v075-small-fixes.md +++ /dev/null @@ -1,31 +0,0 @@ -# v0.0.75 — кнопка при 0 файлов, фильтр zip, README-риски (2026-08-24) - -_code-fixes. Закрытие отложенных пунктов списка «что далее»._ - -## Что сделано -1. **Кнопка «Обфусцировать» активна при 0 файлов** (`index.html::resetAll`) — - убрано перекрытие `ub.disabled = false` после `rr()` (rr() уже ставит disabled при пустом списке). - Из TODO (`docs/WhatTODO.md`, пункт 1). -2. **idx-мисматч `expand_zips`** (`drhider/extractor.py`) — синхронизирован фильтр расширений - с фронтом `listZipFiles`: из zip берутся ТОЛЬКО документы (`.pdf .doc .docx .txt .md`) - и вложенные `.zip`. Unit-тест: zip [txt,pdf,xlsx,ini] → раскрыты только txt,pdf. -3. **README** — добавлена секция «Известные ограничения / риски»: LLM prompt injection, - кейс «zip без документов». - -## Не сделано (решение) -- **`session_files` оставлен** — используется для диагностики (тесты), - SID теперь 128-бит (не угадывается), риск мал. - -## ⚠️ Замечено (требует решения) -- В `README.md` в открытом виде закоммичен `LLM_API_KEY` (секрет!). Желательно вынести - в переменные окружения Штурвала и убрать из README. - -## Проверка -- `py_compile` (app.py, extractor.py) — OK; `node --check` — OK. -- `expand_zips` unit-тест — OK (только документы). -- `resetAll` больше не содержит `ub.disabled = false` (5 оставшихся мест — нужные ветки). -- Версия 0.0.74 → 0.0.75. - -## Проверка на проде (редеплой 20:20, v0.0.75) -- Кнопка «Обфусцировать»: disabled при 0 файлов → активна с файлом → disabled после «Новой сессии». -- Регресс: zip с документом раскрыт (справка.txt), «Обработано 2 файлов: 4.4с» — фильтр zip не сломал. diff --git a/History/code-fixes/2026-08-24-zip-name-cyrillic-frontend.md b/History/code-fixes/2026-08-24-zip-name-cyrillic-frontend.md deleted file mode 100644 index 4e15bdf..0000000 --- a/History/code-fixes/2026-08-24-zip-name-cyrillic-frontend.md +++ /dev/null @@ -1,29 +0,0 @@ -# v0.0.67 — Фикс кириллических имён из ZIP на фронтенде (2026-08-24) - -_code-fixes. Имя `TKM_6й_Семестр_ЭКЗАМЕН.docx` из архива отображалось как -`TKM_6╨╣_╨б╨╡╨╝╨╡╤Б╤В╤А_...` (мусор)._ - -## Причина -Архиватор записал имя **в UTF-8, но без UTF-8-флага** (bit 11). Фронт (`decodeZipName`) -видел «флага нет», шёл в legacy-ветку и декодировал **UTF-8-байты как CP866** → -`D0 99` («й») → `╨╣` (псевдографика CP866). - -## Фикс (index.html, `decodeZipName`) -- Первым делом — **строгая проверка UTF-8** (`TextDecoder('utf-8', {fatal:true})`): - если байты — валидный UTF-8 с кириллицей или печатаемым текстом → берём как есть. -- Только если строгий UTF-8 не проходит (реальные CP437/CP866 из 1С) — legacy-путь - (CP437 → CP866) не меняется. -- CP866-кириллица (0x80–0xAF) — это продолжения UTF-8 без ведущих байтов, поэтому - строгий UTF-8 для них честно падает и legacy-путь работает как раньше. - -## Согласованность с бэком -Бэк (`extractor.py`, `_decode_name`) это уже умел (v0.0.54, «В2 — фикс кракозябр имён zip»). -Теперь фронт и бэк обрабатывают имена ZIP одинаково. - -## Проверка (node) -- `TKM_6й_Семестр_ЭКЗАМЕН.docx` (UTF-8 без флага) — декодируется верно ✅. -- ASCII — ✅. Legacy CP437/CP866-путь не тронут. -- `node --check` — OK. - -## Версия -- 0.0.66 → 0.0.67. diff --git a/History/code-fixes/2026-08-31-api-bp-syntax-error.md b/History/code-fixes/2026-08-31-api-bp-syntax-error.md deleted file mode 100644 index bf51190..0000000 --- a/History/code-fixes/2026-08-31-api-bp-syntax-error.md +++ /dev/null @@ -1,26 +0,0 @@ -# 2026-08-31 — удалена случайная строка из `api_bp.py` - -## Ответ Fable: анализ архитектуры DrHider - -Сервис предназначен для обезличивания документов `.docx`, `.pdf`, `.doc`, `.txt` и `.zip`. -Основной пайплайн: `extractor` → `scanner` (regex + LLM) → `replacer` → `builder`, -оркестрация выполняется через `TwoPassObfuscator`. Веб-часть использует Flask, -blueprint'ы `main`, `health` и `api`, а загрузка файлов реализована через переиспользуемый -модуль с ВМ-буфером и pull. - -## Найденная проблема - -В `site/routes/api_bp.py` между инициализацией blueprint и определением первой функции -находилась строка: - -```text -TMP/примеры_договоров_для_ИИ -``` - -Это невалидный Python-код и причина `SyntaxError` при импорте приложения. Строка удалена -без изменения остального API. - -## Версия и проверка - -- Исправление выпущено как версия `0.0.77` вместо `0.0.76`. -- Рабочая копия до исправления не содержала незакоммиченных изменений. \ No newline at end of file diff --git a/History/code-fixes/2026-08-31-vm-sync-readme.md b/History/code-fixes/2026-08-31-vm-sync-readme.md deleted file mode 100644 index 36ae83f..0000000 --- a/History/code-fixes/2026-08-31-vm-sync-readme.md +++ /dev/null @@ -1,13 +0,0 @@ -# 2026-08-31 — документирован sync проекта на ВМ - -В README добавлен рабочий порядок синхронизации текущего checkout на ВМ: - -- источник: `/home/naeel/nubes/drhider`; -- цель: `/home/naeel/drhider`; -- инструмент: `rsync` через SSH; -- исключены `.git`, кэши Python, `.venv` и `.pytest_cache`; -- `--delete` не используется; -- после копирования выполняются проверка версии и `py_compile`. - -Legacy-каталог `/home/naeel/contracts/drhider` явно отмечен как недопустимая цель: -там работает старый standalone-сервис `contracts-drhider`. \ No newline at end of file diff --git a/History/infra/2026-07-14-browser-rst-debug.md b/History/infra/2026-07-14-browser-rst-debug.md deleted file mode 100644 index dc8dded..0000000 --- a/History/infra/2026-07-14-browser-rst-debug.md +++ /dev/null @@ -1,237 +0,0 @@ -# Диагностика ERR_CONNECTION_RESET в браузере - -**Дата:** 2026-07-14 - ---- - -## Хронология - -### Фаза 1: MTU (решено) - -Проблема: 63KB POST из браузера → `ERR_CONNECTION_RESET`. Причина: MTU 1500 + Geneve 50 = 1550 > underlay 1450. -Решение: Cilium MTU = 1400. Проверено curl с ВМ (30/30). - -### Фаза 2: HTTP/2 - -HTTP/2 → `ERR_HTTP2_PROTOCOL_ERROR`. HTTP/1.1 → `ERR_CONNECTION_RESET`. -Решение: `use-http2: "false"`. - -### Фаза 3: Поиск корневой причины RST - -Баг: браузер → свежая страница → POST 63KB → `ERR_CONNECTION_RESET`. - -## Полная матрица тестов - -| # | Сценарий | Результат | -|---|----------|-----------| -| 1 | Браузер: свежая страница → POST 63KB | ❌ RST / `⏳ 100%` hang | -| 2 | Браузер: свежая страница → GET (до Flask) → POST 63KB | ✅ 200 OK | -| 3 | Браузер: свежая страница → POST 10B → POST 63KB | ✅ 200 OK | -| 4 | Браузер: свежая страница → GET (nginx сам, 405) → POST 63KB | ❌ RST | -| 5 | `curl` с ВМ (5.172.178.213) → POST 63KB | ✅ всегда <100ms | -| 6 | Сразу после `kubectl rollout restart` ingress → браузер | ✅ работает | -| 7 | Через 5-10 мин после рестарта → браузер | ❌ снова RST | - -## Что исключено - -| Гипотеза | Статус | Почему | -|----------|--------|--------| -| MTU | ❌ исключено | 1400, curl с ВМ всегда работает | -| HTTP/2 | ❌ исключено | `use-http2: false`, та же проблема | -| XHR vs fetch | ❌ исключено | оба метода падают одинаково | -| `proxy_request_buffering: off` | ❌ исключено | `on` для `location /` | -| `client-body-timeout: 10` | ❌ исключено | 63KB < 1 сек | -| FD/worker-лимиты | ❌ исключено | 1M FD, 16K connections, 62MB mem | -| OOM/Restart | ❌ исключено | 0 рестартов, нет OOMKilled | -| Stale upstream keepalive | ❌ исключено | `keepalive=0` не помогло | -| nginx→Flask (upstream) | ❌ исключено | curl с ВМ доказывает что работает | - -## Эксперименты - -### Перезапуск ingress временно чинит - -После `kubectl rollout restart` — браузер работает. Через 5-10 мин — снова RST. Накопление состояния. - -### Ресурсы в норме - -``` -worker_rlimit_nofile: 1047552 (1M FD) -worker_connections: 16384 -worker_processes: 4 -Memory: 62 MiB, CPU: 3-7m -Restarts: 0, OOMKilled: нет -``` - -### `upstream-keepalive-connections: 0` — НЕ фикс - -Баг вернулся через 5-10 минут. Keepalive pool не при чём. - -## Критическое противоречие - -- `curl` с ВМ (тот же датацентр) → POST 63KB → **всегда 200 OK <100ms** -- Браузер через 5-10 мин после рестарта ingress → **ERR_CONNECTION_RESET** - -curl и браузер идут через один VIP (185.247.187.151), на одни ingress-поды. -Разница: клиент (curl vs Chrome) и сетевой путь (локальная сеть ДЦ vs интернет/VPN). - -## Текущая конфигурация (2026-07-14) - -| Параметр | Значение | -|----------|----------| -| `use-http2` | `"false"` | -| `upstream-keepalive-connections` | `"0"` | -| `error-log-level` | `debug` | -| `proxy-body-size` | `1024m` | -| `client-body-timeout` | `"10"` | -| `keep-alive` | `"10"` | -| Cilium MTU | `1400` | -| `externalTrafficPolicy` | `Cluster` | -| Ingress controller | `1.12.6` (Штурвал) | - ---- - -## Вопрос к Claude (Sonnet/Opus) - -### Симптом - -Браузер (Chrome/Electron, Windows) → POST 63KB multipart/form-data → `ERR_CONNECTION_RESET` или зависание на `⏳ 100%`. Тело уходит полностью, ответ не приходит — TCP RST. - -### Окружение - -``` -Кластер: bare-metal K8s 1.34.1, 4 ноды (3 worker + 1 control-plane) -CNI: Cilium Geneve, MTU подов = 1400 -VIP: kube-vip 185.247.187.151 (ARP на все 4 ноды) -Ingress: nginx-ingress (Штурвал Helm), контроллер 1.12.6 - 2 реплики, externalTrafficPolicy: Cluster -Приложение: Flask/Waitress, 1 реплика (Штурвал managed) -``` - -Конфиг ingress: -```yaml -use-http2: "false" -upstream-keepalive-connections: "0" -client-body-timeout: "10" -keep-alive: "10" -worker-processes: 4 -worker-rlimit-nofile: 1047552 -worker-connections: 16384 -proxy-body-size: 1024m -proxy-request-buffering: on # для location / -error-log-level: debug -``` - -### Критическое противоречие - -- `curl` с ВМ (5.172.178.213, тот же датацентр) → POST 63KB → **всегда 200 OK <100ms** -- Браузер через 5-10 мин после рестарта ingress → **ERR_CONNECTION_RESET** -- Сразу после рестарта ingress → браузер работает - -curl и браузер идут через один VIP (185.247.187.151), на одни ingress-поды. Разница: клиент (curl vs Chrome) и сетевой путь до VIP (локальная сеть ДЦ vs интернет/VPN). - -### Вопросы - -1. Почему рестарт ingress помогает на 5-10 минут, а потом браузерные запросы снова получают RST, при том что curl с ВМ продолжает работать? - -2. Может ли `externalTrafficPolicy: Cluster` + kube-vip создавать conntrack-записи, которые со временем ломают соединения из интернета (с другими TCP options/MSS) но не из локальной сети? - -3. Стоит ли попробовать `externalTrafficPolicy: Local`? Какие риски? - -4. Какие ещё эксперименты можно провести без tcpdump (нет доступа к нодам по SSH, нет tcpdump в подах)? - ---- - -## TODO - -- [ ] Ответ от Claude -- [ ] `externalTrafficPolicy: Local` — эксперимент -- [ ] Воспроизвести с `curl` из WSL на машине пользователя -- [ ] tcpdump через `kubectl debug` (требует координации) -- [ ] Обновить ingress controller 1.12.6 → актуальный -- [ ] Проверить `client-body-timeout: 60` + `keep-alive: 75` - ---- - -## Ответ Claude (Sonnet) — 2026-07-14 - -### Два независимых механизма - -Симптом объясняется наложением двух механизмов: - -### Механизм 1: kube-vip ARP flapping + conntrack asymmetry (основной) - -Именно это объясняет 5-10-минутное окно и разницу между browser и curl. - -**Как работает с `externalTrafficPolicy: Cluster`:** - -``` -Browser → VIP → Node N1 (kube-vip leader) → iptables DNAT+SNAT → nginx pod - │ - └── conntrack entry создаётся на N1: - (browser_ip:srcport → 185.247.187.151:443) - ↔ (N1_ip:eph → nginx_pod:443) -``` - -Проблема возникает когда kube-vip меняет лидера: - -``` -kube-vip: N1 → N2 (истёк lease или GARP от N2) -Коммутатор: обновляет ARP-кэш, VIP теперь на N2 - -Browser (старое HTTP keep-alive соединение): - → PSH/ACK 63KB → попадает на N2 - N2: нет conntrack записи → TCP RST - -curl (новое соединение): - → SYN → попадает на N2 - N2: создаёт новую conntrack → всё ОК -``` - -**Почему 5-10 минут:** время жизни ARP-записи на коммутаторе или интервал переизбрания kube-vip лидера. "ARP на все 4 ноды" — периодические GARP-анонсы могут уходить с разных нод. - -**Почему GET-to-Flask чинит последующий POST (тесты #2 vs #4):** -- GET → Flask: создаётся *новое* TCP-соединение (новый SYN) → правильный conntrack на текущей ноде. POST идёт по *тому же* соединению → conntrack жив → OK. -- GET → 405: nginx отвечает сам и закрывает соединение с `Connection: close`. POST создаёт *новое* соединение, но VIP уже на другой ноде → RST. - -### Механизм 2: `keepalive_timeout: 10s` — слишком агрессивно - -`keep-alive: "10"` в ConfigMap = `keepalive_timeout 10s` в nginx. - -``` -T=0: Browser загружает страницу (GET /) -T=10: nginx закрывает idle соединение (отправляет FIN) -T=?: Пользователь заполняет форму (>10 сек → почти всегда) -T=X: Browser отправляет POST по "живому" с его точки зрения соединению - → nginx уже закрыл socket → RST -``` - -Браузер не всегда замечает FIN немедленно (TCP буферизация ОС). curl — каждый раз новое соединение. - -### Почему curl всегда работает - -| | Browser | curl | -|---|---|---| -| Соединение | HTTP keep-alive, долгоживущее | Новое для каждого запроса | -| conntrack | Создано при SYN, может устареть | Новый SYN на каждый вызов | -| keepalive | Держит idle >10s | Сразу POST → не ждёт | -| VIP flap | Пакет идёт на новую ноду без conntrack | SYN на новой ноде создаёт conntrack | - -### Ключевой инсайт - -> Разница не в TCP-опциях (MSS, window size). Разница в **типе пакета**: curl всегда начинает с SYN → conntrack создаётся на правильной ноде. Browser держит соединение между запросами → когда VIP мигрирует, браузерные пакеты (ACK/PSH) приходят на новую ноду без предшествующего SYN → нет conntrack-записи → RST. - -### Итоговая таблица причин - -| Причина | Механизм | Почему не curl | -|---|---|---| -| kube-vip ARP lease renewal | Смена ноды → conntrack orphan | Новый SYN всегда | -| `keepalive_timeout: 10s` | nginx закрывает idle conn → browser не замечает | Нет idle | - -Оба фактора вместе: рестарт сбрасывает состояние, 5-10 минут → kube-vip переизбирается / браузер держит соединение дольше 10s → RST. - -### Рекомендации - -1. **`keep-alive: "75"`** — исключить механизм #2 (самое простое, безопасное) -2. **`externalTrafficPolicy: Local`** — исключить механизм #1 (риск: потеря source IP для других сервисов) -3. **JS: Connection warmer** — GET на Flask при загрузке страницы (уже доказано тестом #2) -4. **JS: пересоздавать соединение** — использовать `fetch()` без keep-alive (credentials: 'omit' или отдельный subdomain) diff --git a/History/infra/2026-07-15-cluster-dump.md b/History/infra/2026-07-15-cluster-dump.md deleted file mode 100644 index d28cd7f..0000000 --- a/History/infra/2026-07-15-cluster-dump.md +++ /dev/null @@ -1,158 +0,0 @@ -# Cluster Dump — 2026-07-15 17:00 MSK - -## 1. НОДЫ (4 шт) - -| Нода | Роль | Taints | -|------|------|--------| -| iot-naeel-control-plane-xb699 | control-plane | `NoSchedule:control-plane` | -| iot-naeel-workers-vqphm-6f74n | workers | нет | -| iot-naeel-workers-vqphm-bhbvs | workers | нет | -| iot-naeel-workers-vqphm-v8zq4 | workers | нет | - -## 2. INGRESS (shturval-ingress-controller) - -### Поды (2 реплики) - -| Под | Нода | Возраст | -|-----|------|---------| -| ...86jsp | control-plane-xb699 | ~6ч | -| ...jm4kl | workers-vqphm-v8zq4 | ~6ч | - -### Сервис -- Тип: LoadBalancer -- External IP: 185.247.187.151 -- `externalTrafficPolicy: Cluster` -- NodePorts: 80:30739, 443:32391 - -### Ingress ConfigMap - -| Параметр | Значение | -|----------|----------| -| keep-alive | **10** | -| client-body-timeout | **10** | -| client-header-timeout | **10** | -| proxy-body-size | **8m** | -| upstream-keepalive-connections | **0** | -| use-http2 | false | -| worker-processes | 4 | -| error-log-level | info | - -### Helm -- Chart: shturval-ingress-controller-2.12.1 -- Ревизия 9 (сегодня 11:53) — `replicaCount: 2`, hostPort enabled -- Ревизия 8 (сегодня 02:00) — то же самое -- Более старых ревизий нет (почищены) - -### Affinity (ingress deploy) -```yaml -preferredDuringScheduling: - - weight: 10 → избегать control-plane - - weight: 80 → предпочитать node-role: ingress - - weight: 50 → предпочитать node-role: infra -``` - -### События (каждые 51 сек!) -``` -Warning SyncLoadBalancerFailed failed to ensure load balancer: no address pools could be found -Normal EnsuringLoadBalancer Ensuring load balancer -``` - -## 3. KUBE-VIP - -### DaemonSet: shturval-vip (4 пода, на ВСЕХ 4 нодах) -- Image: r.shturval.tech/kube-vip:v1.0.0 -- ARP mode: `vip_arp: true` -- Election: `svc_election: true` -- Leaderelection: `vip_leaderelection: false` -- Lease: `sht-uservip-cp-lock` - -### Cloud Provider: shturval-vip-provider (1 под, на bhbvs) -- Image: r.shturval.tech/kubevip/kube-vip-cloud-provider:v0.0.12 -- Ищет ConfigMap: `kubevip` в неймспейсе `kube-system` - -### ❌ ConfigMap `kubevip` в `kube-system` — **ПУСТОЙ!** -```yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: kubevip - namespace: kube-system -data: {} # <-- НЕТ ДАННЫХ -``` - -**Это причина ошибки «no address pools could be found»!** - -## 4. DRHIDER (pythonk8s) - -### Деплой -- 1 реплика -- Ревизия: 49 -- Инит-контейнер: клонирует из Git, потом pip install + python app.py -- Ресурсы: 500m CPU, 1Gi RAM -- Проба: TCP :5000 - -### ❌ PodAffinity (добавлен сегодня 16:46, непрошеный) -```yaml -requiredDuringScheduling: - podAffinity → ingress pod на той же ноде -``` - -### Сервис -- ClusterIP: 10.102.171.63 -- Port 80 → targetPort 5000 -- `internalTrafficPolicy: Cluster` - -### Ingress (drhider) -- Host: drhider.pythonk8s.dev.nubes.ru -- Аннотации: proxy-body-size=1024m, connect=120s, read/send=600s -- TLS: letsencrypt-prod - -### Поды -| Под | Нода | Возраст | -|-----|------|---------| -| pythonk8s-6698c6c78-dkwsq | workers-vqphm-v8zq4 | ~15 мин | - -Drhider на одной ноде с ingress-подом jm4kl. - -## 5. ВСЕ INGRESS-РЕСУРСЫ - -| Хост | Неймспейс | Возраст | -|------|-----------|---------| -| drhider.pythonk8s.dev.nubes.ru | 20a75175-... | 4д | -| loadtest.pythonk8s.dev.nubes.ru | 9039a501-... | 5д | -| contractor.pythonk8s.dev.nubes.ru | b4523aba-... | 7ч | -| capire.kube5s.ru | default | 54д | -| grafana.kube5s.ru | grafana | 66д | -| keycloak.k8c.ru | keycloak | 96д | -| qu.kube5s.ru | shared-sqs | 93д | -| iot.kube5s.ru | sless | 94д | -| terra.k8c.ru | terra | 92д | - -## 6. CILIUM - -4 пода (по одному на ноду), все Running. MTU был изменён на 1400 (по STATE). - -## 7. КЛЮЧЕВЫЕ НАХОДКИ - -### 🔴 ConfigMap `kubevip` пустой -kube-vip-cloud-provider не может найти address pools → каждые 51 сек ошибка SyncLoadBalancerFailed. При этом `185.247.187.151` работает через ARP-mode kube-vip DaemonSet (не через cloud provider). Вероятно, это не fatal, но указывает на недонастроенную интеграцию. - -### 🔴 Ingress ConfigMap отличается от ожидаемого -- keep-alive: 10 (должен быть 75) -- client-body-timeout: 10 (должен быть 60) -- client-header-timeout: 10 (должен быть 30) -- proxy-body-size: 8m (должен быть 1024m) - -Но для drhider это переопределено в аннотациях ingress-ресурса. - -### 🟡 Ingress 2 реплики на 4 нодах с kube-vip -kube-vip анонсирует 185.247.187.151 на ВСЕХ 4 нодах, но ingress-поды только на 2. При `externalTrafficPolicy: Cluster` трафик на ноды без ingress: SNAT → кросс-нода → риск conntrack RST. - -### 🟡 Helm: 2 апгрейда сегодня -Ревизии 8 (02:00) и 9 (11:53) сегодня. Кто-то обновлял ingress. `replicaCount: 2` в обеих. - -### 🟢 Аннотации drhider ingress — хорошие -proxy-body-size=1024m, connect=120s, read/send=600s. Переопределяют дефолты ConfigMap. - -### 🟢 Cilium — стабильный -4 пода, все Running, MTU 1400. diff --git a/History/infra/2026-07-15-rst-root-cause.md b/History/infra/2026-07-15-rst-root-cause.md deleted file mode 100644 index 9e80f66..0000000 --- a/History/infra/2026-07-15-rst-root-cause.md +++ /dev/null @@ -1,204 +0,0 @@ -# DrHider — итоговая диагностика ERR_CONNECTION_RESET - -**Дата:** 2026-07-15 (финал) -**Версия:** v0.0.29 - ---- - -## Резюме (моё мнение) - -**`keep-alive: 10` — единственная причина ERR_CONNECTION_RESET.** Всё остальное (conntrack, hostPort, ARP flapping, svc_election) — шум, который мы искали 2 дня. - -### Почему это так - -VIP `185.247.187.151` жёстко привязан к control-plane-xb699 через аннотацию `kube-vip.io/vipHost`. Никакого flapping нет — трафик всегда приходит на одну ноду. Второй ingress-под на v8zq4 — избыточен, но не мешает. - -Сценарий: -1. Браузер загружает страницу — opens TCP -2. Пользователь выбирает файлы — проходит >10 сек -3. Nginx (keep-alive=10) закрывает idle соединение -4. Браузер не замечает FIN (буферизация ОС) -5. POST 63KB летит в мёртвый сокет → RST - -### Почему предыдущие гипотезы неверны - -| Гипотеза | Опровержение | -|----------|-------------| -| kube-vip ARP flapping | `vipHost` фиксирует VIP на одной ноде. Lease мёртв — плевать | -| hostPort на 2 из 4 нод | Трафик не на все 4 ноды, а только на control-plane | -| upstream keepalive stale | `keepalive=0` не помог | -| MTU | Решено ещё 14-го, не при чём | -| conntrack | ETPolicy Cluster не при чём — трафик на одну ноду | - -### Что подтверждает keep-alive гипотезу - -| Тест | Объяснение | -|------|-----------| -| Сразу после рестарта ingress → OK | Все соединения свежие | -| Через 5-10 мин → RST | Соединения постарели >10с | -| GET/health → POST → OK | Новое живое соединение | -| curl с ВМ → всегда OK | curl открывает новый SYN каждый раз | -| `keep-alive: 75` + JS warmup → OK на 10+ мин | Увеличили окно, warmup греет | - -## Хронология - -| Дата | Событие | -|------|---------| -| 2026-07-11 | Исходный деплой | -| 2026-07-14 (день) | MTU fix (Cilium 1400) — решило 51с задержку | -| 2026-07-14 (вечер) | RST диагностика: keep-alive 75 + JS warmup — частично | -| 2026-07-15 (11:53) | Helm ревизия 9 — сброс ConfigMap на дефолты (keep-alive:10) | -| 2026-07-15 | podAffinity `required` на drhider (ручной kubectl edit) | -| 2026-07-15 (16:00) | Полная диагностика: hostPort vs cloud LB | - ---- - -## Две независимые проблемы - -### Проблема #1: MTU (решена 2026-07-14) - -**Симптом:** 51с задержка при POST любого размера через внешний VIP. -**Причина:** Geneve +50, underlay 1450, дефолт 1500 → фрагментация/дроп. -**Решение:** Cilium `mtu: 1400`. - -### Проблема #2: ERR_CONNECTION_RESET (хост порт, решена 2026-07-15) - -**Симптом:** Браузер получает TCP RST при POST 63KB через 5-10 мин после рестарта ingress. -**Ранее считалось:** keep-alive 10с или kube-vip conntrack. -**Реальная причина:** Штурвал балансирует трафик на **все 4 ноды**, но ingress стоит `replicas: 2` с **hostPort** — порт 80/443 открыт только на 2 нодах. - -``` -Browser → cloud LB → любая из 4 нод - ├── нода с ingress-подом → hostPort 80 → nginx → ✅ - └── нода БЕЗ ingress-пода → порт 80 не слушается → RST ❌ -``` - -**50% запросов попадает на пустую ноду → RST.** - ---- - -## Подтверждающие данные - -### Helm ревизии (дамп) - -| Ревизия | Время | replicaCount | Другие изменения | -|---------|-------|-------------|------------------| -| 1 (деплой) | 2026-07-11 | 2 | — | -| 8 | 2026-07-15 02:00 | **2** | — | -| 9 | 2026-07-15 11:53 | **2** | — | - -Helm **не менял** replicaCount. Всегда 2. Но ConfigMap сбрасывал на дефолты (keep-alive: 10, proxy-body-size: 8m и т.д.). - -### Ingress ConfigMap (последствия Helm upgrade) - -```yaml -# Было (ручные правки 2026-07-14) → Стало (после ревизии 9) -keep-alive: "75" → "10" # вернулось на дефолт Штурвала -proxy-body-size: "1024m" → "8m" # тоже сброшено -error-log-level: debug → info # сброшено -upstream-keepalive-connections: "0" → сохранилось -``` - -### kube-vip svc_election — мёртв - -```yaml -# Lease ingress/kubevip-shturval-ingress-controller-controller -holderIdentity: "" # пусто — никто не держит -leaseDurationSeconds: 1 # аномально короткий (норма 15-30) -renewTime: 2026-07-11T14:55:28Z # 4 дня назад не обновлялся -leaseTransitions: 17 # но было 17 переходов -``` - -svc_election никогда не работал. VIP назначен через `ipMode: VIP` в статусе сервиса, работает на уровне облака (BGP/маршрутизация). - -### hostPort - -```yaml -# В шаблоне пода ingress (Helm template) -hostPort: 80 -hostPort: 443 -``` - -Включён в ревизиях 8 и 9. hostPort = порт слушается ТОЛЬКО на нодах где стоит под. - -### podAffinity на drhider — временный костыль - -```json -{ - "podAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": [{ - "labelSelector": {"matchLabels": {"app.kubernetes.io/name": "shturval-ingress-controller"}}, - "namespaceSelector": {"matchLabels": {"name": "ingress"}}, - "topologyKey": "kubernetes.io/hostname" - }] - } -} -``` - -Добавлен через `kubectl edit`, НЕ через Helm. Привязывает drhider к ноде с ingress-подом. Это НЕ лечит RST (RST на уровне VIP→нода, не на уровне нода→drhider). При следующем `helm upgrade` — исчезнет. - -### Kube-vip DaemonSet — не участвует - -```yaml -vip_arp: true # ARP включён -vip_leaderelection: false # нет CP election -svc_election: true # должен быть — но lease мёртв -``` - -Даемоны не логгируют VIP `185.247.187.151`. Трафик распределяет облако, не kube-vip. - ---- - -## Решение - -**Единственное полное решение:** ingress-под на каждой ноде куда приходит трафик. - -### Вариант 1 (рекомендуемый): увеличить replicas - -```yaml -# Helm values -shturval-ingress-controller: - replicaCount: 4 -``` - -Или выяснить у DevOps сколько нод в LB-пуле Штурвала и поставить `replicaCount = число нод`. - -### Вариант 2 (если балансировка на все worker'ы): DaemonSet - -```yaml -# Helm values -shturval-ingress-controller: - kind: DaemonSet -``` - ---- - -## Что делать сейчас (ручной воркараунд) - -### 1. Починить ConfigMap (срочно) - -```bash -kubectl patch configmap -n ingress shturval-ingress-controller-controller --type merge \ - -p '{"data":{"keep-alive":"75","proxy-body-size":"1024m","client-body-timeout":"60","client-header-timeout":"30"}}' -``` - -### 2. Масштабировать ingress (временный фикс) - -```bash -kubectl scale deploy -n ingress shturval-ingress-controller-controller --replicas=4 -``` - -Поды раскидаются по всем 4 нодам → hostPort на всех → 0% RST. - -⚠️ **Предупреждение:** после следующего `helm upgrade`: -- ConfigMap сбросится (нужно править Helm values) -- replicas вернётся на 2 -- podAffinity на drhider исчезнет - ---- - -## Вопросы к DevOps - -1. **Сколько нод в LB-пуле Штурвала?** (на какие ноды облако направляет трафик ingress?) -2. **Можно ли увеличить `replicaCount` до числа нод?** -3. **Как правильно изменить Helm values для ingress?** (чтобы ConfigMap не сбрасывался при upgrade) diff --git a/History/infra/2026-08-19-remove-process-names-http2.md b/History/infra/2026-08-19-remove-process-names-http2.md deleted file mode 100644 index a4f3256..0000000 --- a/History/infra/2026-08-19-remove-process-names-http2.md +++ /dev/null @@ -1,37 +0,0 @@ -# v0.0.46 — удалён process_names + git push HTTP/2 проблема — 2026-08-19 - -**Дата:** 2026-08-19 -**Версия:** 0.0.45 → 0.0.46 - ---- - -## Удалён тестовый API process_names - -- `process_names` (обфускация файлов по именам из TEST_INPUT_DIR) **удалён**. -- Причина: сервер не может читать локальные файлы клиента (нет доступа к его - машине). Файлы в поде — неудобно. API бесполезен. -- Удалены: endpoint, `TEST_INPUT_DIR`/`_TEST_API_ENABLED`, `import os`, History-файл. -- Версия 0.0.46. - -## Находка: git push к gitea зависал (HTTP/2) - -**Симптом:** `git push origin master` висел бесконечно (таймауты), хотя -`git ls-remote` и `curl` к gitea работали (200, быстро). - -**Причина:** git использовал **HTTP/2** для отправки pack; шлюз -`gitea.services.ngcloud.ru` блокировал передачу данных по HTTP/2 -(та же проблема, что была в drhider с HTTP/2 — ERR_HTTP2_PROTOCOL_ERROR). - -**Решение:** push через HTTP/1.1 мгновенно прошёл: -```bash -git -c http.version=HTTP/1.1 push origin master -# и закреплено в конфиге: -git config http.version HTTP/1.1 -``` - -**Проверка:** `8d7a7df..d2f2bd3 master -> master`, синхронизировано. - -## Полезно -- На этой машине/шлюзе для git (и возможно других HTTP/2 клиентов) использовать - HTTP/1.1. -- git config `http.version HTTP/1.1` уже установлен. diff --git a/History/infra/2026-08-21-pattern-vm-buffer-pull-plan.md b/History/infra/2026-08-21-pattern-vm-buffer-pull-plan.md deleted file mode 100644 index a71c625..0000000 --- a/History/infra/2026-08-21-pattern-vm-buffer-pull-plan.md +++ /dev/null @@ -1,124 +0,0 @@ -# ПЛАН: перенос паттерна «ВМ-буфер + pull» в drhider - -_Дата: 2026-08-21. Основа: `contracts/loadtest/History/2026-08-21-pattern-vm-buffer-pull.md`._ -_Статус: РЕАЛИЗОВАНО 2026-08-21 (v0.0.58), см. `2026-08-21-vm-upload-implemented.md`._ - ---- - -## Цель - -drhider (Managed Flask, Штурвал, `drhider.pythonk8s.dev.nubes.ru`) получает файлы через -`POST /api/upload` (multipart). Входной шлюз managed-кластера обрывает тела >~64КБ -(HTTP 000 ~10с). Решение — загружать файл на ВМ напрямую, Flask тянет сам (egress). - -## Директива (обязательно) - -**ВСЕ файловые операции загрузки в Flask (из браузера или вообще снаружи) — ТОЛЬКО через ВМ.** -Никаких прямых `POST /api/upload` с телом файла во Flask. - -## Среда (уточнено 2026-08-21) - -- `iot-naeel` — СОБСТВЕННЫЙ кластер, параметры/аннотации правились через kubectl. Пока - тестируем/смотрим поведение на нём (лимит 64КБ там может не воспроизводиться). -- В дальнейшем деплой — на ОБЫЧНЫЙ managed-кластер (лимит 64КБ будет реальным). -- ВМ — ОДНА (`5.172.178.213`, `contracts.kube5s.ru`), та же, что для loadtest. Location - `/drhider-upload/` добавляется в существующий nginx рядом с `/lt-serve/`. -- Вывод: VM-путь строим СЕЙЧАС, независимо от поведения iot-naeel — прод всё равно на обычном. - -## Текущий поток - -`uploadFiles()` (index.html ~413): по одному файлу в цикле → `FormData` → `POST /api/upload`. -Бэк: `MAX_CONTENT_LENGTH=200МБ`, сессия 500МБ. HTTP-клиент — **httpx** (не requests). - -## Целевой поток - -``` -Браузер ──(PUT, файл целиком)──▶ ВМ nginx (без лимита 64КБ) - │ └─ получил URL + токен - │ - └──▶ Flask POST /api/upload_refs (JSON: [{name,size,url}], <64КБ) - │ - ▼ - Flask egress GET с ВМ ──▶ читает файл ──▶ add_file() ──▶ дальше как сейчас -``` - -## Изменения (4 места) - -### 1. ВМ `5.172.178.213`, `nginx-contracts.conf` — приём загрузки -Новый location (рядом с `/lt-serve/`), приём через PUT + статическая отдача: -```nginx -location /drhider-upload/ { - alias /var/www/drhider-upload/; - dav_methods PUT; - create_full_put_path on; - client_max_body_size 1024m; - # CORS: браузер с drhider.pythonk8s.dev.nubes.ru шлёт PUT (не simple → preflight) - add_header Access-Control-Allow-Origin https://drhider.pythonk8s.dev.nubes.ru always; - add_header Access-Control-Allow-Methods 'PUT, GET, OPTIONS, DELETE' always; - add_header Access-Control-Allow-Headers 'Content-Type' always; - if ($request_method = OPTIONS) { return 204; } -} -``` -- Каталог `/var/www/drhider-upload/` (владелец `www-data`), НЕ `/home/naeel` (403). -- Порядок: backup → правка → `nginx -t` → `systemctl reload nginx`. - -### 2. Flask — новый endpoint pull (`site/routes/api_bp.py`) -```python -@api_bp.route("/upload_refs", methods=["POST"]) -def upload_refs(): - data = request.get_json(silent=True) or {} - sid = data.get("session") or create_session() - refs = data.get("files") or [] - if not refs: - return jsonify({"ok": False, "error": "No files"}), 400 - added = 0 - with httpx.Client(timeout=120, follow_redirects=True) as client: - for ref in refs: - name, url = ref.get("name"), ref.get("url") - if not name or not url: - continue - content = b"".join(client.stream("GET", url).iter_bytes()) # egress, по частям - if not add_file(sid, name, content): - return jsonify({"ok": False, "error": "Session not found"}), 404 - added += 1 - return jsonify({"ok": True, "session": sid, "count": file_count(sid)}) -``` -- Импорт `httpx` (уже в requirements.txt) и `file_count` из session. -- `stream(...).iter_bytes()` — НЕ `content` целиком (OOM при 100МБ+). -- Обработка ошибок egress (таймаут/403/404) → вернуть 502 с именем файла. - -### 3. Фронт (`site/templates/index.html`, `uploadFiles()`) -Разбить «Фазу 1» на два шага: -- **Шаг 1a:** каждый файл → `fetch(<ВМ>/drhider-upload//, {method:'PUT', body:file})`, - собирать `[{name, size, url}]`. Прогресс — `xhr.upload.onprogress` заменить на `fetch` + - `ReadableStream` (или оставить XHR, но на PUT к ВМ). -- **Шаг 1b:** один `POST /api/upload_refs` с JSON `{session, files:[...]}` (<64КБ). -- Токен: `crypto.randomUUID()`. URL ВМ — константа/`data-атрибут`. -- Показывать прогресс «загрузка на ВМ» отдельно от «загрузка в Flask». - -### 4. `requirements.txt` — без изменений (httpx уже есть). - -## Безопасность и жизненный цикл -- Токен в пути URL — только `crypto.randomUUID()`, не переиспользуется. -- Flask после `add_file` делает `DELETE` на URL ВМ (или ВМ чистит по TTL — cron/systemd-timer - `find /var/www/drhider-upload -mmin +30 -delete`). -- ВМ отдаёт файл только по валидному URL с токеном (нет открытого листинга: `autoindex off`). - -## Грабли (из pattern-дока + drhider) -- CORS: PUT — не simple-метод → браузер шлёт OPTIONS preflight; nginx обязан отвечать 204. -- `site/` конфликтует со stdlib `site.py` (для loadtest; у drhider — Штурвал, без gunicorn). -- egress через `httpx` с `stream`, timeout 120; большие ОТВЕТЫ проходят (проверено 50МБ). -- OOM: не читать файл в память целиком; сессия 500МБ уже ограничивает. -- Локальные curl на Krupski идут через прокси `172.17.192.1:10808` → для теста ВМ `--noproxy '*'`. - -## Порядок работ + проверка -1. ВМ nginx (location + каталог + CORS) → проверить `curl --noproxy '*' -X PUT`. -2. Flask `/api/upload_refs` → проверить `python3 -c "import py_compile; py_compile.compile(...)"`. -3. Фронт `uploadFiles()` → `node -c` нет (это EJS/HTML+JS внутри шаблона) — ручная проверка. -4. Сквозной тест: файл 10МБ → ВМ → Flask → обфускация → ZIP. -5. Commit + push (правило: после правки + bump версии `VERSION` в `site/app.py`). - -## Решения (закрыты) - -- Кластер: сейчас `iot-naeel` (тест), прод — обычный managed. VM-путь обязателен. -- ВМ: одна, `contracts.kube5s.ru` (`5.172.178.213`), общая с loadtest — добавляем location. diff --git a/History/infra/2026-08-21-vm-files-api-plan.md b/History/infra/2026-08-21-vm-files-api-plan.md deleted file mode 100644 index 9d4e66e..0000000 --- a/History/infra/2026-08-21-vm-files-api-plan.md +++ /dev/null @@ -1,75 +0,0 @@ -# ПЛАН: универсальный файловый сервис на ВМ (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. Ключи приложений — придумать сейчас или сервис сгенерирует при первом старте? diff --git a/History/infra/2026-08-21-vm-upload-implemented.md b/History/infra/2026-08-21-vm-upload-implemented.md deleted file mode 100644 index 33c7885..0000000 --- a/History/infra/2026-08-21-vm-upload-implemented.md +++ /dev/null @@ -1,43 +0,0 @@ -# ВМ-загрузка в drhider — реализовано (v0.0.58, 2026-08-21) - -_Реализация паттерна «ВМ-буфер + pull» по плану `2026-08-21-pattern-vm-buffer-pull-plan.md`._ -_Снимок до изменений: ветка `save-pre-vm-upload-2026-08-21`._ - -## Что сделано - -1. **Flask** (`site/routes/api_bp.py`) — новый `POST /api/upload_refs`: - принимает JSON `{session, files:[{name,size,url}]}`, тянет каждый файл с ВМ - ИСХОДЯЩИМ GET (httpx, stream по частям), кладёт в сессию `add_file()`, - после успешного pull делает DELETE с ВМ. Ошибки egress → 502. -2. **Фронт** (`site/templates/index.html`, `uploadFiles()`) — Фаза 1 переделана: - - 1a: каждый файл → `PUT` на ВМ `https://contracts.kube5s.ru/drhider-upload/<токен>_` - (XHR, прогресс по-прежнему в строке), собирает refs. - - 1b: один маленький `POST /api/upload_refs` (<64КБ). - - Токен — `crypto.randomUUID()`. Константа `VM_UPLOAD_URL`. -3. **ВМ** (`5.172.178.213`) — nginx `nginx-contracts.conf`: - - `location /drhider-upload/` → `alias /var/www/drhider-upload/`, - `dav_methods PUT DELETE`, `create_full_put_path on`, `client_max_body_size 1024m`, - CORS-заголовки для `drhider.pythonk8s.dev.nubes.ru`, OPTIONS → 204. - - Каталог `/var/www/drhider-upload/` (www-data). - - Backup: `/tmp/nginx-contracts.conf.bak.20260821`. - - TTL-чистка в root cron: `*/5 * * * * find /var/www/drhider-upload -type f -mmin +30 -delete`. -4. **Версия** — `site/app.py`: 0.0.57 → 0.0.58. - -## Отклонение от плана -- URL файла на ВМ — БЕЗ имени: `.../drhider-upload/<токен>_` (имя — только в метаданных). - Иначе кириллица/пробелы в имени ломали бы URL. - -## Тесты (2026-08-21) -- ВМ: PUT 10МБ → 201 (1.0с); GET → 200, 10485760B; OPTIONS preflight → 204 с CORS; - DELETE → 204; GET после DELETE → 404. -- Сквозной: PUT на ВМ → `POST /api/upload_refs` → 200, файл в сессии (10485760B), - файл с ВМ удалён (404). -- `py_compile` api_bp/app — OK; `node --check` JS из index.html — OK. - -## Замечания -- ГРАБЛИ: на Krupski `dd` алиасится на `docker compose down` — для генерации файлов - использовать `head -c N /dev/urandom > file`. -- Локальные curl/httpx к contracts.kube5s.ru — через прокси (`172.17.192.1:10808`): - для тестов `--noproxy '*'` / `NO_PROXY=contracts.kube5s.ru`. -- Прод-безопасность (позже): токены без TTL-времени в самом URL, авторизация на ВМ - (сейчас ключ — случайный UUID, файл живёт ≤30 мин по cron). diff --git a/History/infra/2026-08-24-gitea-cert-transient.md b/History/infra/2026-08-24-gitea-cert-transient.md deleted file mode 100644 index 8cdda62..0000000 --- a/History/infra/2026-08-24-gitea-cert-transient.md +++ /dev/null @@ -1,29 +0,0 @@ -# Временный сбой сертификата gitea и push (2026-08-24) - -_infra. Прецедент, чтобы не терять время в следующий раз._ - -## Симптом -`git push origin master` к `gitea.services.ngcloud.ru` падал: -``` -SSL: certificate subject name (*.ngcloud.ru) does not match target host name 'gitea.services.ngcloud.ru' -``` -При этом в браузере `https://gitea.services.ngcloud.ru/Nail/drhider.git` открывался. - -## Диагностика -- `gitea.services.ngcloud.ru` → `194.31.9.41` (стабильно, и с машины, и с ВМ). -- На `194.31.9.41:443` отдавался **wildcard `*.ngcloud.ru`** (SAN: `*.ngcloud.ru, ngcloud.ru`), - который НЕ покрывает `gitea.services.ngcloud.ru` (два уровня: `services.ngcloud.ru`). -- Внутренний ClusterIP `10.96.52.11` (gitea.mgmt.nubes.ru, hosts ВМ) недоступен ни с машины, ни с ВМ. -- На ВМ git-настроек нет (нет sslVerify=false), `credential.helper=store`. - -## Итог -Проблема была **на стороне gitea/ngcloud.ru** — временно отдавался неверный (wildcard) сертификат. -Через ~10-20 минут сертификат вернулся корректным (`CN = gitea.services.ngcloud.ru`, -SAN `DNS:gitea.services.ngcloud.ru`) → `git push` прошёл (`0479afb..e31476e`). - -## Урок -- Если push к gitea падает по SSL «*.ngcloud.ru doesn't match» — это временный сбой сертификата - на стороне gitea (не код). Проверить: `echo | openssl s_client -connect gitea.services.ngcloud.ru:443 -servername gitea.services.ngcloud.ru | openssl x509 -noout -subject -ext subjectAltName`. -- Не менять git config (`sslVerify=false`) и не синкать на ВМ — подождать и повторить push. -- Аналогичный сбой DNS был у `contracts.kube5s.ru` в 18:42 (см. `2026-08-24-pull-dns-transient.md`) — - вероятно, общее инфраструктурное происшествие ngcloud.ru в этот день. diff --git a/History/infra/2026-08-24-llm-key-secret.md b/History/infra/2026-08-24-llm-key-secret.md deleted file mode 100644 index 5ca3e5a..0000000 --- a/History/infra/2026-08-24-llm-key-secret.md +++ /dev/null @@ -1,20 +0,0 @@ -# Секрет-гигиена: LLM_API_KEY в git (2026-08-24) - -_infra/заметка. Решение пользователя: историю git НЕ переписывать._ - -## Что было -- `LLM_API_KEY` (sk-ucI5Yv…) попал в git при написании документации (коммит `0707d53`, - 2026-07-12) — переменные окружения Штурвала скопированы в README/DEPLOY дословно, секрет не вычищен. - -## Что сделано -- Ключ убран из текущих файлов: `README.md`, `docs/DEPLOY.md`, `History/plans/2026-07-12-audit-and-plan.md` - (коммит `f2141ec`, запушен). -- Проверено: в текущих файлах ключа нет. - -## Решение пользователя -- **Историю git не переписываем** (filter-repo/filter-branch — нет, «хуй с ним»). -- Ключ остаётся в git-истории (коммиты `0707d53`, `a2f754a`, `4c3f9d4` и др.), репозиторий публичный. - -## Рекомендация (не выполнено) -- Ротация `LLM_API_KEY` в Штурвале (старый считается скомпрометированным). Решение за пользователем. -- На будущее: секреты в env Штурвала, в git — только заглушки «(секрет)». diff --git a/History/infra/2026-08-24-pull-dns-transient.md b/History/infra/2026-08-24-pull-dns-transient.md deleted file mode 100644 index 2bb4df8..0000000 --- a/History/infra/2026-08-24-pull-dns-transient.md +++ /dev/null @@ -1,28 +0,0 @@ -# Разовый DNS-сбой pull: «Pull failed: [Errno -5] No address associated with hostname» (2026-08-24) - -_infra. Вопрос пользователя «ЭТО ЧТО ????» при загрузке папки CNC (220 файлов, отобрано 8)._ - -## Симптом -Фронт: «Ошибка передачи ссылок: Pull failed: [Errno -5] No address associated with hostname» -при нажатии «Обфусцировать». - -## Диагностика (факты) -- ВМ-буфер: `https://contracts.kube5s.ru/drhider-upload/` (nginx dav), host → 5.172.178.213. -- DNS снаружи (8.8.8.8) и на ВМ: `contracts.kube5s.ru` → 5.172.178.213 — ок; буфер отвечает 403. -- Под drhider: ns `20a75175-a58c-49cb-b8fa-e86367b1a8dc`, pod `pythonk8s-85d8f4dcc8-vjjts` - (создан ~18:36 МСК, редеплой 18:31-18:33). `getent`/`socket` из пода → 5.172.178.213 — ок. -- Логи пода: ЕДИНСТВЕННАЯ ошибка `upload_refs: pull error sid=66ce16c1cda9` в **15:42:43 UTC (18:42 МСК)**: - `connect_tcp.started host='contracts.kube5s.ru'` → `gaierror(-5)`. -- До неё (15:41:41 UTC / 18:41 МСК) сессия пользователя `3ec718dafa5b` с теми же 8 файлами - (info.txt, out1.txt, fatD.pdf, fatihaDownEdited.pdf, fatihaUPEdited.pdf, fatU.pdf, FT0.pdf, sha0.pdf) - УСПЕШНО обработана (worker done, 29.8с, tokens 2138). -- CoreDNS: 2 пода, 0 рестартов, 44d — стабильны. -- Живой тест сейчас: папка 2 txt → «✅ Обработано 2 файлов: 6.6с» — pull работает. - -## Вывод -**Разовый DNS-сбой** CoreDNS/upstream на `contracts.kube5s.ru` в узкий момент 18:42 МСК -(одна ошибка за весь лог). К выбору папки отношения нет; повторный запуск работает. - -## Рекомендация (опция) -Добавить ретраи pull в `upload_refs` (2-3 повтора с паузой при `ConnectError`/DNS-ошибке) — -v0.0.70. Решение за пользователем. diff --git a/History/infra/2026-08-24-two-clusters-deploy.md b/History/infra/2026-08-24-two-clusters-deploy.md deleted file mode 100644 index 15dccc1..0000000 --- a/History/infra/2026-08-24-two-clusters-deploy.md +++ /dev/null @@ -1,27 +0,0 @@ -# Деплой: два кластера, iot-naeel актуальный, naeel-test-3 — не погашен (2026-08-24) - -## Симптом -- health/HTML внешнего URL отдавали **0.0.67**, а под в ns 20a75175 на naeel-test-3 был **0.0.61** - (внутренний health 0.0.61, нет MAX_FILE_BYTES/cancel/ETA/UI). -- Часть запросов (upload_refs) попадала на старый под → тесты вели себя непредсказуемо - (60МБ «принимался»). - -## Причина (подтверждена kubeconfig'ами обоих кластеров) -- drhider развёрнут на **двух кластерах** в одном namespace-ID `20a75175-...`: - - **iot-naeel**: под `pythonk8s-cf686f894-96l7m` (возраст ~42м — свежий редеплой), **v0.0.67**; - ingress `drhider.pythonk8s.dev.nubes.ru` → **185.247.187.151**. - - **naeel-test-3**: под `pythonk8s-797ddd69df-dmdg6` — **старый, не погашенный** (был 0.0.61); - ingress → 185.247.187.147. -- **DNS `drhider.pythonk8s.dev.nubes.ru` → 185.247.187.151 = iot-naeel** → внешний URL обслуживает - iot-naeel (0.0.67). naeel-test-3 в DNS не участвует, но остаётся живым инстансом. - -## Что сделано -- kubeconfig'и на ВМ обновлены на оба кластера (`~/.kube/config` iot-naeel, `~/.kube/config-naeel-test-3`). -- Проверены оба пода: и iot-naeel, и naeel-test-3 теперь **v0.0.67** (naeel-test-3 обновлён - пересозданием пода — `scale 0 → 1` заставил git-clone master@0.0.67). -- Внешний бэкенд — iot-naeel (0.0.67), все тесты прогнаны на нём (см. tests/2026-08-24-test-batch-v067.md). - -## Осталось (рекомендация) -- В Штурвале **погасить/удалить инстанс drhider на naeel-test-3** (ns 20a75175) — он не нужен, - DNS ведёт на iot-naeel. Оставить один актуальный (iot-naeel, 0.0.67). -- Проверить прочие `pythonk8s` на iot-naeel (contractor/loadtest/polygon/atest) — это другие приложения, не drhider. diff --git a/History/infra/2026-08-24-upload-logic-schema.md b/History/infra/2026-08-24-upload-logic-schema.md deleted file mode 100644 index f55c83d..0000000 --- a/History/infra/2026-08-24-upload-logic-schema.md +++ /dev/null @@ -1,43 +0,0 @@ -# Схема логики загрузки + найденные баги/несоответствия (2026-08-24) - -_infra. Подробная схема по коду: index.html (фронт), api_bp.py, session.py, obfuscator.py, extractor.py._ - -## Участники -- **Браузер** — index.html: выбор файлов/папки, PUT на ВМ, SSE-прогресс, статусы. -- **ВМ-буфер** — nginx dav `https://contracts.kube5s.ru/drhider-upload/` (CORS разрешён только для origin `https://drhider.pythonk8s.dev.nubes.ru`). -- **Бэк** — Flask (api_bp.py): upload_refs (pull), process_stream (SSE, воркер-поток), session.py (лимиты), drhider (extractor→scanner→replacer→builder). - -## Этап 0 — выбор файлов (браузер) -1. `fileInput` (multiple, accept) — для `.zip` → `listZipFiles` (раскрытие в браузере, allowedExt = .pdf .doc .docx .txt .md) → `addFileWithDedup`; иначе — как есть. -2. `folderInput` (`webkitdirectory`) — рекурсивно: `webkitRelativePath.slice(1)` (отбрасываем верхнюю папку), zip раскрывается с префиксом пути, документы — с относительным путём, прочее — пропуск. -3. `addFileWithDedup`: дедуп (имя+размер → пропуск; коллизия имени → суффикс `_2`); лимиты 50МБ/файл и 500МБ/сессия → `overNames` («🔥 не учитывается» в обычной таблице). - -## Этап 1 — загрузка (uploadFiles, фаза 1) -4. `toSend` = файлы БЕЗ `overNames`; over-файлы сразу получают статус «пропущен (лимит)» (v0.0.71). -5. Для каждого файла: **PUT** на `VM_UPLOAD_URL + token + '_' + k` (имя в URL не несётся) → прогресс % в ячейке → `✓ N KB/s`. `refs.push({name, size, url})`. Таймаут 300с. -6. **POST /api/upload_refs** {session, files: refs}: - - бэк: для каждого ref: `size > 50МБ` → delete+skip; `GET url` (pull egress, timeout 120с) → `content`; `content > 50МБ` → delete+skip; `add_file(sid, name, content)` (лимит сессии 500МБ); `delete url` с ВМ. - - ошибка сети/DNS → **502 «Pull failed»** (весь запрос падает). -7. Тест-режим `?upload-only=1` — стоп после фазы 1. - -## Этап 2 — обработка (SSE, фаза 2) -8. `EventSource /api/process_stream/`; воркер-поток → `obfuscate_files(all_files)`. -9. **obfuscator (проход 1)**: `expand_zips` (повторная распаковка zip, basename, защита от бомб) → `_dedupe_file_names` → для каждого: `extract_text` (исключение → `skipped`, событие `done` на этапе извлечения) → `scan_regex` → `extract_done` (total_chars, per_file) → `scan_llm_ner` (чанки: file_start/file_chunk/file_done; отмена → CancelRequested). -10. **проход 2**: `apply_replacements` для каждого (битые/скан → пропуск; при отмене — только `llm_done`) → `done` → `build_zip` + `build_mapping_csv` → событие `result`/`cancelled`. -11. Фронт: 3-секционная таблица (Обработанные / Текущий / Ожидают); статусы: `done` (до extract_done = битый → **skipped «не извлечён»** v0.0.71), `current`, `analyzed`, `pending`; `complete`/`cancelled` → статистика, кнопки ZIP/CSV, заморозка сессии. - -## Найденные баги и несоответствия -1. **[исправлено v0.0.71]** Статус «пропущен» использовался для ДВУХ разных причин: лимит и «не извлёкся». Теперь: «пропущен (лимит)» и «не извлечён» (пустой/битый/скан). -2. **[исправлено v0.0.71]** over-файлы во время обработки попадали в группу «Ожидают обработки» (нет procState → pending) с оценкой времени. Теперь — отдельная группа «⛔ Пропущены (сверх лимита)». -3. **[открыто]** `upload_refs` неатомарен: при ошибке pull (DNS/сеть) часть файлов уже добавлена в сессию и удалена с ВМ, но фронт получает 502 и бросает — сессия-сирота, файлы на ВМ частично остаются (инцидент 18:42). Нужны ретраи/атомарность. -4. **[открыто]** `upload_refs` при превышении суммарного лимита сессии отвечает «Session not found» (404) — add_file возвращает False и для отсутствия сессии, и для лимита; сообщение неверное. -5. **[открыто, риск]** Повторный `expand_zips` на бэке (с `os.path.basename` — обрезает пути) при отправке zip как есть (v0.0.69) ломает соответствие idx: количество файлов бэка ≠ фронта → `sendIdx[d.idx]` = undefined. Обычно не срабатывает (фронт сам раскрывает), но риск при расхождении allowedExt. -6. **[открыто]** allowedExt не совпадают: фронт раскрывает из zip только документы, бэк `expand_zips` берёт ВСЕ файлы (без фильтра) — расхождение поведения при zip «как есть». -7. **[открыто, косметика]** Счётчик «Добавлено из папки: N» завышается при дедупе (`added++` безусловно после `addFileWithDedup`). -8. **[открыто]** Три разных отображения одной причины (лимит) до обработки / при старте / в обработке — унифицировано в v0.0.71 (группа over). - -## Проверка -- `node --check` — OK, `py_compile` — OK. -- Локально (v0.0.71): группа «⛔ Пропущены (сверх лимита)» + «пропущен (лимит)» — работает. - Полный цикл локально невозможен из-за CORS ВМ-буфера (разрешён только origin прода). -- Версия 0.0.70 → 0.0.71. diff --git a/History/llm/2026-08-19-llm-metrics.md b/History/llm/2026-08-19-llm-metrics.md deleted file mode 100644 index 2570bdc..0000000 --- a/History/llm/2026-08-19-llm-metrics.md +++ /dev/null @@ -1,45 +0,0 @@ -# v0.0.34 — вывод токенов и времени LLM — 2026-08-19 - -**Дата:** 2026-08-19 -**Версия:** 0.0.33 → 0.0.34 - ---- - -## Суть - -Пользователь замечает, что обфускация долгая. Добавлен вывод в UI: -- суммарных токенов LLM за прогон; -- суммарного времени работы LLM за прогон. - -Строка статуса после завершения: «✅ Обработано N файлов за Xс · LLM: Yс · Z токенов». - -## Изменения - -### `drhider/llm_client.py` -- В `LLMClient.__init__` добавлены кумулятивные счётчики: - - `tokens_prompt`, `tokens_completion`, `llm_sec` (float); - - свойство `tokens_total = prompt + completion`. -- В `complete()` после ответа: - - `self.llm_sec += r.elapsed.total_seconds()` — реальное время HTTP-вызова; - - `usage` (из `r.json()`) → `self.tokens_prompt` / `self.tokens_completion` - (через `.get(..., 0)` — устойчиво к отсутствию `usage`). -- Возврат `complete()` не изменён (по-прежнему `content`) — `scan_llm_ner` не трогали. - -### `site/routes/api_bp.py` -- В `worker()` после `obfuscate_files` в очередь `result` добавляется - `stats = {tokens: llm.tokens_total, llm_sec: round(llm.llm_sec, 1)}`. -- В `event: complete` данные: `{total, tokens, llm_sec}`. - -### `site/templates/index.html` -- В обработчике `complete`: если `d.llm_sec > 0` — добавляет - «· LLM: {llm_sec}с» и, если `d.tokens > 0` — «· {tokens} токенов». - -### `site/app.py` -- `VERSION = "0.0.34"`. - -## Проверка -- `py_compile` + `node --check` — OK. -- Накопление: 2 вызова → 240 prompt + 90 completion = 330 total, llm_sec = 6.0. -- Устойчивость: ответ без `usage` → токены 0, время 1.0 — без падения. -- SSE end-to-end: `complete` приходит с `{"total":1,"tokens":0,"llm_sec":0.0}` - (локально без ключа LLM — 0; в кластере с ключом — ненулевые). diff --git a/History/llm/2026-08-19-llm-stats-block.md b/History/llm/2026-08-19-llm-stats-block.md deleted file mode 100644 index 362ff53..0000000 --- a/History/llm/2026-08-19-llm-stats-block.md +++ /dev/null @@ -1,41 +0,0 @@ -# v0.0.37 — крупный блок статистики LLM в UI — 2026-08-19 - -**Дата:** 2026-08-19 -**Версия:** 0.0.36 → 0.0.37 -**Файл:** `site/templates/index.html`, `site/app.py` (версия) - ---- - -## Суть - -Раньше статистика LLM («... LLM: 5.3с · 1894 токенов») была МЕЛКИМ текстом -в статус-строке под кнопкой — юзер её не замечал. Теперь после обработки -показывается **крупный заметный блок** «📊 Итоги обработки»: - -| Показатель | Значение | -|---|---| -| Общее время | крупное число, с | -| Время ИИ | крупное число, с | -| Токены ИИ | крупное число | - -Оформление: зелёная плашка (фон-градиент, рамка 2px), три белые карточки, -значения шрифтом 24px жирным. Блок скрыт до завершения и при сбросе. - -## Изменения (`index.html`) -- CSS: `.stats-block`, `.stats-title`, `.stats-row`, `.stat-item`, - `.stat-label`, `.stat-value`. -- HTML: блок `#statsBlock` с `#stTotalTime`, `#stLlmTime`, `#stLlmTokens` - (после кнопок скачивания). -- JS: - - `complete`-обработчик — заполняет и показывает блок (общее время всегда; - время ИИ и токены — если `llm_sec > 0`, иначе «—»); - - `resetAll` — скрывает блок. - -## Проверка -- `node --check` — OK. -- Визуально через браузер: блок появился полноширинным, три карточки, - значения крупно — заметно. -- Версия `app.VERSION = 0.0.37`. - -## Бэкенд -- Не менялся (метрики `tokens`/`llm_sec` в `event: complete` уже есть с v0.0.34). diff --git a/History/llm/2026-08-20-llm-full-chunking.md b/History/llm/2026-08-20-llm-full-chunking.md deleted file mode 100644 index d879243..0000000 --- a/History/llm/2026-08-20-llm-full-chunking.md +++ /dev/null @@ -1,41 +0,0 @@ -# 2026-08-20 — LLM обрабатывает ВСЕ данные (чанкинг) — v0.0.57 - -## Задача -Пользователь: LLM должен обрабатывать ВСЕ данные, а не урезку 8000 символов. -Ветка сохранения пре-LLM конфигурации: `save-pre-llm-v0.0.56` (указывает на прежний master). - -## Изменения (drhider/scanner.py) -`scan_llm_ner` переписан: -- Убраны глобальные урезки `t[:3000]` и `combined[:8000]`. -- Каждый файл обрабатывается ПОЛНОСТЬЮ: split_into_chunks(6000, overlap 500). -- Чанки файла — параллельно (ThreadPoolExecutor, concurrency 4), крупные файлы вперёд. -- Для каждого чанка — полный промпт + «Already captured by regex» (mapping). -- Дедуп найденного через normalize_entity (пробелы/регистр/кавычки/тире/ё). -- Верификация _verify_entity против ПОЛНОГО текста файла — отсекает галлюцинации LLM. -- regex-найденное не дублируется. - -Новые хелперы (scanner.py): -- split_into_chunks(text, size=6000, overlap=500) — границы \n\n,\n,. ,? ,! ,; ,, ," " -- normalize_entity(s) — канонизация для дедупа -- _verify_entity(value, full_text) — точное/нормализованное вхождение -- _build_llm_prompt(text, already_found) -- _call_llm(text, llm_client) — вызов + разбор JSON (пусто при ошибке) - -Параметры верхнего уровня (легко тюнить): -- _CHUNK_SIZE = 6000, _CHUNK_OVERLAP = 500, _LLM_CONCURRENCY = 4 - -## Ожидаемый эффект -- LLM покрывает все файлы целиком (в т.ч. хвосты и таблицы после 3000 символа). -- Время на ~300 файлов ≈ 3.5 мин при 4 потоках (~390 вызовов). -- Меньше галлюцинаций (верификация). - -## Проверка -- py_compile OK. -- test_scanner 20/20. -- Юнит хелперов: split (12К→4 чанка ≤6100), verify (галлюцинация→False), normalize OK. -- Все 106 тестов OK. -- VERSION 0.0.57. - -## Примечание -Реальная проверка LLM-покрытия и времени — на наборе T/ при прогоне на проде/локально -с ключом LLM (локально ключ отсутствует → LLM не выполняется, только regex). diff --git a/History/opus/2026-07-13-opus-query-mss-clamping.md b/History/opus/2026-07-13-opus-query-mss-clamping.md deleted file mode 100644 index 33ac575..0000000 --- a/History/opus/2026-07-13-opus-query-mss-clamping.md +++ /dev/null @@ -1,44 +0,0 @@ -# Запрос к Opus: валидация MSS Clamping плана - -**Дата:** 2026-07-13 - ---- - -## Контекст - -Кластер: bare-metal Kubernetes, 4 ноды, Cilium CNI (Geneve-туннель), kube-vip для VIP. -Штурвал управляет ingress через Helm-чарт (`shturval-ingress-controller-2.12.1`). - -## Проблема - -При `externalTrafficPolicy: Cluster` большие файлы (>5 MB) вызывают TCP stall на 51 секунду. -Диагностика: cross-node Geneve-туннель добавляет overhead (~50 байт) → пакет > MTU 1500 → ICMP Frag Needed блокируется → TCP RTO backoff. - -Текущий костыль: `externalTrafficPolicy: Local` + `kubectl scale replicas=4` (Штурвал сбрасывает на 2 каждые ~час). - -## Предлагаемое решение - -Вместо костыля — выставить MTU в Cilium ConfigMap: - -```yaml -# kubectl edit configmap -n kube-system cilium-config -mtu: 1450 -``` - -После этого: -1. Вернуть `externalTrafficPolicy: Cluster` -2. Оставить ingress `replicaCount: 2` (стоковое значение Штурвала) -3. Никаких ручных scale - -## Вопросы к Opus - -**Читать только:** `docs/MSS-CLAMPING-PLAN.md`, `docs/KUBEVIP-EXTERNAL-TRAFFIC-POLICY.md`, `docs/INGRESS-SOLUTIONS.md`. - -1. Корректно ли решение? Есть ли подводные камни при смене MTU через Cilium ConfigMap на работающем кластере? -2. Нужно ли что-то ещё кроме `mtu: 1450`? (например, перезапуск подов, пересоздание туннелей) -3. Как проверить что MSS clamping реально работает после изменения? (конкретные команды) -4. Есть ли риск для других сервисов (Keycloak, etc.) при смене MTU? -5. Если Cilium ConfigMap недоступен — iptables-вариант (`TCPMSS --clamp-mss-to-pmtu`) равноценен? На какую ноду вешать правило? -6. Итоговый вердикт: это правильный путь, или есть более грамотный вариант? - -**Не лезть:** в код приложения, в TMP, в History (кроме указанных трёх файлов). diff --git a/History/opus/2026-07-13-opus-response-document-review.md b/History/opus/2026-07-13-opus-response-document-review.md deleted file mode 100644 index eeb56a6..0000000 --- a/History/opus/2026-07-13-opus-response-document-review.md +++ /dev/null @@ -1,35 +0,0 @@ -# Opus: проверка PROBLEM-AND-SOLUTION.md на ошибки - -**Дата:** 2026-07-13 - ---- - -## Найденные ошибки - -### 1. TCP backoff — неправильные числа - -**Было:** `1с → 3с → 7с → 15с → 25с = 51с` — выдумано. - -**Правильно:** начальный RTO = 200ms, удвоение: -``` -0.2с → 0.4с → 0.8с → 1.6с → 3.2с → 6.4с → 12.8с → 25.6с = 51с -``` -8 ретрансмитов от 200ms = ровно 51 секунда (Linux kernel default). - -### 2. DSR — неверное объяснение причины stall - -**Было:** «двойной инкапсуляции return-трафика (главная причина stall)». - -**Правильно:** stall на **входящем** трафике (клиент → pod), а не на return. DSR оптимизирует обратный путь, но не решает проблему inbound MTU black-hole. - -### 3. Ping измеряет pod MTU, не underlay - -Утверждение что `ping -M do` доказывает underlay = 1450 — неточно. Ping из пода измеряет MTU пода (1450 = 1500 − 50 Cilium). Физический underlay = 1450 подтверждён эмпирически (фикс сработал), а не прямым замером. - -## Что верно - -- Geneve overhead 50 байт: outer IP(20) + UDP(8) + Geneve(8) + inner Eth(14) = 50 ✓ -- `mtu: 1450` → pod MTU = 1400 ✓ -- `rollout restart ds/cilium` обязателен ✓ -- iptables может не работать с eBPF dataplane ✓ -- Корень проблемы и решение верны ✓ diff --git a/History/opus/2026-07-13-opus-response-final-review.md b/History/opus/2026-07-13-opus-response-final-review.md deleted file mode 100644 index 9e37f85..0000000 --- a/History/opus/2026-07-13-opus-response-final-review.md +++ /dev/null @@ -1,31 +0,0 @@ -# Opus: финальная проверка PROBLEM-AND-SOLUTION.md - -**Дата:** 2026-07-13 - ---- - -## Вердикт - -Документ технически добротный. Диагноз и корневое решение (`mtu: 1450` + `Cluster` + `replicaCount: 2`) — правильные. - -## Исправлено - -1. **Ethernet 14** — уточнено: «инкапсулируемый L2-кадр 14» (внутренний Ethernet, не внешний) -2. **51с = прикладной таймаут** — смягчено: «браузер/curl разрывает ожидание» вместо «соединение разрывается» - -## Что подтверждено как верное - -- PMTU blackhole как причина — классический сценарий -- Cilium: `mtu` в configmap = underlay девайса, pod MTU = mtu − overhead -- Geneve overhead = 50 байт (внешний IP 20 + UDP 8 + Geneve 8 + L2 14) -- Решение: `mtu: 1450` → pod 1400 → Geneve-пакет 1450 → влазит в path 1450 -- `rollout restart ds/cilium` обязателен -- `externalTrafficPolicy: Local` → `Cluster` после фикса MTU -- DSR решает только обратный путь, stall на inbound -- iptables может не работать с eBPF dataplane - -## Мелкие замечания (не исправлены, допустимы) - -- 200ms RTO — корректно для сценария (уже установленное TCP-соединение) -- Тип туннеля (Geneve vs VXLAN) — overhead одинаков (50), математика не меняется -- Замеры из таблицы результатов — эмпирические, из кода не подтвердить diff --git a/History/opus/2026-07-13-opus-response-mss-clamping.md b/History/opus/2026-07-13-opus-response-mss-clamping.md deleted file mode 100644 index ba2ebe3..0000000 --- a/History/opus/2026-07-13-opus-response-mss-clamping.md +++ /dev/null @@ -1,77 +0,0 @@ -# Opus: валидация MSS Clamping плана - -**Дата:** 2026-07-13 - ---- - -## Ключевое исправление - -В плане ошибка: `mtu: 1450` в cilium-config — это MTU **underlay-сети**, не туннеля. Cilium сам вычитает 50 байт на Geneve: - -``` -mtu: 1450 → tunnel MTU = 1450 − 50 = 1400 -``` - -По умолчанию Cilium автодетектит underlay 1500 → туннель 1450. Если stall всё равно есть — underlay реально < 1500. **Число надо измерить, не гадать.** - ---- - -## Ответы на вопросы - -### 1. Корректно ли решение? - -Да, направление верное. Но: -- ConfigMap сам не применяется — нужен `rollout restart ds/cilium` -- Число 1450 — гипотеза, нужен замер между нодами -- `mtu` глобален на весь кластер -- Существующие TCP-соединения не поменяют MSS — только новые - -### 2. Что ещё нужно? - -1. Измерить underlay MTU: `ping -M do -s 1472 ` (уменьшать пока не пройдёт) -2. `kubectl rollout restart ds/cilium -n kube-system` -3. Пересоздать поды приложения (ingress, drhider) -4. Вернуть `externalTrafficPolicy: Cluster` - -### 3. Как проверить? - -```bash -# Измерить до: -ping -M do -s 1472 -tracepath - -# После изменения: -kubectl -n kube-system exec ds/cilium -- cilium-dbg status | grep -i mtu -ip link show | grep -E 'cilium|geneve' -tcpdump -ni any 'tcp[tcpflags] & tcp-syn != 0' -vv | grep mss - -# Функционально: файл >5 MB без паузы -``` - -### 4. Риск для других сервисов? - -Понижение MTU безопасно (меньше = консервативнее). Небольшой рост числа пакетов, микро-потеря throughput. Функционально ничего не ломает. Рестарт агентов — возможны короткие блипы, лучше в окно. - -### 5. iptables равноценен? - -**Нет.** Cilium с eBPF обходит netfilter FORWARD — правило может не видеть пакеты. Надёжнее `cilium-config`. Если iptables — то на всех 4 нодах (kube-vip раздаёт VIP на все). - -### 6. Вердикт - -Путь правильный. Уточнённый план: -1. Измерить path MTU между нодами -2. Выставить `mtu` = измеренное значение (вероятно < 1500) -3. Рестарт агентов + подов -4. Вернуть `Cluster` policy - -При `Cluster` проблема реплик исчезает сама (2 хватает). - ---- - -## Моё мнение - -Opus прав. План нужно дополнить **замером**. Без замера `1450` — пальцем в небо. Обновлю `MSS-CLAMPING-PLAN.md` с учётом: -- Добавить шаг 0: измерение path MTU -- Исправить формулу: `mtu` = underlay, туннель = `mtu - 50` -- Добавить `rollout restart ds/cilium` + рестарт подов -- Убрать iptables как основной вариант (ненадёжен с Cilium) diff --git a/History/plans/2026-07-12-audit-and-plan.md b/History/plans/2026-07-12-audit-and-plan.md deleted file mode 100644 index 8db4532..0000000 --- a/History/plans/2026-07-12-audit-and-plan.md +++ /dev/null @@ -1,145 +0,0 @@ -# DrHider — Полный аудит и план рефакторинга - -**Дата:** 2026-07-12 -**Платформа:** Штурвал (Managed Flask) — запускает Flask сам, без Dockerfile/gunicorn -**URL:** https://drhider.pythonk8s.dev.nubes.ru/ -**Репозиторий:** https://gitea.services.ngcloud.ru/Nail/drhider - ---- - -## Текущее состояние (аудит каждого файла) - -| Файл | Статус | Решение | -|---|---|---| -| `Dockerfile` | ❌ НЕ НУЖЕН | Штурвал сам запускает Flask. Удалить. | -| `drhider_server.py` | ❌ НЕ НУЖЕН | Standalone-сервер с ВМ (:8767). Flask app.py уже обрабатывает /api/drhider. Удалить. | -| `requirements.txt` | ⚠️ Нужны правки | Убрать `gunicorn` и `gevent`. Оставить `flask`, `python-docx`, `pdfplumber`, `httpx`. | -| `.gitignore` | ✅ OK | Без изменений. | -| `.gitea/workflows/deploy.yaml` | ✅ OK | CI для Штурвала. | -| `site/app.py` | ⚠️ Нужны правки | Удалить блок `if __name__ == "__main__"` (gunicorn). При рефакторинге — вынести LLMClient. | -| `site/templates/index.html` | ✅ OK | UI. | -| `site/static/favicon.svg` | ✅ OK | Иконка (из loadtest). | -| `drhider.py` | ⚠️ МОНОЛИТ | 560 строк. Разбить на ~20 модулей. | -| `History/` | ✅ OK | Документация. | - ---- - -## План рефакторинга (Фаза 1: удаление лишнего) - -### Шаг 1.1 — Удалить `Dockerfile` -### Шаг 1.2 — Удалить `drhider_server.py` -### Шаг 1.3 — Исправить `requirements.txt` (убрать gunicorn, gevent) -### Шаг 1.4 — Исправить `site/app.py` (убрать блок `if __name__ == "__main__"`) -### Шаг 1.5 — Проверить синтаксис → commit → push - ---- - -## План рефакторинга (Фаза 2: модульная структура) - -### Новая структура `drhider/` пакета (вместо `drhider.py` 560 строк): - -``` -drhider/ # Пакет ядра обфускации -├── __init__.py # re-export: obfuscate_files() -├── config.py # Константы: ENTITY_PATTERNS, COMPANY_PATTERN, PERSON_PATTERN, -│ # RU_SURNAMES, RU_NAMES, RU_PATRONYMICS, -│ # RU_CITIES, RU_STREETS, FAKE_DOMAINS -├── checksum.py # _checksum_inn10, _checksum_inn12, _checksum_ogrn -├── random_utils.py # _random_digits, _random_letters -├── generators/ # Генераторы фиктивных значений -│ ├── __init__.py # ENTITY_GENERATORS маппинг -│ ├── phone.py # generate_phone -│ ├── email.py # generate_email -│ ├── inn.py # generate_inn10, generate_inn12 -│ ├── ogrn.py # generate_ogrn -│ ├── kpp.py # generate_kpp -│ ├── bik.py # generate_bik -│ ├── accounts.py # generate_rs, generate_ks -│ ├── passport.py # generate_passport -│ ├── company.py # generate_company -│ ├── person.py # generate_person -│ └── address.py # generate_address -├── extractor.py # _extract_text, _expand_zips, _convert_pdfs_to_docx -├── scanner.py # Проход 1: _scan_regex, _scan_llm_ner -├── replacer.py # Проход 2: _replace_in_docx, _replace_in_text, _apply_replacements -├── builder.py # Сборка: _build_zip, _build_mapping_csv -├── obfuscator.py # TwoPassObfuscator — оркестратор -└── llm_client.py # LLMClient (из app.py → сюда) - -site/ # Flask-приложение -├── __init__.py -├── app.py # Только create_app() + регистрация blueprint'ов -├── routes/ -│ ├── __init__.py # Регистрация всех blueprint'ов -│ ├── main_bp.py # GET / -│ ├── health_bp.py # GET /health -│ └── api_bp.py # POST /api/drhider -├── templates/index.html -└── static/favicon.svg -``` - -### Принципы: -- Каждый файл ≤ 50 строк (где возможно) -- Каждый файл — одна ответственность -- Максимум docstring и инлайн-комментариев -- Все импорты явные, никаких `import *` - ---- - -## Результат рефакторинга (2026-07-12) - -### Итоговая структура - -``` -drhider/ # Пакет ядра обфускации -├── __init__.py # re-export: obfuscate_files, LLMClient, TwoPassObfuscator -├── config.py # Константы: ENTITY_PATTERNS, словари имён/городов/улиц -├── checksum.py # Контрольные суммы: ИНН10, ИНН12, ОГРН -├── random_utils.py # Утилиты: random_digits, random_letters -├── llm_client.py # LLMClient — HTTP-клиент к LLM API -├── extractor.py # Извлечение текста + expand_zips + convert_pdfs_to_docx -├── scanner.py # Проход 1: scan_regex, scan_llm_ner -├── replacer.py # Проход 2: apply_replacements, replace_in_docx, replace_in_text -├── builder.py # Сборка: build_zip, build_mapping_csv -├── obfuscator.py # TwoPassObfuscator — оркестратор -└── generators/ # Генераторы фиктивных значений - ├── __init__.py # ENTITY_GENERATORS маппинг - ├── phone.py, email.py # Телефон, email - ├── inn.py, ogrn.py, kpp.py # ИНН, ОГРН, КПП - ├── bik.py, accounts.py # БИК, счета (р/с, к/с) - ├── passport.py # Паспорт - ├── company.py, person.py # Компания, ФИО - └── address.py # Адрес - -site/ # Flask-приложение -├── app.py # create_app() + VERSION (20 строк) -├── routes/ -│ ├── __init__.py # register_routes() -│ ├── main_bp.py # GET / -│ ├── health_bp.py # GET /health -│ └── api_bp.py # POST /api/drhider -├── templates/index.html -└── static/favicon.svg -``` - -### Что изменилось - -| Было | Стало | -|---|---| -| `drhider.py` — 560 строк монолит | Пакет `drhider/` — 22 модуля по 20–100 строк | -| `site/app.py` — 100 строк (всё в одном файле) | `app.py` 33 строки + 3 blueprint'а по 20–50 строк | -| `Dockerfile` | Удалён (Штурвал сам) | -| `drhider_server.py` | Удалён (Flask заменяет) | - -### Коммиты - -- `a2f754a` — Фаза 1: удалён Dockerfile, drhider_server.py, gunicorn -- `37581eb` — Фаза 2: config.py, checksum.py, random_utils.py, generators/ -- `2cf4e08` — Фаза 2: llm_client.py, extractor.py, scanner.py, replacer.py, builder.py, obfuscator.py -- `51bc1a7` — Фаза 2: site/app.py → blueprint'ы, старый drhider.py удалён - -| Переменная | Значение | -|---|---| -| `LLM_API_KEY` | (секрет — не хранить в git) | -| `LLM_URL` | `https://api.aillm.ru/v1/chat/completions` | -| `LLM_MODEL` | `gpt-oss-120b` | diff --git a/History/plans/2026-07-12-v3-done.md b/History/plans/2026-07-12-v3-done.md deleted file mode 100644 index ae41473..0000000 --- a/History/plans/2026-07-12-v3-done.md +++ /dev/null @@ -1,45 +0,0 @@ -# v3.0.0 — универсальная архитектура (2026-07-12) - -Переход на Markdown-пайплайн + универсальное LLM-обнаружение. - -## Что изменилось - -### 1. `generators/fallback.py` — новый -Character-class preserving замена для неизвестных типов сущностей. -Цифры→цифры, буквы→буквы, спецсимволы→без изменений. - -### 2. `scanner.py` — универсальный LLM-промпт -- Убран жёсткий список типов (person, company, address, passport) -- LLM САМА придумывает тип (snake_case) — person_name, contract_number, employee_id... -- Если тип не совпадает с известными — fallback-генератор -- Промпт на английском (LLM точнее) - -### 3. `extractor.py` — Markdown-пайплайн -- `docx_to_markdown()`: DOCX → MD (Heading → #, Bold → **, таблицы → |...|) -- `pdf_to_markdown()`: PDF → MD (текст + таблицы, без форматирования) -- `extract_text()` возвращает только str (без docx-объектов) -- Удалён `convert_pdfs_to_docx()` (PDF → MD напрямую) - -### 4. `replacer.py` — упрощён -- Удалён `replace_in_text()` (заменён на `apply_replacements()` + `.encode()`) -- Оставлен `replace_in_docx()` для будущего DOCX-выхода - -### 5. `obfuscator.py` — новый пайплайн -- Pass 1: extract MD → scan_regex + scan_llm_ner -- Pass 2: apply_replacements к MD → .md файлы -- Нет docx-объектов, нет convert_pdfs_to_docx - -## Коммиты - -- `579f9c8` — generators: fallback.py -- `2ceeb05` — scanner.py: универсальный промпт -- `af2f2a9` — extractor.py: MD + obfuscator.py: новый пайплайн -- `f5427b0` — replacer.py: удалён replace_in_text -- `2a2cc14` — v3.0.0 - -## Плюсы - -- LLM точнее (нет XML-шума, только Markdown) -- Новые типы сущностей — без правки кода -- Код проще: -120 строк, -1 функция, -1 модуль (convert_pdfs_to_docx) -- Единый внутренний формат (Markdown) diff --git a/History/plans/2026-07-12-v3-plan.md b/History/plans/2026-07-12-v3-plan.md deleted file mode 100644 index fa89cf0..0000000 --- a/History/plans/2026-07-12-v3-plan.md +++ /dev/null @@ -1,51 +0,0 @@ -# План v3 — универсальная архитектура (2026-07-12) - -## Цель - -Убрать жёсткую привязку к фиксированному списку типов сущностей. -Перейти на Markdown как внутренний формат. - -## Изменения по модулям - -### 1. `scanner.py` — универсальный LLM-промпт -- Убрать список типов из промпта -- LLM сама называет типы (snake_case) -- `value` — verbatim из текста - -### 2. `generators/` — fallback-генератор -- Новый `fallback.py`: character-class preserving замена -- `__init__.py`: добавить в ENTITY_GENERATORS -- `obfuscator.py`: использовать fallback для неизвестных типов - -### 3. `extractor.py` — единый MD-пайплайн -- DOCX → MD (python-docx: стили, форматирование) -- PDF → MD (pdfplumber: текст + таблицы) -- TXT → как есть -- Убрать convert_pdfs_to_docx (не нужен) -- Убрать хранение docx-объектов - -### 4. `replacer.py` — упростить -- `apply_replacements(text)` — уже есть -- `replace_in_docx(doc, mapping)` — сохранить как утилиту для DOCX-выхода -- Удалить `replace_in_text()` (заменяется на apply_replacements) - -### 5. `obfuscator.py` — обновить пайплайн -- Pass 1: extract MD → scan_regex + scan_llm_ner -- Pass 2: apply_replacements к MD -- Опционально: replace_in_docx к оригинальному DOCX - -### 6. `api_bp.py` — output_format -- Параметр `output_format: "md" | "docx"` (по умолчанию md) - -### 7. `.doc` — потом -- Добавим convert_doc_to_docx через LibreOffice отдельно - -## Порядок реализации - -1. `generators/fallback.py` + обновить `__init__.py` -2. `scanner.py` — новый промпт -3. `extractor.py` — MD-конвертация -4. `replacer.py` — упростить -5. `obfuscator.py` — новый пайплайн -6. `api_bp.py` — output_format -7. Проверить всё → commit diff --git a/History/plans/2026-07-12-v3.1-plan.md b/History/plans/2026-07-12-v3.1-plan.md deleted file mode 100644 index af327a3..0000000 --- a/History/plans/2026-07-12-v3.1-plan.md +++ /dev/null @@ -1,30 +0,0 @@ -# План v3.1 — пофайловая обработка + прогресс в таблице (2026-07-12) - -## Проблема -Фронтенд v3.0 отправляет все файлы одним запросом → долгий LLM-вызов → таймаут. -Нужно: обрабатывать по одному, прогресс в таблице, все результаты → один ZIP. - -## Решение - -### Фронтенд (index.html) -1. **XHR** вместо fetch — надёжнее для blob, явный timeout -2. **Колонка «Статус»** в таблице файлов -3. **По одному файлу:** цикл, каждый файл → отдельный XHR → ZIP -4. **Прогресс в таблице:** ⏳ → ✓ 2.1 сек (или ✗ ошибка) -5. **JSZip** — склеивает все полученные ZIP'ы в один -6. **Скачивание** — один файл `drhider_output.zip` в конце - -### Статусы в таблице -| Статус | Отображение | -|---|---| -| Ожидание | ⏳ | -| Успех | ✓ 2.1 сек | -| Ошибка | ✗ сообщение | - -### Бэкенд -Без изменений. API уже принимает один файл. - -### НЕ трогать -- gunicorn не нужен (файлы по одному, каждый запрос ≤10 сек) -- app.run() — остаётся -- drhider пакет — без изменений diff --git a/History/plans/2026-08-24-folder-select-plan.md b/History/plans/2026-08-24-folder-select-plan.md deleted file mode 100644 index d04fd1b..0000000 --- a/History/plans/2026-08-24-folder-select-plan.md +++ /dev/null @@ -1,63 +0,0 @@ -# План для Flash: выбор целой папки рекурсивно (v0.0.68) - -> Самодостаточный план. Файл: `site/templates/index.html` (весь фронт — ванильный JS, без сборки). -> Текущая версия 0.0.67 → поднять до **0.0.68** (в `site/app.py` `VERSION`). - -## Задача -Кнопка «📁 Выбрать папку»: юзер выбирает папку → в таблицу рекурсивно добавляются ВСЕ файлы -(включая вложенные подпапки), как сейчас раскрываются ZIP. **Относительный путь сохраняется** -(без имени верхней выбранной папки): `Подпапка/Внутри/report.pdf`. - -## Что уже есть (ориентиры в index.html) -- `` в `.file-input-wrap`. -- `const fi = document.getElementById('fileInput');` (в начале `