---
title: "คู่มือการติดตั้งระบบ (Deployment Guide)"
titleEn: "Deployment Guide"
clauses: ["4.11.2", "4.4.7", "4.6.2", "ก.1.5.2"]
phase: 3
category: "ส่งมอบ"
generated: false
---

# คู่มือการติดตั้งระบบ (Deployment Guide)

**อ้างอิง TOR:** ข้อ 4.11.2 (Deployment Guide), ข้อ 4.4.7 (ติดตั้งบน Cloud ของมหาวิทยาลัย), ข้อ 4.6.2 (แยกสภาพแวดล้อม Dev/Production), ภาคผนวก ก ข้อ 1.5.2 (โดเมน dhammaverse.mbu.ac.th)
**ฉบับ:** 1.0 — วันที่ 3 ตุลาคม พ.ศ. 2569

> **สถานะ:** ระบบสาธิตติดตั้งและทดสอบบนเครื่องเดียว (sandbox) ตามหัวข้อ 2–4 แล้ว ส่วน **การติดตั้งบน Kubernetes ของมหาวิทยาลัย (หัวข้อ 5–8) ยังไม่ได้ดำเนินการจริง** เพราะ sandbox ไม่มี cluster, registry, DNS และ credential ของมหาวิทยาลัย — ข้อ 4.4.7 และ ก.1.5.2 จึงมีสถานะ todo และ 4.6.2 เป็น partial ในหน้า `/tor` ขั้นตอนในหัวข้อ 5–8 อ้างอิงไฟล์ส่งมอบจริง (`infra/helm/mbu-buddhist-ai/`, `.github/workflows/ci-cd.yml`) แต่ยังไม่ผ่านการรันบนคลัสเตอร์จริง
> เอกสารนี้ไม่ระบุรหัสผ่านหรือ API key ใด ๆ

---

## 1. ภาพรวมการติดตั้ง

| รูปแบบ | ใช้เมื่อ | องค์ประกอบ | สถานะ |
|---|---|---|---|
| ระบบสาธิต (sandbox / เครื่องเดียว) | สาธิต พัฒนา ทดสอบ ตรวจรับเบื้องต้น | Next.js แอปเดียว + SQLite + ไฟล์ในเครื่อง (+ HTTPS self-signed) | ติดตั้งและใช้งานแล้ว |
| Production บน Kubernetes | ให้บริการจริง | Helm chart 8 microservices + PostgreSQL 16 + Redis 7 + Elasticsearch + TEI + Object Storage + Ingress/cert-manager + HPA + WAF | ไฟล์ส่งมอบพร้อม ยังไม่ได้ติดตั้ง |

## 2. ความต้องการของเครื่อง (ระบบสาธิต)

- Node.js ≥22 และ npm
- `sqlite3` (ใช้โดยสคริปต์สำรอง/กู้คืน), `openssl` (สร้างใบรับรอง/คีย์)
- Python 3 (เฉพาะเมื่อต้องการดึงคลังความรู้ใหม่ด้วย `npm run data:fetch`)
- ไม่ต้องใช้ Docker หรือ database server
- เบราว์เซอร์ Chromium/Chrome สำหรับ E2E (ตั้ง `CHROMIUM_PATH` ถ้าไม่อยู่ที่ `/usr/bin/chromium`)

## 3. ติดตั้งระบบสาธิต (Local / Sandbox)

```bash
# 1) ติดตั้ง dependencies
npm install

# 2) ตั้งค่าสภาพแวดล้อม
cp .env.example .env
#    แก้ .env: AUTH_SECRET (≥32 ตัวอักษร), DATA_ENCRYPTION_KEY="base64:$(openssl rand -base64 32)"
#    TYPHOON_API_KEY = คีย์ที่มหาวิทยาลัยจัดหา (ไม่ใส่ = ใช้ Mock LLM อัตโนมัติ)

# 3) สร้างฐานข้อมูล prisma/dev.db (+ trigger ห้ามแก้/ลบ AuditLog)
npx prisma migrate dev

# 4) ข้อมูลตั้งต้น: ผู้ใช้สาธิต, config, prompt, guardrail, คลังความรู้, ชุดทดสอบ, ข้อมูลใช้งานย้อนหลัง 14 วัน
npx prisma db seed

# 5) รัน
npm run dev                      # http://localhost:3000
npm run dev -- -p 3210           # เปลี่ยนพอร์ต (ระบบสาธิตใช้ 3210)
```

