Files
cloud-host/RUNBOOK.fa.md
T
keyhan 837f0fa63f Harden platform security, reliability, and CI after full audit.
Close deployment IDOR and gate stub payment endpoints, add production
secret validation, health probes, Redis-backed build progress, GitHub
Actions CI, expanded tests, billing/k8s refactors, and ops runbooks.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 20:59:49 +03:30

14 KiB
Raw Blame History

CloudHost — راهنمای معماری و اجرای وبسایت (Runbook)

این سند دو بخش دارد:

  1. اپلیکیشن چطور کار می‌کند — معماری و جریان‌ها.
  2. اجرای وبسایت، مرحله‌به‌مرحله — هم برای توسعه‌ی محلی، هم برای استقرار (deploy) روی کلاستر k3s سرور (abrban) به‌همراه راه‌حل‌های مخصوص شبکه‌ی ایران.

اصطلاح‌ها: «پنل» = اپ احرازشده (panel.abrban.com)، «لندینگ» = صفحه‌ی معرفی (abrban.com)، «اپ کاربر» = اپلیکیشنی که مشتری روی CloudHost دیپلوی می‌کند.

به‌روزرسانی ۲۰۲۶: pipeline بیلد فعلی از Kaniko + Dockerfileهای نگهداری‌شده توسط پلتفرم استفاده می‌کند (نه Nixpacks/MinIO). آرشیو سورس روی دیسک/PVC آپلود می‌شود. manifest بوت‌استرپ namespace بیلد: backend/k8s/builds/cloudhost-builds-bootstrap.yaml.


۱.۱ CloudHost چیست

یک PaaS خودسرویس برای بازار ایران: کاربر کد/ریپوی خودش را می‌دهد و CloudHost آن را build و روی Kubernetes اجرا می‌کند، با مدیریت دامنه، دیتابیس، لاگ، فاکتور و کیف پول.

۱.۲ اجزای اصلی

جزء تکنولوژی نقش
Frontend Next.js (App Router, SSR) لندینگ + پنل کاربری/ادمین
Backend NestJS (REST /api/v1) منطق کسب‌وکار، ساخت اپ، احراز هویت
Postgres postgres:16 دیتابیس اصلی (کاربر، اپ، فاکتور، …)
Redis redis:7 کش، صف Bull (مهاجرت اپ، دسترسی موقت)، پیشرفت بیلد
Registry داخلی registry:2 ایمیج‌های build‌شده
Build pipeline Kaniko تبدیل سورس به ایمیج Docker داخل کلاستر (بدون Docker daemon)
Kubernetes k3s (تک‌نود) اجرای همه‌ی موارد بالا + اپ‌های کاربر

۱.۳ دامنه‌ها (همه روی 78.157.39.52، HTTPS با wildcard cert)

  • abrban.com → لندینگ
  • panel.abrban.com → پنل احرازشده
  • api.abrban.com → بک‌اند
  • registry.abrban.com → رجیستری داخلی (pull توسط kubelet)
  • apps.abrban.com → دامنه‌ی پیش‌فرض اپ‌های کاربر

۱.۴ جریان احراز هویت

  • ورود مبتنی بر موبایل + OTP (پیامک از طریق MizbanSMS) یا رمز عبور.
  • توکن JWT (access ~15m، refresh ~7d).
  • JwtStrategy در هر درخواست نقش و فعال‌بودن کاربر را از دیتابیس می‌خواند (نه از توکن) تا تغییر نقش/غیرفعال‌سازی بلافاصله اثر کند.

۱.۵ جریان دیپلویِ «اپ کاربر» (مهم‌ترین بخش)

وقتی کاربر یک اپ را build/redeploy می‌کند:

کاربر (پنل)
   │  آپلود zip  ──────────────►  MinIO (bucket: app-sources)   ┐
   │  یا git URL                                                  │ منبع سورس
   ▼                                                              │
Backend: یک job در صف Bull («app-deploy») می‌گذارد              │
   ▼                                                              │
ساخت یک Kubernetes Job در namespace «cloudhost-builds»:         │
   1) init: fetch-source (دانلود از MinIO) یا git-clone  ◄───────┘
   2) init: nixpacks-prepare
        • اگر سورس Dockerfile دارد → همان (BYO)
        • وگرنه → با Nixpacks یک Dockerfile می‌سازد
   3) container: Kaniko  → build ایمیج → push به registry داخلی
   ▼
(غیرمسدودکننده) اسکن Trivy → خلاصه‌ی آسیب‌پذیری
   ▼
Backend با Helm، اپ را روی کلاستر بالا می‌آورد (Deployment + Service + Ingress)
   ▼
GC رجیستری: روزانه فقط N نسخه‌ی آخر هر اپ را نگه می‌دارد
  • پیشرفت بیلد در Redis نگه‌داری می‌شود؛ فرانت هر ۱.۵ ثانیه build-progress/build-logs را poll می‌کند.
  • توکن git در یک Secret موقتِ هر بیلد می‌نشیند و در finally پاک می‌شود (در manifest درج نمی‌شود).

بخش ۲ — اجرای محلی (Local Dev)

