Files
catalogue_backend/.env.example
Suriyakumarvijayanayagam 00216ae834 Point the docs at the real API domain, mcp.nearle.ai.in
The deployment landed on mcp.nearle.ai.in, not the mcp.catalogue.nearle.ai.in
these references were written against. Only comments, README and .env.example
are affected - nothing reads the hostname at runtime - but a wrong host in the
connection snippet is a wrong host somebody pastes into an MCP client.

API_CORS_ORIGINS is unchanged: it takes the FRONTEND's origin
(catalogue.nearle.ai.in), not the API's, so moving the API does not affect it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 13:29:34 +05:30

216 lines
9.1 KiB
Plaintext

# Copy this file to .env and fill in your own values.
# Nothing here is a real credential.
#
# The host-side values below are for running the backend directly with uvicorn
# (python run_project.py). When the backend runs INSIDE Docker via the root
# docker-compose.yml, four of them are overridden by compose and you do not
# need to change them here:
#
# DB_HOST -> postgres (service name)
# DB_PORT -> 5432
# DB_PASSWORD -> POSTGRES_PASSWORD from the root .env
# OLLAMA_BASE_URL -> http://ollama:11434
#
# The reason is worth internalising: inside a container, `localhost` is the
# container itself, not the host and not a sibling container. A container-bound
# localhost URL points the backend at its own empty ports. Containers reach
# each other by service name over the compose network instead.
# --- Ports (container only) -----------------------------------------------
# The image serves 3000 and 8000 at once - 3000 because that is what Dokploy
# routes a domain to, 8000 because the README, the vite dev proxy and
# docker-compose all use it. Serving both means the container works whichever
# one the platform points at.
#
# PORTS the comma-separated pair to bind. PORT pins a single port instead and
# takes precedence, e.g. PORT=8080 serves only 8080.
#
# Neither affects running uvicorn directly for local development.
# PORTS=3000,8000
# PORT=8080
# --- Persistence (IMPORTANT in Docker) ------------------------------------
# The three directories the app WRITES to at runtime. The defaults point inside
# the repo/image and are right for local development; in a container each one
# needs a volume mounted on it, or a redeploy throws away everything written
# since the last build:
#
# SEED_CATALOG_DIR products added via POST /api/user/products/add and
# /upload-file are appended to the JSON files here
# MODEL_ARTIFACTS_DIR *.joblib bundles written by the training endpoints
# DATA_DIR catalogs saved by the ingestion pipeline
#
# Mount these two paths in Dokploy (SEED_CATALOG_DIR sits inside DATA_DIR, so
# one mount covers both):
#
# /app/data
# /app/app/intelligence/artifacts
#
# Either a named volume or a bind mount works. The image keeps read-only copies
# of the bundled seed catalogs and pre-trained models at /app/.bundled, and the
# app restores whatever a freshly-mounted directory is missing on startup
# without overwriting anything already there - so a bind mount, which starts
# empty and would otherwise hide them, is safe.
#
# Leave all three unset unless the writable data belongs somewhere else.
# DATA_DIR=/app/data
# SEED_CATALOG_DIR=/app/data/seed_catalogs
# MODEL_ARTIFACTS_DIR=/app/app/intelligence/artifacts
# --- CORS (REQUIRED when the API is on its own domain) --------------------
# Comma-separated list of the exact browser origins allowed to call this API.
# Scheme and host both matter; no trailing slash, and no wildcard - the
# frontend sends an Authorization header, and browsers refuse a credentialed
# cross-origin request whose Allow-Origin is "*" (app/main.py logs and turns
# credentials off if it sees one, so a wildcard silently breaks every call).
#
# Production - the React app is served from catalogue.nearle.ai.in and calls
# the API at mcp.nearle.ai.in, so that frontend origin must be listed:
#
# API_CORS_ORIGINS=https://catalogue.nearle.ai.in
#
# Add http://localhost:5173 alongside it if you point a local Vite dev server
# at the deployed API. Server-to-server callers (MCP clients, scripts) are not
# affected by any of this - CORS is a browser rule; they use X-API-Key.
API_CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
# --- Authentication (REQUIRED) -------------------------------------------
# The backend will not start without these while AUTH_ENABLED=true. Generate
# all four lines, plus sign-in passwords, with:
#
# python scripts/make_auth_secrets.py
#
# They guard the 18 write/compute endpoints - catalog generation, ML training,
# the upload endpoints and chat. CORS is not a substitute: browsers enforce it,
# curl ignores it entirely.
#
# AUTH_ENABLED=false turns every guard off and makes the whole API open again.
# It exists so a fresh checkout runs before you have generated secrets. Never
# set it false on a host reachable from the internet.
AUTH_ENABLED=true
# Signs access tokens. Changing it signs everybody out, which is how you revoke
# every issued token at once. Use a DIFFERENT value in production from the one
# on your laptop - a secret that has been on a dev machine is not a secret.
AUTH_SECRET_KEY=
# Only PBKDF2 digests are stored, never passwords. `make_auth_secrets.py`
# prints the password once and the hash to paste here; it cannot be reversed,
# so rerun the script to change a password.
AUTH_ADMIN_USERNAME=admin
AUTH_ADMIN_PASSWORD_HASH=
AUTH_USER_USERNAME=user
AUTH_USER_PASSWORD_HASH=
# Token lifetime in minutes. 12h by default: one sign-in per working day, and
# a leaked token expires by itself.
AUTH_TOKEN_TTL_MINUTES=720
# Failed-login throttle, per username+IP. Stops the login endpoint being an
# unlimited password oracle once it is on the internet.
AUTH_MAX_LOGIN_ATTEMPTS=10
AUTH_LOCKOUT_SECONDS=300
# Local development only: accept ANY password at /api/auth/login. The username
# still picks the role (`admin` -> admin pages, `user` -> user pages), and the
# token issued is a normal one, so every other guard behaves normally. Unlike
# AUTH_ENABLED=false it leaves the login page working - it just stops checking
# the password. Anyone who can reach the port becomes admin: keep it false here.
AUTH_ALLOW_ANY_LOGIN=false
# Machine consumers of api.<domain> - scripts, partner integrations, your own
# backends. Format: name:role:secret, comma-separated, role is admin or user.
# Callers send the secret as an X-API-Key header.
#
# One entry per consumer, always: a shared key cannot be revoked for one caller
# without breaking every other. Mint them with:
# python scripts/make_auth_secrets.py --api-key partner-x:user
#
# Leave empty if only the web app calls the API - it signs in through
# /api/auth/login instead, and a key nobody needs is only risk.
API_KEYS=
USE_OLLAMA=true
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL_NAME=qwen2.5:1.5b
USE_EMBEDDINGS=true
EMBEDDINGS_MODEL=sentence-transformers/all-MiniLM-L6-v2
USE_PGVECTOR=true
DB_HOST=localhost
DB_PORT=5432
DB_NAME=pgvector
DB_USER=postgres
DB_PASSWORD=changeme
USE_S3=true
S3_ACCESS_KEY=your-do-spaces-key
S3_SECRET_KEY=your-do-spaces-secret
S3_ENDPOINT=https://your-space.region.digitaloceanspaces.com
S3_BUCKET=your-bucket
S3_REGION=region
# Optional - leave USE_GOOGLE_CSE=false if you don't have a Google CSE key
USE_GOOGLE_CSE=false
GOOGLE_API_KEY=
GOOGLE_CSE_ID=
USE_DDG_IMAGES=true
USE_OPEN_FACTS=true
USE_WIKIMEDIA=true
USE_PLAYWRIGHT_FALLBACK=true
MIN_IMAGE_BYTES=3000
# Product SKU: try a live web search for a real marketplace product ID
# (Amazon ASIN, Flipkart PID, etc.) before falling back to an internal SKU.
# Set to false to always generate internal SKUs only (faster, offline-safe).
ENABLE_SKU_WEB_LOOKUP=true
# Deterministic product validation (see app/services/product_validator.py).
# Final gate applied to every catalog row before it's kept; rows scoring
# below VALIDATION_REJECT_THRESHOLD are dropped (with reasons recorded),
# rows below VALIDATION_REVIEW_THRESHOLD are kept but flagged for human review.
ENABLE_PRODUCT_VALIDATION=true
VALIDATION_REJECT_THRESHOLD=0.35
VALIDATION_REVIEW_THRESHOLD=0.70
# Per-pack-size product images (see docs/PER_VARIANT_IMAGES.md). When true,
# each size variant of a product (e.g. 100g/200g/500g) gets its own
# size-targeted image search/upload instead of sharing one generic image
# set. Falls back to the old shared behaviour automatically whenever no
# size-specific match is found, so this is safe to leave on. Set to
# "false" to fully restore the old shared-image behaviour.
ENABLE_PER_VARIANT_IMAGES=true
# Max image candidates fetched per size variant (kept smaller than the
# ~24 used for the general product-level search, since a per-size search
# only needs a handful of genuinely matching candidates).
PER_VARIANT_IMAGE_MAX_RESULTS=10
# Barcode Retrieval & Product Enrichment (see
# app/services/enrichment/barcode/ and docs/BARCODE_ENRICHMENT.md). Set to
# false to disable barcode lookup entirely (rows are stored with
# barcode=NULL, barcode_lookup_status="disabled").
ENABLE_BARCODE_LOOKUP=true
BARCODE_LOOKUP_TIMEOUT_SECONDS=10
# 30 days, in seconds
BARCODE_LOOKUP_CACHE_TTL_SECONDS=2592000
BARCODE_LOOKUP_MAX_CONCURRENCY=5
BARCODE_COUNTRY_TAG=india
# GS1 India tier - leave both blank to skip (no free public API exists at
# time of writing; the cascade falls through to Open Food Facts). Fill in
# if/when real GS1 India ("Verified by GS1") API access is provisioned.
GS1_INDIA_API_BASE_URL=
GS1_INDIA_API_KEY=
# UPCItemDB - works out of the box on the free trial tier (no key needed,
# rate limited ~100 req/day). Set to switch to the paid production endpoint.
UPC_DATABASE_API_KEY=
# Lowest-trust fallback tier: DuckDuckGo search + manufacturer page text
# scan for a labelled barcode (uses the same `ddgs` dependency as SKU
# lookup). Set to false to skip this tier.
ENABLE_MANUFACTURER_SITE_LOOKUP=true