**ตรวจว่าระบบขึ้น:**

```bash
curl -s http://localhost:3000/api/health          # { ok: true, ..., index: { chunks: … } }
curl -sI http://localhost:3000/th | head -1        # HTTP/1.1 200
curl -s http://localhost:3000/api/openapi.json | head -c 200
```

### 3.1 HTTPS แบบ self-signed (สาธิต ก.2.1.2)

```bash
bash scripts/generate-dev-cert.sh   # สร้าง certs/dev-key.pem + certs/dev-cert.pem (RSA 2048, อายุ 365 วัน, CN=localhost)
npm run start:https                 # = node server.https.js → https://localhost:3443
curl -sk https://localhost:3443/th | head -c 100
```

ตัวแปรเสริม: `HTTPS_PORT` (ค่าเริ่มต้น 3443), `HTTPS_HOST`, `HTTPS_KEY_FILE`, `HTTPS_CERT_FILE`, `NODE_ENV=production` (ใช้ผล `npm run build`) — เบราว์เซอร์จะเตือนใบรับรองไม่น่าเชื่อถือเพราะเป็น self-signed ซึ่งคาดหมายได้ในระบบสาธิต

### 3.2 Production build บนเครื่องเดียว (แยก Dev/Prod — TOR 4.6.2)

```bash
npm run db:prod:setup     # DATABASE_URL=file:./prod.db → prisma migrate deploy + seed (สร้าง prisma/prod.db)
npm run build
npm start                 # next start (Next.js โหลด .env.production: prod.db + ENABLE_HSTS)
```

| ไฟล์ | สภาพแวดล้อม | ฐานข้อมูล |
|---|---|---|
| `.env.development` | `npm run dev` | `prisma/dev.db` |
| `.env.production` | `npm run build` / `npm start` | `prisma/prod.db` (+ เปิด HSTS) |
| ฐานข้อมูลทดสอบ | vitest | `prisma/test-template-<pid>.db` (สร้าง/ลบอัตโนมัติ) |
| `prisma/perf.db` | ทดสอบบัญชีจำนวนมาก | สร้างโดย `npm run seed:perf-users` |

### 3.3 บัญชีสาธิตตามบทบาท

บัญชีสาธิตสร้างจาก **`seed/users.json`** (รหัสผ่านอยู่ในไฟล์นั้นและใน README — ไม่ระบุในเอกสารนี้; ต้องเปลี่ยนก่อนใช้งานจริง)

| อีเมล | บทบาท | หมายเหตุ |
|---|---|---|
| superadmin@mbu.ac.th | SUPER_ADMIN | ทุกสิทธิ์ รวมสำรอง/ทำลายข้อมูล เปลี่ยน role |
| admin@mbu.ac.th | ADMIN | ผู้ดูแลระบบ |
| committee@mbu.ac.th | ADMIN | บัญชีสำหรับคณะกรรมการตรวจรับ (TOR 7.2.8) |
| monk.demo@example.com | GENERAL_USER | เปิดสิทธิ์ Dhamma Talk แล้ว |
| student.demo@example.com | GENERAL_USER | ค่าเริ่มต้น (Dhamma Talk ปิด) |
| suspended.demo@example.com | GENERAL_USER | สถานะระงับ (ทดสอบการปฏิเสธการเข้าสู่ระบบ) |

นอกจากนี้ seed สร้าง “ผู้ใช้สาธิต 01–30” (`demo.userNN@example.com`) และข้อมูลการใช้งานสังเคราะห์สำหรับ Dashboard — ส่วนผู้ดูแล (`/admin`) เข้าได้เฉพาะ IP ใน `ADMIN_IP_ALLOWLIST`

### 3.4 ทดสอบหลังติดตั้ง

```bash
npm test                 # unit + API + openapi-coverage
npm run test:e2e         # Playwright 3 viewport (dev server ที่ E2E_PORT, ค่าเริ่มต้น 3210)
npm run report:all       # รายงาน security, perf, restore-test, ingestion, evaluation, sla
```

### 3.5 งานตามรอบบนเครื่องเดียว

