diff --git a/deploy/convert_server.py b/deploy/convert_server.py index 9fdda92..d3179de 100755 --- a/deploy/convert_server.py +++ b/deploy/convert_server.py @@ -18,25 +18,41 @@ if os.path.exists(_env_path): os.environ[_k] = _v # ── DB auto-seed ────────────────────────────────────────────────────────── -from db import prompts as db_prompts -from db.connection import DB_CONFIG, execute -db_prompts.seed_defaults() -# Schema migration: classify_raw column (idempotent) -execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS classify_raw text") -execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS classify_input text") -execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS zip_source text") -execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS content_hash text") +try: + from db import prompts as db_prompts + from db.connection import DB_CONFIG, execute + db_prompts.seed_defaults() + # Schema migration: classify_raw column (idempotent) + execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS classify_raw text") + execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS classify_input text") + execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS zip_source text") + execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS content_hash text") -# ── DB modules ──────────────────────────────────────────────────────────── -from db import supplements as db_supplements -from db import documents as db_documents -from db import spec_current as db_spec_current + # ── DB modules ──────────────────────────────────────────────────────────── + from db import supplements as db_supplements + from db import documents as db_documents + from db import spec_current as db_spec_current + DB_OK = True +except Exception as _db_err: + import sys as _sys + print(f"WARNING: DB init failed ({_db_err}), running without DB", file=_sys.stderr) + DB_CONFIG = {"dbname": "NONE"} + DB_OK = False + # Stubs for DB-dependent handlers + db_prompts = None + db_supplements = None + db_documents = None + db_spec_current = None # ── Services ────────────────────────────────────────────────────────────── -from services.upload import handle_upload -from services.unzip import handle_unzip -from services.process import run_pipeline -from llm_prompt import build_prompt +if DB_OK: + from services.upload import handle_upload + from services.unzip import handle_unzip + from services.process import run_pipeline + from llm_prompt import build_prompt +else: + handle_upload = handle_unzip = run_pipeline = None + build_prompt = None class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): @@ -56,7 +72,7 @@ class Handler(BaseHTTPRequestHandler): if parsed.path == "/process-v2": self._handle_process_v2(parsed) elif parsed.path == "/health": - self._json({"ok": True, "db": DB_CONFIG["dbname"]}) + self._json({"ok": True, "db": DB_CONFIG.get("dbname", "NONE")}) elif parsed.path == "/app.js" or parsed.path.startswith("/static/"): self._handle_app_js() elif parsed.path == "/api/supplements": @@ -590,7 +606,8 @@ if __name__ == "__main__": TCPServer.allow_reuse_address = True port = int(os.environ.get("PORT", "8766")) server = ThreadingHTTPServer(("0.0.0.0", port), Handler) - print(f"Contracts VM server on :{port}, db={DB_CONFIG['dbname']}") + db_name = DB_CONFIG.get("dbname", "NONE") if DB_CONFIG else "NONE" + print(f"Contracts VM server on :{port}, db={db_name}") try: server.serve_forever() except KeyboardInterrupt: diff --git a/deploy/nginx-contracts.conf b/deploy/nginx-contracts.conf index d65cce0..ec898be 100644 --- a/deploy/nginx-contracts.conf +++ b/deploy/nginx-contracts.conf @@ -15,6 +15,19 @@ server { proxy_read_timeout 10s; } + location /api/drhider { + proxy_pass http://127.0.0.1:8767; + proxy_read_timeout 600s; + proxy_buffering off; + client_max_body_size 50m; + add_header Access-Control-Allow-Origin * always; + } + + location = /drhider { + alias /var/www/docs/drhider.html; + default_type text/html; + } + location /api/ { proxy_pass http://127.0.0.1:8766; proxy_read_timeout 30s; diff --git a/deploy/services/drhider_server.py b/deploy/services/drhider_server.py new file mode 100644 index 0000000..db91cb5 --- /dev/null +++ b/deploy/services/drhider_server.py @@ -0,0 +1,142 @@ +#!/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, io as io_mod + +DRHIDER_SERVER_VERSION = "1.0" + +# ── Загрузка .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)) + 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)) + 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/site/templates/drhider.html b/site/templates/drhider.html index 5cf83f6..f9c4fd3 100644 --- a/site/templates/drhider.html +++ b/site/templates/drhider.html @@ -4,7 +4,7 @@