From a2f754ab4f276ec192a7f2f2219c7e2decf8aec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sun, 12 Jul 2026 08:19:45 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B0=D0=B7=D0=B0=201:=20=D1=83=D0=B4?= =?UTF-8?q?=D0=B0=D0=BB=D1=91=D0=BD=20Dockerfile,=20drhider=5Fserver.py,?= =?UTF-8?q?=20gunicorn=20=D0=B8=D0=B7=20requirements,=20=5F=5Fmain=5F=5F?= =?UTF-8?q?=20=D0=B1=D0=BB=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 8 -- History/2026-07-12-audit-and-plan.md | 95 +++++++++++++++++ WhatTODO.md | 4 + drhider_server.py | 151 --------------------------- requirements.txt | 2 - site/app.py | 11 +- 6 files changed, 100 insertions(+), 171 deletions(-) delete mode 100644 Dockerfile create mode 100644 History/2026-07-12-audit-and-plan.md create mode 100644 WhatTODO.md delete mode 100644 drhider_server.py diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index a18e98a..0000000 --- a/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM python:3.12-slim -WORKDIR /app -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt -COPY drhider.py drhider_server.py /app/ -COPY site /app/site -EXPOSE 5000 -CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--timeout", "300", "--workers", "1", "--worker-class", "gevent", "--limit-request-field_size", "0", "--limit-request-body", "0", "site.app:app"] diff --git a/History/2026-07-12-audit-and-plan.md b/History/2026-07-12-audit-and-plan.md new file mode 100644 index 0000000..343a9e0 --- /dev/null +++ b/History/2026-07-12-audit-and-plan.md @@ -0,0 +1,95 @@ +# 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 *` + +--- + +## Переменные окружения (для Штурвала) + +| Переменная | Значение | +|---|---| +| `LLM_API_KEY` | `sk-ucI5YvOticoOQ9Kuj5K9mQ` | +| `LLM_URL` | `https://api.aillm.ru/v1/chat/completions` | +| `LLM_MODEL` | `gpt-oss-120b` | diff --git a/WhatTODO.md b/WhatTODO.md new file mode 100644 index 0000000..5248a2a --- /dev/null +++ b/WhatTODO.md @@ -0,0 +1,4 @@ +НЕ СПЕШИ ! не ошибайся. НЕ ПРЕДПОЛАГАЙ ! +не надо ДОГАДОК ! +не надо самостоятельно что либо "УЛуЧШАТЬ" - никаких изменений рабочего кода без прямого разрешения +есть сомнения - лучше остановись и спроси \ No newline at end of file diff --git a/drhider_server.py b/drhider_server.py deleted file mode 100644 index ea5819c..0000000 --- a/drhider_server.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python3 -"""DrHider standalone server — NO DB dependency. Port 8767.""" -from http.server import HTTPServer, BaseHTTPRequestHandler -from socketserver import ThreadingMixIn, TCPServer -from urllib.parse import urlparse -import json, re, os, sys, cgi - -DRHIDER_SERVER_VERSION = "1.3" - -MAX_BODY = 200 * 1024 * 1024 # 200 MB - -# ── Загрузка .env ──────────────────────────────────────────────────────── -_env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".env") -if os.path.exists(_env_path): - with open(_env_path) as _f: - for _line in _f: - _line = _line.strip() - if _line and not _line.startswith("#") and "=" in _line: - _k, _v = _line.split("=", 1) - if _k not in os.environ: - os.environ[_k] = _v - - -class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): - daemon_threads = True - - -class Handler(BaseHTTPRequestHandler): - def do_OPTIONS(self): - self.send_response(200) - self._send_cors() - self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") - self.send_header("Access-Control-Allow-Headers", "Content-Type") - self.end_headers() - - def do_GET(self): - parsed = urlparse(self.path) - if parsed.path == "/health": - self._json({"ok": True, "server": "drhider", "version": DRHIDER_SERVER_VERSION}) - else: - self.send_error(404) - - def do_POST(self): - if self.path == "/api/drhider": - self._handle_drhider() - else: - self.send_error(404) - - # ── DrHider ────────────────────────────────────────────────────────── - - def _handle_drhider(self): - """Обфускация: multipart files → ZIP.""" - from drhider import obfuscate_files - - length = int(self.headers.get("Content-Length", 0)) - if length <= 0 or length > MAX_BODY: - self._json({"ok": False, "error": "Файл слишком большой"}, 413) - return - raw = self.rfile.read(length) - ctype = self.headers.get("Content-Type", "") - - _, params = cgi.parse_header(ctype) - boundary = params.get("boundary", "") - if not boundary: - self._json({"ok": False, "error": "No boundary"}, 400) - return - - parts = raw.split(b"--" + boundary.encode()) - files = [] - for part in parts: - if b"Content-Disposition" not in part: - continue - try: - hdr_end = part.index(b"\r\n\r\n") - except ValueError: - continue - hdrs = part[:hdr_end].decode("latin-1", errors="replace") - body = part[hdr_end + 4:] - if body.endswith(b"\r\n"): - body = body[:-2] - m = re.search(r'filename="([^"]*)"', hdrs) - if not m: - continue - name = os.path.basename(m.group(1)) - try: - name = name.encode('latin-1').decode('utf-8') - except (UnicodeDecodeError, UnicodeEncodeError): - pass - if not name or ".." in name or "/" in name or "\\" in name: - continue - files.append((name, body, "")) - - if not files: - self._json({"ok": False, "error": "Нет файлов"}, 400) - return - - class _VMLLM: - def complete(self, prompt): - import httpx - key = os.environ.get("LLM_KEY") or os.environ.get("LLM_API_KEY", "") - r = httpx.post( - os.environ.get("LLM_URL", "https://api.aillm.ru/v1/chat/completions"), - json={"model": os.environ.get("LLM_MODEL", "gpt-oss-120b"), - "messages": [{"role": "user", "content": prompt}], - "max_tokens": 8000, "temperature": 0.1}, - headers={"Authorization": f"Bearer {key}", - "Content-Type": "application/json"}, - timeout=120 - ) - r.raise_for_status() - return r.json()["choices"][0]["message"]["content"] - - try: - zip_data, _csv = obfuscate_files(files, llm_client=_VMLLM()) - self.send_response(200) - self.send_header("Content-Type", "application/zip") - self.send_header("Content-Disposition", 'attachment; filename="drhider_output.zip"') - self.send_header("Content-Length", str(len(zip_data))) - self.send_header("Access-Control-Allow-Origin", "*") - self.end_headers() - self.wfile.write(zip_data) - except Exception as e: - import traceback - traceback.print_exc() - self._json({"ok": False, "error": str(e)}, 500) - - # ── Helpers ─────────────────────────────────────────────────────────── - - def _json(self, data, status=200): - self.send_response(status) - self._send_cors() - self.send_header("Content-Type", "application/json; charset=utf-8") - self.end_headers() - self.wfile.write(json.dumps(data, ensure_ascii=False).encode()) - - def _send_cors(self): - self.send_header("Access-Control-Allow-Origin", "*") - - def log_message(self, format, *args): - pass - - -if __name__ == "__main__": - TCPServer.allow_reuse_address = True - port = int(os.environ.get("PORT", "8767")) - server = ThreadingHTTPServer(("0.0.0.0", port), Handler) - print(f"DrHider server on :{port} (NO DB)") - try: - server.serve_forever() - except KeyboardInterrupt: - server.shutdown() diff --git a/requirements.txt b/requirements.txt index 574b557..8ca3c32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,4 @@ flask -gunicorn -gevent python-docx pdfplumber httpx diff --git a/site/app.py b/site/app.py index c6f4fe8..0e18951 100644 --- a/site/app.py +++ b/site/app.py @@ -84,13 +84,4 @@ def api_drhider(): return jsonify({"ok": False, "error": str(e)}), 500 -if __name__ == "__main__": - import subprocess - subprocess.run([ - sys.executable, "-m", "gunicorn", "app:app", - "--bind", "0.0.0.0:5000", - "--worker-class", "gevent", - "--workers", "1", - "--worker-connections", "1000", - "--timeout", "300", - ]) +