پیش‌نیاز: Node 20، Docker، Docker Compose.

# ۱) دیتابیس و Redis
docker compose up -d            # از docker-compose.yml ریشه‌ی پروژه

# ۲) بک‌اند
cd backend
cp .env.example .env            # مقادیر را پر کن (DB، JWT، SMS، …)
npm install
npm run start:dev               # روی http://localhost:4000  (پیشوند /api/v1)

# ۳) فرانت
cd ../frontend
npm install
npm run dev                     # روی http://localhost:3000
  • در حالت dev، TypeORM synchronize روشن است و جدول‌ها خودکار ساخته می‌شوند.
  • NEXT_PUBLIC_API_URL فرانت باید به آدرس بک‌اند اشاره کند.

بخش ۳ — استقرار روی کلاستر (abrban / k3s)

این بخش فرض می‌کند کلاستر k3s و wildcard cert از قبل آماده‌اند. context کوبه: default.

۳.۰ ثابت‌های محیط

مقدار
namespace اپ cloudhost
namespace بیلد cloudhost-builds
Helm release cloudhost
چارت backend/helm/cloudhost-platform
values /tmp/abrban/values-abrban.yaml
رجیستری (push داخلی) registry.cloudhost.svc.cluster.local:5000 (HTTP, insecure)
رجیستری (pull توسط kubelet) registry.abrban.com (HTTPS, wildcard cert) — همان storage
پروکسی build http://builder:<pw>@45.129.38.203:9911

۳.۱ نکات شبکه‌ی ایران (چرا کارها این‌شکلی‌اند)

  • کلاستر به Let's Encrypt، github، docker.io، ghcr.io، gcr.io مستقیم نمی‌رسد (یا خیلی کند).
  • gTLS/cert: دستی، secret abrban-wildcard-tls (نه cert-manager).
  • npm: از registry.npmmirror.com مستقیم (نه پروکسی).
  • ایمیج‌های پایه: اول داخل رجیستری داخلی mirror می‌شوند، بعد استفاده.
  • دانلودهای build (apk/nix/pip/…): از طریق پروکسی بالا.

۳.۲ گام‌های استقرار

گام ۱ — بررسی دسترسی کلاستر

kubectl config current-context          # باید default باشد
kubectl get nodes

گام ۲ — mirror کردن ایمیج‌های پایه به رجیستری داخلی ایمیج‌هایی که کلاستر مستقیم نمی‌تواند pull کند را با یک Job داخل کلاستر کپی کن.

  • برای ایمیج‌های کوچک: crane copy <src> registry.cloudhost.svc.cluster.local:5000/<dst> --insecure
  • برای ایمیج‌های بزرگ (مثل nixpacks): از skopeo استفاده کن — چون بلاب را با PUT یکجا آپلود می‌کند و گیر PROTOCOL_ERROR آپلود تکه‌ای crane را ندارد:
    skopeo copy --override-os linux --override-arch amd64 --dest-tls-verify=false \
      docker://<src> docker://registry.cloudhost.svc.cluster.local:5000/<dst>
    
    (با REGISTRY_AUTH_FILE از secret kaniko-docker-config و env پروکسی.)

ایمیج‌های لازم: minio/minio, railwayapp/nixpacks, library/alpine:3.19, library/postgres, library/redis, و base ای که Nixpacks تولید می‌کند.

گام ۳ — MinIO (ذخیره‌ی سورس اپ‌ها) به‌طور خودکار فقط هنگام ثبت کلاستر جدید ساخته می‌شود؛ روی کلاستر موجود دستی بساز: secret minio-credentials (accesskey/secretkey) + PVC ۲۰Gi + Deployment (registry.abrban.com/minio/minio:latest) + Service، همه در cloudhost-builds. کردنشال پیش‌فرض با config بک‌اند می‌خواند.

گام ۴ — pull-secret برای namespace بیلد

# کپی pull-secret رجیستری به ns بیلد
kubectl get secret registry-pull-secret -n cloudhost -o json \
 | jq '.metadata.namespace="cloudhost-builds" | del(.metadata.uid,.metadata.resourceVersion,.metadata.creationTimestamp)' \
 | kubectl apply -f -
# وصل به هر دو ServiceAccount که pod بیلد ممکن است از آن‌ها استفاده کند
kubectl patch sa default        -n cloudhost-builds -p '{"imagePullSecrets":[{"name":"registry-pull-secret"}]}'
kubectl patch sa kaniko-builder -n cloudhost-builds -p '{"imagePullSecrets":[{"name":"registry-pull-secret"}]}'

⚠️ kaniko-builder حتماً لازم است: pod بیلد با همین SA اجرا می‌شود و init container نیکس‌پکس ایمیجش را از registry.abrban.com می‌کشد.

گام ۵ — build ایمیج‌های frontend/backend (Kaniko) سورس را در PVC بیلد (build-src) از طریق pod srcsync قرار بده، سپس Job‌های Kaniko را اجرا کن.

