diff --git a/INDEX.md b/INDEX.md index 15bec79..6bada60 100644 --- a/INDEX.md +++ b/INDEX.md @@ -25,6 +25,7 @@ | [REMAINING-TASKS.md](cms/REMAINING-TASKS.md) | وضعیت ۴۹/۴۹ متد — همه انجام شده ✅ | | [FRONTOFFICE-CMS-API-COMPATIBILITY.md](cms/FRONTOFFICE-CMS-API-COMPATIBILITY.md) | ماتریس سازگاری API بین FrontOffice و CMS | | [ICURRENTUSERSERVICE-IMPLEMENTATION.md](cms/ICURRENTUSERSERVICE-IMPLEMENTATION.md) | پترن JWT + ICurrentUserService در endpointهای Customer | +| [ADMIN-CUSTOMER-SEPARATION-FIX.md](cms/ADMIN-CUSTOMER-SEPARATION-FIX.md) | 🆕 فیکس جداسازی Admin/Customer: حذف JWT fallback از ۸ handler + resolve صریح در ۴ endpoint | | [payment-gateway.md](cms/payment-gateway.md) | IPaymentGatewayService: Mock vs Daya، پیاده‌سازی payout | | [payment-architecture-pyms.md](cms/payment-architecture-pyms.md) | معماری PYMS: جریان پرداخت BFF→PYMS→Gateway→CMS | | [chatika-integration.md](cms/chatika-integration.md) | یکپارچه‌سازی Chatika AI: Hangfire worker، retry logic | @@ -40,6 +41,7 @@ | فایل | توضیح | |------|-------| | [OFFLINE-DEPLOYMENT-GUIDE.md](deployment/OFFLINE-DEPLOYMENT-GUIDE.md) | راهنمای جامع استقرار آفلاین + تنظیمات Nexus | +| [CICD-PIPELINE-GUIDE.md](deployment/CICD-PIPELINE-GUIDE.md) | 🆕 راهنمای CI/CD Pipeline: معماری DinD، فیکس‌های dockerd، Runner ConfigMap، عیب‌یابی | | [INFRASTRUCTURE-GUIDE.md](deployment/INFRASTRUCTURE-GUIDE.md) | مشخصات سرور، DB credentials، Gitea، وضعیت استقرار | | [SERVER-MIRRORS-CONFIG.md](deployment/SERVER-MIRRORS-CONFIG.md) | تنظیمات mirror: K3s registries.yaml، containerd | | [INGRESS-NGINX-WARNING.md](deployment/INGRESS-NGINX-WARNING.md) | ⚠️ هشدار K3s: مشکل hostNetwork در ingress-nginx | @@ -66,4 +68,5 @@ | فاز ۲ (حذف obsolete عمیق) | 41 | ۹۴ | | فاز ۳ (ساختاردهی + merge) | **28** | ۱۳ | | cleanup نهایی (+2 فایل جدید) | **30** | — | -| **نهایی** | **30 + INDEX** | **۱۹۱ فایل حذف/ادغام** | +| session CI/CD + Admin fix (+2) | **32** | — | +| **نهایی** | **32 + INDEX** | **۱۹۱ فایل حذف/ادغام** | diff --git a/cms/ADMIN-CUSTOMER-SEPARATION-FIX.md b/cms/ADMIN-CUSTOMER-SEPARATION-FIX.md new file mode 100644 index 0000000..1d3fbbd --- /dev/null +++ b/cms/ADMIN-CUSTOMER-SEPARATION-FIX.md @@ -0,0 +1,153 @@ +# 🔀 جداسازی سرویس‌های Admin و Customer + +> آخرین بروزرسانی: February 10, 2026 +> مرتبط با: [ICURRENTUSERSERVICE-IMPLEMENTATION.md](ICURRENTUSERSERVICE-IMPLEMENTATION.md) + +--- + +## 🐛 مشکل + +پنل ادمین BackOffice بجای نمایش اطلاعات **همه کاربران**، فقط اطلاعات **خود ادمین** رو نشان میداد. + +### علت ریشه‌ای: +Query Handler ها وقتی `UserId = 0` دریافت می‌کردند، بجای اینکه "همه کاربران" رو برگردانند، به JWT fallback می‌کردند و UserId ادمین رو از توکن استخراج می‌کردند: + +```csharp +// ❌ الگوی قدیمی (مشکل‌دار) +var userId = request.UserId == 0 + ? (long.TryParse(_currentUser.UserId, out var uid) ? uid : 0) // ← fallback به JWT + : request.UserId; +``` + +### مشکل: +- **BackOffice (Admin)** → `UserId = 0` ارسال میکنه → Handler از JWT ادمین میخونه → فقط اطلاعات ادمین برمیگرده +- **FrontOffice (Customer)** → `UserId = 0` ارسال میکنه → Handler از JWT مشتری میخونه → اتفاقاً درسته، ولی دلیلش اشتباهه + +--- + +## ✅ الگوی جدید + +### اصل طراحی: +> **Handler ها بی‌خبر از JWT هستند.** وظیفه resolve کردن کاربر، به عهده **Service Layer (gRPC endpoint)** است. + +### الگوی Handler: +```csharp +// ✅ الگوی جدید +// UserId = 0 → بدون فیلتر (نمایش همه) — مناسب Admin +// UserId > 0 → فیلتر بر اساس کاربر خاص — مناسب Customer یا Admin + +public async Task Handle(SomeQuery request, CancellationToken ct) +{ + var userId = request.UserId; + + var query = _context.SomeEntity.AsNoTracking(); + + if (userId > 0) + query = query.Where(x => x.UserId == userId); + + // userId == 0 → no filter → return all + return await query.ToListAsync(ct); +} +``` + +### الگوی Customer Service (JWT رو خودش resolve میکنه): +```csharp +// ✅ Customer endpoint → حتماً JWT resolve میکنه +public override async Task GetMyData(Request request, ServerCallContext context) +{ + if (!long.TryParse(_currentUserService.UserId, out var userId) || userId == 0) + throw new RpcException(new Status(StatusCode.Unauthenticated, "User not authenticated")); + + var query = new GetDataQuery { UserId = userId }; // ← userId صریح + var result = await _sender.Send(query, context.CancellationToken); + return MapToResponse(result); +} +``` + +### الگوی Admin Service (UserId رو از request میگیره): +```csharp +// ✅ Admin endpoint → UserId از request (0 = همه) +public override async Task GetAllData(Request request, ServerCallContext context) +{ + // request.UserId = 0 → handler همه رو برمیگردونه + // request.UserId > 0 → handler فیلتر میکنه + var result = await _dispatcher.Send(request, context); + return result; +} +``` + +--- + +## 📝 لیست تغییرات + +### 🔧 ۸ Query Handler اصلاح‌شده: + +| # | Handler | تغییر | رفتار `UserId = 0` | +|---|---------|-------|---------------------| +| 1 | `GetCustomerOrdersQueryHandler` | حذف `ICurrentUserService` + JWT fallback | بدون فیلتر → همه سفارشات | +| 2 | `GetCustomerOrderQueryHandler` | حذف `ICurrentUserService` + JWT fallback | هر سفارشی با OrderId | +| 3 | `GetUserWeeklyBalancesQueryHandler` | حذف `ICurrentUserService` + JWT fallback | بدون فیلتر → همه تعادل‌ها | +| 4 | `GetUserCommissionPayoutsQueryHandler` | حذف `ICurrentUserService` + JWT fallback | بدون فیلتر → همه پرداخت‌ها | +| 5 | `GetNetworkStatisticsQueryHandler` | حذف `ICurrentUserService` + JWT fallback | آمار root user (کل شبکه) | +| 6 | `GetNetworkTreeQueryHandler` | حذف JWT fallback + خطا اگر UserId نباشد | `ArgumentException` (الزامی) | +| 7 | `GetUserQueryHandler` | حذف JWT fallback + خطا اگر UserId نباشد | `ArgumentException` (الزامی) | +| 8 | `GetUserWalletQueryHandler` | حذف JWT fallback + خطا اگر UserId نباشد | `ArgumentException` (الزامی) | + +### 🌐 ۴ Customer Service Endpoint اصلاح‌شده: + +| # | Service / Method | تغییر | +|---|-----------------|-------| +| 1 | `UserOrderService.GetCustomerOrders` | JWT resolve → ارسال `customerUserId` به handler | +| 2 | `UserOrderService.GetCustomerOrder` | JWT resolve → ارسال `customerUserId` به handler | +| 3 | `NetworkMembershipService.GetMyNetworkStatistics` | افزودن `ICurrentUserService` + JWT resolve | +| 4 | `UserWalletService.GetCustomerWallet` | تغییر از `Id = 0` به `Id = userId` (از JWT) | + +--- + +## 📐 دیاگرام جریان + +### درخواست Admin (BackOffice): +``` +BackOffice Panel → gRPC (UserId=0) → Admin Service → Handler (UserId=0 → no filter → ALL users) ✅ +BackOffice Panel → gRPC (UserId=42) → Admin Service → Handler (UserId=42 → filter → one user) ✅ +``` + +### درخواست Customer (FrontOffice): +``` +FrontOffice App → gRPC → Customer Service → JWT resolve (UserId=42) → Handler (UserId=42 → filter) ✅ +``` + +--- + +## ⚠️ نکات مهم + +1. **Handler ها هرگز `ICurrentUserService` رو inject نمیکنند** (بعد از این فیکس) +2. فقط **Customer Service endpoints** مسئول JWT resolve هستند +3. **Admin endpoints** از `IDispatchRequestToCQRS` استفاده میکنند و UserId مستقیم از proto request میاد +4. Handler هایی که UserId **الزامی** دارند (مثل GetUser, GetUserWallet, GetNetworkTree) → `ArgumentException` پرتاب میکنند +5. Handler هایی که لیست برمیگردونند (مثل GetCustomerOrders, GetWeeklyBalances) → `UserId = 0` یعنی "بدون فیلتر" + +--- + +## 🔗 فایل‌های تغییر‌یافته + +### Application Layer: +``` +CMS/src/CMSMicroservice.Application/ +├── OrdersCQ/Queries/GetCustomerOrders/GetCustomerOrdersQueryHandler.cs +├── OrdersCQ/Queries/GetCustomerOrder/GetCustomerOrderQueryHandler.cs +├── UserWeeklyBalanceCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs +├── CommissionPayoutCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs +├── NetworkStatisticsCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs +├── NetworkTreeCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs +├── UserCQ/Queries/GetUser/GetUserQueryHandler.cs +└── UserWalletCQ/Queries/GetUserWallet/GetUserWalletQueryHandler.cs +``` + +### WebApi Layer: +``` +CMS/src/CMSMicroservice.WebApi/Services/ +├── UserOrderService.cs (GetCustomerOrders + GetCustomerOrder) +├── NetworkMembershipService.cs (GetMyNetworkStatistics) +└── UserWalletService.cs (GetCustomerWallet) +``` diff --git a/deployment/CICD-PIPELINE-GUIDE.md b/deployment/CICD-PIPELINE-GUIDE.md new file mode 100644 index 0000000..5927d81 --- /dev/null +++ b/deployment/CICD-PIPELINE-GUIDE.md @@ -0,0 +1,418 @@ +# 🔧 راهنمای CI/CD Pipeline — Gitea Actions + K3s + +> آخرین بروزرسانی: February 11, 2026 + +--- + +## 📐 معماری کلی + +``` +┌─────────────────────────────────────────────────────────┐ +│ K3s Cluster (194.5.195.53) │ +│ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ gitea-runner Pod (2 containers) │ │ +│ │ │ │ +│ │ ┌──────────────────┐ ┌──────────────────────┐ │ │ +│ │ │ docker (DinD) │ │ runner (act_runner) │ │ │ +│ │ │ docker:dind │ │ gitea/act_runner │ │ │ +│ │ │ privileged: true │ │ DOCKER_HOST= │ │ │ +│ │ │ port: 2375 │ │ tcp://localhost:2375│ │ │ +│ │ └──────────────────┘ └──────────────────────┘ │ │ +│ │ ▲ shared volumes: docker-storage │ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────┐ ┌───────────┐ ┌────────────────┐ │ +│ │ Gitea │ │ Nexus │ │ Docker Reg. │ │ +│ │ :3000 │ │ :32081 │ │ :32082 (pull) │ │ +│ │ │ │ (NuGet) │ │ :30080 (push) │ │ +│ └─────────────┘ └───────────┘ └────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### سه لایه Docker-in-Docker: +``` +K3s containerd (لایه ۱) + └── gitea-runner Pod → docker container (DinD daemon) (لایه ۲) + └── workflow: docker run / docker build (لایه ۳) +``` + +--- + +## 📁 فایل‌های Workflow + +### 🐳 K8s Pipelines (Docker + K3s) — آفلاین + +| سرویس | فایل | Branch | Image | Deploy | +|--------|------|--------|-------|--------| +| CMS | `kub-deploy.yml` | `kub-stage` | `admin/cms` | SSH → kubectl | +| BackOffice | `kub-deploy.yml` | `kub-stage` | `admin/backoffice` | SSH → kubectl | +| FrontOffice | `kub-deploy.yml` | `kub-stage` | `admin/frontoffice` | SSH → kubectl | +| CMS | `prod-deploy.yml` | `production` | `admin/cms:prod` | SSH → kubectl | +| BackOffice | `prod-deploy.yml` | `production` | `admin/backoffice:prod` | SSH → kubectl | +| FrontOffice | `prod-deploy.yml` | `production` | `admin/frontoffice:prod` | SSH → kubectl | + +### 🪟 Windows/IIS Pipelines (Legacy) — آنلاین + +| سرویس | فایل | Branch | Target | +|--------|------|--------|--------| +| CMS | `cms-stage.yml` | `stage_new` | IIS → `cms.kbs1.ir` | +| BackOffice | `bo-stage.yml` | `stage-new` | IIS → `admin.kbs1.ir` | +| FrontOffice | `fo-stage.yml` | `stage-new` | IIS → `kbs1.ir` | + +> ⚠️ Stage pipeline ها از Windows runner + IIS استفاده میکنن و Docker ندارن. + +### ساختار مشترک Pipeline: +``` +1. Start Docker daemon (DinD) +2. Checkout code (git clone) +3. Login to Docker registries (32082 + 30080) +4. [CMS only] Publish Protobuf packages +5. Build Docker Image +6. Push to Registry +7. Deploy to Kubernetes (SSH → kubectl rollout restart) +``` + +--- + +## 🐛 مشکلات حل‌شده و راه‌حل‌ها + +### مشکل ۱: `iptables failed: Permission denied` + +**خطا:** +``` +iptables v1.8.10 (nf_tables): Could not fetch rule set generation id: Permission denied +``` + +**علت:** K3s containerd به Docker daemon اجازه تغییر iptables نمیده. + +**راه‌حل:** غیرفعال کردن networking در dockerd: +```bash +dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs & +``` + +> ⚠️ با `--bridge=none` نیاز به شبکه‌سازی Docker نیست چون فقط build و push انجام میشه. + +--- + +### مشکل ۲: `failed to unmount overlayfs: operation not permitted` + +**خطا:** +``` +failed to register layer: unshare: operation not permitted +``` + +**علت:** `overlay2` storage driver نیاز به mount namespace داره که داخل K3s مجاز نیست. + +**راه‌حل:** استفاده از `vfs` storage driver: +```bash +dockerd --storage-driver=vfs & +``` + +> ⚠️ `vfs` کندتره ولی هیچ mount syscall خاصی نیاز نداره. برای CI/CD کافیه. + +--- + +### مشکل ۳: `no basic auth credentials` هنگام pull ایمیج + +**خطا:** +``` +Error response from daemon: Head "https://194.5.195.53:32082/v2/dotnet/sdk/manifests/9.0": +no basic auth credentials +``` + +**علت:** `docker login` فقط قبل از push انجام میشد، ولی `docker build` (یا `docker run`) هم از `32082` ایمیج pull میکنه. + +**راه‌حل:** اضافه کردن step "Login to Docker registries" بلافاصله بعد از Checkout: +```yaml +- name: Login to Docker registries + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin +``` + +--- + +### مشکل ۴: `unshare: operation not permitted` هنگام extract لایه‌ها + +**خطا:** +``` +docker: failed to register layer: unshare: operation not permitted +``` + +**علت اصلی (دو بخش):** + +**بخش ۱:** Gitea act_runner دیفالت `container.privileged: false` داره. یعنی job container ها بدون privileged ساخته میشن — حتی اگه workflow بنویسه `options: --privileged`. + +**بخش ۲:** env var `CONFIG_FILE` در runner container ست نبود → `run.sh` فلگ `--config` رو به `act_runner daemon` پاس نمیداد → config.yaml اصلاً لود نمیشد! + +**راه‌حل (سمت سرور):** + +۱. ساخت ConfigMap: +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: runner-config + namespace: default +data: + config.yaml: | + log: + level: info + runner: + file: .runner + capacity: 1 + timeout: 3h + container: + privileged: true + options: "--security-opt seccomp=unconfined --security-opt apparmor=unconfined" + valid_volumes: + - "**" +``` + +۲. Mount کردن در Deployment + env var: +```bash +kubectl patch deployment gitea-runner --type=json -p='[ + {"op":"add","path":"/spec/template/spec/containers/1/env/-", + "value":{"name":"CONFIG_FILE","value":"/data/config.yaml"}}, + {"op":"add","path":"/spec/template/spec/containers/1/volumeMounts/-", + "value":{"name":"runner-config","mountPath":"/data/config.yaml","subPath":"config.yaml"}}, + {"op":"add","path":"/spec/template/spec/volumes/-", + "value":{"name":"runner-config","configMap":{"name":"runner-config"}}} +]' +``` + +> ⚠️ **نکته مهم:** بدون `CONFIG_FILE=/data/config.yaml` env var، فایل `run.sh` داخل act_runner image فلگ `--config` رو پاس نمیده! + +--- + +### مشکل ۵: Protobuf restore از nuget.org بجای Nexus + +**علت:** `dotnet restore` بدون `--configfile` از دیفالت NuGet sources استفاده میکنه. + +**راه‌حل:** +```bash +dotnet restore "$proj" --configfile src/NuGet.config +``` + +--- + +### مشکل ۶: عدم دسترسی شبکه با `--bridge=none` + +**علت:** `dockerd --bridge=none` شبکه Docker bridge رو غیرفعال میکنه. در نتیجه container هایی که با `docker run` یا `docker build` ساخته میشن، دسترسی شبکه ندارن (مثلاً `dotnet restore` نمیتونه به Nexus وصل بشه). + +**راه‌حل:** استفاده از `--network host` در `docker run` و `docker build`: +```bash +# Protobuf step +docker run --rm --network host -v $(pwd):/src -w /src ... + +# Build step +DOCKER_BUILDKIT=0 docker build --network host -t ... . +``` + +--- + +### مشکل ۷: `failed to prepare ... as ...: invalid argument` (BuildKit) + +**خطا:** +``` +ERROR: failed to build: failed to solve: failed to prepare xxx as yyy: invalid argument +``` + +**علت:** BuildKit (بیلدر پیش‌فرض Docker ≥23) از snapshotter overlay استفاده میکنه که با `--storage-driver=vfs` سازگاری نداره. + +**راه‌حل:** غیرفعال کردن BuildKit: +```bash +DOCKER_BUILDKIT=0 docker build --network host -t ... . +``` + +> ⚠️ Legacy builder از vfs بدون مشکل استفاده میکنه. + +--- + +## 🔄 تغییرات prod-deploy (قدیم → جدید) + +| مورد | قدیم (prod-deploy) | جدید | +|------|-------------------|------| +| Container image | `docker:latest` | `docker-sshpass:latest` (شامل sshpass + git) | +| Proxy | `HTTP_PROXY` + `HTTPS_PROXY` | حذف شد (آفلاین) | +| Registry | `gitea-svc:3000` + external | فقط `194.5.195.53:30080` | +| kubectl | `apk add` + `curl` از اینترنت | SSH → `kubectl` مستقیم روی سرور | +| Auth | hardcoded password | `secrets.REGISTRY_PASSWORD` + `secrets.SERVER_PASSWORD` | +| BuildKit | فعال (دیفالت) | `DOCKER_BUILDKIT=0` | +| Network | Docker bridge (دیفالت) | `--network host` | +| dockerd | دیفالت | `--iptables=false --ip6tables=false --bridge=none --storage-driver=vfs` | +| Deploy | `KUBECONFIG_PROD` (base64) | SSH + sshpass (مثل kub-stage) | + +> ✅ حالا همه ۶ K8s pipeline (۳ stage + ۳ prod) از **یک الگوی مشترک آفلاین** استفاده میکنن. + +--- + +containers: + - name: docker # DinD sidecar + image: 194.5.195.53:32082/docker:dind + securityContext: + privileged: true + env: + - DOCKER_TLS_CERTDIR: "" + volumeMounts: + - /var/lib/docker → docker-storage + - /etc/docker/daemon.json → docker-config (ConfigMap) + + - name: runner # Gitea act_runner + image: 194.5.195.53:32082/gitea/act_runner:latest + env: + - GITEA_INSTANCE_URL: http://gitea-svc:3000 + - DOCKER_HOST: tcp://localhost:2375 + - CONFIG_FILE: /data/config.yaml # ← حیاتی! بدون این runner config لود نمیشه + volumeMounts: + - /data → runner-data + - /data/config.yaml → runner-config (ConfigMap) +``` + +### ConfigMaps: +| نام | محتوا | Mount Path | +|-----|-------|------------| +| `docker-daemon-config` | `daemon.json` با insecure-registries | `/etc/docker/daemon.json` | +| `runner-config` | `config.yaml` با privileged + seccomp | `/data/config.yaml` | + +### Labels (ثبت‌شده در Gitea): +``` +ubuntu-latest → docker://docker.gitea.com/runner-images:ubuntu-latest +ubuntu-24.04 → docker://docker.gitea.com/runner-images:ubuntu-24.04 +ubuntu-22.04 → docker://docker.gitea.com/runner-images:ubuntu-22.04 +``` + +--- + +## 🔑 Secrets مورد نیاز (Gitea → Settings → Secrets) + +| Secret | استفاده | +|--------|---------| +| `REGISTRY_PASSWORD` | پسورد Docker registry (admin) | +| `SERVER_PASSWORD` | پسورد SSH سرور (root) | + +--- + +## 🔧 dockerd فلگ‌های نهایی + +```bash +dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs & +``` + +| Flag | دلیل | +|------|-------| +| `--iptables=false` | K3s اجازه تغییر iptables نمیده | +| `--ip6tables=false` | مشابه بالا برای IPv6 | +| `--bridge=none` | نیازی به Docker bridge network نیست | +| `--storage-driver=vfs` | overlay2 نمیتونه mount کنه داخل K3s | + +--- + +## 🔍 عیب‌یابی Pipeline + +### ۱. چک وضعیت Runner: +```bash +# SSH به سرور +ssh root@194.5.195.53 + +# آیا runner pod بالاست؟ +kubectl get pods -l app=gitea-runner + +# لاگ runner +kubectl logs -c runner --tail=30 + +# لاگ DinD +kubectl logs -c docker --tail=30 +``` + +### ۲. تست Docker داخل Runner: +```bash +# exec به DinD container +kubectl exec -c docker -- docker info + +# آیا registry قابل دسترسیه؟ +kubectl exec -c docker -- docker pull 194.5.195.53:32082/dotnet/sdk:9.0 +``` + +### ۳. چک config runner: +```bash +# آیا config.yaml mount شده؟ +kubectl exec -c runner -- cat /data/config.yaml + +# آیا privileged فعاله؟ +kubectl exec -c docker -- docker inspect \ + --format '{{.HostConfig.Privileged}} {{.HostConfig.SecurityOpt}}' +``` + +### ۴. ری‌استارت runner: +```bash +kubectl rollout restart deployment/gitea-runner +kubectl rollout status deployment/gitea-runner --timeout=120s +``` + +--- + +## 📋 Workflow Template (کامل) + +```yaml +name: Build and Deploy to Kubernetes + +on: + push: + branches: + - kub-stage + +env: + REGISTRY: 194.5.195.53:30080 + IMAGE_NAME: admin/ + K8S_SERVER: 194.5.195.53 + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + container: + image: 194.5.195.53:32082/docker-sshpass:latest + options: --privileged + steps: + - name: Start Docker daemon + run: | + mkdir -p /etc/docker + cat > /etc/docker/daemon.json << 'DAEMON' + { + "insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"] + } + DAEMON + dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs & + for i in $(seq 1 90); do + if docker info >/dev/null 2>&1; then + echo "✅ Docker ready"; break + fi + sleep 2 + done + + - name: Checkout code + run: | + git clone --depth 1 --branch kub-stage http://gitea-svc:3000/admin/.git . + + - name: Login to Docker registries + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin + + - name: Build Docker Image + run: | + DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest . + + - name: Push to Registry + run: | + docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + + - name: Deploy to Kubernetes + run: | + export SSHPASS="${{ secrets.SERVER_PASSWORD }}" + sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " + kubectl rollout restart deployment/ + kubectl rollout status deployment/ --timeout=180s + " +```