| งาน | วิธี |
|---|---|
| ลบบทสนทนาหมดอายุ | อัตโนมัติใน process (ทุก `retention.purgeIntervalMinutes`) หรือ `npm run purge:conversations` |
| สำรองข้อมูล | ตั้ง cron เรียก `bash scripts/backup-db.sh` (ดู `/documents/backup-restore-guide`) |
| Log | `logs/app.log` (หมุนไฟล์ด้วย logrotate ของระบบปฏิบัติการ) |

## 4. การเปลี่ยนค่าที่ต้องทำก่อนเปิดใช้งานจริง (Checklist)

1. ตั้ง `AUTH_SECRET` และ `DATA_ENCRYPTION_KEY` ใหม่ที่สุ่มอย่างปลอดภัย (เก็บสำรอง `DATA_ENCRYPTION_KEY` ไว้อย่างปลอดภัย — หากหายจะถอดรหัสอีเมล/ชื่อผู้ใช้ไม่ได้)
2. เปลี่ยนรหัสผ่านบัญชีผู้ดูแลทั้งหมด และระงับ/ลบบัญชีสาธิตที่ไม่ใช้
3. ตั้ง `ADMIN_IP_ALLOWLIST` เป็นเครือข่ายของมหาวิทยาลัย และวางระบบหลัง reverse proxy ที่เขียน `x-forwarded-for` ทับ
4. ใส่ API key ของ LLM ที่มหาวิทยาลัยจัดหา แล้วกด **ทดสอบ LLM** ที่ `/admin/ai-config`
5. ตั้ง `NEXT_PUBLIC_BASE_URL` เป็นโดเมนจริง (https) เพื่อให้ cookie มีแฟล็ก `Secure`, เปิด `ENABLE_HSTS=1`
6. ตั้ง OAuth (Google/Facebook) และ SMTP จริงถ้าต้องการ
7. ลบข้อมูลการใช้งานสังเคราะห์ก่อนใช้งานจริง (`/admin/data-destruction` ขอบเขตบทสนทนา/โทเคน/ผู้ใช้ทั่วไป) หรือ seed ฐานข้อมูลใหม่โดยไม่รันขั้นข้อมูลสังเคราะห์

---

## 5. Production บน Kubernetes ด้วย Helm (แผนติดตั้ง)

### 5.1 สถาปัตยกรรมเป้าหมาย

```mermaid
flowchart TB
  DNS["dhammaverse.mbu.ac.th (DNS → Load Balancer)"] --> WAF["WAF / Anti-DDoS ของผู้ให้บริการคลาวด์"]
  WAF --> ING["Ingress (NGINX) + TLS (cert-manager)"]
  ING -->|"/"| WEB["frontend: mbu-buddhist-ai-web (Next.js SSR, release แยก)"]
  ING -->|"/api/auth, /api/me, /api/privacy, /api/admin/users ..."| ID["identity"]
  ING -->|"/api/chat, /api/conversations, /api/messages"| CHAT["chat"]
  ING -->|"/api/chunks"| RET["retrieval"]
  ING -->|"/api/admin/knowledge-base"| INGS["ingestion"]
  ING -->|"/api/admin/evaluation"| EVAL["evaluation"]
  ING -->|"/api/status, /api/admin/ai-config, prompts, guardrails ..."| CFG["config"]
  ING -->|"/api/documents, /api/admin/dashboard, alerts, sla-report, defect-log"| REP["report"]
  CHAT --> AIO["ai-orchestrator (LangGraph, ภายใน cluster)"]
  AIO --> RET
  AIO --> LLM["Typhoon API (OpenAI-compatible)"]
  subgraph DATA["namespace mbu-data / mbu-ai"]
    PG[("PostgreSQL 16")]
    RD[("Redis 7")]
    ES[("Elasticsearch")]
    TEI["TEI: bge-m3 / bge-reranker-v2-m3"]
    S3[["Object Storage (S3)"]]
  end
  ID & CHAT & CFG & REP & EVAL --> PG
  RET --> ES
  RET --> TEI
  INGS --> S3
  CFG --> RD
```

### 5.2 ไฟล์ส่งมอบ (`infra/helm/mbu-buddhist-ai/`)

