fix: drhider standalone server port 8767, inline SVG, DB try/except
Deploy contracts-flask / validate (push) Successful in 0s
Deploy contracts-flask / validate (push) Successful in 0s
This commit is contained in:
@@ -18,6 +18,7 @@ if os.path.exists(_env_path):
|
|||||||
os.environ[_k] = _v
|
os.environ[_k] = _v
|
||||||
|
|
||||||
# ── DB auto-seed ──────────────────────────────────────────────────────────
|
# ── DB auto-seed ──────────────────────────────────────────────────────────
|
||||||
|
try:
|
||||||
from db import prompts as db_prompts
|
from db import prompts as db_prompts
|
||||||
from db.connection import DB_CONFIG, execute
|
from db.connection import DB_CONFIG, execute
|
||||||
db_prompts.seed_defaults()
|
db_prompts.seed_defaults()
|
||||||
@@ -31,12 +32,27 @@ execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS content_hash text")
|
|||||||
from db import supplements as db_supplements
|
from db import supplements as db_supplements
|
||||||
from db import documents as db_documents
|
from db import documents as db_documents
|
||||||
from db import spec_current as db_spec_current
|
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 ──────────────────────────────────────────────────────────────
|
# ── Services ──────────────────────────────────────────────────────────────
|
||||||
|
if DB_OK:
|
||||||
from services.upload import handle_upload
|
from services.upload import handle_upload
|
||||||
from services.unzip import handle_unzip
|
from services.unzip import handle_unzip
|
||||||
from services.process import run_pipeline
|
from services.process import run_pipeline
|
||||||
from llm_prompt import build_prompt
|
from llm_prompt import build_prompt
|
||||||
|
else:
|
||||||
|
handle_upload = handle_unzip = run_pipeline = None
|
||||||
|
build_prompt = None
|
||||||
|
|
||||||
|
|
||||||
class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
|
class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
|
||||||
@@ -56,7 +72,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
if parsed.path == "/process-v2":
|
if parsed.path == "/process-v2":
|
||||||
self._handle_process_v2(parsed)
|
self._handle_process_v2(parsed)
|
||||||
elif parsed.path == "/health":
|
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/"):
|
elif parsed.path == "/app.js" or parsed.path.startswith("/static/"):
|
||||||
self._handle_app_js()
|
self._handle_app_js()
|
||||||
elif parsed.path == "/api/supplements":
|
elif parsed.path == "/api/supplements":
|
||||||
@@ -590,7 +606,8 @@ if __name__ == "__main__":
|
|||||||
TCPServer.allow_reuse_address = True
|
TCPServer.allow_reuse_address = True
|
||||||
port = int(os.environ.get("PORT", "8766"))
|
port = int(os.environ.get("PORT", "8766"))
|
||||||
server = ThreadingHTTPServer(("0.0.0.0", port), Handler)
|
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:
|
try:
|
||||||
server.serve_forever()
|
server.serve_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|||||||
@@ -15,6 +15,19 @@ server {
|
|||||||
proxy_read_timeout 10s;
|
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/ {
|
location /api/ {
|
||||||
proxy_pass http://127.0.0.1:8766;
|
proxy_pass http://127.0.0.1:8766;
|
||||||
proxy_read_timeout 30s;
|
proxy_read_timeout 30s;
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>DrHider — обфускация документов</title>
|
<title>DrHider — обфускация документов</title>
|
||||||
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='4' fill='%23001C34'/%3E%3Ctext x='16' y='24' text-anchor='middle' font-size='22' font-weight='bold' fill='white' font-family='sans-serif'%3EN%3C/text%3E%3C/svg%3E" type="image/svg+xml">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--brand: #2563eb; --brand-dark: #1d4ed8;
|
--brand: #2563eb; --brand-dark: #1d4ed8;
|
||||||
@@ -59,12 +59,12 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<img src="/static/logo.svg" alt="Nubes">
|
<svg width="82" height="18" viewBox="0 0 82 18" style="display:block"><rect width="82" height="18" rx="2" fill="#001C34"/><text x="41" y="14" text-anchor="middle" font-size="12" font-weight="bold" fill="white" font-family="sans-serif">NUBES</text></svg>
|
||||||
<span class="sep">|</span>
|
<span class="sep">|</span>
|
||||||
<a href="/">Сверка договоров</a>
|
<a href="/">Сверка договоров</a>
|
||||||
<span class="sep">|</span>
|
<span class="sep">|</span>
|
||||||
<strong>DrHider</strong>
|
<strong>DrHider</strong>
|
||||||
<span style="font-size:11px;color:var(--muted);">v1.2</span>
|
<span style="font-size:11px;color:var(--muted);">v1.3</span>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|||||||
Reference in New Issue
Block a user