roglag / html /plan.html
deploy
deploy ROGLAG threat console
c4f5819
Raw
History Blame Contribute Delete
26.6 kB
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ROGLAG — Production Plan & Stack</title>
<style>
:root{
--bg:#0b0f17; --panel:#121826; --panel2:#0f1420; --line:#1f2a3d;
--txt:#e6edf6; --muted:#90a0b8; --accent:#4da3ff; --accent2:#7c5cff;
--ok:#34d399; --warn:#fbbf24; --crit:#f87171; --code:#0a0e16;
--chip:#16203250;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
margin:0; background:radial-gradient(1200px 600px at 80% -10%, #15203a 0%, var(--bg) 55%);
color:var(--txt); font-family:-apple-system,"Segoe UI",Roboto,"Noto Sans Thai",sans-serif;
line-height:1.65; font-size:15px;
}
.wrap{max-width:1080px; margin:0 auto; padding:32px 22px 96px}
header.hero{
border:1px solid var(--line); border-radius:18px; padding:28px 30px;
background:linear-gradient(135deg,#13203a,#0f1420); margin-bottom:14px;
}
.hero h1{margin:0 0 6px; font-size:30px; letter-spacing:.3px}
.hero p{margin:0; color:var(--muted)}
.tagline{display:flex; gap:8px; flex-wrap:wrap; margin-top:16px}
.chip{font-size:12px; padding:5px 11px; border:1px solid var(--line);
border-radius:999px; background:var(--chip); color:var(--accent)}
nav.toc{
position:sticky; top:0; z-index:5; backdrop-filter:blur(8px);
background:#0b0f17cc; border:1px solid var(--line); border-radius:14px;
padding:12px 16px; margin:18px 0 26px; display:flex; gap:14px; flex-wrap:wrap;
}
nav.toc a{color:var(--muted); text-decoration:none; font-size:13px}
nav.toc a:hover{color:var(--accent)}
section{
border:1px solid var(--line); border-radius:16px; padding:24px 26px;
background:var(--panel); margin-bottom:22px;
}
h2{font-size:21px; margin:0 0 4px; display:flex; align-items:center; gap:10px}
h2 .num{font-size:13px; color:var(--accent2); border:1px solid var(--line);
border-radius:8px; padding:2px 8px; background:var(--panel2)}
h3{font-size:16px; margin:22px 0 8px; color:var(--accent)}
.sub{color:var(--muted); margin:2px 0 18px; font-size:13.5px}
p{margin:8px 0}
ul,ol{margin:8px 0 8px 4px; padding-left:20px}
li{margin:4px 0}
code{background:var(--code); border:1px solid var(--line); border-radius:6px;
padding:1px 6px; font-size:13px; color:#cfe3ff; font-family:"SF Mono",Menlo,Consolas,monospace}
pre{background:var(--code); border:1px solid var(--line); border-radius:12px;
padding:16px 18px; overflow:auto; font-size:12.8px; line-height:1.55}
pre code{background:none; border:none; padding:0; color:#d6e2f2}
table{width:100%; border-collapse:collapse; margin:12px 0; font-size:13.5px}
th,td{border:1px solid var(--line); padding:9px 12px; text-align:left; vertical-align:top}
th{background:var(--panel2); color:var(--accent); font-weight:600}
td b{color:#fff}
.grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:14px; margin:14px 0}
.card{border:1px solid var(--line); border-radius:12px; padding:16px; background:var(--panel2)}
.card h4{margin:0 0 8px; font-size:14.5px; color:var(--txt)}
.card p{margin:0; color:var(--muted); font-size:13px}
.pill{display:inline-block; font-size:11px; padding:2px 8px; border-radius:999px; margin-left:6px}
.p-now{background:#0e2a1e; color:var(--ok); border:1px solid #1c4b36}
.p-next{background:#2a230e; color:var(--warn); border:1px solid #4b3d1c}
.p-later{background:#241a2e; color:#c4a3ff; border:1px solid #3d2c4b}
.flow{display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin:14px 0; font-size:13px}
.node{border:1px solid var(--line); background:var(--panel2); border-radius:10px; padding:8px 12px}
.arrow{color:var(--accent2); font-weight:700}
.callout{border-left:3px solid var(--accent); background:#0e1a2e; border-radius:0 10px 10px 0;
padding:12px 16px; margin:14px 0; color:#c8d8ee; font-size:13.5px}
.callout.warn{border-left-color:var(--warn); background:#241d0e}
.callout.crit{border-left-color:var(--crit); background:#2a1414}
footer{color:var(--muted); text-align:center; font-size:12px; margin-top:30px}
.legend{display:flex; gap:14px; flex-wrap:wrap; font-size:12px; color:var(--muted); margin-top:8px}
</style>
</head>
<body>
<div class="wrap">
<header class="hero">
<h1>🛡️ ROGLAG — Production Plan & Tech Stack</h1>
<p>แผนยกระดับจาก demo (stdlib HTTP + in-memory) ไปสู่ระบบ Security Log RAG + Automation ระดับ production</p>
<div class="tagline">
<span class="chip">RAG</span>
<span class="chip">Security Log Analytics</span>
<span class="chip">n8n Automation</span>
<span class="chip">Vector DB</span>
<span class="chip">Docker</span>
<span class="chip">FastAPI</span>
<span class="chip">PostgreSQL</span>
</div>
<div class="legend">
<span><span class="pill p-now">NOW</span> เฟส 1 (MVP รันได้)</span>
<span><span class="pill p-next">NEXT</span> เฟส 2 (production core)</span>
<span><span class="pill p-later">LATER</span> เฟส 3 (scale & ops)</span>
</div>
</header>
<nav class="toc">
<a href="#run">▶ วิธีรัน</a>
<a href="#assess">① ประเมินของเดิม</a>
<a href="#arch">② Target Architecture</a>
<a href="#stack">③ Stack รวบรวม</a>
<a href="#pipeline">④ Pipeline จริง</a>
<a href="#n8n">⑤ n8n Workflows</a>
<a href="#db">⑥ Database</a>
<a href="#docker">⑦ Docker</a>
<a href="#roadmap">⑧ Roadmap</a>
</nav>
<!-- RUN -->
<section id="run">
<h2><span class="num">RUN</span> วิธีรันโปรเจกต์ตอนนี้</h2>
<p class="sub">ต้องมี Python 3.10+ — local mode ใช้ stdlib ล้วน ไม่ต้องลง dependency</p>
<pre><code># Local mode (keyword RAG + local fallback) — ใช้ได้ทันที
python3 server.py # เปิด http://127.0.0.1:8765
# เปิด LLM summary ผ่าน OpenRouter
export OPENROUTER_API_KEY="your_key"
python3 server.py
# Vector RAG จริง (build index ก่อน)
python3 build_vector_index.py --limit 10000 --batch-size 32
python3 server.py
# รันกับชุด 50k
LOGRAG_DATA_FILE=Data/files/security_logs_extended_50k.jsonl python3 server.py</code></pre>
<div class="callout">
โหมด local ทำงานได้แม้ไม่มี API key — ระบบ fallback ทุกชั้น (vector → keyword → rule-based local answer)
</div>
</section>
<!-- ASSESS -->
<section id="assess">
<h2><span class="num"></span> ประเมินของเดิม (ทำไมยังไม่ production-ready)</h2>
<p class="sub">เข้าใจข้อจำกัดก่อน เพื่อรู้ว่าต้องเปลี่ยนอะไร</p>
<table>
<tr><th>ด้าน</th><th>ของเดิม (demo)</th><th>ปัญหาตอนขึ้นจริง</th></tr>
<tr><td><b>Web server</b></td><td><code>http.server</code> + ThreadingHTTPServer</td><td>ไม่มี async/connection pool, ไม่เหมาะ traffic จริง, ไม่มี auth</td></tr>
<tr><td><b>Data store</b></td><td>โหลด JSONL ทั้งไฟล์เข้า RAM ตอน start</td><td>ไม่ scale, restart ช้า, query ใหม่ไม่ได้, ไม่มี retention</td></tr>
<tr><td><b>Index</b></td><td>สร้าง TF-IDF/vector ใน memory</td><td>หายเมื่อ restart, ไม่ incremental, single process</td></tr>
<tr><td><b>Logic ซ้ำ</b></td><td><code>server.py</code> กับ <code>api/_lograg.py</code> ซ้ำกันเกือบหมด</td><td>แก้ rule ต้องแก้ 2 ที่ — แหล่ง bug</td></tr>
<tr><td><b>Ingestion</b></td><td>ไม่มี — ใช้ไฟล์สังเคราะห์ static</td><td>ไม่มีทางรับ log จริงแบบ real-time</td></tr>
<tr><td><b>Automation</b></td><td>หน้าเว็บ poll <code>/api/alerts</code> ทุก 15 วิ</td><td>ไม่มี action จริง (block IP, ticket, notify)</td></tr>
<tr><td><b>Secrets/Auth</b></td><td>ไม่มี authentication เลย</td><td>เปิด public = ใครก็เรียก API ได้</td></tr>
</table>
<div class="callout crit">
<b>สิ่งที่เก็บไว้ได้:</b> CATEGORY_META (rule + MITRE + runbook), logic <code>detect_rule</code>, query expansion ไทย/อังกฤษ, โครงสร้าง API — ทั้งหมดนี้ port ไป FastAPI ได้ตรงๆ
</div>
</section>
<!-- ARCHITECTURE -->
<section id="arch">
<h2><span class="num"></span> Target Architecture (production)</h2>
<p class="sub">แยกเป็นชั้นชัดเจน: ingest → process → store → RAG/API → automation → notify</p>
<div class="flow">
<span class="node">Log Sources<br><small>auth/nginx/fw/syslog</small></span>
<span class="arrow"></span>
<span class="node">Collector<br><small>Vector / Fluent Bit</small></span>
<span class="arrow"></span>
<span class="node">Queue<br><small>Redpanda / Redis Stream</small></span>
<span class="arrow"></span>
<span class="node">Processor<br><small>classify + embed</small></span>
<span class="arrow"></span>
<span class="node">Stores<br><small>PG + Qdrant + OpenSearch</small></span>
</div>
<div class="flow">
<span class="node">RAG API<br><small>FastAPI</small></span>
<span class="arrow"></span>
<span class="node">n8n<br><small>orchestration</small></span>
<span class="arrow"></span>
<span class="node">Enrich<br><small>GeoIP / threat intel</small></span>
<span class="arrow"></span>
<span class="node">Action<br><small>ticket / block / notify</small></span>
</div>
<p>หลักการ: ทุก critical alert ที่ classifier จับได้ → ยิงเข้า n8n → enrich ด้วย threat intel → เรียก RAG API สรุปเป็นภาษาไทย → สร้าง ticket + แจ้งเตือน + (ออปชัน) block IP อัตโนมัติ</p>
</section>
<!-- STACK -->
<section id="stack">
<h2><span class="num"></span> Tech Stack รวบรวม</h2>
<p class="sub">เลือกแบบ self-hostable ก่อน (ลง Docker ได้หมด) แล้วค่อยสลับไป managed service ทีหลัง</p>
<h3>Core Pipeline</h3>
<table>
<tr><th>ชั้น</th><th>เลือกใช้</th><th>ทางเลือก</th><th>เฟส</th></tr>
<tr><td><b>Log collector</b></td><td>Vector.dev</td><td>Fluent Bit, Filebeat</td><td><span class="pill p-next">NEXT</span></td></tr>
<tr><td><b>Message queue</b></td><td>Redpanda (Kafka API)</td><td>Redis Streams, NATS, Kafka</td><td><span class="pill p-next">NEXT</span></td></tr>
<tr><td><b>API / RAG service</b></td><td>FastAPI + Uvicorn</td><td>Litestar, Flask</td><td><span class="pill p-now">NOW</span></td></tr>
<tr><td><b>Task worker</b></td><td>ARQ / Celery + Redis</td><td>Dramatiq, RQ</td><td><span class="pill p-next">NEXT</span></td></tr>
</table>
<h3>Data Stores</h3>
<table>
<tr><th>ชนิด</th><th>เลือกใช้</th><th>ใช้เก็บอะไร</th></tr>
<tr><td><b>Relational</b></td><td>PostgreSQL 16</td><td>alerts, incidents, users, audit, runbook, rule config</td></tr>
<tr><td><b>Vector DB</b></td><td>Qdrant <small>(โค้ดเดิมรองรับอยู่แล้ว)</small></td><td>embeddings ของ log message สำหรับ RAG</td></tr>
<tr><td><b>Log search</b></td><td>OpenSearch <small>(หรือ ClickHouse)</small></td><td>full-text + time-series log จำนวนมหาศาล</td></tr>
<tr><td><b>Cache / queue</b></td><td>Redis</td><td>rate-limit, task queue, dedup alert key</td></tr>
</table>
<div class="callout">
<b>ทางลัดประหยัด:</b> ใช้ <code>pgvector</code> บน PostgreSQL แทน Qdrant ได้ในเฟสแรก เหลือ DB เดียว — แต่ Qdrant scale ดีกว่าเมื่อ vector เยอะ. ClickHouse เหมาะกว่า OpenSearch ถ้าเน้น analytics/ราคาถูก
</div>
<h3>AI / RAG</h3>
<table>
<tr><th>หน้าที่</th><th>เลือกใช้</th><th>ทางเลือก</th></tr>
<tr><td><b>Embeddings</b></td><td>OpenRouter <small>(เดิม)</small></td><td>OpenAI, Ollama (local: nomic-embed-text)</td></tr>
<tr><td><b>LLM สรุป</b></td><td>OpenRouter <small>(เดิม)</small></td><td>Ollama (llama3/qwen), vLLM self-host</td></tr>
<tr><td><b>RAG framework</b></td><td>เขียนเอง <small>(เบา, คุมได้)</small></td><td>LlamaIndex, Haystack</td></tr>
</table>
<h3>Automation, Frontend & Ops</h3>
<div class="grid">
<div class="card"><h4>Automation</h4><p>n8n (self-host) — orchestrate alert → enrich → action</p></div>
<div class="card"><h4>Frontend</h4><p>คงหน้า HTML เดิมได้ หรือย้ายไป Next.js + Tailwind เมื่อต้อง auth/role</p></div>
<div class="card"><h4>Notify</h4><p>Slack / Discord / Email (SMTP) / PagerDuty / LINE Notify</p></div>
<div class="card"><h4>Threat Intel</h4><p>AbuseIPDB, VirusTotal, GreyNoise, MaxMind GeoIP</p></div>
<div class="card"><h4>Observability</h4><p>Prometheus + Grafana + Loki</p></div>
<div class="card"><h4>Auth/Gateway</h4><p>Traefik / Caddy + OAuth2-proxy หรือ Keycloak</p></div>
<div class="card"><h4>Secrets</h4><p>Doppler / Infisical / .env + SOPS</p></div>
<div class="card"><h4>Container/CI</h4><p>Docker Compose → Kubernetes, GitHub Actions</p></div>
</div>
</section>
<!-- PIPELINE -->
<section id="pipeline">
<h2><span class="num"></span> Pipeline จริง (data flow ละเอียด)</h2>
<p class="sub">เส้นทางของ log ตั้งแต่เข้าระบบจนเกิด action</p>
<ol>
<li><b>Ingest</b> — Vector/Fluent Bit อ่าน log จาก source (syslog, file, http) → ส่งเข้า Redpanda topic <code>logs.raw</code></li>
<li><b>Classify</b> — worker consume <code>logs.raw</code> → รัน <code>detect_rule()</code> (port มาจากของเดิม) → ใส่ category/severity/MITRE → เขียนลง PostgreSQL + OpenSearch</li>
<li><b>Embed</b> — worker เดียวกัน (หรือแยก) เรียก embedding model → upsert vector เข้า Qdrant พร้อม payload (row + metadata)</li>
<li><b>Detect critical</b> — ถ้า severity = critical → group ตาม <code>alert_key</code> (logic เดิม) → publish เข้า topic <code>alerts.critical</code> + insert ตาราง <code>alerts</code></li>
<li><b>Orchestrate</b> — n8n trigger จาก webhook/queue → enrich → RAG → action (รายละเอียดข้อ ⑤)</li>
<li><b>Query/RAG</b> — FastAPI <code>/api/ask</code>: retrieve จาก Qdrant (vector) + OpenSearch (keyword) → hybrid rank → ส่ง context ให้ LLM → ตอบภาษาไทย</li>
</ol>
<pre><code>logs.raw ──&gt; [classify worker] ──&gt; PostgreSQL (structured + alerts)
│ └─&gt; OpenSearch (full-text search)
└─&gt; [embed worker] ──&gt; Qdrant (vector RAG)
severity=critical ──&gt; alerts.critical (queue) ──&gt; n8n webhook ──&gt; enrich+RAG+action
User/Frontend ──&gt; FastAPI /api/ask ──&gt; hybrid retrieve (Qdrant + OpenSearch)
└─&gt; LLM (OpenRouter/Ollama) ──&gt; Thai answer</code></pre>
</section>
<!-- N8N -->
<section id="n8n">
<h2><span class="num"></span> n8n Workflows</h2>
<p class="sub">หัวใจของ automation — แต่ละ workflow คือ DAG ของ node</p>
<h3>WF-1: Critical Alert Responder</h3>
<div class="flow">
<span class="node">Webhook<br><small>/critical</small></span><span class="arrow"></span>
<span class="node">GeoIP + AbuseIPDB<br><small>enrich src_ip</small></span><span class="arrow"></span>
<span class="node">HTTP: RAG<br><small>POST /api/ask</small></span><span class="arrow"></span>
<span class="node">Switch<br><small>by category</small></span><span class="arrow"></span>
<span class="node">Create Ticket<br><small>Jira/GitHub</small></span><span class="arrow"></span>
<span class="node">Notify<br><small>Slack/LINE</small></span>
</div>
<ul>
<li><b>Trigger:</b> n8n Webhook node รับ payload จาก <code>alerts.critical</code> (หรือ Schedule poll <code>/api/alerts?since=</code>)</li>
<li><b>Enrich:</b> HTTP Request → AbuseIPDB/VirusTotal เช็ค reputation ของ <code>src_ip</code>, MaxMind GeoIP หาประเทศ</li>
<li><b>RAG summary:</b> POST <code>/api/ask</code> ด้วย <code>context_rows</code> ของ alert → ได้สรุปไทย + runbook</li>
<li><b>Route:</b> Switch ตาม category → <code>priv_esc/data_exfil</code> = แจ้ง on-call ทันที (PagerDuty); อื่นๆ = ticket ปกติ</li>
<li><b>Action:</b> สร้าง ticket, ส่ง Slack/LINE, (ออปชัน) เรียก firewall API block IP ที่ reputation แย่</li>
</ul>
<h3>WF-2: Daily Threat Digest</h3>
<p>Schedule (เช้า 9 โมง) → query <code>/api/stats</code> + top IPs → RAG สรุปภาพรวม 24 ชม. → ส่งรายงานเข้า Slack channel</p>
<h3>WF-3: Auto-block Feedback Loop</h3>
<p>เมื่อ analyst กด "confirm threat" บน dashboard → webhook → n8n update firewall rule + บันทึก audit ลง PostgreSQL + (ออปชัน) ใส่ IP เข้า blocklist ที่ feed กลับ collector</p>
<div class="callout warn">
<b>ข้อควรระวัง auto-block:</b> การ block IP อัตโนมัติคือ action ที่ผลกระทบสูง — ควรมี allowlist (IP ภายใน/พาร์ทเนอร์), require approval node สำหรับ critical action, และ TTL ให้ block ชั่วคราวก่อน
</div>
</section>
<!-- DB -->
<section id="db">
<h2><span class="num"></span> Database Schema (PostgreSQL)</h2>
<p class="sub">โครงเริ่มต้น — เก็บ structured log, alert grouping, audit trail</p>
<pre><code>-- log ที่ผ่าน classify แล้ว (hot 30-90 วัน, retention policy แยก)
CREATE TABLE log_events (
id BIGSERIAL PRIMARY KEY,
ts TIMESTAMPTZ NOT NULL,
source TEXT NOT NULL, -- auth/nginx/firewall/system
category TEXT NOT NULL, -- brute_force, c2_beacon, ...
severity TEXT NOT NULL, -- info/medium/high/critical
mitre TEXT,
risk_score SMALLINT,
src_ip INET,
dst_ip INET,
"user" TEXT,
message TEXT NOT NULL,
raw JSONB NOT NULL, -- เก็บ field ดิบทั้งหมด
embedded BOOLEAN DEFAULT FALSE -- ส่งเข้า Qdrant แล้วหรือยัง
);
CREATE INDEX ON log_events (ts DESC);
CREATE INDEX ON log_events (category, severity);
CREATE INDEX ON log_events (src_ip);
-- alert ที่ group แล้ว (จาก alert_key เดิม)
CREATE TABLE alerts (
id TEXT PRIMARY KEY, -- ALT-xxxxxx
alert_key TEXT NOT NULL,
title TEXT NOT NULL,
category TEXT NOT NULL,
severity TEXT NOT NULL DEFAULT 'critical',
mitre TEXT,
status TEXT NOT NULL DEFAULT 'new', -- new/ack/resolved/false_positive
event_count INT NOT NULL,
first_seen TIMESTAMPTZ NOT NULL,
last_seen TIMESTAMPTZ NOT NULL,
src_ip INET, dst_ip INET, "user" TEXT,
rag_summary TEXT, -- คำตอบจาก LLM
enrichment JSONB, -- ผล GeoIP/threat intel
assigned_to TEXT,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- audit ทุก action (โดยเฉพาะ auto-block)
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
ts TIMESTAMPTZ DEFAULT now(),
actor TEXT NOT NULL, -- user / n8n / system
action TEXT NOT NULL, -- block_ip / create_ticket / ack
target TEXT,
alert_id TEXT REFERENCES alerts(id),
detail JSONB
);
-- rule config (ย้าย CATEGORY_META ออกจาก hardcode มาเป็น data)
CREATE TABLE rule_config (
category TEXT PRIMARY KEY,
label TEXT, mitre TEXT, risk SMALLINT, runbook TEXT,
enabled BOOLEAN DEFAULT TRUE
);</code></pre>
<div class="callout">
ย้าย <code>CATEGORY_META</code> จาก hardcode ใน Python มาเป็นตาราง <code>rule_config</code> → แก้ runbook/risk ได้โดยไม่ต้อง deploy ใหม่ และเลิกปัญหา logic ซ้ำ 2 ไฟล์
</div>
</section>
<!-- DOCKER -->
<section id="docker">
<h2><span class="num"></span> Docker (โครงอ้างอิงสำหรับขึ้น proj จริง)</h2>
<p class="sub">ไฟล์อ้างอิง — ปรับ version/secret ตอนทำจริง</p>
<h3>Dockerfile (FastAPI service)</h3>
<pre><code>FROM python:3.12-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
# ใช้ uvicorn worker จริง (อย่าใช้ http.server เดิม)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]</code></pre>
<h3>docker-compose.yml (full stack dev)</h3>
<pre><code>services:
api:
build: .
ports: ["8000:8000"]
env_file: .env
depends_on: [postgres, qdrant, redis]
worker: # classify + embed consumer
build: .
command: python -m app.worker
env_file: .env
depends_on: [postgres, qdrant, redis, redpanda]
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: ${PG_PASSWORD}
POSTGRES_DB: roglag
volumes: ["pgdata:/var/lib/postgresql/data"]
ports: ["5432:5432"]
qdrant:
image: qdrant/qdrant:latest
volumes: ["qdrant:/qdrant/storage"]
ports: ["6333:6333"]
redis:
image: redis:7-alpine
ports: ["6379:6379"]
redpanda: # Kafka-compatible, เบากว่า Kafka
image: redpandadata/redpanda:latest
command: redpanda start --smp 1 --overprovisioned --node-id 0
ports: ["9092:9092"]
n8n:
image: n8nio/n8n:latest
environment:
N8N_HOST: localhost
WEBHOOK_URL: http://n8n:5678/
volumes: ["n8n:/home/node/.n8n"]
ports: ["5678:5678"]
vector: # log collector
image: timberio/vector:latest-alpine
volumes: ["./vector.toml:/etc/vector/vector.toml:ro"]
depends_on: [redpanda]
volumes:
pgdata: {}
qdrant: {}
n8n: {}</code></pre>
<div class="callout warn">
ทุก service เปิด port ใน compose นี้ไม่มี auth — สำหรับ <b>dev เท่านั้น</b>. ตอนขึ้น production ให้วาง Traefik/Caddy + OAuth ข้างหน้า, ปิด port ที่ไม่จำเป็น, ใส่ password ทุกตัวผ่าน secret manager
</div>
</section>
<!-- ROADMAP -->
<section id="roadmap">
<h2><span class="num"></span> Migration Roadmap</h2>
<p class="sub">ทำเป็นเฟส — แต่ละเฟสรันได้จริงก่อนไปต่อ</p>
<h3><span class="pill p-now">เฟส 1</span> Refactor core (1-2 สัปดาห์)</h3>
<ul>
<li>รวม <code>server.py</code> + <code>api/_lograg.py</code> เป็น package เดียว ลบ logic ซ้ำ</li>
<li>ย้ายไป FastAPI + Uvicorn, เพิ่ม Pydantic models สำหรับ request/response</li>
<li>เก็บ vector ใน Qdrant (เลิกใช้ SQLite blob), keyword ใน OpenSearch หรือ pgvector</li>
<li>เพิ่ม API key auth ขั้นต่ำ + rate limit</li>
</ul>
<h3><span class="pill p-next">เฟส 2</span> Real ingestion + automation (2-4 สัปดาห์)</h3>
<ul>
<li>ต่อ Vector/Fluent Bit → Redpanda → classify/embed worker</li>
<li>PostgreSQL schema (ข้อ ⑥) + retention policy</li>
<li>n8n WF-1 (Critical Responder) + WF-2 (Daily Digest)</li>
<li>ต่อ threat intel (AbuseIPDB/GeoIP), Slack/LINE notify</li>
</ul>
<h3><span class="pill p-later">เฟส 3</span> Scale & Ops (ต่อเนื่อง)</h3>
<ul>
<li>Observability: Prometheus + Grafana + Loki</li>
<li>ย้าย compose → Kubernetes (Helm), HPA สำหรับ worker</li>
<li>Self-host LLM (Ollama/vLLM) ลดค่า API + ข้อมูลไม่ออกนอก</li>
<li>Hybrid search tuning, eval RAG ด้วยชุด 50k, A/B model</li>
<li>WF-3 auto-block + approval flow, SOAR-style playbook</li>
</ul>
</section>
<footer>
ROGLAG Production Plan · สร้างเป็น HTML · อัปเดตแผนได้ตามเฟสที่ทำจริง
</footer>
</div>
</body>
</html>