From a587be08bda06dea8defee83bd5f4e56f1080bff Mon Sep 17 00:00:00 2001 From: jigoong Date: Wed, 20 May 2026 17:42:39 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20MinIO=20integration=20=E2=80=94=20bucke?= =?UTF-8?q?t=20finance,=20API=20service=20upload,=20Nginx=20routing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 01-infra/nginx-configs: add MinIO /minio/ and /minio-console/ location blocks (port 9000 S3 API, port 9001 Console UI, path stripping via rewrite) - 03-apiservice: integrate MinIO minio-python SDK for file upload - requirements.txt: add minio==7.2.11 - app/core/config.py: add MINIO_ENDPOINT, ACCESS_KEY, SECRET_KEY, BUCKET_FINANCE, USE_SSL - app/services/minio_client.py: new — upload_file(), get_presigned_url(), delete_file() - app/routes/pages.py: replace local /data/uploads/ write with MinIO upload to finance bucket - docker-compose.yml: pass MinIO env vars to container - .env.example: document MinIO vars - 07-minio/.env.example: add MINIO_SVC_ACCESS_KEY/SECRET_KEY section - 07-minio/README.md: add Python minio SDK and Airflow DAG usage guide - CLAUDE.md: project context (servers, SSH, paths, service distribution) - document-obsidiant/: initial Obsidian docs for all services --- 01-infra/nginx-configs/default-all.conf | 51 +++ 03-apiservice/.env.example | 8 + 03-apiservice/app/core/config.py | 7 + 03-apiservice/app/routes/pages.py | 33 +- 03-apiservice/app/services/minio_client.py | 48 +++ 03-apiservice/docker-compose.yml | 5 + 03-apiservice/requirements.txt | 2 +- 07-minio/.env.example | 8 + 07-minio/README.md | 127 ++++++ CLAUDE.md | 55 +++ .../00-Project-Overview.md | 112 +++++ .../01-Infrastructure.md | 202 +++++++++ .../02-Supabase.md | 232 ++++++++++ .../03-API-Service.md | 251 +++++++++++ .../04-Airflow.md | 237 +++++++++++ .../05-Analytics-Superset.md | 243 +++++++++++ .../06-MinIO.md | 264 ++++++++++++ .../07-Security-Strategy.md | 217 ++++++++++ .../08-Operations-Runbook.md | 401 ++++++++++++++++++ .../09-Port-Reference.md | 111 +++++ 20 files changed, 2601 insertions(+), 13 deletions(-) create mode 100644 03-apiservice/app/services/minio_client.py create mode 100644 CLAUDE.md create mode 100644 document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/00-Project-Overview.md create mode 100644 document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/01-Infrastructure.md create mode 100644 document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/02-Supabase.md create mode 100644 document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/03-API-Service.md create mode 100644 document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/04-Airflow.md create mode 100644 document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/05-Analytics-Superset.md create mode 100644 document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/06-MinIO.md create mode 100644 document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/07-Security-Strategy.md create mode 100644 document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/08-Operations-Runbook.md create mode 100644 document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/09-Port-Reference.md diff --git a/01-infra/nginx-configs/default-all.conf b/01-infra/nginx-configs/default-all.conf index adc3083..ac03153 100644 --- a/01-infra/nginx-configs/default-all.conf +++ b/01-infra/nginx-configs/default-all.conf @@ -343,6 +343,57 @@ server { # proxy_request_buffering off; # } + # ============================================= + # MinIO Object Storage (Server 2: 192.168.100.9) + # ============================================= + + # MinIO S3 API — port 9000 + # Path MUST be stripped before passing to MinIO + location /minio/ { + proxy_pass http://192.168.100.9:9000/; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-NginX-Proxy true; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + + client_max_body_size 1G; + proxy_request_buffering off; + proxy_buffering off; + } + + # MinIO Console UI — port 9001 (NOT 9000!) + # Path MUST be stripped: /minio-console/foo → /foo + location /minio-console/ { + rewrite ^/minio-console/(.*) /$1 break; + proxy_pass http://192.168.100.9:9001; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-NginX-Proxy true; + + # WebSocket support (Console uses WebSocket for real-time updates) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_buffering off; + chunked_transfer_encoding off; + } + + # Redirect /minio-console → /minio-console/ + location = /minio-console { + return 301 $scheme://$http_host/minio-console/; + } + #listen 443 ssl; # managed by sriphat #ssl_certificate /etc/letsencrypt/live/ai.bda.co.th/fullchain.pem; # managed by Certbot #ssl_certificate_key /etc/letsencrypt/live/ai.bda.co.th/privkey.pem; # managed by Certbot diff --git a/03-apiservice/.env.example b/03-apiservice/.env.example index be9c4c8..ced0428 100644 --- a/03-apiservice/.env.example +++ b/03-apiservice/.env.example @@ -47,3 +47,11 @@ KEYCLOAK_REDIRECT_URI=http://localhost:8040/apiservice/auth/callback AIRFLOW_API_URL=http://airflow-webserver:8080 AIRFLOW_API_TOKEN=your-airflow-api-token AIRFLOW_DAG_ID_FINANCE=process_finance_excel + +# MinIO Object Storage (server 2: 192.168.100.9) +# ใช้ service account sp_service_ac (ไม่ใช้ root credentials) +MINIO_ENDPOINT=192.168.100.9:9000 +MINIO_SVC_ACCESS_KEY=sp_service_ac +MINIO_SVC_SECRET_KEY=your-minio-service-account-secret +MINIO_BUCKET_FINANCE=finance +MINIO_USE_SSL=false diff --git a/03-apiservice/app/core/config.py b/03-apiservice/app/core/config.py index 6c5555d..31aa098 100644 --- a/03-apiservice/app/core/config.py +++ b/03-apiservice/app/core/config.py @@ -48,5 +48,12 @@ class Settings(BaseSettings): AIRFLOW_API_TOKEN: str = "" AIRFLOW_DAG_ID_FINANCE: str = "process_finance_excel" + # MinIO Object Storage + MINIO_ENDPOINT: str = "192.168.100.9:9000" + MINIO_ACCESS_KEY: str = "" + MINIO_SECRET_KEY: str = "" + MINIO_BUCKET_FINANCE: str = "finance" + MINIO_USE_SSL: bool = False + settings = Settings() diff --git a/03-apiservice/app/routes/pages.py b/03-apiservice/app/routes/pages.py index ba35b17..4459313 100644 --- a/03-apiservice/app/routes/pages.py +++ b/03-apiservice/app/routes/pages.py @@ -19,6 +19,7 @@ from app.security.permissions import require_role, Roles from app.db.session import get_db from app.models.upload import UploadHistory from app.services.airflow_client import airflow_client +from app.services import minio_client logger = logging.getLogger(__name__) @@ -28,7 +29,7 @@ router = APIRouter() templates_dir = Path(__file__).parent.parent / "templates" templates = Jinja2Templates(directory=str(templates_dir)) -# Upload directory +# Local fallback directory (used only if MinIO is not configured) UPLOAD_DIR = Path("/data/uploads") UPLOAD_DIR.mkdir(parents=True, exist_ok=True) @@ -120,29 +121,37 @@ async def upload_finance_file( timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") safe_filename = file.filename.replace(" ", "_") unique_filename = f"{timestamp}_{safe_filename}" - filepath = UPLOAD_DIR / unique_filename - - # Save file + + # Read file content try: content = await file.read() - with open(filepath, "wb") as f: - f.write(content) except Exception as e: - raise HTTPException( - status_code=500, - detail=f"Failed to save file: {str(e)}" + raise HTTPException(status_code=500, detail=f"Failed to read file: {str(e)}") + + # Upload to MinIO finance bucket + object_key = f"finance/{unique_filename}" + try: + minio_client.upload_file( + bucket=settings.MINIO_BUCKET_FINANCE, + object_name=object_key, + data=content, + content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ) - + filepath_stored = object_key # store MinIO key in DB + except Exception as e: + logger.error(f"MinIO upload failed: {e}") + raise HTTPException(status_code=500, detail=f"Failed to upload file to storage: {str(e)}") + # Get username from session user = request.session.get("user") username = user.get("username") if user else "anonymous" - + # Create upload record in database upload_id = f"upload_{timestamp}" upload_record = UploadHistory( upload_id=upload_id, filename=file.filename, - filepath=str(filepath), + filepath=filepath_stored, # MinIO object key: finance/ description=description, status="pending", uploaded_by=username diff --git a/03-apiservice/app/services/minio_client.py b/03-apiservice/app/services/minio_client.py new file mode 100644 index 0000000..ba28e60 --- /dev/null +++ b/03-apiservice/app/services/minio_client.py @@ -0,0 +1,48 @@ +import io +import logging +from minio import Minio +from minio.error import S3Error +from app.core.config import settings + +logger = logging.getLogger(__name__) + +_client: Minio | None = None + + +def get_client() -> Minio: + global _client + if _client is None: + _client = Minio( + endpoint=settings.MINIO_ENDPOINT, + access_key=settings.MINIO_ACCESS_KEY, + secret_key=settings.MINIO_SECRET_KEY, + secure=settings.MINIO_USE_SSL, + ) + return _client + + +def upload_file(bucket: str, object_name: str, data: bytes, content_type: str = "application/octet-stream") -> str: + """Upload bytes to MinIO. Returns the object key.""" + client = get_client() + client.put_object( + bucket_name=bucket, + object_name=object_name, + data=io.BytesIO(data), + length=len(data), + content_type=content_type, + ) + logger.info(f"Uploaded {object_name} to bucket {bucket}") + return object_name + + +def get_presigned_url(bucket: str, object_name: str, expires_seconds: int = 3600) -> str: + """Generate presigned GET URL valid for expires_seconds (default 1h).""" + from datetime import timedelta + client = get_client() + return client.presigned_get_object(bucket, object_name, expires=timedelta(seconds=expires_seconds)) + + +def delete_file(bucket: str, object_name: str) -> None: + client = get_client() + client.remove_object(bucket, object_name) + logger.info(f"Deleted {object_name} from bucket {bucket}") diff --git a/03-apiservice/docker-compose.yml b/03-apiservice/docker-compose.yml index 4998942..3e8bace 100644 --- a/03-apiservice/docker-compose.yml +++ b/03-apiservice/docker-compose.yml @@ -30,6 +30,11 @@ services: - KEYCLOAK_CLIENT_ID=${API_KEYCLOAK_CLIENT_ID} - KEYCLOAK_CLIENT_SECRET=${API_KEYCLOAK_CLIENT_SECRET} - KEYCLOAK_REDIRECT_URI=${API_KEYCLOAK_REDIRECT_URI} + - MINIO_ENDPOINT=${MINIO_ENDPOINT:-192.168.100.9:9000} + - MINIO_ACCESS_KEY=${MINIO_SVC_ACCESS_KEY} + - MINIO_SECRET_KEY=${MINIO_SVC_SECRET_KEY} + - MINIO_BUCKET_FINANCE=${MINIO_BUCKET_FINANCE:-finance} + - MINIO_USE_SSL=${MINIO_USE_SSL:-false} - LOG_LEVEL=debug ports: - "8040:8040" diff --git a/03-apiservice/requirements.txt b/03-apiservice/requirements.txt index 615ef82..f5582a6 100644 --- a/03-apiservice/requirements.txt +++ b/03-apiservice/requirements.txt @@ -17,4 +17,4 @@ cryptography==42.0.5 python-keycloak==3.9.0 Authlib==1.3.0 python-jose[cryptography]==3.3.0 - +minio==7.2.11 diff --git a/07-minio/.env.example b/07-minio/.env.example index 5634656..8cb8321 100644 --- a/07-minio/.env.example +++ b/07-minio/.env.example @@ -49,6 +49,14 @@ MINIO_IDENTITY_OPENID_SCOPES=openid,profile,email # Redirect URI after authentication MINIO_IDENTITY_OPENID_REDIRECT_URI=https://ai.sriphat.com/minio-console/oauth_callback +# ============================================================================ +# Service Account — Web Service +# สร้างหลัง MinIO start แล้วด้วย mc CLI +# mc admin user svcacct add --access-key --secret-key sriphat admin +# ============================================================================ +MINIO_SVC_ACCESS_KEY=sp_service_ac +MINIO_SVC_SECRET_KEY=your-service-account-secret-here + # ============================================================================ # Timezone # ============================================================================ diff --git a/07-minio/README.md b/07-minio/README.md index ee8d602..062ac35 100644 --- a/07-minio/README.md +++ b/07-minio/README.md @@ -166,6 +166,133 @@ mc ls myminio/my-bucket mc rm myminio/my-bucket/myfile.txt ``` +### **Python SDK (minio — แนะนำสำหรับ Sriphat Platform)** + +ใช้ `minio` package (Official MinIO Python SDK) แทน boto3 สำหรับ internal services: + +```python +from minio import Minio +import io + +# Connection — ใช้ internal IP จาก service บน server อื่น +client = Minio( + endpoint="192.168.100.9:9000", # internal IP, ไม่ใช่ public URL + access_key="sp_service_ac", + secret_key="", + secure=False, # HTTP ภายใน network +) + +# Upload file +with open("report.xlsx", "rb") as f: + data = f.read() +client.put_object( + bucket_name="finance", + object_name="finance/20260520_report.xlsx", + data=io.BytesIO(data), + length=len(data), + content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", +) + +# Download file (คืนค่าเป็น HTTPResponse) +response = client.get_object("finance", "finance/20260520_report.xlsx") +data = response.read() +response.close() +response.release_conn() + +# Download ไปยัง file โดยตรง +client.fget_object("finance", "finance/20260520_report.xlsx", "/tmp/report.xlsx") + +# List objects ใน bucket +for obj in client.list_objects("finance", prefix="finance/", recursive=True): + print(obj.object_name, obj.size) + +# Generate presigned URL (สำหรับให้ภายนอกดาวน์โหลด ใช้ได้ 1 ชั่วโมง) +from datetime import timedelta +url = client.presigned_get_object("finance", "finance/report.xlsx", expires=timedelta(hours=1)) +print(url) +``` + +--- + +### **Airflow DAG — อ่านไฟล์จาก MinIO finance bucket** + +Airflow อยู่บน server .9 (server เดียวกับ MinIO) ใช้ container name `minio:9000` หรือ `192.168.100.9:9000`: + +```python +from minio import Minio +import pandas as pd +import io +from airflow.decorators import dag, task +from airflow.utils.dates import days_ago + +@dag(schedule=None, start_date=days_ago(1), catchup=False) +def process_finance_excel(): + + @task + def download_and_process(filepath: str, **context): + """ + filepath = MinIO object key เช่น "finance/20260520_123000_report.xlsx" + ส่งมาจาก API Service ผ่าน DAG trigger conf + """ + client = Minio( + endpoint="minio:9000", # container name บน shared_data_network + access_key="sp_service_ac", # ใช้ service account เดียวกับ API + secret_key="{{ var.value.MINIO_SVC_SECRET_KEY }}", # เก็บใน Airflow Variables + secure=False, + ) + + # Download file จาก MinIO + response = client.get_object(bucket_name="finance", object_name=filepath) + file_bytes = response.read() + response.close() + response.release_conn() + + # ประมวลผลด้วย pandas + df = pd.read_excel(io.BytesIO(file_bytes)) + print(f"Loaded {len(df)} rows from {filepath}") + + # ... process data ... + return {"rows": len(df), "filepath": filepath} + + @task + def get_filepath(**context): + conf = context["dag_run"].conf or {} + return conf.get("filepath", "") + + fp = get_filepath() + download_and_process(fp) + +process_finance_excel() +``` + +**ตั้งค่า Airflow Connection (ทางเลือก — ใช้ S3Hook)** + +ถ้าต้องการใช้ `S3Hook` หรือ Airflow Operators: +``` +Connection ID : minio_s3 +Connection Type: Amazon Web Services +Extra (JSON) : {"endpoint_url": "http://minio:9000", "region_name": "ap-southeast-1"} +Login : sp_service_ac +Password : +``` + +```python +from airflow.providers.amazon.aws.hooks.s3 import S3Hook + +hook = S3Hook(aws_conn_id="minio_s3") +obj = hook.get_key(key="finance/20260520_report.xlsx", bucket_name="finance") +data = obj.get()["Body"].read() +df = pd.read_excel(io.BytesIO(data)) +``` + +**Airflow Variables ที่ต้องสร้าง:** +| Key | Value | +|-----|-------| +| `MINIO_ENDPOINT` | `192.168.100.9:9000` | +| `MINIO_SVC_SECRET_KEY` | (ดูจาก `07-minio/.env`) | + +--- + ### **Python SDK (boto3)** ```python diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a8a53b1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,55 @@ +# Sriphat Data Platform — Claude Context + +## Project Paths + +| Environment | Path | +|-------------|------| +| **Local (WSL2)** | `/mnt/e/git3/sriphat-dataplatform` | +| **Server .8** | `/home/bdadmin/sriphat-dataplatform` | +| **Server .9** | `/home/bdadmin/sriphat-dataplatform` (เฉพาะ folders ของ service ที่รันบนเครื่องนี้) | +| **Windows** | `E:\git3\sriphat-dataplatform` | + +## Remote Hosts (WSL2 SSH) + +| Server | IP | SSH Script | +|--------|----|-----------| +| **Server 1** | 192.168.100.8 | `~/key/ssh_sriphat_8.sh` | +| **Server 2** | 192.168.100.9 | `~/key/ssh_sriphat_9.sh` | + +## Service Distribution + +### Server 1 — 192.168.100.8 (folders ทั้งหมด) +``` +00-network/ # Docker network setup +01-infra/ # Nginx, Keycloak, PostgreSQL, Redis, Dozzle +02-supabase/ # Supabase full stack (13 containers) +03-apiservice/ # Custom FastAPI service +06-analytics/ # Apache Superset +``` + +### Server 2 — 192.168.100.9 (เฉพาะ folders ของ service ที่ใช้บนเครื่องนี้) +``` +05-airflow/ # Apache Airflow (CeleryExecutor) +07-minio/ # MinIO Object Storage +``` + +> **หมายเหตุ:** OpenMetadata อยู่บน server .9 แต่ไม่อยู่ใน repo นี้ + +## Global Environment File + +- **Local dev:** `.env.global` (root of project) +- **Server:** `/home/bdadmin/sriphat-dataplatform/.env.global` +- ไฟล์นี้อยู่ใน `.gitignore` — ต้องสร้างและ sync เองบนแต่ละเครื่อง + +## Docker Network + +ทุก service ใช้ network ร่วม ต้องสร้างก่อนรัน: +```bash +docker network create shared_data_network +``` + +## Daily Log Convention + +- Path: `_daily-log/YYYY-MM-DD-sriphat-dataplatform.md` +- Folder `_daily-log/` อยู่ใน `.gitignore` +- ดู convention ที่: `_daily-log/` (ถ้ามี README) หรือจาก BDA standard diff --git a/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/00-Project-Overview.md b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/00-Project-Overview.md new file mode 100644 index 0000000..e2a67f7 --- /dev/null +++ b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/00-Project-Overview.md @@ -0,0 +1,112 @@ +--- +tags: + - project/sriphat + - dataplatform + - infrastructure +created: 2026-05-07 +status: active +project: 2026-SRI-PJ-001 +--- + +# Sriphat Data Platform — Project Overview + +## ข้อมูลโครงการ + +| รายการ | รายละเอียด | +|--------|-----------| +| **โครงการ** | Sriphat AI Transformation Data Platform | +| **รหัสโครงการ** | 2026-SRI-PJ-001 | +| **องค์กร** | โรงพยาบาลศรีพัฒน์ (Sriphat Hospital) | +| **Domain** | `ai.sriphat.com` / `sriphat.local` | +| **Server IP** | `192.168.100.9` | +| **Timezone** | Asia/Bangkok (UTC+7) | + +## วัตถุประสงค์ + +สร้างระบบ **Modern Data Stack** สำหรับโรงพยาบาลศรีพัฒน์ โดยเน้น: +- **Security** — ระบบยืนยันตัวตนกลาง (SSO) ผ่าน Keycloak +- **Versatility** — รองรับข้อมูลหลายรูปแบบ (SQL Server, Oracle, REST API, Excel) +- **Single Sign-On** — ผู้ใช้ล็อกอินครั้งเดียวเข้าได้ทุก service + +## Architecture Overview + +``` +┌──────────────────────────────────────────────────────────┐ +│ Nginx Reverse Proxy │ +│ (Gateway + SSL + Subpath Routing) │ +│ ai.sriphat.com │ +└──────────────────────────────────────────────────────────┘ + │ │ │ + ┌──────▼──────┐ ┌──────▼──────┐ ┌────▼────────┐ + │ Keycloak │ │ API Service │ │ Superset │ + │ (SSO) │ │ (FastAPI) │ │ (BI) │ + │ /keycloak │ │ /apiservice │ │ /superset │ + └─────────────┘ └─────────────┘ └─────────────┘ + │ │ │ + └────────────────┼───────────────┘ + │ + ┌─────────────┼──────────────┐ + │ │ │ + ┌─────▼────┐ ┌─────▼─────┐ ┌────▼─────┐ + │PostgreSQL│ │ Supabase │ │ MinIO │ + │(Infra DB)│ │(BaaS/API) │ │(S3 Store)│ + └──────────┘ └───────────┘ └──────────┘ + │ + ┌──────▼──────┐ + │ Airflow │ + │ (Workflow) │ + └─────────────┘ +``` + +## Tech Stack (Layer Map) + +| Layer | Tool | หน้าที่ | +|-------|------|--------| +| **Gateway** | Nginx | จัดการ Domain, SSL, Subpath routing | +| **Identity (SSO)** | Keycloak 23.0 | ยืนยันตัวตนกลาง (OIDC/OAuth2), รองรับ LDAP/AD | +| **Backend API** | FastAPI (Python) | Custom API endpoints, API Key management | +| **Database (Infra)** | PostgreSQL 15 | ฐานข้อมูลหลักสำหรับ Keycloak, API Service | +| **BaaS** | Supabase | PostgreSQL + Auth + Realtime + Storage + Edge Functions | +| **Workflow** | Apache Airflow 3.1.5 | DAG-based workflow orchestration (CeleryExecutor) | +| **Ingestion** | Airbyte | ETL จาก HIS, Oracle, REST API (ปัจจุบัน commented out) | +| **Analytics** | Apache Superset | Business Intelligence Dashboard | +| **Object Storage** | MinIO | S3-compatible storage, รองรับ ML/AI workflows | +| **Cache/Queue** | Redis 7.2 | Celery broker สำหรับ Airflow | +| **Monitoring** | Dozzle | Docker container log viewer | + +## Docker Network + +ทุก service ใช้ network ร่วมกันชื่อ `shared_data_network` (external) + +```bash +docker network create shared_data_network +``` + +## Service Ports (Quick Reference) + +| Service | Container Port | Host Port | URL | +|---------|---------------|-----------|-----| +| Nginx Proxy | 80 | 8020 | `http://localhost:8020` | +| Keycloak | 8080 | 8085 | `http://localhost:8085/keycloak` | +| PostgreSQL (Infra) | 5432 | 5435 | internal | +| Supabase Studio | 3000 | 3010 | `http://localhost:3010` | +| Supabase Kong API | 8000 | 8100 | `http://localhost:8100` | +| Supabase DB | 5432 | 5434 | internal | +| Supabase Pooler | 6543 | 6544 | internal | +| API Service | 8040 | 8040 | `http://localhost:8040/apiservice` | +| Airflow API Server | 8080 | 8200 | `http://localhost:8200` | +| Superset | 8088 | 8088 | `http://localhost:8088` | +| MinIO API | 9000 | 9000 | `http://localhost:9000` | +| MinIO Console | 9001 | 9001 | `http://localhost:9001` | +| Dozzle | 8080 | 9999 | `http://localhost:9999/dozzle` | + +## Related Documents + +- [[01-Infrastructure]] — Nginx, Keycloak, PostgreSQL, Redis, Dozzle +- [[02-Supabase]] — BaaS layer (PostgreSQL + Auth + Realtime + Storage) +- [[03-API-Service]] — FastAPI custom endpoints +- [[04-Airflow]] — Workflow orchestration +- [[05-Analytics-Superset]] — BI Dashboard +- [[06-MinIO]] — Object Storage +- [[07-Security-Strategy]] — Security model และ SSO +- [[08-Operations-Runbook]] — Deploy, Backup, Troubleshoot diff --git a/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/01-Infrastructure.md b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/01-Infrastructure.md new file mode 100644 index 0000000..d41b457 --- /dev/null +++ b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/01-Infrastructure.md @@ -0,0 +1,202 @@ +--- +tags: + - project/sriphat + - infrastructure + - nginx + - keycloak + - postgresql +created: 2026-05-07 +status: active +folder: 01-infra +--- + +# Infrastructure Layer (01-infra) + +> **Docker Compose:** `01-infra/docker-compose.yml` +> **Env File:** `.env.global` + +## Services ใน Layer นี้ + +| Container | Image | Port | หน้าที่ | +|-----------|-------|------|--------| +| `nginx-proxy-manager` | nginx:latest | `8020:80` | Reverse proxy + Subpath routing | +| `keycloak` | quay.io/keycloak/keycloak:23.0 | `8085:8080` | SSO / Identity Provider | +| `postgres` | postgres:15-alpine | `5435:5432` | ฐานข้อมูลหลัก (Keycloak + API Service) | +| `redis` | redis:7.2-bookworm | internal | Cache / Message broker สำหรับ Airflow | +| `dozzle` | amir20/dozzle:latest | `9999:8080` | Docker log monitoring | + +--- + +## Nginx Proxy Manager + +**Image:** `nginx:latest` + +### Subpath Routing Table + +| Service | Subpath | Backend | +|---------|---------|---------| +| API Service | `/apiservice` | `apiservice:8040` | +| Supabase Studio | `/supabase` | `sdp-studio:3000` | +| Supabase Kong API | `/supabase-api` | `sdp-kong:8000` | +| Keycloak | `/keycloak` | `keycloak:8080` | +| Superset | `/superset` | `superset:8088` | +| Airflow | `/airflow` | `airflow-apiserver:8080` | +| Dozzle | `/dozzle` | `dozzle:8080` | +| MinIO API | `/minio` | `minio:9000` | +| MinIO Console | `/minio-console` | `minio:9001` | + +**Config directory:** `01-infra/nginx-configs/` + +### การตั้งค่า Nginx + +```nginx +# เพิ่ม config ผ่าน Custom Nginx Configuration ใน Proxy Host +# หรือ mount file ไปที่ /etc/nginx/conf.d/default.conf +``` + +--- + +## Keycloak (SSO) + +**Image:** `quay.io/keycloak/keycloak:23.0` +**URL:** `http://localhost:8085/keycloak` หรือ `https://ai.sriphat.com/keycloak` + +### Configuration + +```yaml +KC_DB: postgres +KC_DB_URL: jdbc:postgresql://postgres:5432/${KEYCLOAK_DB_NAME} +KC_HTTP_RELATIVE_PATH: "/keycloak" +KC_HOSTNAME_PATH: "/keycloak" +KC_PROXY: edge +KC_HTTP_ENABLED: "true" +``` + +### Keycloak SSO Clients ที่ต้องสร้าง + +| Client ID | Service | Protocol | +|-----------|---------|---------| +| `apiservice` | API Service | OIDC | +| `superset-client` | Apache Superset | OIDC | +| `minio-client` | MinIO | OIDC | +| `airflow-client` | Apache Airflow | OIDC | + +### ขั้นตอนตั้งค่า Keycloak หลัง Deploy + +1. เข้า Admin Console: `/keycloak/admin` +2. สร้าง Realm: `sriphat` +3. สร้าง Clients สำหรับแต่ละ service +4. เชื่อมต่อ LDAP/AD ของโรงพยาบาล (optional) +5. สร้าง Groups และ Roles +6. Map roles ให้กับ users + +--- + +## PostgreSQL (Infra DB) + +**Image:** `postgres:15-alpine` +**Port:** `5435` (host) → `5432` (container) + +### Databases ใน PostgreSQL นี้ + +| Database | เจ้าของ | +|----------|--------| +| `postgres` | Default + API Service | +| `keycloak` | Keycloak | +| `superset` | Apache Superset | +| `airflow` | Apache Airflow | + +### Init Scripts + +**Path:** `01-infra/init/` + +| File | หน้าที่ | +|------|--------| +| `00-create-keycloak-database.sql` | สร้าง database สำหรับ Keycloak | +| `03-create-airflow-databases.sql` | สร้าง database สำหรับ Airflow | + +### Connection String + +``` +Host: postgres (internal) / 192.168.100.9 (external) +Port: 5432 (internal) / 5435 (external) +User: ${DB_USER} +Password: ${DB_PASSWORD} +Database: postgres +``` + +--- + +## Redis + +**Image:** `redis:7.2-bookworm` +**Port:** `6379` (internal only) + +ใช้เป็น: +- Celery broker สำหรับ Apache Airflow +- Message queue + +``` +URL: redis://:@redis:6379/0 +``` + +--- + +## Dozzle (Log Monitoring) + +**Image:** `amir20/dozzle:latest` +**URL:** `http://localhost:9999/dozzle` หรือ `https://ai.sriphat.com/dozzle` + +### Features +- ดู Docker container logs แบบ real-time +- รองรับ Remote Agent (เชื่อมต่อ server อื่น) +- Filter และ search logs + +### Remote Agent Configuration + +```bash +# ใน .env.global +DOZZLE_REMOTE_AGENT=192.168.100.9:7007 +``` + +Server ที่ monitor: +- Main server (local) +- `192.168.100.9` — Airflow + MinIO + OpenMetadata server + +**Setup Guide:** `REMOTE_HOSTS_DOZZLE_SETUP.md` — คู่มือตั้งค่า Dozzle agent บน remote server + +--- + +## Environment Variables (.env.global) + +```bash +# Project +PROJECT_NAME=sriphat-data +DOMAIN=sriphat.local +TZ=Asia/Bangkok + +# Database +DB_HOST=postgres +DB_PORT=5432 +DB_PORT_EXPOSE=5435 +DB_USER=postgres +DB_PASSWORD= +DB_NAME=postgres + +# Keycloak +KEYCLOAK_ADMIN=admin +KEYCLOAK_ADMIN_PASSWORD= +KEYCLOAK_DB_NAME=keycloak + +# Dozzle +DOZZLE_PORT=9999 +DOZZLE_BASE=/dozzle +DOZZLE_HOSTNAME=Sriphat Main Server +DOZZLE_REMOTE_AGENT=192.168.100.9:7007 +``` + +## Related + +- [[00-Project-Overview]] +- [[07-Security-Strategy]] +- [[08-Operations-Runbook]] diff --git a/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/02-Supabase.md b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/02-Supabase.md new file mode 100644 index 0000000..4096109 --- /dev/null +++ b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/02-Supabase.md @@ -0,0 +1,232 @@ +--- +tags: + - project/sriphat + - supabase + - postgresql + - baas +created: 2026-05-07 +status: active +folder: 02-supabase +--- + +# Supabase Layer (02-supabase) + +> **Docker Compose:** `02-supabase/docker-compose.yml` +> **Env File:** `02-supabase/.env` + +Supabase เป็น Backend-as-a-Service (BaaS) แบบ self-hosted ที่รวม PostgreSQL, Auth, Realtime, Storage และ Edge Functions ไว้ในที่เดียว + +## Services + +| Container | Image | Port | หน้าที่ | +|-----------|-------|------|--------| +| `sdp-supabase-studio` | supabase/studio:2026.02.16 | `3010:3000` | Web UI สำหรับจัดการ database | +| `sdp-supabase-kong` | kong:2.8.1 | `8100:8000`, `8444:8443` | API Gateway (routing ทุก request) | +| `sdp-supabase-auth` | supabase/gotrue:v2.186.0 | internal | Authentication service | +| `sdp-supabase-rest` | postgrest/postgrest:v12.2.3 | internal | Auto-generated REST API จาก PostgreSQL | +| `sdp-realtime-dev` | supabase/realtime:v2.76.5 | internal | WebSocket realtime subscriptions | +| `sdp-supabase-storage` | supabase/storage-api:v1.37.8 | internal | File storage | +| `sdp-supabase-imgproxy` | darthsim/imgproxy:v3.30.1 | internal | Image transformation | +| `sdp-supabase-meta` | supabase/postgres-meta:v0.95.2 | internal | PostgreSQL metadata API | +| `sdp-supabase-edge-functions` | supabase/edge-runtime:v1.70.3 | internal | Deno edge functions | +| `sdp-supabase-analytics` | supabase/logflare:1.31.2 | internal | Log analytics (Logflare) | +| `sdp-supabase-db` | supabase/postgres:15.8.1.085 | `5434:5432` | PostgreSQL database หลัก | +| `sdp-supabase-vector` | timberio/vector:0.53.0-alpine | internal | Log collector | +| `sdp-supabase-pooler` | supabase/supavisor:2.7.4 | `6544:6543` | Connection pooler (PgBouncer-like) | + +--- + +## สถาปัตยกรรม Supabase + +``` +Client / API Service + │ + ▼ + sdp-supabase-kong (API Gateway: port 8100) + │ + ┌────┼────────────────────┐ + │ │ │ + ▼ ▼ ▼ +Auth REST API Realtime +GoTrue PostgREST Supabase Realtime + │ │ │ + └────┴────────────────────┘ + │ + ▼ + sdp-supabase-db (PostgreSQL 15) + │ + sdp-supabase-pooler + (Supavisor: port 6544) +``` + +--- + +## PostgreSQL Database (sdp-supabase-db) + +**Image:** `supabase/postgres:15.8.1.085` +**Port:** `5434` (host) + +### Init SQL Files + +| File | หน้าที่ | +|------|--------| +| `volumes/db/realtime.sql` | Setup replication สำหรับ Realtime | +| `volumes/db/webhooks.sql` | Database webhook functions | +| `volumes/db/roles.sql` | PostgreSQL roles setup | +| `volumes/db/jwt.sql` | JWT helper functions | +| `volumes/db/_supabase.sql` | Internal Supabase schema | +| `volumes/db/logs.sql` | Logging tables | +| `volumes/db/pooler.sql` | Connection pooler config | + +### Connection Strings + +``` +# Direct Connection +postgresql://postgres:@sdp-supabase-db:5432/postgres + +# Via Pooler (Transaction mode) +postgresql://postgres.tenant:@sdp-supabase-pooler:6543/postgres + +# External (from host) +postgresql://postgres:@192.168.100.9:5434/postgres +``` + +--- + +## Kong API Gateway + +**Image:** `kong:2.8.1` +**Port:** `8100` (HTTP), `8444` (HTTPS) + +Kong ทำหน้าที่ route requests ไปยัง services ต่างๆ: + +``` +/auth/v1/* → sdp-supabase-auth (GoTrue) +/rest/v1/* → sdp-supabase-rest (PostgREST) +/realtime/v1/ → sdp-realtime (WebSocket) +/storage/v1/* → sdp-supabase-storage +/functions/v1/*→ sdp-supabase-edge-functions +/meta/* → sdp-supabase-meta +``` + +**Kong Config:** `volumes/api/kong.yml` + +--- + +## Supavisor (Connection Pooler) + +**Image:** `supabase/supavisor:2.7.4` +**Port:** `6544` (transaction mode pooler) + +```bash +# Transaction mode (ใช้สำหรับ serverless/edge functions) +postgresql://postgres.sriphat:@sdp-supabase-pooler:6543/postgres + +POOLER_TENANT_ID: sriphat +POOLER_DEFAULT_POOL_SIZE: +POOLER_MAX_CLIENT_CONN: +``` + +--- + +## Authentication (GoTrue) + +**Image:** `supabase/gotrue:v2.186.0` + +### Features ที่เปิดใช้ + +| Feature | ค่า | +|---------|-----| +| Email Signup | `${ENABLE_EMAIL_SIGNUP}` | +| Anonymous Users | `${ENABLE_ANONYMOUS_USERS}` | +| Email Autoconfirm | `${ENABLE_EMAIL_AUTOCONFIRM}` | +| Phone Signup | `${ENABLE_PHONE_SIGNUP}` | + +### JWT Configuration + +``` +JWT_SECRET: <จาก env> +JWT_EXPIRY: <จาก env> +JWT_AUD: authenticated +JWT_DEFAULT_GROUP: authenticated +``` + +--- + +## Storage + +**Image:** `supabase/storage-api:v1.37.8` +**Data path:** `volumes/storage/` + +``` +FILE_SIZE_LIMIT: 52428800 (50MB) +STORAGE_BACKEND: file +ENABLE_IMAGE_TRANSFORMATION: true +``` + +--- + +## Supabase Studio + +**URL:** `http://localhost:3010` หรือ `https://ai.sriphat.com/supabase` +**Image:** `supabase/studio:2026.02.16-sha-26c615c` + +Studio เชื่อมต่อผ่าน: +- PostgreSQL Meta API (`sdp-meta:8080`) +- Kong API (`sdp-kong:8000`) +- Logflare (`sdp-analytics:4000`) + +**Snippets path:** `volumes/snippets/` +**Functions path:** `volumes/functions/` + +--- + +## Environment Variables (สำคัญ) + +```bash +# PostgreSQL +POSTGRES_HOST=sdp-supabase-db +POSTGRES_PORT=5432 +POSTGRES_DB=postgres +POSTGRES_PASSWORD= + +# JWT +JWT_SECRET= +JWT_EXPIRY=3600 + +# API Keys +ANON_KEY= +SERVICE_ROLE_KEY= + +# Studio +STUDIO_DEFAULT_ORGANIZATION=Sriphat Hospital +STUDIO_DEFAULT_PROJECT=DataPlatform +SUPABASE_PUBLIC_URL=https://ai.sriphat.com/supabase-api + +# Logflare +LOGFLARE_PUBLIC_ACCESS_TOKEN= +LOGFLARE_PRIVATE_ACCESS_TOKEN= +``` + +--- + +## การใช้งาน Supabase จาก API Service + +```python +# ใน 03-apiservice +SUPABASE_DB_HOST=sdp-supabase-db +SUPABASE_DB_PORT=5432 +SUPABASE_DB_USER=postgres.1 +SUPABASE_DB_NAME=postgres + +SUPABASE_API_URL=http://sdp-kong:8000 +SUPABASE_API_KEY= +``` + +--- + +## Related + +- [[00-Project-Overview]] +- [[03-API-Service]] +- [[07-Security-Strategy]] diff --git a/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/03-API-Service.md b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/03-API-Service.md new file mode 100644 index 0000000..5370f91 --- /dev/null +++ b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/03-API-Service.md @@ -0,0 +1,251 @@ +--- +tags: + - project/sriphat + - apiservice + - fastapi + - python +created: 2026-05-07 +status: active +folder: 03-apiservice +--- + +# API Service (03-apiservice) + +> **Docker Compose:** `03-apiservice/docker-compose.yml` +> **Env File:** `03-apiservice/.env` +> **Language:** Python / FastAPI + +## Overview + +Custom FastAPI service สำหรับ: +- รับข้อมูล Checkpoint จาก HIS (Hospital Information System) +- จัดการ API Keys แบบ permission-based +- Admin UI สำหรับบริหาร API Clients +- รองรับ Keycloak SSO สำหรับหน้าเว็บ Admin + +## Container + +| รายการ | ค่า | +|--------|-----| +| **Container** | `apiservice` | +| **Image** | `03-apiservice-apiservice:latest` (build local) | +| **Port** | `8040:8040` | +| **URL** | `https://ai.sriphat.com/apiservice` | +| **Health Check** | `http://localhost:8040/apiservice/docs` | + +--- + +## API Endpoints (หลัก) + +### Data Feed Endpoints + +``` +POST /apiservice/api/v1/feed/checkpoint +``` + +**Payload ตัวอย่าง:** +```json +[ + { + "id": 1, + "hn": 123, + "vn": 456, + "location": "OPD", + "type": "Scan", + "timestamp_in": "2026-02-16T10:00:00", + "timestamp_out": null, + "waiting_time": null, + "bu": "SRIPHAT" + } +] +``` + +**Required Permission:** `feed.checkpoint:write` + +### Admin Endpoints + +``` +GET /apiservice/admin/ # Admin dashboard +POST /apiservice/admin/api-keys/generate # สร้าง API Key ใหม่ +GET /apiservice/admin/api-clients # รายการ API Clients +``` + +### Documentation + +``` +GET /apiservice/docs # Swagger UI +GET /apiservice/redoc # ReDoc +``` + +--- + +## Database Schema + +API Service ใช้ PostgreSQL (Infra) และ Supabase: + +### Tables (PostgreSQL Infra) + +| Table | ใช้สำหรับ | +|-------|---------| +| `fastapi.ApiClient` | ข้อมูล API Client (ระบบที่ขอใช้ API) | +| `fastapi.ApiKey` | API Keys ที่เข้ารหัสแล้ว | + +### Tables (Supabase) + +| Table | Schema | ใช้สำหรับ | +|-------|--------|---------| +| `RawWaitingTime` | `operationbi` | ข้อมูล waiting time ดิบ | +| `RawOpdCheckpoint` | — | ข้อมูล OPD checkpoint | + +--- + +## Authentication + +### 1. API Key Authentication (สำหรับ System Integration) + +```bash +# Request header +Authorization: Bearer + +# หรือ query param +?api_key= +``` + +API Key สร้างได้จาก Admin UI โดยกำหนด permissions: +- `feed.checkpoint:write` — บันทึกข้อมูล checkpoint +- (สามารถเพิ่ม permissions เพิ่มเติมได้) + +### 2. Keycloak SSO (สำหรับ Admin Web UI) + +```bash +# Environment variables +KEYCLOAK_SERVER_URL=http://keycloak:8080 +KEYCLOAK_REALM=master +KEYCLOAK_CLIENT_ID=apiservice +KEYCLOAK_CLIENT_SECRET= +KEYCLOAK_REDIRECT_URI=http://localhost:8040/apiservice/auth/callback +``` + +--- + +## File Structure + +``` +03-apiservice/ +├── app/ +│ ├── api/v1/ +│ │ ├── routes.py # API endpoints +│ │ └── schemas.py # Pydantic schemas +│ ├── core/ +│ │ └── config.py # Settings / Config +│ ├── db/ +│ │ ├── models.py # SQLAlchemy models +│ │ ├── init_db.py # Database initialization +│ │ └── session.py # DB session +│ ├── middleware/ # Custom middleware +│ ├── models/ # Additional models +│ ├── routes/ # Additional routes +│ ├── security/ +│ │ ├── api_key.py # API Key handling +│ │ ├── keycloak_auth.py # Keycloak integration +│ │ ├── permissions.py # Permission system +│ │ └── dependencies.py # FastAPI dependencies +│ ├── services/ # Business logic +│ ├── templates/ # HTML templates (Admin UI) +│ └── utils/ +│ └── supabase_client.py +├── data/uploads/ # File uploads +├── docker-compose.yml +├── requirements.txt +└── .env +``` + +--- + +## Environment Variables + +```bash +# Application +APP_NAME=APIsService +ROOT_PATH=/apiservice +TIMEZONE=Asia/Bangkok + +# PostgreSQL (Infra DB) +DB_HOST=postgres +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD= +DB_NAME=postgres +DB_SSLMODE=prefer + +# Supabase DB (สำหรับ RawOpdCheckpoint) +SUPABASE_DB_HOST=sdp-supabase-db +SUPABASE_DB_PORT=5432 +SUPABASE_DB_USER=postgres.1 +SUPABASE_DB_NAME=postgres + +# Supabase API +SUPABASE_API_URL=http://sdp-kong:8000 +SUPABASE_API_KEY= + +# Admin +ADMIN_SECRET_KEY= +ADMIN_USERNAME=admin +ADMIN_PASSWORD= +API_KEY_ENC_SECRET= + +# Keycloak +KEYCLOAK_SERVER_URL=http://keycloak:8080 +KEYCLOAK_REALM=master +KEYCLOAK_CLIENT_ID=apiservice +KEYCLOAK_CLIENT_SECRET= +KEYCLOAK_REDIRECT_URI= + +# Airflow Integration +AIRFLOW_API_URL=http://airflow-webserver:8080 +AIRFLOW_API_TOKEN= +AIRFLOW_DAG_ID_FINANCE=process_finance_excel + +# Debug +DEBUG_AUTH=false +LOG_LEVEL=debug +``` + +--- + +## Build & Deploy + +```bash +# Build image +cd 03-apiservice +docker compose --env-file ../.env.global build + +# Start service +docker compose --env-file ../.env.global up -d + +# View logs +docker logs apiservice -f + +# Restart +docker restart apiservice +``` + +--- + +## Airflow Integration + +API Service มี integration กับ Apache Airflow: +- ส่ง trigger ไปยัง Airflow DAG +- DAG `process_finance_excel` สำหรับประมวลผล Excel files + +ดูรายละเอียดที่ `03-apiservice/AIRFLOW_INTEGRATION.md` + +--- + +## Related + +- [[00-Project-Overview]] +- [[01-Infrastructure]] +- [[02-Supabase]] +- [[04-Airflow]] +- [[07-Security-Strategy]] diff --git a/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/04-Airflow.md b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/04-Airflow.md new file mode 100644 index 0000000..493d200 --- /dev/null +++ b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/04-Airflow.md @@ -0,0 +1,237 @@ +--- +tags: + - project/sriphat + - airflow + - workflow + - etl +created: 2026-05-07 +status: active +folder: 05-airflow +--- + +# Apache Airflow (05-airflow) + +> **Docker Compose:** `05-airflow/docker-compose.yaml` +> **Env File:** `05-airflow/.env` +> **Version:** Apache Airflow 3.1.5 + +## Overview + +Apache Airflow ใช้สำหรับ Workflow Orchestration: +- รัน DAGs (Directed Acyclic Graphs) แบบตั้งเวลา +- ประมวลผล Excel/CSV files จาก Finance +- ETL pipeline orchestration +- Integration กับ API Service + +**Executor:** CeleryExecutor (ใช้ Redis เป็น broker) + +--- + +## Services + +| Container | หน้าที่ | Port | +|-----------|--------|------| +| `airflow-apiserver` | REST API + Web UI | `8200:8080` | +| `airflow-scheduler` | DAG scheduling | internal | +| `airflow-dag-processor` | DAG file parsing | internal | +| `airflow-worker` | Task execution (Celery) | internal | +| `airflow-triggerer` | Deferred task triggering | internal | +| `airflow-init` | Database migration (one-time) | — | +| `airflow-cli` | CLI tool (debug profile) | — | +| `flower` | Celery monitoring (optional) | `5555:5555` | + +--- + +## Architecture + +``` + ┌─────────────────┐ + │ airflow- │ + │ apiserver │ ← Web UI + REST API (port 8200) + │ (port 8080) │ + └────────┬────────┘ + │ + ┌───────────────┼───────────────┐ + │ │ │ + ┌──────▼──────┐ ┌──────▼──────┐ ┌─────▼──────┐ + │ airflow- │ │ airflow- │ │ airflow- │ + │ scheduler │ │ dag- │ │ triggerer │ + │ │ │ processor │ │ │ + └──────┬──────┘ └─────────────┘ └────────────┘ + │ + ▼ (Celery tasks via Redis) + ┌──────────────┐ + │ airflow- │ + │ worker │ ← รัน tasks จริง + └──────────────┘ + │ + ▼ + ┌──────────────┐ + │ PostgreSQL │ (Airflow metadata DB) + │ Redis │ (Celery broker) + └──────────────┘ +``` + +--- + +## Database Configuration + +Airflow ใช้ PostgreSQL บน Infra server: + +```bash +# Connection string +AIRFLOW__DATABASE__SQL_ALCHEMY_CONN= + postgresql+psycopg2://${AIRFLOW_DB_USER}:${AIRFLOW_DB_PASSWD}@${AIRFLOW_DB_HOST}:${AIRFLOW_DB_PORT}/${AIRFLOW_DB_NAME} + +AIRFLOW__CELERY__RESULT_BACKEND= + db+postgresql://${AIRFLOW_DB_USER}:${AIRFLOW_DB_PASSWD}@${AIRFLOW_DB_HOST}:${AIRFLOW_DB_PORT}/${AIRFLOW_DB_NAME} + +# Redis broker +AIRFLOW__CELERY__BROKER_URL=redis://:@redis:6379/0 +``` + +--- + +## Volume Mounts + +``` +05-airflow/ +├── dags/ → /opt/airflow/dags (DAG files) +├── logs/ → /opt/airflow/logs (Task logs) +├── config/ → /opt/airflow/config (airflow.cfg) +│ └── airflow.cfg +└── plugins/ → /opt/airflow/plugins (Custom plugins) +``` + +--- + +## Web UI + +**URL:** `http://localhost:8200` หรือ `https://ai.sriphat.com/airflow` + +```bash +# Config +AIRFLOW__WEBSERVER__BASE_URL=https://ai.sriphat.com/airflow +AIRFLOW__WEBSERVER__WEB_SERVER_PORT=8080 +``` + +Default credentials (ถ้าไม่เปลี่ยน): +- Username: `airflow` +- Password: `airflow` + +--- + +## DAGs ที่มีอยู่ + +| DAG ID | หน้าที่ | ถูก Trigger จาก | +|--------|--------|----------------| +| `process_finance_excel` | ประมวลผล Excel ของ Finance | API Service | + +--- + +## Airflow Configuration (airflow.cfg) + +**Path:** `05-airflow/config/airflow.cfg` + +Key settings: +```ini +[core] +executor = CeleryExecutor +load_examples = False +dags_are_paused_at_creation = True + +[webserver] +base_url = https://ai.sriphat.com/airflow + +[execution_api] +execution_api_server_url = http://airflow-apiserver:8080/execution/ +``` + +--- + +## Environment Variables + +```bash +# Airflow image +AIRFLOW_IMAGE_NAME=apache/airflow:3.1.5 + +# Database +AIRFLOW_DB_USER= +AIRFLOW_DB_PASSWD= +AIRFLOW_DB_HOST= +AIRFLOW_DB_PORT=5432 +AIRFLOW_DB_NAME=airflow + +# Security +AIRFLOW__CORE__FERNET_KEY= + +# Admin user +_AIRFLOW_WWW_USER_USERNAME=airflow +_AIRFLOW_WWW_USER_PASSWORD= + +# Optional pip packages +_PIP_ADDITIONAL_REQUIREMENTS= +``` + +--- + +## Deploy Commands + +```bash +cd 05-airflow + +# Initialize (first time only) +docker compose up airflow-init + +# Start all services +docker compose up -d + +# View logs +docker logs airflow-apiserver -f +docker logs airflow-scheduler -f +docker logs airflow-worker -f + +# Run Celery Flower monitoring +docker compose --profile flower up -d + +# Scale workers (เพิ่ม worker) +docker compose up -d --scale airflow-worker=3 +``` + +--- + +## System Requirements + +Airflow ต้องการ resources ขั้นต่ำ: +- **RAM:** ≥ 4 GB +- **CPU:** ≥ 2 cores +- **Disk:** ≥ 10 GB + +--- + +## Ingestion Layer (04-ingestion / Airbyte) + +> **หมายเหตุ:** `04-ingestion/docker-compose.yml` ปัจจุบัน **commented out ทั้งหมด** +> Airbyte ถูก deploy แยกต่างหาก (ผ่าน `abctl` หรือ standalone) + +### Airbyte ที่ระบุในแผน + +| Source | ชนิดข้อมูล | +|--------|----------| +| SQL Server (HIS) | ข้อมูลผู้ป่วย, OPD | +| Oracle (Lab) | ผลตรวจทางห้องปฏิบัติการ | +| REST API | External data | +| Excel/CSV | Finance, รายงาน | + +**Destination:** PostgreSQL `raw_data` schema + +**Port:** `8030` (เมื่อ deploy แล้ว) + +--- + +## Related + +- [[00-Project-Overview]] +- [[01-Infrastructure]] +- [[03-API-Service]] +- [[08-Operations-Runbook]] diff --git a/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/05-Analytics-Superset.md b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/05-Analytics-Superset.md new file mode 100644 index 0000000..043d664 --- /dev/null +++ b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/05-Analytics-Superset.md @@ -0,0 +1,243 @@ +--- +tags: + - project/sriphat + - superset + - analytics + - bi + - dashboard +created: 2026-05-07 +status: active +folder: 06-analytics +--- + +# Apache Superset — Analytics Layer (06-analytics) + +> **Docker Compose:** `06-analytics/docker-compose.yml` +> **Env File:** `.env` (global) + +## Overview + +Apache Superset ใช้เป็น Business Intelligence (BI) platform สำหรับ: +- สร้าง Dashboard และ Visualization +- เชื่อมต่อกับ PostgreSQL Data Warehouse +- **Embedded Superset SDK** — embed dashboard ใน applications อื่นโดยไม่ต้อง login +- สร้าง Report สำหรับผู้บริหารและแพทย์ + +> **SSO Keycloak:** ยังอยู่ในแผน ยังไม่ได้ implement + +--- + +## Container + +| รายการ | ค่า | +|--------|-----| +| **Container** | `superset` | +| **Image** | Build จาก `Dockerfile` ใน `06-analytics/` | +| **Port** | `8088:8088` | +| **URL** | `http://localhost:8088` หรือ `https://ai.sriphat.com/superset` | +| **Network** | `shared_data_network` | + +--- + +## Configuration + +### Database Connection (Superset metadata) + +Superset เก็บ metadata ใน PostgreSQL (Infra): + +``` +Database: superset +Host: ${DB_HOST} +Port: 5432 +User: ${DB_USER} +Password: ${DB_PASSWORD} +``` + +### Superset Config File + +**Path:** `06-analytics/superset_config.py` + +```python +SECRET_KEY = os.environ.get('SUPERSET_SECRET_KEY') +ENABLE_PROXY_FIX = True +PUBLIC_ROLE_LIKE = "Gamma" +GUEST_ROLE_NAME = "Gamma" + +# CSRF +WTF_CSRF_ENABLED = False +WTF_CSRF_TIME_LIMIT = None + +# CORS — อนุญาตทุก origin (ปรับ production ให้ restrictive กว่านี้) +ENABLE_CORS = True +CORS_OPTIONS = { + 'supports_credentials': True, + 'allow_headers': ['*'], + 'resources': ['*'], + 'origins': ['*'] +} + +SESSION_COOKIE_SAMESITE = "Lax" +SESSION_COOKIE_SECURE = False + +# Embedded Superset SDK +FEATURE_FLAGS = {"EMBEDDED_SUPERSET": True} +EMBEDDED_SUPERSET = True +TALISMAN_ENABLED = False +ENABLE_TEMPLATE_PROCESSING = True +LOGO_TARGET_PATH = '/superset/welcome/' + +# Guest Token (สำหรับ embedded dashboard ไม่ต้อง login) +GUEST_TOKEN_JWT_SECRET = '' +GUEST_TOKEN_JWT_EXP_SECONDS = 300 # 5 นาที +GUEST_TOKEN_JWT_ALGORITHM = "HS256" + +# Domain whitelist สำหรับ embed +EMBEDDED_SDK_HOST_WHITELIST = [ + "https://ai.sriphat.com", + "http://localhost:8800", + "http://127.0.0.1:8800" +] +``` + +> **หมายเหตุ:** Keycloak SSO ยังไม่ได้ integrate — ปัจจุบันใช้ Username/Password login + Embedded SDK สำหรับ embed dashboard ใน applications อื่น + +### Environment Variables + +```bash +SUPERSET_SECRET_KEY=${SUPERSET_SECRET_KEY} +DATABASE_DIALECT=postgresql +DATABASE_HOST=${DB_HOST} +DATABASE_PORT=5432 +DATABASE_DB=superset +DATABASE_USER=${DB_USER} +DATABASE_PASSWORD=${DB_PASSWORD} +SUPERSET_LOAD_EXAMPLES=no +SUPERSET_BIND_ADDRESS=0.0.0.0 +SUPERSET_PORT=8088 +TZ=Asia/Bangkok +``` + +--- + +## Startup Process + +เมื่อ container เริ่มทำงาน จะรันคำสั่งต่อไปนี้โดยอัตโนมัติ: + +```bash +# 1. Migrate database +superset db upgrade + +# 2. Create admin user +superset fab create-admin \ + --username ${SUPERSET_ADMIN_USERNAME} \ + --firstname Admin \ + --lastname User \ + --email admin@sriphat.local \ + --password ${SUPERSET_ADMIN_PASSWORD} + +# 3. Initialize Superset +superset init + +# 4. Start Gunicorn server +gunicorn --bind 0.0.0.0:8088 \ + --workers 4 \ + --timeout 120 \ + 'superset.app:create_app()' +``` + +--- + +## Data Sources ที่ Connect ได้ + +### PostgreSQL Data Warehouse (Infra) + +``` +postgresql://postgres:@postgres:5432/postgres +``` + +**Schemas ที่แนะนำให้ expose:** +- `analytics` — ข้อมูลที่ transform แล้ว (read-only สำหรับ BI) +- `operationbi` — ข้อมูล Operation BI + +### Supabase PostgreSQL + +``` +postgresql://postgres:@sdp-supabase-db:5432/postgres +``` + +--- + +## Dashboard ที่ควรสร้าง + +| Dashboard | ข้อมูล | ผู้ใช้ | +|-----------|--------|--------| +| OPD Waiting Time | `RawWaitingTime`, `RawOpdCheckpoint` | ผู้บริหาร, พยาบาล | +| Patient Flow | HIS data จาก Airbyte | แพทย์, ผู้บริหาร | +| Finance Overview | Excel จาก Finance | CFO, ผู้บริหาร | +| Department KPIs | Aggregated data | หัวหน้าแผนก | + +--- + +## Security (Row-Level Security) + +ตั้งค่า RLS ใน Superset เพื่อจำกัดข้อมูล: + +```sql +-- ตัวอย่าง: แพทย์เห็นเฉพาะผู้ป่วยของตัวเอง +-- ใน Superset: Security → Row Level Security → Add Rule +-- Filter: department = '{{current_username}}' +``` + +--- + +## Volume Mounts + +``` +06-analytics/ +├── data/superset_home/ # Superset config + cache +└── superset_config.py # Custom configuration +``` + +--- + +## Build & Deploy + +```bash +cd 06-analytics + +# Build image (มี custom Dockerfile สำหรับเพิ่ม packages) +docker compose --env-file ../.env.global build + +# Start +docker compose --env-file ../.env.global up -d + +# View logs +docker logs superset -f +``` + +--- + +## Development Version (06-analytics-dev) + +มี docker-compose สำหรับ development แยกต่างหาก: +- **Path:** `06-analytics-dev/docker-compose.yml` +- ใช้สำหรับทดสอบ config ก่อน deploy production + +--- + +## Access + +| รายการ | ค่า | +|--------|-----| +| URL | `http://localhost:8088` | +| Admin Username | ค่าจาก `SUPERSET_ADMIN_USERNAME` | +| Admin Password | ค่าจาก `SUPERSET_ADMIN_PASSWORD` | + +--- + +## Related + +- [[00-Project-Overview]] +- [[01-Infrastructure]] (Keycloak SSO) +- [[02-Supabase]] (Data Source) +- [[07-Security-Strategy]] diff --git a/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/06-MinIO.md b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/06-MinIO.md new file mode 100644 index 0000000..319ea37 --- /dev/null +++ b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/06-MinIO.md @@ -0,0 +1,264 @@ +--- +tags: + - project/sriphat + - minio + - storage + - s3 +created: 2026-05-07 +status: active +folder: 07-minio +--- + +# MinIO Object Storage (07-minio) + +> **Docker Compose:** `07-minio/docker-compose.yml` +> **Env File:** `07-minio/.env` + +## Overview + +MinIO เป็น S3-compatible object storage สำหรับ: +- เก็บ raw data files (CSV, JSON, Parquet) +- เก็บ ML/AI models และ training data +- เก็บ backups และ reports +- Keycloak SSO integration + +--- + +## Container + +| รายการ | ค่า | +|--------|-----| +| **Container** | `minio` | +| **Image** | `minio/minio:latest` | +| **API Port** | `9000:9000` | +| **Console Port** | `9001:9001` | +| **Console URL** | `https://ai.sriphat.com/minio-console` | +| **API URL** | `https://ai.sriphat.com/minio` | +| **Direct (Dev)** | `http://192.168.100.9:9001` (console) | +| **Region** | `ap-southeast-1` | + +--- + +## Use Cases + +| Use Case | ตัวอย่าง | +|----------|---------| +| **Data Lake** | Raw CSV, JSON, Parquet จาก Airbyte | +| **ML/AI Workflows** | Model files, training datasets, experiment artifacts | +| **Backup Storage** | Database dumps, application backups | +| **Report Files** | Excel, PDF reports จาก Finance | +| **Media Storage** | Images, documents จากระบบ HIS | +| **Application Storage** | File uploads จาก API Service | + +--- + +## Authentication + +### 1. Root Credentials (Default) + +```bash +MINIO_ROOT_USER=minioadmin +MINIO_ROOT_PASSWORD= +``` + +### 2. Keycloak SSO (แนะนำ) + +เชื่อมต่อผ่าน OpenID Connect: + +```bash +MINIO_IDENTITY_OPENID_CONFIG_URL=https://ai.sriphat.com/keycloak/realms/sriphat/.well-known/openid-configuration +MINIO_IDENTITY_OPENID_CLIENT_ID=minio-client +MINIO_IDENTITY_OPENID_CLIENT_SECRET= +MINIO_IDENTITY_OPENID_CLAIM_NAME=policy +MINIO_IDENTITY_OPENID_SCOPES=openid,profile,email +MINIO_IDENTITY_OPENID_REDIRECT_URI=https://ai.sriphat.com/minio-console/oauth_callback +``` + +**Policy Mapping:** User ใน Keycloak ต้องมี attribute `policy` ที่ map กับ MinIO policy + +--- + +## Environment Variables + +```bash +# Credentials +MINIO_ROOT_USER=minioadmin +MINIO_ROOT_PASSWORD= + +# URLs +MINIO_SERVER_URL=https://ai.sriphat.com/minio +MINIO_BROWSER_REDIRECT_URL=https://ai.sriphat.com/minio-console + +# Region +MINIO_REGION=ap-southeast-1 + +# Keycloak SSO +MINIO_IDENTITY_OPENID_CONFIG_URL= +MINIO_IDENTITY_OPENID_CLIENT_ID= +MINIO_IDENTITY_OPENID_CLIENT_SECRET= +MINIO_IDENTITY_OPENID_CLAIM_NAME=policy +MINIO_IDENTITY_OPENID_SCOPES=openid,profile,email +MINIO_IDENTITY_OPENID_REDIRECT_URI= + +TZ=Asia/Bangkok +``` + +--- + +## Volume Mounts + +``` +07-minio/ +├── data/ → /data (object storage data) +└── certs/ → /root/.minio/certs:ro (SSL certificates) +``` + +--- + +## การใช้งาน MinIO Client (mc) + +```bash +# Install +wget https://dl.min.io/client/mc/release/linux-amd64/mc +chmod +x mc && sudo mv mc /usr/local/bin/ + +# Config alias +mc alias set sriphat https://ai.sriphat.com/minio minioadmin + +# List buckets +mc ls sriphat + +# Create bucket +mc mb sriphat/raw-data +mc mb sriphat/ml-models +mc mb sriphat/backups +mc mb sriphat/reports + +# Upload +mc cp data.csv sriphat/raw-data/ +mc cp -r ./models/ sriphat/ml-models/ + +# Set bucket policy +mc anonymous set none sriphat/raw-data # private +mc anonymous set download sriphat/public # public read +``` + +--- + +## Python SDK (boto3) + +```python +import boto3 +from botocore.client import Config + +s3 = boto3.client( + 's3', + endpoint_url='https://ai.sriphat.com/minio', + aws_access_key_id='minioadmin', + aws_secret_access_key='', + config=Config(signature_version='s3v4'), + region_name='ap-southeast-1' +) + +# Upload file +s3.upload_file('data.csv', 'raw-data', 'data.csv') + +# Download file +s3.download_file('raw-data', 'data.csv', 'local-data.csv') + +# List objects +for obj in s3.list_objects_v2(Bucket='raw-data').get('Contents', []): + print(obj['Key']) +``` + +--- + +## Recommended Bucket Structure + +``` +sriphat/ +├── raw-data/ # ข้อมูลดิบจาก Airbyte / HIS +│ ├── his/ +│ ├── oracle-lab/ +│ └── finance-excel/ +├── processed-data/ # ข้อมูลที่ transform แล้ว +├── ml-models/ # ML/AI model files +│ ├── waiting-time/ +│ └── patient-flow/ +├── reports/ # Excel, PDF reports +├── backups/ # Database backups +│ └── postgres/ +└── uploads/ # User uploads จาก API Service +``` + +--- + +## Security + +```bash +# สร้าง read-only policy +cat > readonly-policy.json << 'EOF' +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["s3:GetObject", "s3:ListBucket"], + "Resource": ["arn:aws:s3:::*"] + } + ] +} +EOF + +mc admin policy create sriphat readonly-policy readonly-policy.json + +# Assign policy ให้ user +mc admin policy attach sriphat readonly-policy --user=analyst-user +``` + +--- + +## Health Check + +```bash +# ตรวจสอบสถานะ +curl -f http://localhost:9000/minio/health/live +docker exec minio curl -f http://localhost:9000/minio/health/live +``` + +--- + +## Backup Strategy + +```bash +# Backup data directory +tar -czf minio-backup-$(date +%Y%m%d).tar.gz 07-minio/data/ + +# Sync to remote +rsync -avz 07-minio/data/ backup-server:/backups/minio/ + +# Restore +docker compose down +tar -xzf minio-backup-20260501.tar.gz +docker compose up -d +``` + +--- + +## Keycloak Setup (สำหรับ SSO) + +ดูรายละเอียดที่ `07-minio/KEYCLOAK_INTEGRATION.md` + +1. สร้าง Client `minio-client` ใน Keycloak Realm `sriphat` +2. ตั้งค่า Valid Redirect URIs: `https://ai.sriphat.com/minio-console/oauth_callback` +3. สร้าง Client Scope `minio-policy` +4. เพิ่ม User Attribute Mapper `policy` +5. กำหนด `policy` attribute ให้กับ users ตาม MinIO policies + +--- + +## Related + +- [[00-Project-Overview]] +- [[01-Infrastructure]] (Keycloak SSO) +- [[07-Security-Strategy]] diff --git a/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/07-Security-Strategy.md b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/07-Security-Strategy.md new file mode 100644 index 0000000..c88fd2a --- /dev/null +++ b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/07-Security-Strategy.md @@ -0,0 +1,217 @@ +--- +tags: + - project/sriphat + - security + - sso + - keycloak + - rbac +created: 2026-05-07 +status: active +--- + +# Security Strategy + +## หลักการออกแบบ + +ระบบออกแบบตามหลัก **Defense in Depth** สำหรับสภาพแวดล้อมโรงพยาบาล: + +1. **Network Isolation** — ทุก service อยู่ใน `shared_data_network` +2. **Centralized Authentication** — SSO ผ่าน Keycloak เท่านั้น +3. **Schema Separation** — แยก raw / analytics / production data +4. **Row-Level Security** — PostgreSQL RLS จำกัดข้อมูลต่อ user +5. **API Key Management** — Permission-based API access + +--- + +## Network Security + +``` +Internet + │ + ▼ +Nginx Reverse Proxy (port 80/443) + │ + │ ← ทุก request ต้องผ่าน Nginx + │ +shared_data_network (Docker internal) + ├── keycloak + ├── postgres + ├── sdp-supabase-* (12 containers) + ├── apiservice + ├── superset + ├── airflow-* + ├── minio + └── redis +``` + +**Rules:** +- ไม่มี service ใดที่ bind ตรงกับ `0.0.0.0` ยกเว้นผ่าน Nginx +- Redis ไม่ expose port ออกภายนอก +- Supabase services ไม่ expose port ออกภายนอก (ผ่าน Kong เท่านั้น) + +--- + +## Authentication (SSO with Keycloak) + +### Flow + +``` +User + │ + ├─ Web UI → Keycloak (OIDC Login) → redirect back with token + │ + └─ API Client → API Key (จาก Admin UI) → Bearer token +``` + +### Keycloak Realms + +| Realm | Services | +|-------|---------| +| `master` | Admin (ปิด public access) | +| `sriphat` | Superset, MinIO, Airflow, API Service | + +### Client Configurations + +| Service | Client ID | Flow | +|---------|-----------|------| +| API Service | `apiservice` | Authorization Code | +| Superset | `superset-client` | Authorization Code | +| MinIO | `minio-client` | Authorization Code + PKCE | +| Airflow | `airflow-client` | Authorization Code | + +--- + +## API Key Security (API Service) + +### Permission System + +``` +API Client (ระบบที่ขอใช้ API) + │ + └── API Keys (ถูก encrypt ด้วย AES) + │ + └── Permissions: + - feed.checkpoint:write + - (เพิ่มได้ตามต้องการ) +``` + +### API Key Lifecycle + +```bash +# 1. Admin สร้าง API Client +POST /apiservice/admin/api-clients + +# 2. Generate API Key สำหรับ Client +POST /apiservice/admin/api-keys/generate + ?client_id=1 + &permissions=feed.checkpoint:write + &name=his-production-key + +# 3. ใช้งาน API Key +curl -H "Authorization: Bearer " \ + https://ai.sriphat.com/apiservice/api/v1/feed/checkpoint +``` + +--- + +## Database Security (Schema Separation) + +### PostgreSQL Schemas + +| Schema | ใช้งาน | สิทธิ์ | +|--------|--------|--------| +| `public` | Default | แล้วแต่ config | +| `raw_data` | ข้อมูลดิบจาก Airbyte | Airflow write, BI read-only | +| `analytics` | ข้อมูลที่ transform แล้ว | BI read-only | +| `operationbi` | Operation KPIs | API Service write, Superset read | +| `fastapi` | API Service metadata | API Service only | +| `_analytics` | Supabase Logflare | Internal only | +| `_realtime` | Supabase Realtime | Internal only | + +### Row-Level Security (RLS) + +```sql +-- ตัวอย่าง: จำกัดข้อมูลตาม department +ALTER TABLE patient_data ENABLE ROW LEVEL SECURITY; + +CREATE POLICY department_isolation ON patient_data + USING (department = current_setting('app.current_department')); + +-- Set context ใน connection +SET app.current_department = 'OPD'; +``` + +--- + +## Secrets Management + +### ไฟล์ Secrets + +| ไฟล์ | ความสำคัญ | ต้องทำ | +|------|---------|--------| +| `.env.global` | HIGH | ไม่ commit ลง git | +| `02-supabase/.env` | HIGH | ไม่ commit ลง git | +| `03-apiservice/.env` | HIGH | ไม่ commit ลง git | +| `07-minio/.env` | HIGH | ไม่ commit ลง git | + +### Key Secrets ที่ต้อง Rotate + +| Secret | Location | แนะนำ Rotation | +|--------|----------|--------------| +| `DB_PASSWORD` | `.env.global` | 90 วัน | +| `JWT_SECRET` (Supabase) | `02-supabase/.env` | เมื่อมีเหตุ | +| `KEYCLOAK_ADMIN_PASSWORD` | `.env.global` | 90 วัน | +| `ADMIN_SECRET_KEY` (API) | `03-apiservice/.env` | 90 วัน | +| `MINIO_ROOT_PASSWORD` | `07-minio/.env` | 90 วัน | +| `AIRFLOW__CORE__FERNET_KEY` | `05-airflow/.env` | ไม่ rotate (data loss) | +| `SUPERSET_SECRET_KEY` | `.env.global` | ไม่ rotate (session loss) | + +--- + +## Security Checklist + +### Pre-Production + +- [ ] เปลี่ยน passwords ทั้งหมดจาก default +- [ ] เปิด HTTPS ใน Nginx (Let's Encrypt หรือ internal CA) +- [ ] ตั้งค่า Keycloak realm `sriphat` (ไม่ใช่ `master`) +- [ ] เชื่อมต่อ LDAP/AD ของโรงพยาบาล +- [ ] Enable RLS ใน PostgreSQL +- [ ] ตั้งค่า firewall rules (จำกัด inbound ports) +- [ ] Setup audit logging +- [ ] กำหนด session timeout ใน Keycloak + +### Ongoing + +- [ ] Review API Keys ที่ active ทุก 30 วัน +- [ ] Monitor Dozzle สำหรับ unusual access patterns +- [ ] Backup secrets ไว้ใน secure vault (HashiCorp Vault หรือ similar) +- [ ] Rotate passwords ตามกำหนด + +--- + +## SSL/TLS Configuration + +Nginx จัดการ SSL termination: + +```nginx +server { + listen 443 ssl; + server_name ai.sriphat.com; + + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + + # Force HTTPS + # Redirect http → https ผ่าน Nginx +} +``` + +--- + +## Related + +- [[00-Project-Overview]] +- [[01-Infrastructure]] (Keycloak setup) +- [[03-API-Service]] (API Key management) +- [[08-Operations-Runbook]] diff --git a/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/08-Operations-Runbook.md b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/08-Operations-Runbook.md new file mode 100644 index 0000000..5c7162c --- /dev/null +++ b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/08-Operations-Runbook.md @@ -0,0 +1,401 @@ +--- +tags: + - project/sriphat + - operations + - runbook + - deployment + - troubleshooting +created: 2026-05-07 +status: active +--- + +# Operations Runbook + +## Quick Reference — Service Status + +```bash +# ดู containers ทั้งหมดที่รันอยู่ +docker ps + +# ดู resource usage +docker stats + +# ดู logs แบบ realtime (ผ่าน Dozzle) +# https://ai.sriphat.com/dozzle +``` + +--- + +## First-Time Deployment + +### Prerequisites + +- Docker + Docker Compose installed +- RAM ≥ 8 GB +- Disk ≥ 50 GB +- Port 80, 443 accessible + +### Step 1: Setup Network + +```bash +# สร้าง Docker network ร่วม +docker network create shared_data_network +``` + +### Step 2: Configure Environment + +```bash +# Copy และแก้ไขค่า +cp .env.example .env.global +nano .env.global + +# Supabase env +cd 02-supabase +cp .env.example .env +nano .env + +# API Service env +cd ../03-apiservice +cp .env.example .env +nano .env + +# MinIO env +cd ../07-minio +cp .env.example .env +nano .env +``` + +### Step 3: Start Services (ตามลำดับ) + +```bash +# 1. Infrastructure (Nginx + Keycloak + PostgreSQL + Redis) +cd 01-infra +docker compose --env-file ../.env.global up -d + +# รอ PostgreSQL พร้อม (~30 วินาที) +sleep 30 + +# 2. Supabase +cd ../02-supabase +docker compose up -d + +# รอ Supabase DB พร้อม (~60 วินาที) +sleep 60 + +# 3. API Service +cd ../03-apiservice +docker compose --env-file ../.env.global up --build -d + +# 4. Airflow (ถ้าใช้งาน) +cd ../05-airflow +docker compose up airflow-init # รอให้ init เสร็จ +docker compose up -d + +# 5. Analytics (Superset) +cd ../06-analytics +docker compose --env-file ../.env.global build +docker compose --env-file ../.env.global up -d + +# 6. MinIO +cd ../07-minio +docker compose up -d +``` + +### Step 4: Verify Services + +```bash +docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" +``` + +Expected containers: +- `nginx-proxy-manager` ✅ +- `keycloak` ✅ +- `postgres` ✅ +- `redis` ✅ +- `dozzle` ✅ +- `sdp-supabase-db` ✅ +- `sdp-supabase-studio` ✅ +- `sdp-supabase-kong` ✅ +- `sdp-supabase-auth` ✅ +- `sdp-supabase-rest` ✅ +- `sdp-realtime-dev` ✅ +- `sdp-supabase-storage` ✅ +- `sdp-supabase-pooler` ✅ +- `sdp-supabase-analytics` ✅ +- `sdp-supabase-meta` ✅ +- `apiservice` ✅ +- `superset` ✅ +- `minio` ✅ + +--- + +## Post-Installation Setup + +### Setup Keycloak + +1. เข้า `https://ai.sriphat.com/keycloak/admin` +2. Login ด้วย `KEYCLOAK_ADMIN` credentials +3. สร้าง Realm: `sriphat` +4. สร้าง Clients: + - `apiservice` (API Service) + - `superset-client` (Apache Superset) + - `minio-client` (MinIO) + - `airflow-client` (Apache Airflow) +5. เชื่อมต่อ LDAP/AD (optional) + +### Initialize API Service + +```bash +# เข้า Admin UI +https://ai.sriphat.com/apiservice/admin/ + +# สร้าง API Client และ Generate Key +curl -X POST "https://ai.sriphat.com/apiservice/admin/api-keys/generate" \ + -H "Cookie: session=" \ + -d "client_id=1&permissions=feed.checkpoint:write&name=his-key" +``` + +### Setup Superset Data Sources + +1. เข้า `https://ai.sriphat.com/superset` +2. Settings → Database Connections → Add +3. เพิ่ม PostgreSQL: + ``` + postgresql://postgres:@postgres:5432/postgres + ``` + +--- + +## Daily Operations + +### Start All Services + +```bash +# Infrastructure +cd 01-infra && docker compose --env-file ../.env.global up -d && cd .. + +# Supabase +cd 02-supabase && docker compose up -d && cd .. + +# API Service +cd 03-apiservice && docker compose --env-file ../.env.global up -d && cd .. + +# Airflow +cd 05-airflow && docker compose up -d && cd .. + +# Analytics +cd 06-analytics && docker compose --env-file ../.env.global up -d && cd .. + +# MinIO +cd 07-minio && docker compose up -d && cd .. +``` + +### Stop All Services + +```bash +cd 01-infra && docker compose down && cd .. +cd 02-supabase && docker compose down && cd .. +cd 03-apiservice && docker compose down && cd .. +cd 05-airflow && docker compose down && cd .. +cd 06-analytics && docker compose down && cd .. +cd 07-minio && docker compose down && cd .. +``` + +--- + +## Backup & Restore + +### Backup PostgreSQL (Infra) + +```bash +# Backup ทั้ง database +docker exec postgres pg_dumpall -U postgres > backup_all_$(date +%Y%m%d_%H%M).sql + +# Backup เฉพาะ database +docker exec postgres pg_dump -U postgres postgres > backup_postgres_$(date +%Y%m%d).sql +docker exec postgres pg_dump -U postgres superset > backup_superset_$(date +%Y%m%d).sql +docker exec postgres pg_dump -U postgres airflow > backup_airflow_$(date +%Y%m%d).sql +``` + +### Backup Supabase PostgreSQL + +```bash +docker exec sdp-supabase-db pg_dump -U postgres postgres > backup_supabase_$(date +%Y%m%d).sql +``` + +### Backup MinIO + +```bash +# ใช้ mc mirror +mc mirror sriphat/ ./minio-backup-$(date +%Y%m%d)/ + +# หรือ tar data directory +tar -czf minio-backup-$(date +%Y%m%d).tar.gz 07-minio/data/ +``` + +### Restore PostgreSQL + +```bash +# Restore +docker exec -i postgres psql -U postgres postgres < backup_postgres_20260501.sql +``` + +--- + +## Update Services + +```bash +# Pull latest images +cd 01-infra && docker compose --env-file ../.env.global pull && cd .. +cd 02-supabase && docker compose pull && cd .. +cd 06-analytics && docker compose --env-file ../.env.global pull && cd .. +cd 07-minio && docker compose pull && cd .. + +# Rebuild API Service (มี code changes) +cd 03-apiservice +docker compose --env-file ../.env.global build +docker compose --env-file ../.env.global up -d +``` + +--- + +## Troubleshooting + +### PostgreSQL ไม่ start / connection refused + +```bash +# ตรวจสอบ health +docker exec postgres pg_isready -U postgres + +# ดู logs +docker logs postgres --tail 50 + +# Check schemas +docker exec postgres psql -U postgres -c "\dn" +``` + +### Keycloak ไม่ start + +```bash +# ดู logs (มักเกิดจาก PostgreSQL ยังไม่พร้อม) +docker logs keycloak --tail 50 + +# Restart หลัง PostgreSQL พร้อม +docker restart keycloak +``` + +### API Service ไม่ connect database + +```bash +# ตรวจสอบ network +docker network inspect shared_data_network + +# ตรวจสอบ env vars +docker exec apiservice env | grep DB_ + +# Test connection จาก container +docker exec apiservice python -c "import psycopg2; psycopg2.connect(host='postgres', user='postgres', password='')" +``` + +### Supabase services unhealthy + +```bash +# ตรวจสอบทุก container +docker ps --filter "name=sdp-" + +# Restart ตามลำดับ dependency +docker restart sdp-supabase-db +sleep 10 +docker restart sdp-supabase-analytics +sleep 10 +docker restart sdp-supabase-kong +docker restart sdp-supabase-auth +docker restart sdp-supabase-rest +``` + +### Airflow worker ไม่ pick tasks + +```bash +# ตรวจสอบ Redis connectivity +docker exec airflow-worker redis-cli -h redis ping + +# ตรวจสอบ worker +docker logs airflow-worker --tail 50 + +# Restart worker +docker restart airflow-worker +``` + +### Nginx 502 Bad Gateway + +```bash +# ตรวจสอบว่า backend container ทำงานอยู่ +docker ps | grep + +# ตรวจสอบ logs +docker logs nginx-proxy-manager --tail 50 +docker logs --tail 50 + +# ตรวจสอบ network +docker network inspect shared_data_network | grep -A5 +``` + +### MinIO ไม่ accessible + +```bash +# Health check +curl -f http://localhost:9000/minio/health/live + +# Logs +docker logs minio --tail 50 + +# Disk space +df -h +``` + +--- + +## Monitoring + +### Dozzle (Docker Logs) + +**URL:** `https://ai.sriphat.com/dozzle` + +Dozzle monitor ทั้ง: +- Main server (local) +- Remote agent: `192.168.100.9:7007` + +### Container Health + +```bash +# ดู health status +docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Health}}" + +# ดู resource usage +docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" +``` + +--- + +## Access Points Summary + +| Service | URL | Credentials | +|---------|-----|-------------| +| Nginx Proxy | `http://192.168.100.9:8020` | — | +| Keycloak Admin | `https://ai.sriphat.com/keycloak/admin` | `KEYCLOAK_ADMIN` | +| Supabase Studio | `https://ai.sriphat.com/supabase` | DB credentials | +| Supabase API | `https://ai.sriphat.com/supabase-api` | ANON_KEY / SERVICE_ROLE_KEY | +| API Service | `https://ai.sriphat.com/apiservice` | Admin credentials | +| Airflow | `https://ai.sriphat.com/airflow` | Airflow admin | +| Superset | `https://ai.sriphat.com/superset` | `SUPERSET_ADMIN_*` | +| MinIO Console | `https://ai.sriphat.com/minio-console` | `MINIO_ROOT_*` | +| MinIO API | `https://ai.sriphat.com/minio` | S3 credentials | +| Dozzle | `https://ai.sriphat.com/dozzle` | — (no auth default) | + +--- + +## Related + +- [[00-Project-Overview]] +- [[01-Infrastructure]] +- [[07-Security-Strategy]] diff --git a/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/09-Port-Reference.md b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/09-Port-Reference.md new file mode 100644 index 0000000..0911e41 --- /dev/null +++ b/document-obsidiant/2026-SRI-PJ-001 Sriphat AI Transformation/09-Port-Reference.md @@ -0,0 +1,111 @@ +--- +tags: + - project/sriphat + - reference + - ports + - network +created: 2026-05-07 +status: active +--- + +# Port & Service Reference + +## Host Ports (External Access) + +| Port | Service | Container | Protocol | +|------|---------|-----------|---------| +| **8020** | Nginx Reverse Proxy | `nginx-proxy-manager` | HTTP | +| **8085** | Keycloak | `keycloak` | HTTP | +| **5435** | PostgreSQL (Infra) | `postgres` | TCP | +| **9999** | Dozzle (Log Monitor) | `dozzle` | HTTP | +| **3010** | Supabase Studio | `sdp-supabase-studio` | HTTP | +| **8100** | Supabase Kong API | `sdp-supabase-kong` | HTTP | +| **8444** | Supabase Kong API | `sdp-supabase-kong` | HTTPS | +| **5434** | Supabase PostgreSQL | `sdp-supabase-db` | TCP | +| **6544** | Supabase Pooler | `sdp-supabase-pooler` | TCP | +| **8040** | API Service | `apiservice` | HTTP | +| **8200** | Airflow API Server | `airflow-apiserver` | HTTP | +| **5555** | Flower (Celery UI) | `flower` | HTTP (optional) | +| **8088** | Apache Superset | `superset` | HTTP | +| **9000** | MinIO API | `minio` | HTTP | +| **9001** | MinIO Console | `minio` | HTTP | + +## Internal-Only Ports (Docker Network) + +| Port | Service | Container | ใช้งาน | +|------|---------|-----------|--------| +| **5432** | PostgreSQL (Infra) | `postgres` | Keycloak, API Service, Superset, Airflow | +| **6379** | Redis | `redis` | Airflow Celery broker | +| **8080** | Keycloak | `keycloak` | Internal (Nginx proxy → external 8085) | +| **9999** | GoTrue (Auth) | `sdp-supabase-auth` | Supabase auth | +| **3000** | PostgREST | `sdp-supabase-rest` | Supabase REST API | +| **4000** | Realtime | `sdp-realtime-dev` | WebSocket | +| **5000** | Storage API | `sdp-supabase-storage` | File storage | +| **5001** | ImgProxy | `sdp-supabase-imgproxy` | Image transform | +| **8080** | Postgres Meta | `sdp-supabase-meta` | DB metadata API | +| **4000** | Logflare | `sdp-supabase-analytics` | Log analytics | +| **4000** | Supavisor | `sdp-supabase-pooler` | Pooler management | +| **9001** | Vector | `sdp-supabase-vector` | Health check | +| **8080** | Airflow Scheduler | `airflow-scheduler` | Health check | +| **8974** | Airflow Scheduler | `airflow-scheduler` | Health check endpoint | + +## Nginx Subpath Routing + +| Subpath | Backend Container | Port | +|---------|-----------------|------| +| `/apiservice` | `apiservice` | 8040 | +| `/keycloak` | `keycloak` | 8080 | +| `/supabase` | `sdp-supabase-studio` | 3000 | +| `/supabase-api` | `sdp-supabase-kong` | 8000 | +| `/superset` | `superset` | 8088 | +| `/airflow` | `airflow-apiserver` | 8080 | +| `/minio` | `minio` | 9000 | +| `/minio-console` | `minio` | 9001 | +| `/dozzle` | `dozzle` | 8080 | + +## DNS / Hosts + +| Name | IP | ใช้งาน | +|------|----|--------| +| `dev.sriphat.com` | `192.168.100.9` | extra_hosts ใน containers | +| `ai.sriphat.com` | ตาม production DNS | Production URL | + +## Docker Network + +``` +Network: shared_data_network (external, bridge) + +Containers ที่ join: +├── nginx-proxy-manager +├── keycloak +├── postgres +├── redis +├── dozzle +├── sdp-supabase-studio +├── sdp-supabase-kong +├── sdp-supabase-auth +├── sdp-supabase-rest +├── sdp-realtime-dev +├── sdp-supabase-storage +├── sdp-supabase-imgproxy +├── sdp-supabase-meta +├── sdp-supabase-edge-functions +├── sdp-supabase-analytics +├── sdp-supabase-db +├── sdp-supabase-vector +├── sdp-supabase-pooler +├── apiservice +├── airflow-apiserver +├── airflow-scheduler +├── airflow-dag-processor +├── airflow-worker +├── airflow-triggerer +├── superset +└── minio +``` + +## Related + +- [[00-Project-Overview]] +- [[01-Infrastructure]] +- [[08-Operations-Runbook]]