diff --git a/Dockerfile.app b/Dockerfile.app new file mode 100644 index 0000000..98c5992 --- /dev/null +++ b/Dockerfile.app @@ -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"] diff --git a/EASYPANEL-README.md b/EASYPANEL-README.md new file mode 100644 index 0000000..481da94 --- /dev/null +++ b/EASYPANEL-README.md @@ -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 +``` diff --git a/EASYPANEL.md b/EASYPANEL.md new file mode 100644 index 0000000..53ee5aa --- /dev/null +++ b/EASYPANEL.md @@ -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) diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 0000000..b61393d --- /dev/null +++ b/supervisord.conf @@ -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"