# Qlockify deployment adaptation contract - Status: Approved planning baseline - Source: `D:/Programing/Scripts/Qlockify/qlockify-deployment` (read-only) > **2026-08-22 ingress amendment:** ADR 0006 replaces the copied Nginx/Certbot edge with the host's existing Caddy deployment and external `caddy_proxy` network. Qlockify's nested-checkout, manual-build, environment, data-service, and operational patterns remain the baseline; its edge container and certificate lifecycle do not. - Target: `/home/ubuntu/zarinpal-analytics-deployment` - Implementation authorization: Granted by user on 2026-08-20 - Last updated: 2026-08-20 ## Purpose Use Qlockify's proven single-server deployment shape and environment-file layout for ZarinPal Analytics without requiring production IPs, credentials, or operator names during planning. Real values are supplied on the server when deployment occurs; they are not planning blockers and never enter public Git. ## Repository layout ```text /home/ubuntu/zarinpal-analytics-deployment/ ├── .env ignored deployment/TLS/backup values ├── backend/ ignored backend Git checkout │ └── .env ignored backend/runtime values ├── frontend/ ignored frontend Git checkout │ └── .env ignored public build values only ├── data/incoming/ ignored one-time CSV input ├── data/reports/ ignored import reports ├── releases/ ignored non-secret release records └── backups/ ignored encrypted local backups ``` This deliberately simplifies Qlockify's extra application subdirectory layer. Each service checkout is directly under the deployment root and keeps an independent `.git` directory. ## File adaptation matrix | Qlockify source | Planned ZarinPal artifact | Adaptation | |---|---|---| | `.env.sample` | `.env.example` | Keep TLS and ParsPack variable names; use the ZarinPal domain/prefix; remove alternate-domain and duplicate database values. | | `backend/.env.sample` | backend-owned `.env.example`, consumed as `backend/.env` | Replace Django, JWT, Celery, OAuth, SMS, and demo-cleanup variables with the Go API/worker/import contracts. | | `frontend/.env.sample` | frontend-owned `.env.example`, consumed as `frontend/.env` | Keep only public same-origin build configuration; never place a secret here. | | `docker-compose.yml` | `compose.yaml` | Keep single-host orchestration and nested build contexts; replace Django/Celery/media services with API, worker, migrate, importer, and admin boundaries. | | `backend/Dockerfile` | deployment backend image definition | Build the backend checkout once and select API/worker/migrate/import/admin commands from the same recorded backend revision. | | `frontend/Dockerfile` | deployment frontend image definition | Build the ordinary frontend checkout, including its tracked font and license. | | `nginx/*` | `nginx/*` | Keep HTTP-01, automatic renewal, temporary bootstrap certificate, and reload flow; use only `zarinpal.amiirkhl.ir` and ZarinPal routes. | | `scripts/deploy.sh` | `scripts/deploy.sh` plus release-record helper | Keep manual Compose build/start structure; Git pulls happen interactively before the script; remove fetch/reset and partial-repository release selection. | | `scripts/backup.sh` | `scripts/backup.sh` | Keep plain-SQL `pg_dump` and temporary archive structure; include PostgreSQL plus non-secret manifest/checksums only. | | `scripts/backup-upload-s3.sh` | same filename | Keep root `.env` loading, OpenSSL/rclone flow, ParsPack support, and 3/7 retention; retain encrypted local artifacts. | | `scripts/restore.sh` | same filename | Keep plain-SQL restore basis; remove env/media restore and add empty-target plus production guards. | | `scripts/restore-from-s3.sh` | same filename | Keep rclone/decrypt handoff; require an explicit object id for production and validate it before restore. | | `.gitea/workflows/*` | none | Do not port. ZarinPal deployment is manual and has no CI/CD pipeline or runner. | ## Environment layout Future tracked examples contain placeholders or safe public defaults only. Production files are Git-ignored, owned by `ubuntu`, mode `0600`, excluded from backup, and loaded without printing resolved values. ### Deployment root `.env` The future `.env.example` uses this deployment-owned contract: ```dotenv COMPOSE_PROJECT_NAME=zarinpal_analytics SSL_CERT_MODE=letsencrypt LETSENCRYPT_EMAIL= PRIMARY_DOMAIN=zarinpal.amiirkhl.ir CERTBOT_STAGING=0 S3_BACKUP_BUCKET= S3_BACKUP_PREFIX=backup/zarinpal-analytics S3_BACKUP_ENDPOINT_URL= S3_BACKUP_ACCESS_KEY_ID= S3_BACKUP_SECRET_ACCESS_KEY= BACKUP_ENCRYPTION_PASSPHRASE= BACKUP_LOCAL_KEEP_LATEST=3 BACKUP_REMOTE_KEEP_LATEST=7 ``` There is no `ALT_DOMAINS`, runner, registry, deploy-host, SSH-key, or CI variable. The endpoint, bucket, email, access keys, and passphrase may remain blank in the tracked example; the future validation command requires them only for the operation that consumes them. ### Backend `backend/.env` The backend repository owns the complete Go application example. Deployment requires these semantic groups and consumes their eventual exact backend-owned names without maintaining a second competing template: - production mode and structured-log level - API listen address and public origin `https://zarinpal.amiirkhl.ir` - `POSTGRES_HOST=postgres`, port `5432`, database/user/password - `REDIS_HOST=redis`, port `6379`, password, and database selection required by the backend/Asynq contract - session/cookie secrets and secure same-origin cookie settings - analytics TTL, rate limits, worker concurrency `16`, per-merchant concurrency `4`, 60-second attempt timeout, and retry policy - import source/report paths, schema version, readiness timeout, and bounded batch/resource settings The same protected file supplies PostgreSQL, Redis, API, worker, migrate, importer, and admin services only with the variables each service needs. Qlockify-only Django, JWT, Celery, Google OAuth, SMS, demo-environment, media, and scheduled-cleanup variables are not carried over. ### Frontend `frontend/.env` The frontend repository owns its exact public variable name. Its production value is the same-origin API prefix, initially `/api/v1`. No database, Redis, session, backup, AvalAI, or other secret is allowed in this file or the browser bundle. ## Manual deployment flow 1. The operator manually runs `git pull --ff-only origin main` in deployment, backend, and frontend checkouts and enters source-forge credentials interactively. 2. The future deploy command acquires the operation lock and verifies exact paths, remotes, `main`, equality with `origin/main`, and clean trees. It performs no Git fetch, pull, checkout, or reset. 3. It records all three commit hashes and validates the root/backend/frontend environment contracts without printing values. 4. It validates Compose, builds from the three recorded revisions, starts data services, runs the one-off migration, and replaces application services only after the required gates pass. 5. It validates private readiness plus public TLS/routing/font/SSE behavior, observes for 15 minutes, then marks the release active. 6. Rollback selects the prior release's three hashes, rebuilds, and re-runs the same checks. It never automatically reverses a migration. The ordinary deployment path never invokes CSV import or credential generation. The first server uses the separately planned `first-start.sh` path after migration and before the first public activation. ## Always / Ask / Never ### Always - Adapt from the named tracked Qlockify source file and record intentional differences. - Keep all three real `.env` files ignored, mode `0600`, and out of backups/logs. - Validate only the variables needed by the requested operation. - Preserve exact-path guards before any mutation, cleanup, restore, or retention action. ### Ask first - Rename a deployment-owned variable after implementation begins. - Add a public origin/domain, external provider, persistent data class, or secret consumer. - Change the manual three-repository release rule or Qlockify-derived backup encryption/retention contract. ### Never - Port Qlockify's Gitea workflows, runner/deploy-key variables, or automated deployment. - Port Qlockify's destructive `git reset --hard` synchronization. - Copy Qlockify credentials, domains, database names, service names, or secret values. - Back up or restore `.env` files, Redis, TLS keys, raw CSV, credentials, logs, or frontend assets. - Create a second font path or private asset-injection workflow. ## Planning acceptance - Every future deployment artifact maps to a named Qlockify source or a documented ZarinPal-only requirement. - Root/backend/frontend environment ownership is unambiguous and has no duplicate secret source. - Tracked examples can be committed with blank secret placeholders and useful safe defaults. - Planning and local validation do not require a server IP, real DNS access, real ParsPack values, or named operators. - Real values are required only at the first-server or operation-specific execution gate.