Use the seeded kaniko v1.27.6-debug image, retry npm ci, and push via harbor-core without HTTP_PROXY so Harbor UI metadata and blob uploads keep working.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add REGISTRY_PUSH_URL config, route CI Kaniko to harbor-core, and document dual-host kaniko auth for core push plus registry base-image pull.
Co-authored-by: Cursor <cursoragent@cursor.com>
Route k3s registry mirrors through harbor-core ClusterIP with hostname-only auth keys, use HTTP EXT_ENDPOINT so OAuth tokens work on port 80, extend deploy readiness timeout, and harden Kaniko build/dockerfile fallbacks.
Co-authored-by: Cursor <cursoragent@cursor.com>
Mount docker config as config.json (Kaniko requirement), push via
harbor-registry internal URL, and wire harbor_registry_user credentials
in Helm/GitOps values.
Co-authored-by: Cursor <cursoragent@cursor.com>
Use seeded abrban/ images instead of flaky proxy-gcr pulls, fix Kaniko dockerfile path for v1.27, correct docker auth host keys, route /v2/abrban/ through harbor-core, and prefer abrban/ for base images.
Co-authored-by: Cursor <cursoragent@cursor.com>
v1.27.6-debug does not exist on gcr.io; proxy-gcr pulls are flaky on kubelet. Seed and reference abrban/kaniko-executor:v1.23.2 instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
Inject registry-egress-proxy into Kaniko and network init containers so npm/apk/composer/pip/git clone work on restricted egress clusters.
Co-authored-by: Cursor <cursoragent@cursor.com>
Enable backend.sms in the chart so MizbanSMS credentials from the platform
Secret are injected into the backend deployment for production OTP delivery.
Co-authored-by: Cursor <cursoragent@cursor.com>
Hardcoded Basic auth in probes broke after password rotation; exec probes
read the live secret so Elasticsearch stays healthy when credentials change.
Co-authored-by: Cursor <cursoragent@cursor.com>
Backend validate-production-config requires a non-default ELASTIC_PASSWORD;
read it from abrban-platform-secrets.elastic-password like other credentials.
Co-authored-by: Cursor <cursoragent@cursor.com>
User-app builds no longer pull gcr.io/docker.io directly when build.images
is configured in values.yaml, fixing ImagePullBackOff on clusters without
upstream registry access.
Co-authored-by: Cursor <cursoragent@cursor.com>
Harden preview/deploy flows, OTP generation, zip extraction, and multi-replica billing races; document full remediation status in AUDIT-STATUS.fa.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
Close billing, tenancy, migration, build, and CI/CD gaps identified in the
audit: wallet/gateway guards, full-UUID namespaces, idempotent migrations with
base schema, stateful service stability, safer Dockerfiles/git builds, and
platform chart hardening (Redis auth, RollingUpdate, backups, Swagger off).
Co-authored-by: Cursor <cursoragent@cursor.com>
Kaniko builds failed with context deadline exceeded pulling node:24-alpine
from index.docker.io through the egress proxy. Seed node:24-alpine into
abrban/ and pass BASE_IMAGE build-arg so builds use the internal registry.
Co-authored-by: Cursor <cursoragent@cursor.com>
Backend now fails production validation without CLUSTER_KUBECONFIG_KEY.
Add cluster-kubeconfig-key to the chart secret and env, plus
secrets.existingSecret so GitOps deployments can use a pre-created
(sealed) Secret instead of the lookup/randAlphaNum template that churns
under Argo CD's helm template rendering.
Co-authored-by: Cursor <cursoragent@cursor.com>
Harbor in-cluster builds via Kaniko, ArgoCD auto-sync, and production Helm values for abrban.com domains.
Co-authored-by: Cursor <cursoragent@cursor.com>
Reject zip uploads when the selected runtime does not match archive contents, and re-validate before Kaniko builds to fail fast instead of producing the wrong Dockerfile.
Co-authored-by: Cursor <cursoragent@cursor.com>
Backend
- Add platform-wide global discount (platform_settings: global_discount_percent),
applied centrally in PricingCatalogService.computeTotalsFromDb so it reaches
every real charge (previews, deploys, renewals, upgrades, invoices). Admin
GET/PATCH /billing/settings/global-discount.
- Add unauthenticated PublicPricingController (catalog + calculate) for the
public landing page, returning gross/net and the discount percentage.
- Bill application replicas by the user-selected footprint: app CPU/RAM/storage
now all scale by replica count; the single-replica database stays unscaled.
Frontend
- Landing: Services bar (PaaS active, DBaaS, KaaS/LaaS "coming soon" with
expandable runtime/database menus), transparent Pricing section (per-resource
rate cards with cycle toggle + discount strikethrough), and a cost Estimator
("estimate your package").
- Optional services and the database are priced like runtimes: the estimator
lets users pick their CPU/RAM/storage (and DB type) so the cost scales by need.
- Admin billing: global-discount editor.
- i18n: fa/en strings for services, pricing, estimator and global discount.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rework the application build/deploy pipeline for scalability, reproducibility,
and security:
- Build queue: deploys run through a bounded-concurrency Bull queue
(BUILD_CONCURRENCY, default 3) so concurrent user deploys can't flood the
cluster with Kaniko jobs. Build state (progress / cancel / session) moves from
in-memory Maps to Redis, so cancel + live logs work across backend replicas.
- Nixpacks + BYO Dockerfile: code runtimes build via Nixpacks (or the user's own
Dockerfile when present); the hand-written per-runtime Dockerfile generators
and runtime auto-detection are removed. WordPress keeps its templated path.
Build-time mirror env (NIXPACKS_BUILD_ENV) supports the Iran network.
- Source upload to MinIO: archives stream to in-cluster MinIO; build pods pull
via a presigned URL. Removes the PVC + helper pod + kubectl cp upload path.
- Report-only Trivy scan after build; per-severity summary stored on the
deployment and shown as a badge in the dashboard. Never gates a deploy.
- Registry GC: a Redis-locked daily job keeps the newest N image tags per app
(REGISTRY_KEEP_VERSIONS, default 3) and reclaims disk via garbage-collect.
- Hardening: git tokens are delivered via a per-build Secret + git credential
store instead of being embedded in the clone URL / Job manifest; build timeout
is configurable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Admins can create coupon codes that discount specific services (app
runtimes, optional services, managed products, custom-domain addon, or
all) and restrict them to specific users or make them public, with total
and per-user usage caps and an active date window.
Coupons apply in deploy, renewal, and upgrade flows: cost-breakdown lines
are tagged with a service key, the eligible portion is discounted and
capped to the payable amount, the invoice records discountAmount/
discountCode, and the redemption is recorded once when the invoice is
fully paid (covering wallet, gateway, and mixed payments).
- Discount + DiscountRedemption entities; invoice discount columns
- DiscountService (CRUD, validation, redemption) + admin/validate API
- Idempotent schema bootstrap on init so production (synchronize off)
provisions the tables/columns without a migration runner
- Admin discounts UI, coupon entry in deploy/renewal, invoice discount line
- fa/en strings; discount.service unit spec
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Translate persisted English invoice line labels/descriptions/reasons to
Persian at display time (new lib/invoice-labels.ts), covering both new and
historical invoices without a data migration.
- Generate a styled, RTL Persian invoice PDF on the client (lib/invoice-pdf.ts)
with the Abrban logo, line-item table and totals, via html2canvas + jsPDF.
- Wire both the user and admin invoice pages to the translator and new download.
- Add invoices.pdf dictionary keys (fa/en).
- Remove the now-dead ASCII-only backend PDF endpoints and generateInvoicePdf.
- Add frontend/.npmrc (npmmirror registry + high timeouts) for Iran-network installs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the admin-only "cluster assignment" UI from the resources &
config step of the deploy wizard and the related backend override.
App placement is now always decided automatically by the allocator.
- frontend: remove cluster/pool selection block, review-step cluster
row, clusterMode state, public cluster/pool queries, and clusterId/
poolId from CreateApplicationDto
- backend: drop clusterId/poolId override from the create DTO and
simplify ApplicationsService.create to always auto-allocate; widen
selectClusterForApplication param to keep the fallback path working
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the SMS_PROVIDER switch, the MizbanSMS credential/route/template vars
(now the default provider), and FRONTEND_URL (CORS + domain resolution),
so a fresh checkout has an accurate, complete env reference.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
JwtStrategy read `role` straight from the JWT payload, so a role change
or deactivation stayed ineffective until the 1h access token expired:
after a promotion the menus showed (via /users/me) but admin endpoints
returned 403 because RolesGuard still saw the old token role; after a
demotion the old admin kept API access. Load the user from the DB in
validate() and use the current role; reject inactive users.
Frontend: poll /users/me in the dashboard layout (+ on window focus) so
the sidebar reflects role changes without a hard reload, and redirect
away from pages the new role can no longer reach.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Let super admins act as a user from the user detail dashboard for
support/debugging ("full with guardrails", audit-only).
Backend: AuthService.impersonate issues a short-lived token for the
target carrying an `act` claim (acting admin); refresh preserves it and
JwtStrategy surfaces `impersonatedBy`. Guardrails: cannot impersonate an
admin or a deactivated account; new ImpersonationGuard blocks sensitive
self-service (change own password/phone) while impersonating. New
AuditLog entity records impersonation start/stop (admin, target, ip,
time); admin endpoints POST users/:id/impersonate + .../impersonation/
stop and GET users/:id/audit.
Frontend: lib/impersonation swaps admin/impersonation tokens in
localStorage; persistent banner with exit; "Login as user" button and an
"Admin access log" tab on the detail page; logout clears impersonation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a read-only User Detail dashboard for super admins, reachable by
clicking a user name in the admin users list.
Backend: new `admin` module aggregating existing domain services
(no new entities). ADMIN-only endpoints under /api/v1/admin:
overview (profile, account status, wallet balance, revenue, summary
counts), wallet transactions, applications (incl. deleted/docked with
restore eligibility), build/deploy errors, tickets with conversation,
and a composite activity timeline. Adds BillingService.getRevenueSummary
and guards against a wallet get-or-create race in the overview reads.
Frontend: tabbed detail page (overview/applications/activity/errors/
tickets) with lazy per-tab queries; user names in the admin list link to
it (admin only); fa/en i18n keys and response types.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The API returns its result inside a JSON array (e.g. "[1008]" for an
error, "[100002656565]" for a delivered message id). The previous parser
matched the raw string against the error map, so any array-wrapped error
slipped through as a false success and the caller got 201 with no SMS
sent. Unwrap the array (or bare/quoted value) before the error lookup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`string | null` / `uuid | null` columns reflect as Object and crash the
backend at metadata build (DataTypeNotSupportedError). Declare explicit
`type` on users.phone/email and cluster_allocation_logs FK id columns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Admin user management now creates accounts with a required mobile
number and an optional contact email, matching mobile-only auth.
Admin-created accounts are pre-verified (phoneVerified) so the user
can sign in by password immediately. The user list and search now
include phone.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Separate the marketing landing site from the authenticated app by host.
Next.js middleware reads PANEL_HOST/LANDING_HOST at runtime and redirects
authenticated routes (/dashboard, /login, /register) from the landing host
to the panel host, and the landing root on the panel host to /dashboard.
Disabled (single-origin) when PANEL_HOST is unset, so local dev is unchanged.
Helm: add ingress.panel.host with a third ingress rule + TLS host routing to
the frontend service, pass LANDING_HOST/PANEL_HOST to the frontend, and append
the panel origin to the backend CORS list (frontend URL stays first so
PLATFORM_DOMAIN resolution is unaffected).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Register and login by mobile number; email is now an optional
contact field only (never used to authenticate)
- After registration, the phone is verified via a 6-digit SMS code
- Login supports both password and one-time-code (OTP) methods
- Phone OTP delivered via Kavenegar (verify/lookup); API key in env
- Account page: edit name/optional email, change password, and
change mobile number with OTP re-verification
- Codes are hashed, expire in 5m, capped at 5 attempts, rate-limited
- Seed gives the admin a verified phone so mobile login still works
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Show real-time kaniko/init-container build output inside the build
progress modal during the building/deploying phases, alongside the
existing percentage. The build-logs endpoint now returns live pod logs
while a build is in progress (falling back to the persisted log once
finished), and the modal polls it every 2s with auto-scroll. Minimize
behaviour is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Let users size the database workload from the deploy wizard the same way
Redis/RabbitMQ are configured: a resource card (CPU request/limit, memory
request/limit, storage) on the Resources step, stored in
optionalServiceResources.database.
- entity/DTO: add `database` to optionalServiceResources
- k8s: resolveDatabaseResources() applies user-selected resources on both
the Helm and K8s-API deploy paths (was hardcoded 100m/256Mi→500m/512Mi)
- billing: bill database CPU/RAM as a separate line on top of the app's
resources; merge it through the upgrade path too
- wizard: db resource card on the Resources step, disk moved into the card,
cost preview + review summary include the database resources
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FRONTEND_URL may hold a list of origins (e.g. CORS needs both
panel.abrban.com and abrban.com). The domain resolvers ran new URL()
on the whole string, so new URL('https://a,https://b').hostname became
"a,https" and leaked into ingress hosts, which k8s then rejected with
the generic "HTTP request failed" surfaced in the UI. CORS likewise
never split the list, so the second origin never matched.
Parse only the first URL for domain/preview-root resolution, and split
the list into an array for enableCors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removing a custom domain left the Ingress with a stale custom-domain rule
and no preview host, so the platform preview URL 404'd. updateIngress now
resolves the app's stable preview number from its latest deployment and
re-emits the preview host (with cert-manager TLS) whenever no verified
custom domain is set, and removeCustomDomain re-applies the Ingress.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Derive the CNAME target shown to users from the site's public root
domain (PREVIEW_BASE_DOMAIN, e.g. 3fase.ir) instead of the internal
platform domain, which is not a valid public suffix and cannot get a
Let's Encrypt cert. On DNS verification, re-apply the Ingress so Traefik
routes the custom domain and cert-manager issues a cert via HTTP-01,
mirroring the preview-domain flow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ingress / preview URLs:
- Default the app Ingress class and ACME HTTP-01 solver to Traefik
(k3s default) via a new INGRESS_CLASS env, instead of hardcoding nginx —
fixes 404s on clusters without ingress-nginx.
- Only put public, real-TLD hosts (custom domain + preview) in the TLS
block; the internal *.apps.cloudhost.local host no longer poisons the
Let's Encrypt order, so certs actually issue.
- Make the per-app preview number stable across redeploys so URLs stop
breaking, and let PREVIEW_BASE_DOMAIN configure the base domain.
Registry pulls:
- Point the k3s registries.yaml mirror endpoint at the registry NodePort on
loopback so node containerd never depends on cluster DNS (image pulls
survive node restarts).
Builds:
- Pin the Kaniko image, use IfNotPresent pull policy, drop the dead build
queue/processor, and retry transient Kubernetes API errors while polling
build jobs.
Logs & apps list:
- fluent-bit reads log files from head so startup output reaches
Elasticsearch.
- Order joined deployments newest-first so the apps list shows the latest
deployment status.
Allocation:
- Reserve in-flight (pending/building) capacity and stop globally degrading
the cluster on a single allocation failure, so concurrent deploys don't
starve or wrongly report "no healthy cluster".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduce a catalog-driven Tools Management section under Clusters so
admins can install/uninstall infrastructure tools per cluster: cert-manager
(Helm/jetstack), ClusterIssuer (email + HTTP01 form, depends on cert-manager),
and central Elasticsearch. Cluster creation no longer auto-installs Elastic
or the cloudhost-node-cluster-dns DaemonSet; build bootstrap stays automatic.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Build hosts as <userPrefix>-<deploymentNumber>-preview.<rootDomain> from FRONTEND_URL, wire them through ingress/TLS, and open them from the preview API.
Co-authored-by: Cursor <cursoragent@cursor.com>
Auto port-forward using the registered cluster kubeconfig, default to loopback outside Kubernetes, route log APIs by app cluster, and document platform env settings.
Co-authored-by: Cursor <cursoragent@cursor.com>
Bootstrap configures systemd-resolved for *.cluster.local, installs k3s registries.yaml for the internal registry only, removes the legacy external-registry DaemonSet, and aligns Helm REGISTRY_PULL_URL with the in-cluster registry URL.
Co-authored-by: Cursor <cursoragent@cursor.com>
Assign unpredictable subdomains on create when no custom domain is set, and update deploy UI CNAME hints to reference the app Platform Domain after creation.
Co-authored-by: Cursor <cursoragent@cursor.com>