# 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`](backend/k8s/builds/cloudhost-builds-bootstrap.yaml). > **استقرار از صفر روی سرور:** [`RUNBOOK-DEPLOY.fa.md`](RUNBOOK-DEPLOY.fa.md) — مراحل values، Secretها، deploy، greenfield reset. --- ### ۱.۱ CloudHost چیست یک **PaaS خودسرویس** برای بازار ایران: کاربر کد/ریپوی خودش را می‌دهد و CloudHost آن را build و روی Kubernetes اجرا می‌کند، با مدیریت دامنه، دیتابیس، لاگ، فاکتور و کیف پول. ### ۱.۲ اجزای اصلی | جزء | تکنولوژی | نقش | |---|---|---| | **Frontend** | Next.js (App Router, SSR) | لندینگ + پنل کاربری/ادمین | | **Backend** | NestJS (REST `/api/v1`) | منطق کسب‌وکار، ساخت اپ، احراز هویت | | **Postgres** | postgres:16 | دیتابیس اصلی (کاربر، اپ، فاکتور، …) | | **Redis** | redis:7 | کش، صف Bull (مهاجرت اپ، دسترسی موقت)، پیشرفت بیلد | | **Registry داخلی** | Harbor + registry:2 (legacy) | ایمیج‌های build و platform؛ جزئیات: [`RUNBOOK-HARBOR.fa.md`](RUNBOOK-HARBOR.fa.md) | | **Storage (Ceph)** | Rook-Ceph | PVC (`rook-ceph-block`) + bucket zip (`rook-ceph-bucket`)؛ جزئیات: [`RUNBOOK-CEPH.fa.md`](RUNBOOK-CEPH.fa.md) | | **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` → Harbor (UI + proxy-cache) + registry قدیمی برای ایمیج‌های platform — [`RUNBOOK-HARBOR.fa.md`](RUNBOOK-HARBOR.fa.md) - `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. ```bash # ۱) دیتابیس و 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:@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/…)**: از طریق پروکسی بالا. ### ۳.۲ گام‌های استقرار **گام ۱ — بررسی دسترسی کلاستر** ```bash kubectl config current-context # باید default باشد kubectl get nodes ``` **گام ۲ — mirror کردن ایمیج‌های پایه به رجیستری داخلی** ایمیج‌هایی که کلاستر مستقیم نمی‌تواند pull کند را با یک Job داخل کلاستر کپی کن. - برای ایمیج‌های **کوچک**: `crane copy registry.cloudhost.svc.cluster.local:5000/ --insecure` - برای ایمیج‌های **بزرگ** (مثل nixpacks): از **skopeo** استفاده کن — چون بلاب را با PUT یکجا آپلود می‌کند و گیر `PROTOCOL_ERROR` آپلود تکه‌ای crane را ندارد: ``` skopeo copy --override-os linux --override-arch amd64 --dest-tls-verify=false \ docker:// docker://registry.cloudhost.svc.cluster.local:5000/ ``` (با `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 بیلد** ```bash # کپی 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 را اجرا کن. ```bash # سینک سورس (از working tree؛ tsbuildinfo و dist را حذف کن!) cd 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-.yaml kubectl apply -f /tmp/abrban/kaniko-backend-.yaml ``` **گام ۶ — استقرار با Helm** ```bash helm upgrade cloudhost backend/helm/cloudhost-platform \ -n cloudhost -f /tmp/abrban/values-abrban.yaml \ --set images.backend.tag= \ --set images.frontend.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). **گام ۸ — تأیید** ```bash 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= --set migrations.enabled=false` (بدون `--wait`). 6. تأیید کن: podها 1/1، helm `deployed`، endpoint‌ها سالم.