The app had no cleanup of any kind: in multi-user mode every first visit mints a users row, so the demo has been accumulating one permanent account per visitor along with everything they generated. cleanup.py sweeps guests idle for AIDND_GUEST_RETENTION_DAYS (default 5), once at startup and then every few hours. Startup is the load-bearing trigger — the free tier sleeps after ~15 minutes, so a long timer rarely gets to fire. Idle is COALESCE(last_seen_at, created_at), not last_seen_at: _touch only writes that column hourly, and a guest minted by /auth/me has it NULL until its second request, so the simpler query would have deleted brand-new visitors mid-session. It's one Core DELETE rather than db.delete(user), which would SELECT every adventure, action and memory into Python purely to delete them — the same egress pattern as the 189x fix. Every FK from users down is ON DELETE CASCADE, so the database does the whole graph and returns a count. The filter requires is_guest AND email IS NULL, so registered users (who upgrade in place) and local mode's implicit user are both out of reach, and is_public is output-only so a guest can never own content another user can see. Session cookies have no expiry and can outlive a swept row; that path 401s and the frontend's existing retry re-mints a session. Guests are told: /auth/me serves guest_retention_days and the signup modal states the window, sourced from the server so it can't drift from what is enforced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CYEJKobJ2Re4Dv7qUoSA7
85 lines
4.3 KiB
Bash
85 lines
4.3 KiB
Bash
# Environment variables read by the backend.
|
|
#
|
|
# NOTE: the app reads real environment variables — it does NOT auto-load this
|
|
# file. Set them in your shell, in docker-compose.yml, or in your host's
|
|
# dashboard. This file is documentation (and a template for deploy configs).
|
|
|
|
# Absolute path for the SQLite database file. Parent directory is created if
|
|
# missing. Default when unset: backend/data.db
|
|
# Docker compose sets this to /data/data.db (a named volume).
|
|
AIDND_DB_PATH=
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Phase 9 — production hardening
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Switch from SQLite to a server database (hosted deploys use Neon Postgres).
|
|
# Any SQLAlchemy URL; postgres:// and postgresql:// schemes are rewritten to
|
|
# the psycopg3 driver automatically. The platform-conventional DATABASE_URL
|
|
# is honored too (AIDND_DATABASE_URL wins if both are set). Unset = SQLite.
|
|
AIDND_DATABASE_URL=
|
|
|
|
# Comma-separated list of allowed CORS origins. Only needed when the frontend
|
|
# is served from a different origin than the API; the production build is
|
|
# served same-origin by FastAPI, so hosted deploys can leave this unset.
|
|
# Default: http://localhost:5173,http://127.0.0.1:5173 (the Vite dev server).
|
|
AIDND_CORS_ORIGINS=
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Phase 8 — optional accounts & multi-user (all optional; defaults keep the
|
|
# app in frictionless single-user "local mode")
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# "1"/"true" turns on multi-user mode: guest sessions via signed cookies,
|
|
# register/login UI, per-user data. Leave unset for local installs.
|
|
AIDND_MULTI_USER=
|
|
|
|
# Secret for signing session cookies and encrypting stored API keys at rest.
|
|
# If unset in local mode, one is auto-generated into `secret.key` next to the
|
|
# database (fine for local/docker-volume runs). REQUIRED when
|
|
# AIDND_MULTI_USER is on — the app refuses to start without it, because a
|
|
# regenerated secret on an ephemeral hosted filesystem would log out every
|
|
# user on each deploy. Generate one:
|
|
# python -c "import secrets; print(secrets.token_urlsafe(48))"
|
|
AIDND_SECRET_KEY=
|
|
|
|
# Session cookie Secure flag (HTTPS-only). Defaults to on when
|
|
# AIDND_MULTI_USER is on, off otherwise — set 0/1 only to override (e.g. 0
|
|
# when testing multi-user mode over plain http on a LAN address).
|
|
AIDND_COOKIE_SECURE=
|
|
|
|
# --- Shared demo key (BYOK fallback; only active when AIDND_MULTI_USER=1) ---
|
|
# Users with no API key of their own get this server-funded endpoint with a
|
|
# model whitelist and a per-day turn cap. Unset = no demo, users must bring
|
|
# their own key. Memory bank/auto-summarization are disabled on demo turns.
|
|
AIDND_DEMO_API_KEY=
|
|
# Default endpoint if unset: https://openrouter.ai/api/v1
|
|
AIDND_DEMO_ENDPOINT_URL=
|
|
# Comma-separated model whitelist. Default: google/gemma-4-26b-a4b-it:free
|
|
AIDND_DEMO_MODELS=
|
|
# Successful AI turns per user per day on the demo key. Default: 20
|
|
AIDND_DEMO_TURNS_PER_DAY=
|
|
# Comma-separated emails of "power users" (trusted testers) who bypass the daily
|
|
# demo cap entirely — unmetered turns on the shared demo key — and get the AI Chat
|
|
# page (a plain scratchpad for talking to a model, hidden from everyone else).
|
|
# Registered accounts only (guests have no email). Matched case-insensitively.
|
|
# Local (single-user) installs are always treated as power users.
|
|
AIDND_POWER_USERS=
|
|
|
|
# --- Guest retention (only active when AIDND_MULTI_USER=1) ---
|
|
# Every first visit mints a guest account, so a public demo collects one row
|
|
# per visitor. A guest with no activity for this many days is deleted along
|
|
# with its scenarios, adventures and actions. Registered accounts are never
|
|
# touched. Default: 5. Set 0 to keep guests forever.
|
|
AIDND_GUEST_RETENTION_DAYS=
|
|
# How often a running process re-checks. The sweep also runs once at startup,
|
|
# which is what actually fires on hosts that sleep. Default: 6
|
|
AIDND_CLEANUP_INTERVAL_HOURS=
|
|
|
|
# The AI endpoint/API key/model are NOT env vars — they are configured at
|
|
# runtime in the app's Settings page and stored (encrypted) in the database.
|
|
#
|
|
# Rate limits, request size limits, and per-user row caps are hardcoded with
|
|
# generous values (see backend/app/limits.py) and active only in multi-user
|
|
# mode — local installs are never throttled.
|