| ไฟล์ | เนื้อหา |
|---|---|
| `Chart.yaml` | chart `mbu-buddhist-ai` version 0.1.0, `kubeVersion >=1.27.0-0` |
| `values.yaml` | ค่าร่วม: 8 services (image, port 3001–3008, replicas, resources, HPA CPU 70%, ingressPaths), env ร่วม (PostgreSQL/Redis/Elasticsearch/TEI/Typhoon/S3), `existingSecret`, pod/container security context, probes `/api/health`, Ingress + TLS + ตัวอย่าง WAF annotation |
| `values-dev.yaml` / `values-test.yaml` / `values-prod.yaml` | ค่าทับรายสภาพแวดล้อม |
| `templates/deployment-<service>.yaml` ×8 | Deployment ต่อ service |
| `templates/service.yaml`, `hpa.yaml`, `ingress.yaml`, `configmap.yaml`, `_helpers.tpl` | Service (ClusterIP), HPA (autoscaling/v2), Ingress, ConfigMap (env ร่วม + `APP_ENV` + `<SERVICE>_SERVICE_URL` ของทุก service) |

### 5.3 สามสภาพแวดล้อม (TOR 4.6.2)

| Environment | Namespace | values | Host | Trigger (CI/CD) | ลักษณะ |
|---|---|---|---|---|---|
| dev | `mbu-dev` | `values-dev.yaml` | dev.dhammaverse.mbu.ac.th | push → branch `develop` | replica เดียวทุก service, ปิด HPA, `LOG_LEVEL=debug`, cert Let’s Encrypt staging, ฐานข้อมูล `mbu_buddhist_ai_dev` |
| test | `mbu-test` | `values-test.yaml` | test.dhammaverse.mbu.ac.th | push → branch `release/*` | SIT/UAT, เปิด HPA ขนาดเล็ก (max 2–3), ฐานข้อมูล `mbu_buddhist_ai_test` |
| prod | `mbu-prod` | `values-prod.yaml` | dhammaverse.mbu.ac.th | git tag `v*` บน `main` + อนุมัติ environment `prod` | HA ≥2 replicas (chat/ai-orchestrator 3), HPA (chat/ai-orchestrator max 12), soft pod anti-affinity ข้าม node, WAF |

### 5.4 สิ่งที่ต้องเตรียมก่อนติดตั้ง

1. Kubernetes ≥1.27 พร้อม **metrics-server** (จำเป็นต่อ HPA), NGINX Ingress Controller (หรือปรับ `ingress.className`/annotation ตาม controller ที่มี), **cert-manager** พร้อม ClusterIssuer `letsencrypt-prod` / `letsencrypt-staging`
2. บริการข้อมูล: PostgreSQL 16, Redis 7, Elasticsearch, TEI (bge-m3, bge-reranker-v2-m3), Object Storage — ตาม hostname ใน `values*.yaml` (`postgresql.mbu-data.svc.cluster.local` ฯลฯ) หรือแก้ให้ตรงกับของจริง
3. Container registry (ค่าเริ่มต้น `ghcr.io`, image `mbu-ac-th/mbu-<service>-service`) และ `imagePullSecrets` ถ้าเป็น private
4. Secret ชื่อ `mbu-buddhist-ai-secrets` ในแต่ละ namespace (สร้างด้วย External Secrets / Sealed Secrets / kubectl — ไม่อยู่ใน Git) มีคีย์: `DB_PASSWORD`, `REDIS_PASSWORD`, `ELASTICSEARCH_PASSWORD`, `AUTH_SECRET`, `DATA_ENCRYPTION_KEY`, `OPENAI_API_KEY` (คีย์ Typhoon), `GEMINI_API_KEY`/`ANTHROPIC_API_KEY` (ถ้าใช้), `GOOGLE_OAUTH_CLIENT_SECRET`, `FACEBOOK_OAUTH_CLIENT_SECRET`, `SMTP_PASS`
5. DNS และ IP allowlist ของส่วนผู้ดูแล (แนะนำแยก Ingress ของ `/api/admin` แล้วใช้ `whitelist-source-range`)

### 5.5 งานพัฒนาที่ต้องทำก่อนติดตั้ง Production ได้จริง

จากการตรวจ repository ปัจจุบัน — ระบบสาธิตเป็น Next.js แอปเดียว จึงยังต้อง:

