feat: add Easypanel deployment config
Some checks failed
CI / Detect changes (push) Has been cancelled
CI / API Lint (push) Has been cancelled
CI / Admin UI Tests (push) Has been cancelled
CI / Admin UI Build (push) Has been cancelled
CI / API Tests (push) Has been cancelled
CI / Scanner Lint (push) Has been cancelled
CI / Scanner Tests (push) Has been cancelled
CI / Banner Lint & Typecheck (push) Has been cancelled
CI / Banner Tests (push) Has been cancelled
CI / Banner Build (push) Has been cancelled
CI / Admin UI Typecheck (push) Has been cancelled

- Dockerfile.app: single container with supervisord (API + Worker + Beat + Scanner)
- supervisord.conf: process manager for 4 services in one container
- EASYPANEL.md: step-by-step deploy guide for Easypanel
- EASYPANEL-README.md: repo structure and deploy flow overview
This commit is contained in:
Ami Bot
2026-04-20 18:37:20 +07:00
parent d8e0a34e04
commit 062a384444
4 changed files with 471 additions and 0 deletions

52
Dockerfile.app Normal file
View File

@@ -0,0 +1,52 @@
# ── Build stage ──────────────────────────────────────────────────────
FROM python:3.12-slim AS builder
WORKDIR /build
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libpq-dev curl \
&& rm -rf /var/lib/apt/lists/*
# Copy pyproject.toml for both api and scanner
COPY apps/api/pyproject.toml ./api/pyproject.toml
COPY apps/scanner/pyproject.toml ./scanner/pyproject.toml
# Install API dependencies
RUN pip install --no-cache-dir --prefix=/install api/.
# Install Scanner dependencies (Playwright + Chromium)
RUN pip install --no-cache-dir --prefix=/install scanner/. \
&& playwright install chromium --with-deps
# ── Runtime stage ────────────────────────────────────────────────────
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 curl tini \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
# Copy installed dependencies from builder
COPY --from=builder /install /usr/local
# Copy application code
COPY apps/api/src ./src
COPY apps/scanner/src ./src_scanner
COPY supervisord.conf /etc/supervisord.conf
# Move scanner source into api structure
RUN if [ -d src_scanner ]; then \
cp -r src_scanner/* src/ 2>/dev/null || true; \
fi
# Healthcheck for API
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Use tini as init system for proper signal handling
ENTRYPOINT ["/usr/bin/tini", "--"]
# supervisord manages multiple processes
CMD ["supervisord", "-c", "/etc/supervisord.conf"]

88
EASYPANEL-README.md Normal file
View File

@@ -0,0 +1,88 @@
# ConsentOS — Repository Structure for Easypanel
## Files to Add to Repository
```
consent-os/ ← ต้อง push ขึ้น Git
├── Dockerfile.app ← ✅ สร้างใหม่ (API container)
├── supervisord.conf ← ✅ สร้างใหม่ (process manager)
├── docker-compose.yml ← ✅ สร้างให้แล้ว
├── .env.example ← ✅ สร้างให้แล้ว
├── DEPLOY.md ← ✅ คู่มือ deploy ทั่วไป
└── EASYPANEL.md ← ✅ คู่มือ deploy บน Easypanel
```
## Files ที่มีอยู่แล้วใน Repo (อย่าลบ)
```
consent-os/
├── apps/
│ ├── api/ ← ✅ keep
│ │ ├── Dockerfile ← ใช้แทน Dockerfile.app สำหรับ dev
│ │ ├── src/ ← ✅ keep
│ │ ├── pyproject.toml ← ✅ keep
│ │ └── ...
│ ├── scanner/ ← ✅ keep
│ │ ├── Dockerfile ← ใช้แทน Dockerfile.app สำหรับ dev
│ │ └── ...
│ ├── admin-ui/ ← ✅ keep (deploy แยก)
│ │ └── Dockerfile
│ └── banner/ ← ✅ keep (admin-ui ดึงมาตอน build)
└── ...
```
## Deploy Flow on Easypanel
```
Git push
├──► Project: consentos
│ Service: consentos-db (Postgres) ───┐
│ Service: consentos-redis (Redis) ───┤
│ Service: consentos-app (App) ───┤
│ ───┘
│ (auto network)
└──► Project: consentos-admin
Service: consentos-admin (Admin UI)
```
## Important Notes
### 1. Database Connection
- Easypanel ใช้ **service name** เป็น hostname ภายใน Docker network
- `DATABASE_URL=postgresql+asyncpg://consentos:PASS@consentos-db:5432/consentos`
- `REDIS_URL=redis://:PASS@consentos-redis:6379/0`
### 2. Dockerfile.app Location
- ต้องอยู่ที่ **root ของ repo** ไม่ใช่ใน apps/
- เพราะ Easypanel build จาก repo root
### 3. Admin UI Deploy แยก
- Admin UI deploy เป็น **separate project/service** เพราะ:
- ใช้ Dockerfile คนละตัว
- ต้องการ domain + SSL แยก
- nginx รันใน container ของตัวเอง
### 4. CORS
- หลัง deploy admin UI → copy domain ไปใส่ใน `ALLOWED_ORIGINS` ของ consentos-app
## Quick Setup Commands
```bash
# Clone repo
git clone https://github.com/kunthawat/consentos.git
cd consentos
# Copy deployment files
cp ~/consent-deploy/Dockerfile.app .
cp ~/consent-deploy/supervisord.conf .
cp ~/consent-deploy/docker-compose.yml .
cp ~/consent-deploy/.env.example .env
cp ~/consent-deploy/EASYPANEL.md .
# Push to your Git
git add .
git commit -m "Add Easypanel deployment config"
git push
```

273
EASYPANEL.md Normal file
View File

@@ -0,0 +1,273 @@
# Deploy ConsentOS on Easypanel
## Architecture
```
consentos (Project)
├── consentos-db ← PostgreSQL (Easypanel managed)
├── consentos-redis ← Redis (Easypanel managed)
└── consentos-app ← 1 container รันทุกอย่าง (API + Worker + Beat + Scanner)
└── 5 services ภายใน via supervisord
consentos-admin (Separate Project)
└── consentos-admin ← Admin UI (nginx, static files)
```
---
## Step 1: Clone Repo to Git
```bash
git clone https://github.com/kunthawat/consentos.git
# Push to your own Git repo (GitHub/Gitea)
# ต้องมี Dockerfile.app + supervisord.conf + apps/ ที่ root
```
> ถ้าต้องการแยก repo — ต้อง push เฉพาะ apps/ กับ Dockerfile.app + supervisord.conf + docker-compose.yml
---
## Step 2: Create Database Services
### 2.1 PostgreSQL
ใน Easypanel → สร้าง **Postgres Service**:
| Field | Value |
|-------|-------|
| Name | `consentos-db` |
| Database | `consentos` |
| User | `consentos` |
| Password | (generate strong password) |
**Copy connection details** — จะได้ใช้ใน env:
- Host: `consentos-db` (internal Docker network)
- Port: `5432`
- User: `consentos`
- Database: `consentos`
### 2.2 Redis
ใน Easypanel → สร้าง **Redis Service**:
| Field | Value |
|-------|-------|
| Name | `consentos-redis` |
| Password | (generate strong password) |
---
## Step 3: Create App Service (Backend)
ใน Easypanel → สร้าง **App Service**:
### Source
| Field | Value |
|-------|-------|
| Build Method | **Dockerfile** |
| Dockerfile Path | `Dockerfile.app` |
### Environment Variables
```env
# ── Application ───────────────────────────────────────────────────────
APP_NAME=ConsentOS
ENVIRONMENT=production
DEBUG=false
LOG_LEVEL=INFO
# ── Database (use Easypanel service names as host) ───────────────────
DATABASE_URL=postgresql+asyncpg://consentos:PASSWORD@consentos-db:5432/consentos
# ── Redis ─────────────────────────────────────────────────────────────
REDIS_URL=redis://:PASSWORD@consentos-redis:6379/0
# ── Authentication ────────────────────────────────────────────────────
# Generate with: openssl rand -base64 48
JWT_SECRET_KEY=YOUR_JWT_SECRET_HERE
PSEUDONYMISATION_SECRET=YOUR_JWT_SECRET_HERE
# ── Admin Bootstrap (runs once on first deploy) ──────────────────────
INITIAL_ADMIN_EMAIL=admin@yourdomain.com
INITIAL_ADMIN_PASSWORD=YOUR_ADMIN_PASSWORD
INITIAL_ADMIN_FULL_NAME=Admin
INITIAL_ORG_NAME=Your Company
INITIAL_ORG_SLUG=your-company
# ── CORS ───────────────────────────────────────────────────────────────
# ตั้ง domain ของ admin UI ที่จะ deploy ใน step ถัดไป
ALLOWED_ORIGINS=https://admin.yourdomain.com,https://consent.yourdomain.com
# ── Scanner (optional) ────────────────────────────────────────────────
ENABLE_SCANNER=false
CRAWLER_HEADLESS=true
CRAWLER_TIMEOUT_MS=30000
MAX_PAGES_PER_SCAN=50
# ── Performance ──────────────────────────────────────────────────────
API_WORKERS=2
```
### Mounts (Data Persistence)
| Type | mountPath |
|------|-----------|
| **Volume** | `/var/log/supervisor` |
### Ports
| Published | Target |
|-----------|--------|
| `8000` | `8000` |
### Deploy Settings
| Field | Value |
|-------|-------|
| Container Replicas | `1` |
| Shm Size | `256mb` |
---
## Step 4: Create Admin UI (Separate App)
สร้าง **อีก Project** ชื่อ `consentos-admin`:
### Source
| Field | Value |
|-------|-------|
| Build Method | **Dockerfile** |
| Dockerfile Path | `apps/admin-ui/Dockerfile` |
### Environment Variables
```env
# URL ของ API service (ใช้ service name ของ Easypanel)
VITE_API_URL=https://consent.yourdomain.com
```
### Domains
เพิ่ม domain `admin.yourdomain.com` → ใช้ SSL auto ของ Easypanel
---
## Step 5: Update CORS + Deploy
หลัง deploy admin UI ได้ domain แล้ว:
1. กลับไปที่ `consentos-app`**Environment** → แก้ `ALLOWED_ORIGINS`:
```
ALLOWED_ORIGINS=https://admin.yourdomain.com,https://consent.yourdomain.com
```
2. **Redeploy** `consentos-app`
---
## Step 6: First-Time Setup
หลัง container start ครั้งแรก → bootstrap script รันอัตโนมัติ:
- Database migrations (Alembic)
- Initial admin user creation
- Seed known cookies
**ตรวจสอบ logs:**
```
Easypanel → consentos-app → Logs
```
---
## Environment Variables Reference
### Required (ต้องกำหนดเอง)
| Variable | Example | ที่ไหนได้มา |
|----------|---------|-------------|
| `JWT_SECRET_KEY` | `openssl rand -base64 48` | Generate |
| `DATABASE_URL` | `postgresql+asyncpg://consentos:PASS@consentos-db:5432/consentos` | From Easypanel PostgreSQL |
| `REDIS_URL` | `redis://:PASS@consentos-redis:6379/0` | From Easypanel Redis |
| `INITIAL_ADMIN_EMAIL` | `admin@example.com` | กำหนดเอง |
| `INITIAL_ADMIN_PASSWORD` | `Str0ng!Pass` | กำหนดเอง |
| `ALLOWED_ORIGINS` | `https://admin.example.com` | หลัง deploy admin UI |
### Optional (มี default แล้ว)
| Variable | Default | คำอธิบาย |
|----------|---------|-----------|
| `API_WORKERS` | `2` | จำนวน uvicorn workers |
| `ENABLE_SCANNER` | `false` | เปิด scanner (ใช้ RAM เยอะ) |
| `LOG_LEVEL` | `INFO` | DEBUG สำหรับ verbose logs |
| `DEBUG` | `false` | เปิด FastAPI debug mode |
---
## Data Persistence
| Data | Storage | หายไหมตอน redeploy? |
|------|---------|---------------------|
| Database | Easypanel `consentos-db` volume | ✅ ไม่หาย |
| Redis | Easypanel `consentos-redis` volume | ✅ ไม่หาย |
| Code | Container image | ❌ Rebuild ตามปกติ |
| Logs | `/var/log/supervisor` mount | ✅ Mounted volume |
---
## Update / Redeploy
```bash
# 1. Pull code ใน Git repo
git pull
# 2. Redeploy ใน Easypanel
# consentos-app → Deploy (Redeploy button)
# consentos-admin → Deploy (Redeploy button)
```
หรือตั้ง **Auto Deploy** → Easypanel จะ deploy อัตโนมัติเมื่อ push ไปที่ Git
---
## Troubleshooting
### Bootstrap failed
```bash
# ดู logs
consentos-app → Logs
# ถ้า admin สร้างไปแล้ว → bootstrap script จะ skip
# ถ้าต้องการ reset admin:
# ไปที่ console แล้ว:
docker exec -it consentos-app python -m src.cli.bootstrap_admin
```
### CORS errors
เพิ่ม domain ใหม่เข้า `ALLOWED_ORIGINS` แล้ว redeploy
### Scanner กิน RAM เยอะ
```env
ENABLE_SCANNER=false # ปิดไปก่อน
```
### Celery worker ไม่ทำงาน
```bash
# ดู worker logs
consentos-app → Console
supervisorctl status
supervisorctl tail worker
```
---
## Memory Requirements
| Service | RAM (approximate) |
|---------|------------------|
| consentos-db | ~256-512 MB |
| consentos-redis | ~64-128 MB |
| consentos-app (API) | ~256-512 MB |
| consentos-app (Worker) | ~256-512 MB |
| consentos-app (Scanner) | ~512-1024 MB (ถ้าเปิด) |
| **Total (without scanner)** | ~576-1152 MB |
| **Total (with scanner)** | ~1088-2176 MB |
**แนะนำ:** VPS/Server อย่างน้อย **2 GB RAM** (ถ้าไม่ใช้ scanner) หรือ **4 GB** (ถ้าใช้ scanner)

58
supervisord.conf Normal file
View File

@@ -0,0 +1,58 @@
[supervisord]
nodaemon=true
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
user=root
[program:api]
command=sh -c "uvicorn src.main:app --host 0.0.0.0 --port 8000 --workers ${API_WORKERS:-4} --access-log --proxy-headers --forwarded-allow-ips '*'"
directory=/app
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stopwaitsecs=10
killasgroup=true
priority=100
[program:worker]
command=celery -A src.celery_app worker --loglevel=info --concurrency=2
directory=/app
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stopwaitsecs=30
killasgroup=true
priority=200
[program:beat]
command=celery -A src.celery_app beat --loglevel=info
directory=/app
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stopwaitsecs=10
killasgroup=true
priority=300
[program:scanner]
command=python -m src.worker
directory=/app
autostart=${ENABLE_SCANNER:-false}
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stopwaitsecs=10
killasgroup=true
priority=400
environment=PYTHONUNBUFFERED="1"