Files
backend_fiesta/.env.example
2026-09-30 14:58:30 +05:30

216 lines
11 KiB
Plaintext

# Fiesta configuration — the complete list of settings, with local values.
#
# How the files are picked (config/config.go):
#
# APP_ENV unset / local → .env.local then .env
# APP_ENV=production → .env.production then .env
#
# A real environment variable always wins over a file, and no file has to
# exist: on the deployed host the values come from the platform's environment
# settings (Dokploy / Kubernetes), not from a file. Anything added here must be
# added there too — a variable in this file and not in the platform is a
# variable that is unset in production.
#
# Startup checks every required setting and prints everything that is missing
# in one go, before any connection is attempted.
#
# The credentials in the committed .env.production have to be treated as
# public: rotate them, and keep new values out of git.
# ── Environment ─────────────────────────────────────────────────────────────
# local | production. Production requires POS_TOKEN_SECRET and never falls
# back to localhost defaults. Set in the real environment, not in a file: a
# file cannot decide which file gets loaded.
#APP_ENV=local
# ── Where it listens ────────────────────────────────────────────────────────
# 1122 locally; production serves on 1009 (matching the Dockerfile's EXPOSE).
APP_PORT=1122
# ── The main database (nearledb) ────────────────────────────────────────────
#
# ⚠️ POINTING THIS AT PRODUCTION MAKES LOCAL TESTING WRITE TO PRODUCTION.
#
# There is no "local mode" that protects you: `go run .` against the live host
# creates real tenants, real logins and real stock movements, and main.go runs
# schema migrations on boot. Startup warns when APP_ENV=local and DB_HOST is
# not a local address, but it does not stop you.
#
# These match docker-compose.local.yml, so `docker compose -f
# docker-compose.local.yml up -d` and `go run .` work together with no edits.
DB_HOST=localhost
DB_PORT=5433
DB_NAME=nearledb
DB_USER=nearle
DB_PASSWORD=localdev
# ── The catalogue database (pgvector) ───────────────────────────────────────
#
# A separate connection on purpose, so catalogue work never touches nearledb.
# Leave CATALOGUE_DB_HOST blank to start without it: catalogue endpoints then
# fail at query time rather than at boot. With a host set, the other four are
# required. 5434, not 5432: a developer machine usually has something on 5432.
CATALOGUE_DB_HOST=localhost
CATALOGUE_DB_PORT=5434
CATALOGUE_DB_NAME=cataloguedb
CATALOGUE_DB_USER=nearle
CATALOGUE_DB_PASSWORD=localdev
# ── Redis — POS terminal presence, under a TTL ──────────────────────────────
#
# Optional: leave REDIS_HOST blank to run without it. Losing the health board
# is an inconvenience; losing a sale is not, so the API runs without it.
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_USER=default
REDIS_PASSWORD=
REDIS_DB=0
# ── DigitalOcean Spaces (S3-compatible) — catalogue product images ──────────
#
# Optional locally. With USE_S3=true every S3_* value below is required.
USE_S3=false
S3_ACCESS_KEY=
S3_SECRET_KEY=
S3_ENDPOINT=
S3_BUCKET=
S3_REGION=
# ── POS terminals — the MQTT broker the in-store tills publish to ───────────
#
# Optional locally: with MQTT_URL blank the ingest and the console live stream
# stay quiet and the HTTP endpoints still work. In production a blank MQTT_URL
# means every till queues its bills silently — on startup you should see
# three lines reading "pos: subscribed to nearle/pos/+/+/...".
MQTT_URL=
MQTT_USER=
MQTT_PASSWORD=
# Unique per replica: a second connection with the same id evicts the first.
# Defaults to HOSTNAME (the pod name) when unset.
MQTT_CLIENT_ID=
# always | never — otherwise a StatefulSet pod ending in "-0" is elected and
# anything else consumes. See messaging/posmqtt.go.
#POS_MQTT_CONSUMER=
# ── Auth ────────────────────────────────────────────────────────────────────
# Signs POS terminal sessions; at least 16 characters. Falls back to
# JWT_SECRET_KEY when unset. Required in production.
POS_TOKEN_SECRET=local-dev-signing-secret-not-real
JWT_SECRET_KEY=
USER_CONTEXT_KEY=nearle
# true to make the POS routes require a terminal session.
#POS_AUTH_REQUIRED=false
# ── Scan-to-order — the embedding model behind product recognition ─────────
#
# MUST be the model that filled the catalogue's `embedding` column: vectors
# from two models are not comparable and pgvector will rank garbage without
# complaint. The first search reads the column's width and refuses a mismatch
# with an error that names both numbers.
# Optional: with no provider the search matches on words alone (works, ranks
# worse). openai = any OpenAI-compatible /embeddings endpoint (set
# EMBEDDING_BASE_URL for Azure, Ollama, vLLM...); gemini = Google AI Studio.
EMBEDDING_PROVIDER=
EMBEDDING_MODEL=
EMBEDDING_API_KEY=
EMBEDDING_BASE_URL=
# 0 = the model's default width.
EMBEDDING_DIMENSIONS=0
# ── Geocoding ───────────────────────────────────────────────────────────────
# Google Geocoding when set; OpenStreetMap's Nominatim otherwise.
GEOCODER_API_KEY=
# ── Nutrition ───────────────────────────────────────────────────────────────
#
# The catalogue-intelligence service — the same host the console reads its
# health score card from. The customer app product screen gets its nutrition
# panel from here, through `getproductbyvariant`.
#
# Read server-side rather than by the app: the brand-spelling resolution below
# would otherwise have to be reimplemented in the app, and a wrong spelling
# returns a well-formed record with every figure null — indistinguishable from
# a product nobody has scored.
#
# Unset means product screens carry no nutrition panel and nothing else changes.
NUTRITION_BASE=https://mcp.nearle.ai.in/api
# ── Email ───────────────────────────────────────────────────────────────────
#
# Sending the first-password invitation a newly onboarded merchant receives.
# Without MAIL_HOST the server still boots and still onboards tenants — the
# create response comes back `invited: false` with the reason — but nobody is
# emailed, and the only way into a new account is a Nearle staff member using
# Resend invite.
#
# SMTP, because every provider speaks it. Any transactional service is the same
# five variables: its host, 587, the API key as MAIL_PASSWORD, and whatever
# username it documents.
#
# WE USE GOOGLE WORKSPACE SMTP, authenticating as care@nearledaily.com with a
# 16-character App Password — never the account's login password, because an App
# Password can be revoked on its own. See docs/MAIL_SETUP.md for the setup and
# for the DNS records, which are what actually decide whether the invitation
# reaches an inbox rather than a spam folder.
#
# Self-hosting (Postal) was the earlier plan and is the better answer at volume.
# At a few dozen invitations a month the work is not the software, it is IP
# reputation, rDNS and blocklists — so this buys the reputation instead.
#
# Google Workspace: smtp.gmail.com 587 an App Password
# via an SMTP relay: smtp-relay.gmail.com 587 if an admin sets one up
# Amazon SES: email-smtp.<region>.amazonaws.com 587
# SendGrid: smtp.sendgrid.net 587 username literally "apikey"
# Resend: smtp.resend.com 587 username literally "resend"
#
# Credentials belong in .env.secrets (git-ignored, read first), or in the
# deployment platform's own environment — NOT in this file and not in .env.
MAIL_HOST=
MAIL_PORT=587
# Optional. Leave both empty for a relay that authenticates by network rather
# than by credentials.
MAIL_USERNAME=
MAIL_PASSWORD=
# Who the invitation appears to come from. Separate from MAIL_USERNAME because
# most providers authenticate as one identity and send as another, and using
# the login as the From address is how mail lands in spam.
#
# ON NEARLEDAILY.COM, DELIBERATELY. The link in the mail points at
# app.nearledaily.com, and a password link arriving from a DIFFERENT domain than
# the one it sends you to is the exact shape of a phishing mail — to a filter
# and to the merchant reading it. Sender and link stay on one domain.
#
# `care@` rather than `no-reply@`, also deliberately: somebody who replies "I
# never got this" is the single most useful reply this system can receive, and
# it should reach a person.
MAIL_FROM=care@nearledaily.com
MAIL_FROM_NAME=Nearle
# Where the invitation link points — the MERCHANT console, always. A merchant
# sets their password there and nowhere else, so this is never the platform
# console's address.
MAIL_CONSOLE_URL=https://app.nearledaily.com
# ── Nearle Buddy ────────────────────────────────────────────────────────────
#
# ONE variable. The provider, endpoint and model are defaults in config.go
# (openai / api.groq.com / openai/gpt-oss-120b) because each has one right
# answer for this product — and three variables that must be typed correctly
# into a hosting platform are three ways for the assistant to sit silently off,
# which is how it spent its first week.
#
# The key is the only one that differs per deployment and the only one that
# cannot live in this repository. Locally it goes in `.env.secrets`, which git
# ignores; in production it is set on the platform.
ASSISTANT_API_KEY=
# Overrides, none of them needed for the shipped setup.
# Ollama on a laptop: ASSISTANT_BASE_URL=http://localhost:11434/v1 and
# ASSISTANT_MODEL=llama3 — a local endpoint needs no key.
ASSISTANT_PROVIDER=
ASSISTANT_BASE_URL=
ASSISTANT_MODEL=
# Per-tier overrides. ASSISTANT_MODEL alone sets all three.
ASSISTANT_MODEL_FAST=
ASSISTANT_MODEL_BALANCED=
ASSISTANT_MODEL_DEEP=