1. แยกโค้ดตาม bounded context เป็น 8 service (โครงสร้าง `src/domain`/`src/application`/`src/server` แบ่งไว้แล้ว) และสร้าง `services/<name>/Dockerfile` (CI อ้างถึงแต่ **ยังไม่มีไฟล์ใน repository**) พร้อม release ของ frontend `mbu-buddhist-ai-web`
2. เปลี่ยน Prisma datasource จาก `sqlite` เป็น `postgresql` และสร้าง migration ใหม่ (รวม trigger ห้าม UPDATE/DELETE `AuditLog` แบบ PostgreSQL)
3. ย้ายสถานะในหน่วยความจำไป Redis (rate limit, คิวงาน ingestion → BullMQ, การจองโทเคน) และย้ายไฟล์อัปโหลดไป Object Storage
4. implement `ElasticsearchSearchAdapter` (ปัจจุบันเป็นโครง) และตั้ง `TEI_EMBED_URL`/`TEI_RERANK_URL`
5. ทางเลือกระยะเปลี่ยนผ่าน: deploy แอปเดียว (Next.js) เป็น Deployment เดียวหลัง Ingress เดียวกันพร้อม PostgreSQL ก่อน แล้วค่อยแยก service

### 5.6 คำสั่งติดตั้ง/อัปเกรด

```bash
CHART=infra/helm/mbu-buddhist-ai
# ตรวจ chart ทั้ง 3 environment (เหมือนงาน lint ใน CI)
for e in dev test prod; do
  helm lint "$CHART" -f "$CHART/values.yaml" -f "$CHART/values-$e.yaml"
  helm template mbu-buddhist-ai "$CHART" -n "mbu-$e" -f "$CHART/values.yaml" -f "$CHART/values-$e.yaml" > /dev/null
done

# ติดตั้ง/อัปเกรด (ตัวอย่าง prod) — --atomic: ล้มเหลวแล้ว Helm ย้อนกลับเอง
helm upgrade --install mbu-buddhist-ai "$CHART" \
  --namespace mbu-prod --create-namespace \
  -f "$CHART/values.yaml" -f "$CHART/values-prod.yaml" \
  --set global.image.tag=v1.0.0 \
  --atomic --wait --timeout 15m --history-max 10

# ตรวจผล
kubectl -n mbu-prod get deploy,hpa,ingress -l app.kubernetes.io/instance=mbu-buddhist-ai
curl -fsS https://dhammaverse.mbu.ac.th/api/health
```

ปกติการ deploy ทำผ่าน pipeline อัตโนมัติ (`/documents/cicd-pipeline`) ซึ่งใช้คำสั่งเดียวกัน (dev/test timeout 10 นาที, prod 15 นาที)

### 5.7 Migration ฐานข้อมูลบน Production

ก่อนอัปเกรดเวอร์ชันที่มี migration: สำรองฐานข้อมูล (pg_dump / snapshot) → รัน `npx prisma migrate deploy` (Job หรือ init container ที่ชี้ `DATABASE_URL` ของ environment นั้น) → deploy แอป → smoke test

## 6. Rollback

| สถานการณ์ | วิธี |
|---|---|
| `helm upgrade` ล้มเหลว (pod ไม่ ready ภายใน timeout) | `--atomic` ย้อนกลับ revision เดิมให้อัตโนมัติ |
| Deploy ผ่านแต่ smoke test ไม่ผ่าน (ใน CI) | step “Rollback (helm rollback) on failure” ย้อนไป revision ที่บันทึกไว้ก่อน deploy |
| พบปัญหาหลังใช้งาน | `helm history mbu-buddhist-ai -n mbu-prod` → `helm rollback mbu-buddhist-ai <REVISION> -n mbu-prod --wait` (เก็บประวัติ 10 revision `--history-max 10`, Deployment เก็บ `revisionHistoryLimit: 5`) |
| ปัญหาจากการตั้งค่า AI/Prompt (ไม่ใช่โค้ด) | ย้อนกลับในแอปทันที: `/admin/ai-config` → snapshot → ย้อนกลับ; `/admin/prompts` → ใช้เวอร์ชันก่อนหน้า (ไม่ต้อง redeploy) |
| Migration ฐานข้อมูลทำให้ข้อมูลเสีย | กู้คืนจาก backup ก่อน migration (`/documents/backup-restore-guide`) แล้ว rollback แอปเป็นเวอร์ชันที่ตรงกับ schema |
| ระบบสาธิตบนเครื่องเดียว | หยุดแอป → checkout เวอร์ชันก่อนหน้า → `npm install && npm run build` → `bash scripts/restore-db.sh <ไฟล์สำรอง>` ถ้า schema เปลี่ยน → เปิดแอป |

