Apply configurable-demo-persona: per-instance demo identity + voice

Adds DEMO_BOT_PERSONA env var (inline JSON or file:<path>) so
self-hosted instances can replace Bruno-in-Berlin with their own
demo account identity and content pools. No-op for trails.cool — the
built-in Bruno persona remains the default.

- DemoPersona type + Zod schema validation
- loadPersona() cached at boot; falls back to default on any failure
- ensureDemoUser throws DemoPersonaUsernameClashError when the
  persona username is already a real user; server declines to
  schedule demo jobs for that process
- isDemoUser flag moved from hardcoded "bruno" check to loader-supplied
  boolean computed from the running persona
- docs/demo-persona.md explains the schema + operator flow

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-19 10:30:32 +02:00
parent f447852d70
commit fc4485f6ef
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
15 changed files with 877 additions and 113 deletions

View file

@ -7,10 +7,15 @@ JWT_SECRET=change-me
S3_ACCESS_KEY=
S3_SECRET_KEY=
# Demo-activity-bot (Bruno). Only enable in prod. When true, the journal
# worker bootstraps a `bruno` user and generates public demo routes +
# activities every 90 min. Set DEMO_BOT_REGION to override the default
# Berlin bbox — JSON {"bbox":[w,s,e,n]}. Retention is in days (default 14).
# Demo-activity-bot. Only enable in prod. When true, the journal worker
# bootstraps a demo user and generates public demo routes + activities
# every 90 min. Retention is in days (default 14).
# DEMO_BOT_ENABLED=true
# DEMO_BOT_RETENTION_DAYS=14
# DEMO_BOT_REGION={"bbox":[13.25,52.45,13.55,52.60]}
#
# Override the default Bruno persona (identity + generated copy) via
# either an inline JSON blob or a file path. See docs/demo-persona.md
# for the schema.
# DEMO_BOT_PERSONA=file:/etc/trails-cool/persona.json
# DEMO_BOT_PERSONA={"username":"hamish","displayName":"Hamish","bio":"...","locales":["en"],"content":{"names":{"en":["...","...","..."]},"descriptions":{"en":["...","...","..."]}}}

View file

@ -34,13 +34,13 @@ services:
WAHOO_CLIENT_ID: ${WAHOO_CLIENT_ID:-}
WAHOO_CLIENT_SECRET: ${WAHOO_CLIENT_SECRET:-}
WAHOO_WEBHOOK_TOKEN: ${WAHOO_WEBHOOK_TOKEN:-}
# Demo-activity-bot (Bruno). Disabled by default everywhere; flip to
# "true" only in prod to let the background worker generate public
# demo routes + activities and prune old ones. Region is a JSON blob
# `{"bbox":[w,s,e,n]}`; retention is in days (default 14).
# Demo-activity-bot. Disabled by default everywhere; flip to "true"
# only in prod. When unset, DEMO_BOT_PERSONA uses the built-in
# Bruno/Berlin persona. See docs/demo-persona.md for the schema.
DEMO_BOT_ENABLED: ${DEMO_BOT_ENABLED:-}
DEMO_BOT_RETENTION_DAYS: ${DEMO_BOT_RETENTION_DAYS:-}
DEMO_BOT_REGION: ${DEMO_BOT_REGION:-}
DEMO_BOT_PERSONA: ${DEMO_BOT_PERSONA:-}
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:3000/api/health || exit 1"]
interval: 15s