Files
petal/deploy/petal.env.example
T
prosolis 24c3533e18 Give read-aloud a Portuguese voice, and a slower one
Phase 21's infra half. Two things the pt-PT pair needs from TTS, and one
thing every learner has wanted since Phase 11.

**A language is no longer a code change.** The handler knew exactly two
languages, named in the Config struct: English on TTS_ENDPOINT and Chinese
on TTS_ENDPOINT_ZH. Petal now discovers its Piper instances from the
environment — English keeps the unsuffixed pair it has always had, and
every other language is a TTS_ENDPOINT_<LANG>/TTS_VOICE_<LANG> pair — so
fr and es cost a compose service and two lines of .env. <LANG> is the base
tag, because an environment variable name cannot hold pt-PT's hyphen and
only one Portuguese model is loaded either way. A language configured by
halves is dropped rather than routed: half a configuration should reach
the client as "no voice here, use Web Speech", not as an instance that
errors on every tap. The startup line now names the voices it actually
resolved rather than the English endpoint it was handed — the same lesson
the dictionary line learned last week.

**pt_PT-tugão-medium is the only European voice Piper ships.** The other
five pt models in the catalogue are Brazilian, so the default anyone
reaches for is the wrong country — the same trap as `dictionary-pt`
packaging VERO, arriving through the catalogue rather than through the
model. Named explicitly in compose, with the query that checks it in the
deploy README.

**The slow replay** (SUGGESTIONS §5e) is `slow: true` on /api/tts, raising
Piper's length_scale to ~4/3. Piper stretches durations rather than
resampling, so it stays a voice instead of a groan. The pace is part of
the cache key — without it the slow replay of a word already heard at
normal speed would be served back at normal speed, which is the one
request where the difference is the whole point. 🐢 sits beside 🔊 on the
word card, the selection bubble and the garden flashcard; the Web Speech
fallback slows too, so the button means the same thing when Piper is down.

**And the other reading gets her own voice.** The `alsoIn` block — the
Portuguese sense of a word that is also English — now speaks in the pair's
locale, which the pack names (`locale`) rather than anything inferring it
from the letters. "comum" is spelled identically in both halves; a
detector would have to guess, and this is the same reason the gloss shows
both directions instead of picking one.

Tests: config discovery (both existing deployment shapes, half-configured
languages dropped, the pre-map voice defaults preserved), the slow scale
and its separate cache entry, pt routing on the base tag with pt-BR
landing on the European instance, and speech.ts's request body. The i18n
shape suite now asserts every pack names a speakable locale in its own
language — and that pt-PT's is not pt-BR.

Verified: go build/vet/test, tsc, vitest 125/125, vite build. Live smoke
against two fake Piper servers: en/pt × normal/slow all reached the right
instance at the right length_scale with four distinct cache entries, and
an unconfigured language still 404s.
2026-07-27 13:21:45 -07:00

80 lines
3.8 KiB
Bash

# Petal — production environment for the parodia.dev VPS.
# Copy to the stack directory as `.env` (docker-compose.yml reads it via
# env_file) and fill in the model names. Values the image already fixes
# (PORT, DATABASE_PATH, IMAGE_DIR, TTS_CACHE_DIR, TTS endpoints) are set in
# docker-compose.yml, not here.
# --- Routing -----------------------------------------------------------------
# Must match the DNS A record and the Traefik Host() rule.
PETAL_HOST=petal.parodia.dev
# Absolute origin the app knows itself by. Phase 16's OIDC redirect URI is
# built from this, so it has to be the real public HTTPS origin.
BASE_URL=https://petal.parodia.dev
# The companion's bedtime nag and the night theme read the container clock.
TZ=Europe/Lisbon
# The container runs as this uid/gid so it can write the ./data bind mount.
# Set both to the output of `id -u` / `id -g` for the account owning the stack
# directory. Wrong values show up as "unable to open database file (14)".
PETAL_UID=1001
PETAL_GID=1001
# --- Interim edge gate (delete when Phase 16 auth lands) ---------------------
# Petal has no authentication of its own yet — StaticResolver hands every
# request the same local user — so Traefik holds the door with basic auth until
# the OIDC flow exists. user:bcrypt-hash, as produced by:
# htpasswd -nbB petal 'your-password'
# /api/health is deliberately exempt (its own router) so monitoring still works.
PETAL_BASIC_AUTH=
# --- LLM (millenia, over headscale) ------------------------------------------
# The only cross-VPN dependency. Petal degrades warmly when it's unreachable:
# spell check, gloss, garden, search, export and read-aloud all keep working and
# the status bar shows 小助手在休息 · Petal's helper is resting.
#
# 100.64.0.2 is millenia on the headscale network. vLLM must be bound to that
# interface (NOT 0.0.0.0 — this host is public); see deploy/README.md.
LLM_BACKEND=vllm
LLM_ENDPOINT=http://100.64.0.2:8000
LLM_MODEL=
LLM_CHAT_MODEL=
# 30s is the local-network default. Over WAN + VPN, with the voice and
# collocation passes sending a whole document, that truncates real work — the
# request is a hard deadline on Complete, and a timeout surfaces as the same
# warm 502 as an unreachable model. 90s leaves headroom without letting a
# genuinely wedged backend hang the pass forever.
LLM_TIMEOUT=90s
# --- Read-aloud (Piper sidecars) ---------------------------------------------
# Endpoints are wired in docker-compose.yml; these pick the voice each sidecar
# loads. Changing one means recreating that container so it downloads the model.
#
# A language is routable only when both halves are set — a TTS_ENDPOINT_XX with
# no TTS_VOICE_XX reads as "no voice for this language" and the browser's own
# synthesizer takes over, rather than as an instance that errors on every
# request. Adding fr or es is a compose service plus a pair of lines here.
#
# pt_PT-tugão-medium is the only European Portuguese voice Piper ships; every
# other pt model in the catalogue is Brazilian.
TTS_VOICE_EN=en_US-amy-medium
TTS_VOICE_ZH=zh_CN-huayan-medium
TTS_VOICE_PT=pt_PT-tugão-medium
TTS_AUDIO_FORMAT=mp3
TTS_TIMEOUT=15s
# --- Auth (Authentik OIDC) ---------------------------------------------------
# Authentik already runs on this host. Set all three and Petal authenticates
# for itself; leave any unset and it falls back to the single `local` user
# (which on a public host means the Traefik basic-auth gate must stay).
#
# AUTHENTIK_URL is the provider's issuer, and the redirect URI to register in
# Authentik is https://petal.parodia.dev/auth/callback.
# AUTHENTIK_URL=https://auth.parodia.dev/application/o/petal/
# AUTHENTIK_CLIENT_ID=petal
# AUTHENTIK_CLIENT_SECRET=
#
# Who may sign in: comma-separated subject ids and/or emails. Empty = anyone
# Authentik authenticates, which is wider than this instance wants.
# PETAL_ALLOWED_SUBS=