## 7. DNS และ TLS

| รายการ | Production (แผน) | ระบบสาธิต |
|---|---|---|
| โดเมน | `dhammaverse.mbu.ac.th` (prod), `test.` / `dev.dhammaverse.mbu.ac.th` | `localhost` |
| DNS | ระเบียน A/CNAME ชี้ Load Balancer ของ Ingress — มหาวิทยาลัยเป็นผู้จัดการโซน `mbu.ac.th` | ไม่มี — **`dhammaverse.mbu.ac.th` ยัง resolve DNS ไม่ได้ ณ วันที่พัฒนา** (ก.1.5.2 = todo) |
| TLS | cert-manager ออก/ต่ออายุใบรับรองอัตโนมัติ (`cert-manager.io/cluster-issuer`), secret `dhammaverse-mbu-ac-th-tls`, บังคับ redirect HTTPS (`ssl-redirect`, `force-ssl-redirect`) | self-signed `server.https.js` :3443 |
| HSTS | `ENABLE_HSTS=1` | ตั้งใน `.env.production` (มีผลจริงเมื่อเสิร์ฟผ่าน HTTPS) |
| SSE/อัปโหลด | Ingress `proxy-buffering: off`, `proxy-read/send-timeout: 300`, `proxy-body-size: 50m` | — |

## 8. ความปลอดภัยของการติดตั้ง

- Pod: `runAsNonRoot` (UID/GID 1000), `seccompProfile: RuntimeDefault`, container `readOnlyRootFilesystem`, `allowPrivilegeEscalation: false`, drop ALL capabilities
- Secret อ้างผ่าน `$(KEY)` ใน env ของ Deployment (ไม่อยู่ใน ConfigMap)
- WAF/Anti-DDoS (ก.2.2.1): เลือกเปิดตัวอย่าง annotation ใน `values.yaml` ตามผู้ให้บริการคลาวด์ (ModSecurity + OWASP CRS, rate limit ที่ Ingress, AWS WAFv2/Shield, Azure Application Gateway WAF, Google Cloud Armor หรือ CDN/WAF ภายนอก)
- คีย์ LLM: มหาวิทยาลัยเป็นผู้จัดหาและใส่เองใน Secret ของแต่ละ namespace (TOR 4.6.1)

## 9. สิ่งที่ทำไม่ได้ใน sandbox และเหตุผล

| รายการ | TOR | เหตุผล | สิ่งที่ส่งมอบแทน |
|---|---|---|---|
| ติดตั้งบน Cloud/Kubernetes ของมหาวิทยาลัย | 4.4.7 | ไม่มีคลัสเตอร์ บัญชีคลาวด์ หรือ kubeconfig ของมหาวิทยาลัย | Helm chart 3 environment + คู่มือนี้ + pipeline |
| โดเมน dhammaverse.mbu.ac.th | ก.1.5.2 | DNS ยังไม่ resolve และไม่มีสิทธิ์จัดการโซน | Ingress host ตั้งไว้แล้ว |
| Auto Scaling จริง | ก.1.6.2 | ต้องมีคลัสเตอร์ + metrics-server | `templates/hpa.yaml` |
| WAF / Anti-DDoS | ก.2.2.1 | เป็นบริการของผู้ให้บริการคลาวด์ | rate limit ระดับแอป + ตัวอย่าง annotation |
| Container image ของ 8 services | 4.6.3 | sandbox ไม่มี Docker และระบบสาธิตเป็นแอปเดียว | ขั้นตอนใน CI (ทำงานเมื่อมี Dockerfile และ `DEPLOY_ENABLED=true`) |
| PostgreSQL/Redis/Elasticsearch/TEI จริง | 2.6 | ไม่มี database/search server ใน sandbox | SQLite + ดัชนีในหน่วยความจำ + adapter interfaces |
| CI/CD บน GitHub Actions | 4.6.3 | ไม่ได้ push ไป GitHub ที่ตั้ง secrets | `.github/workflows/ci-cd.yml` |
| OAuth/SMTP จริง | ก.3.3.2, ก.4.10.2 | ไม่มี client secret / SMTP | Mock OAuth Provider, `AlertEmailLog` |
