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>
14 KiB
CloudHost — راهنمای معماری و اجرای وبسایت (Runbook)
این سند دو بخش دارد:
- اپلیکیشن چطور کار میکند — معماری و جریانها.
- اجرای وبسایت، مرحلهبهمرحله — هم برای توسعهی محلی، هم برای استقرار (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از secretkaniko-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 بررسی کن.
بخش ۶ — بهروزرسانی نسخه (خلاصه)
- تغییرات کد را در سورس اعمال کن (
tsc --noEmitبگیر). - tag جدید انتخاب کن.
- سورس را در
srcsyncسینک کن (با حذفtsbuildinfo). - Job Kaniko را با tag جدید بساز.
helm upgrade ... --set images.*.tag=<tag> --set migrations.enabled=false(بدون--wait).- تأیید کن: podها 1/1، helm
deployed، endpointها سالم.