# سینک سورس (از working tree؛ tsbuildinfo و dist را حذف کن!)
cd <repo>
tar czf - --exclude=node_modules --exclude=.next --exclude=.git frontend \
  | kubectl exec -i srcsync -n cloudhost -- sh -c 'rm -rf /workspace/frontend && tar xzf - -C /workspace'
rm -f backend/tsconfig.tsbuildinfo      # ← مهم
tar czf - --exclude=node_modules --exclude=dist --exclude=.git backend \
  | kubectl exec -i srcsync -n cloudhost -- sh -c 'rm -rf /workspace/backend && tar xzf - -C /workspace'

# build (manifestهای kaniko-*.yaml: registry-mirror + proxy + npmmirror)
kubectl apply -f /tmp/abrban/kaniko-frontend-<tag>.yaml
kubectl apply -f /tmp/abrban/kaniko-backend-<tag>.yaml

گام ۶ — استقرار با Helm

helm upgrade cloudhost backend/helm/cloudhost-platform \
  -n cloudhost -f /tmp/abrban/values-abrban.yaml \
  --set images.backend.tag=<tag> \
  --set images.frontend.tag=<tag> \
  --set migrations.enabled=false        # ← مهاجرت‌ها روی DB زنده تداخل دارند

بدون --wait اجرا کن (وگرنه به‌خاطر کندیِ pull، status اشتباهاً failed می‌شود درحالی‌که rollout موفق است).

گام ۷ — bootstrap اسکیمای دیتابیس (فقط روی DB تازه) در پروداکشن synchronize خاموش است. روی DB کاملاً تازه: موقتاً NODE_ENV=development کن تا synchronize جدول‌ها را بسازد و pricing خودش seed شود، بعد به production برگردان. روی DB موجود، فقط مهاجرت‌های idempotent جدید را با یک Job جدا اعمال کن (نه helm hook).

گام ۸ — تأیید

kubectl get deploy -n cloudhost                       # backend/frontend 1/1
helm status cloudhost -n cloudhost                    # STATUS: deployed
curl -s -o /dev/null -w '%{http_code}\n' https://panel.abrban.com   # 307
curl -s -X POST https://api.abrban.com/api/v1/auth/otp/request \
  -H 'Content-Type: application/json' -d '{"phone":"09xxxxxxxxx"}'  # {"sent":true}

بخش ۴ — env‌های کلیدی build pipeline (روی بک‌اند)

این‌ها در backend.env فایل values ست می‌شوند:

env مقدار/توضیح
MINIO_SECRET_KEY کلید MinIO (هماهنگ با secret)
NIXPACKS_IMAGE registry.abrban.com/railwayapp/nixpacks:latest (mirror)
NIXPACKS_BUILD_ENV NPM_CONFIG_REGISTRY=https://registry.npmmirror.com
BUILD_HTTP_PROXY پروکسی build (تزریق به Kaniko + init containerها)
BUILD_REGISTRY_MIRROR registry.cloudhost.svc.cluster.local:5000 (pull پایه از mirror)
BUILD_SCAN_ENABLED false تا وقتی ایمیج Trivy mirror شود
SMS_PROVIDER + MIZBANSMS_* بدون این‌ها ارسال OTP خطای 503 می‌دهد

بخش ۵ — عیب‌یابی رایج

نشانه علت راه‌حل
backend کرش: Cannot find module '/app/dist/main.js' tsconfig.tsbuildinfo کهنه در سورس → tsc فایل‌ها را دوباره emit نمی‌کند قبل از build، tsconfig.tsbuildinfo را حذف کن
init container بیلد: no basic auth credentials SA kaniko-builder بدون pull-secret گام ۴ را اجرا کن
pull ایمیج: not found با اینکه push شده crane در آپلود تکه‌ایِ بلاب بزرگ شکست خورده (tag ناقص) با skopeo دوباره mirror کن
pull از docker.io: TLS handshake timeout docker.io از کلاستر بسته است ایمیج را mirror کن
nixpacks: not found در init باینری نیکس‌پکس روی PATH پیش‌فرض نیست command را با مسیر/ENTRYPOINT درست صدا بزن
helm: no template "...namespace" یا nil pointer redis.enabled فایل‌های چارت (_helpers.tpl/values.yaml) از /tmp پاک شده‌اند از سورس اصلی بازیابی + ادیت‌های deploy را دوباره اعمال کن
OTP خطای 503 env پیامک ست نیست SMS_PROVIDER/MIZBANSMS_* را ست کن

⚠️ پوشه‌ی /tmp در macOS فایل‌های قدیمی‌تر از ~۳ روز را پاک می‌کند؛ درخت کاری deploy در /tmp ممکن است فایل از دست بدهد — قبل از build بررسی کن.


بخش ۶ — به‌روزرسانی نسخه (خلاصه)

  1. تغییرات کد را در سورس اعمال کن (tsc --noEmit بگیر).
  2. tag جدید انتخاب کن.
  3. سورس را در srcsync سینک کن (با حذف tsbuildinfo).
  4. Job Kaniko را با tag جدید بساز.
  5. helm upgrade ... --set images.*.tag=<tag> --set migrations.enabled=false (بدون --wait).
  6. تأیید کن: podها 1/1، helm deployed، endpoint‌ها سالم.