feat: MinIO integration — bucket finance, API service upload, Nginx routing
- 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
This commit is contained in:
@@ -343,6 +343,57 @@ server {
|
|||||||
# proxy_request_buffering off;
|
# 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
|
#listen 443 ssl; # managed by sriphat
|
||||||
#ssl_certificate /etc/letsencrypt/live/ai.bda.co.th/fullchain.pem; # managed by Certbot
|
#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
|
#ssl_certificate_key /etc/letsencrypt/live/ai.bda.co.th/privkey.pem; # managed by Certbot
|
||||||
|
|||||||
@@ -47,3 +47,11 @@ KEYCLOAK_REDIRECT_URI=http://localhost:8040/apiservice/auth/callback
|
|||||||
AIRFLOW_API_URL=http://airflow-webserver:8080
|
AIRFLOW_API_URL=http://airflow-webserver:8080
|
||||||
AIRFLOW_API_TOKEN=your-airflow-api-token
|
AIRFLOW_API_TOKEN=your-airflow-api-token
|
||||||
AIRFLOW_DAG_ID_FINANCE=process_finance_excel
|
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
|
||||||
|
|||||||
@@ -48,5 +48,12 @@ class Settings(BaseSettings):
|
|||||||
AIRFLOW_API_TOKEN: str = ""
|
AIRFLOW_API_TOKEN: str = ""
|
||||||
AIRFLOW_DAG_ID_FINANCE: str = "process_finance_excel"
|
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()
|
settings = Settings()
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from app.security.permissions import require_role, Roles
|
|||||||
from app.db.session import get_db
|
from app.db.session import get_db
|
||||||
from app.models.upload import UploadHistory
|
from app.models.upload import UploadHistory
|
||||||
from app.services.airflow_client import airflow_client
|
from app.services.airflow_client import airflow_client
|
||||||
|
from app.services import minio_client
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ router = APIRouter()
|
|||||||
templates_dir = Path(__file__).parent.parent / "templates"
|
templates_dir = Path(__file__).parent.parent / "templates"
|
||||||
templates = Jinja2Templates(directory=str(templates_dir))
|
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 = Path("/data/uploads")
|
||||||
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
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")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
safe_filename = file.filename.replace(" ", "_")
|
safe_filename = file.filename.replace(" ", "_")
|
||||||
unique_filename = f"{timestamp}_{safe_filename}"
|
unique_filename = f"{timestamp}_{safe_filename}"
|
||||||
filepath = UPLOAD_DIR / unique_filename
|
|
||||||
|
# Read file content
|
||||||
# Save file
|
|
||||||
try:
|
try:
|
||||||
content = await file.read()
|
content = await file.read()
|
||||||
with open(filepath, "wb") as f:
|
|
||||||
f.write(content)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=500, detail=f"Failed to read file: {str(e)}")
|
||||||
status_code=500,
|
|
||||||
detail=f"Failed to save 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
|
# Get username from session
|
||||||
user = request.session.get("user")
|
user = request.session.get("user")
|
||||||
username = user.get("username") if user else "anonymous"
|
username = user.get("username") if user else "anonymous"
|
||||||
|
|
||||||
# Create upload record in database
|
# Create upload record in database
|
||||||
upload_id = f"upload_{timestamp}"
|
upload_id = f"upload_{timestamp}"
|
||||||
upload_record = UploadHistory(
|
upload_record = UploadHistory(
|
||||||
upload_id=upload_id,
|
upload_id=upload_id,
|
||||||
filename=file.filename,
|
filename=file.filename,
|
||||||
filepath=str(filepath),
|
filepath=filepath_stored, # MinIO object key: finance/<filename>
|
||||||
description=description,
|
description=description,
|
||||||
status="pending",
|
status="pending",
|
||||||
uploaded_by=username
|
uploaded_by=username
|
||||||
|
|||||||
48
03-apiservice/app/services/minio_client.py
Normal file
48
03-apiservice/app/services/minio_client.py
Normal file
@@ -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}")
|
||||||
@@ -30,6 +30,11 @@ services:
|
|||||||
- KEYCLOAK_CLIENT_ID=${API_KEYCLOAK_CLIENT_ID}
|
- KEYCLOAK_CLIENT_ID=${API_KEYCLOAK_CLIENT_ID}
|
||||||
- KEYCLOAK_CLIENT_SECRET=${API_KEYCLOAK_CLIENT_SECRET}
|
- KEYCLOAK_CLIENT_SECRET=${API_KEYCLOAK_CLIENT_SECRET}
|
||||||
- KEYCLOAK_REDIRECT_URI=${API_KEYCLOAK_REDIRECT_URI}
|
- 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
|
- LOG_LEVEL=debug
|
||||||
ports:
|
ports:
|
||||||
- "8040:8040"
|
- "8040:8040"
|
||||||
|
|||||||
@@ -17,4 +17,4 @@ cryptography==42.0.5
|
|||||||
python-keycloak==3.9.0
|
python-keycloak==3.9.0
|
||||||
Authlib==1.3.0
|
Authlib==1.3.0
|
||||||
python-jose[cryptography]==3.3.0
|
python-jose[cryptography]==3.3.0
|
||||||
|
minio==7.2.11
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ MINIO_IDENTITY_OPENID_SCOPES=openid,profile,email
|
|||||||
# Redirect URI after authentication
|
# Redirect URI after authentication
|
||||||
MINIO_IDENTITY_OPENID_REDIRECT_URI=https://ai.sriphat.com/minio-console/oauth_callback
|
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 <KEY> --secret-key <SECRET> sriphat admin
|
||||||
|
# ============================================================================
|
||||||
|
MINIO_SVC_ACCESS_KEY=sp_service_ac
|
||||||
|
MINIO_SVC_SECRET_KEY=your-service-account-secret-here
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Timezone
|
# Timezone
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -166,6 +166,133 @@ mc ls myminio/my-bucket
|
|||||||
mc rm myminio/my-bucket/myfile.txt
|
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="<MINIO_SVC_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 : <MINIO_SVC_SECRET_KEY>
|
||||||
|
```
|
||||||
|
|
||||||
|
```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 SDK (boto3)**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|||||||
55
CLAUDE.md
Normal file
55
CLAUDE.md
Normal file
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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=<secret>
|
||||||
|
DB_NAME=postgres
|
||||||
|
|
||||||
|
# Keycloak
|
||||||
|
KEYCLOAK_ADMIN=admin
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD=<secret>
|
||||||
|
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]]
|
||||||
@@ -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:<password>@sdp-supabase-db:5432/postgres
|
||||||
|
|
||||||
|
# Via Pooler (Transaction mode)
|
||||||
|
postgresql://postgres.tenant:<password>@sdp-supabase-pooler:6543/postgres
|
||||||
|
|
||||||
|
# External (from host)
|
||||||
|
postgresql://postgres:<password>@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:<password>@sdp-supabase-pooler:6543/postgres
|
||||||
|
|
||||||
|
POOLER_TENANT_ID: sriphat
|
||||||
|
POOLER_DEFAULT_POOL_SIZE: <from env>
|
||||||
|
POOLER_MAX_CLIENT_CONN: <from env>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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=<secret>
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
JWT_SECRET=<long-random-string>
|
||||||
|
JWT_EXPIRY=3600
|
||||||
|
|
||||||
|
# API Keys
|
||||||
|
ANON_KEY=<jwt-anon-key>
|
||||||
|
SERVICE_ROLE_KEY=<jwt-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=<token>
|
||||||
|
LOGFLARE_PRIVATE_ACCESS_TOKEN=<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=<anon-or-service-role-key>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [[00-Project-Overview]]
|
||||||
|
- [[03-API-Service]]
|
||||||
|
- [[07-Security-Strategy]]
|
||||||
@@ -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 <api-key>
|
||||||
|
|
||||||
|
# หรือ query param
|
||||||
|
?api_key=<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=<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=<secret>
|
||||||
|
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=<anon-or-service-role-key>
|
||||||
|
|
||||||
|
# Admin
|
||||||
|
ADMIN_SECRET_KEY=<secret>
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=<secret>
|
||||||
|
API_KEY_ENC_SECRET=<encryption-key>
|
||||||
|
|
||||||
|
# Keycloak
|
||||||
|
KEYCLOAK_SERVER_URL=http://keycloak:8080
|
||||||
|
KEYCLOAK_REALM=master
|
||||||
|
KEYCLOAK_CLIENT_ID=apiservice
|
||||||
|
KEYCLOAK_CLIENT_SECRET=<secret>
|
||||||
|
KEYCLOAK_REDIRECT_URI=<redirect-url>
|
||||||
|
|
||||||
|
# Airflow Integration
|
||||||
|
AIRFLOW_API_URL=http://airflow-webserver:8080
|
||||||
|
AIRFLOW_API_TOKEN=<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]]
|
||||||
@@ -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=<user>
|
||||||
|
AIRFLOW_DB_PASSWD=<password>
|
||||||
|
AIRFLOW_DB_HOST=<postgres-host>
|
||||||
|
AIRFLOW_DB_PORT=5432
|
||||||
|
AIRFLOW_DB_NAME=airflow
|
||||||
|
|
||||||
|
# Security
|
||||||
|
AIRFLOW__CORE__FERNET_KEY=<fernet-key>
|
||||||
|
|
||||||
|
# Admin user
|
||||||
|
_AIRFLOW_WWW_USER_USERNAME=airflow
|
||||||
|
_AIRFLOW_WWW_USER_PASSWORD=<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]]
|
||||||
@@ -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 = '<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:<password>@postgres:5432/postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
**Schemas ที่แนะนำให้ expose:**
|
||||||
|
- `analytics` — ข้อมูลที่ transform แล้ว (read-only สำหรับ BI)
|
||||||
|
- `operationbi` — ข้อมูล Operation BI
|
||||||
|
|
||||||
|
### Supabase PostgreSQL
|
||||||
|
|
||||||
|
```
|
||||||
|
postgresql://postgres:<password>@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]]
|
||||||
@@ -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=<strong-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=<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=<secret>
|
||||||
|
|
||||||
|
# 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=<keycloak-oidc-url>
|
||||||
|
MINIO_IDENTITY_OPENID_CLIENT_ID=<client-id>
|
||||||
|
MINIO_IDENTITY_OPENID_CLIENT_SECRET=<secret>
|
||||||
|
MINIO_IDENTITY_OPENID_CLAIM_NAME=policy
|
||||||
|
MINIO_IDENTITY_OPENID_SCOPES=openid,profile,email
|
||||||
|
MINIO_IDENTITY_OPENID_REDIRECT_URI=<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 <password>
|
||||||
|
|
||||||
|
# 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='<password>',
|
||||||
|
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]]
|
||||||
@@ -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 <key>" \
|
||||||
|
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]]
|
||||||
@@ -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=<admin-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:<password>@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='<pass>')"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 <service-name>
|
||||||
|
|
||||||
|
# ตรวจสอบ logs
|
||||||
|
docker logs nginx-proxy-manager --tail 50
|
||||||
|
docker logs <service-name> --tail 50
|
||||||
|
|
||||||
|
# ตรวจสอบ network
|
||||||
|
docker network inspect shared_data_network | grep -A5 <service-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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]]
|
||||||
@@ -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]]
|
||||||
Reference in New Issue
Block a user