Skip to content

Getting Started

Prerequisites

Tool Version Needed for
Node.js 20.x (both Dockerfiles use node:20-alpine) backend, admin portal
npm ships with Node 20 backend (package-lock.json), portal
Bun 1.3 (oven/bun:1.3-alpine in the Dockerfile) website build
Docker + Compose v2 syntax (prod runs Docker 29 / Compose v5) local data stores, prod stack
Flutter / Dart Dart SDK ≥ 3.3 (aimgold_app/pubspec.yaml) mobile app only
Go 1.25 (aim-gold-backend/go.mod) only the parked Go rewrite

Install and run locally

Backend (customer API + admin API)

cd aim-digigold-real
docker compose up -d          # local postgres:16, redis:7, minio on default ports
npm ci
# create .env.development — see the configuration table below; the minimum is
# DB_*, REDIS_HOST, JWT_SECRET, FIELD_ENCRYPTION_KEY (64 hex), SAFEGOLD_BASE_URL
npm run start:api             # :3000, Swagger at http://localhost:3000/docs
npm run start:admin-api       # :3001, Swagger at http://localhost:3001/docs

Both apps load .env.<NODE_ENV> then .env (apps/*/src/app.module.ts). Useful scripts: npm run lint:ci, npm test, npm run migration:generate -- libs/database/src/migrations/<Name>, npm run migration:run (TS, local) / npm run migration:run:prod (compiled).

Seed an admin user for the portal / admin API:

npm run seed:admin            # prompts for email/password (or ADMIN_EMAIL/ADMIN_PASSWORD env)

Website

cd aimgold-website
bun install
bun run dev                   # http://localhost:5199
bun run typecheck && bun run build

Admin portal

cd digigold-admin
npm ci
# .env.local: NEXTAUTH_URL=http://localhost:3002, NEXTAUTH_SECRET=<random>,
#             ADMIN_API_INTERNAL_URL=http://localhost:3001
npm run dev                   # http://localhost:3002

Mobile app

cd aimgold_app
flutter pub get
flutter run --flavor development -t lib/main_development.dart

Production

Production is deploy-by-push: see Architecture → Deploy flow. The stack lives on the VPS under /opt/aim/deploy; deploy/gen-env.sh generates a fresh .env.production on a new box, and deploy/README.md is the ops runbook (rollback, seeding, backups, migrations).

Configuration reference

Every environment variable read by the deployed system, by component. (The parked Go rewrite documents its own surface in aim-gold-backend/.env.example.)

Secrets

Never commit values. Production values live only in /opt/aim/deploy/.env.production, /opt/aim/deploy/portal.env.production and /opt/aim/monitoring/.env on the server.

Backend (aim-digigold-real — validated by libs/config/src/validation.schema.ts)

Variable Purpose Required Example
NODE_ENV environment selector; picks .env.<NODE_ENV> no (default development) production
API_PORT customer API port no (default 3000) 3000
ADMIN_API_PORT admin API port no (default 3001) 3001
API_PUBLIC_URL public origin for absolute URLs (image proxy links) no (default https://api.aimgold.org) https://api.example.org
DB_HOST Postgres host (compose overrides to digigold_postgres) yes localhost
DB_PORT Postgres port no (default 5432) 5432
DB_USER Postgres user (also POSTGRES_USER in compose + backup step) yes digigold
DB_PASSWORD Postgres password yes <random>
DB_NAME database name yes digigold
DB_SYNCHRONIZE TypeORM auto-sync — must stay false; schema is migration-managed no (default false) false
DB_LOGGING TypeORM query logging no (default false) false
REDIS_HOST Redis host (compose overrides to digigold_redis) yes localhost
REDIS_PORT Redis port no (default 6379) 6379
REDIS_PASSWORD Redis auth (also feeds --requirepass in compose) prod yes <random>
JWT_SECRET HMAC secret for all JWTs yes (min 16 chars) <64-hex>
JWT_ACCESS_TTL customer access-token TTL no (default 15m) 15m
JWT_REFRESH_TTL customer refresh-token TTL no (default 30d) 30d
ADMIN_JWT_ACCESS_TTL admin token TTL (matches 8 h portal session) no (default 8h) 8h
FIELD_ENCRYPTION_KEY AES key for PII fields — unrecoverable if lost yes (exactly 64 hex) <64-hex>
DEV_FIXED_OTP dev-only: forces every OTP to this value — never set in prod no 000000
SAFEGOLD_BASE_URL SafeGold partner API base yes (uri) https://partners-staging.safegold.com
SAFEGOLD_API_KEY SafeGold bearer key (env-only; required in production) prod yes <key>
SAFEGOLD_GOLD_TOKEN SafeGold gold-product token (env-only; required in production) prod yes <token>
SAFEGOLD_ENCRYPTED legacy AES-envelope wire format toggle no (default false) false
SAFEGOLD_PARTNER_ID SafeGold partner id no <id>
RAZORPAY_KEY_ID / RAZORPAY_KEY_SECRET Razorpay payments keys (payments stubbed until set) no rzp_live_…
RAZORPAY_WEBHOOK_SECRET payment webhook HMAC secret no <secret>
RAZORPAY_X_KEY_ID / RAZORPAY_X_KEY_SECRET / RAZORPAY_X_ACCOUNT RazorpayX payout keys + source account no <…>
RAZORPAY_X_WEBHOOK_SECRET payout webhook HMAC secret no <secret>
KYC_PROVIDER / KYC_API_KEY / KYC_WEBHOOK_SECRET KYC provider wiring (stubbed until set) no <…>
S3_ENDPOINT S3/MinIO endpoint (compose overrides to http://digigold_minio:9000) no http://localhost:9000
S3_ACCESS_KEY / S3_SECRET_KEY S3 creds (also MinIO root creds in compose) prod yes <random>
S3_BUCKET app bucket (auto-created by minio_init) prod yes digigold
SMS_USERNAME / SMS_APIKEY alots.in SMS gateway creds (env-only; required in production; OTP sends are live once set) prod yes <…>
SMS_SENDER / SMS_TEMPLATE_ID / SMS_ROUTE / SMS_BASE_URL SMS sender id, DLT template, route, endpoint no (defaults in code) AIMECO
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS outbound email (stubbed until set) no smtp.example.com
ADMIN_PORTAL_ORIGIN CORS allowlist for admin API — unset = closed (no cross-origin) recommended https://admin.example.org
GITHUB_PAT server-side PAT powering the portal's Build Tools (503 without it; not in gen-env.sh — add manually) Build Tools only ghp_…
GITHUB_REPO repo the Build Tools target no (default aimgoldorg/Karat) org/repo
ENV_FILE dotenv override for TypeORM CLI + seed CLIs no .env.production
ADMIN_EMAIL / ADMIN_NAME / ADMIN_PASSWORD / ADMIN_ROLE seed:admin CLI inputs (prompted if unset) no

Deploy layer (deploy/docker-compose.yml, values in server .env.production)

Variable Purpose Required Example
API_IMAGE_TAG GHCR tag for the backend image — pin sha-… for rollback no (default latest) sha-a1b2c3d
WEBSITE_IMAGE_TAG GHCR tag for the website image no (default latest) sha-a1b2c3d
ADMIN_PORTAL_IMAGE_TAG GHCR tag for the portal image no (default latest) sha-a1b2c3d

Admin portal (portal.env.production on the server; NEXT_PUBLIC_* baked at image build)

Variable Purpose Required Example
NEXTAUTH_URL portal canonical URL yes at runtime https://admin.example.org
NEXTAUTH_SECRET NextAuth session secret yes at runtime <random 32B>
ADMIN_API_INTERNAL_URL admin API base for proxy/login (compose sets container URL) no (default http://localhost:3001) http://digigold_admin_api:3001
NEXT_PUBLIC_API_WS_URL customer-API origin for the price ticker (build-time; Dockerfile bakes prod value) no https://api.example.org
NEXT_PUBLIC_BRAND_NAME brand label (build-time) no (default DigiGold) MyBrand
NEXT_PUBLIC_MONITORING_URL Monitoring link target (build-time) no (default https://monitor.aimgold.org) https://monitor.example.org
PORT / HOSTNAME / NODE_ENV / NEXT_TELEMETRY_DISABLED standalone server bind + mode (set in Dockerfile) no 3002 / 0.0.0.0

Website (build-time only)

Variable Purpose Required Example
VITE_API_BASE_URL customer API origin baked into the bundle (src/lib/api.ts); Dockerfile doesn't set it, so images use the code default https://api.aimgold.org no https://api.example.org

Monitoring (/opt/aim/monitoring/.env on the server)

Variable Purpose Required Example
GRAFANA_ADMIN_USER Grafana admin login no (default admin) admin
GRAFANA_ADMIN_PASSWORD Grafana admin password — compose refuses to start without it yes <strong>

CI (GitHub repo secrets)

Secret Purpose
VPS_HOST / VPS_USER / VPS_SSH_KEY SSH target + key for deploys and pinned health checks
GHCR_PULL_USER / GHCR_PULL_TOKEN read:packages credentials the VPS uses to pull images
GITHUB_TOKEN auto-provided; pushes built images to GHCR

First task: run the login → price → portfolio flow locally

  1. Start the data stores and both APIs as above, with DEV_FIXED_OTP=000000 in .env.development.
  2. Request an OTP (no SMS is sent when the fixed OTP is set):
    curl -X POST localhost:3000/v1/auth/send-otp \
      -H 'Content-Type: application/json' -d '{"phone":"+919999999999"}'
    
  3. Verify it — this auto-creates the user and returns tokens:
    curl -X POST localhost:3000/v1/auth/verify-otp \
      -H 'Content-Type: application/json' \
      -d '{"phone":"+919999999999","otp":"000000","deviceId":"dev-1"}'
    
  4. Use the accessToken:
    curl localhost:3000/v1/price/current
    curl -H "Authorization: Bearer $TOKEN" localhost:3000/v1/portfolio/summary
    
  5. Explore the rest interactively via Swagger at localhost:3000/docs and localhost:3001/docs, or read the API Reference.