The image's own petal user (uid 10001) has no claim on a bind-mounted host directory, so SQLite came up with "unable to open database file (14)" and the container restart-looped. Run as the stack directory's owner instead of chowning ./data to 10001 -- the backup script gzips snapshots in place from the host, so that account needs write access to the same directory. Still non-root.
Deploying read-aloud (Piper TTS) to millenia
Petal's read-aloud generates audio with a local Piper neural-TTS server and
transcodes it to mp3 with ffmpeg. Both run on millenia (192.168.1.212);
nothing leaves the box. If Piper is down or TTS_ENDPOINT is unset, the frontend
falls back to the browser's Web Speech API automatically.
1. Piper as a systemd service (one-time)
# from this repo, on your workstation:
scp deploy/piper.service deploy/setup-piper.sh 192.168.1.212:/tmp/
ssh 192.168.1.212 'cd /tmp && sudo ./setup-piper.sh'
setup-piper.sh creates ~/piper/venv, installs piper-tts[http], downloads the
en_US-amy-medium voice into ~/piper/voices, installs+enables piper.service
(loopback :5005), and smoke-tests it. Idempotent.
Check it any time: systemctl status piper, journalctl -u piper -f.
2. Point petal at Piper + redeploy the binary
Add to petal's environment (its .env or launch env):
TTS_ENDPOINT=http://127.0.0.1:5005
TTS_VOICE_EN=en_US-amy-medium
TTS_AUDIO_FORMAT=mp3
Then ship the rebuilt binary (go build -o petal ./cmd/server already done) and
restart the petal :8088 session. Confirm the log line:
read-aloud enabled (TTS endpoint=http://127.0.0.1:5005).
3. Verify
# on millenia — end-to-end through petal, including ffmpeg transcode:
curl -sf -X POST localhost:8088/api/tts \
-H 'Content-Type: application/json' \
-d '{"text":"hello there","lang":"en-US"}' -o /tmp/petal-tts.mp3 \
&& file /tmp/petal-tts.mp3 # expect: Audio file ... MPEG ... layer III
- Second identical call is served from the cache (
~/petal/.../data/tts/*.mp3). - A language with no configured instance returns 404 → client uses Web Speech.
- Browser check via the uitest harness (
~/petal/uitest): tap a word in the WordCard / select a sentence and hit speak — expect the natural Piper voice.
Chinese voice (live)
Each Piper HTTP server loads ONE model, so Chinese runs as a second instance:
piper-zh.service on :5006 with zh_CN-huayan-medium. Deployed via:
scp deploy/piper-zh.service 192.168.1.212:~/.config/systemd/user/
ssh 192.168.1.212 'export XDG_RUNTIME_DIR=/run/user/$(id -u)
~/piper/venv/bin/python -m piper.download_voices zh_CN-huayan-medium --data-dir ~/piper/voices
systemctl --user daemon-reload && systemctl --user enable --now piper-zh.service'
Then in petal's start.sh: TTS_ENDPOINT_ZH=http://127.0.0.1:5006 and
TTS_VOICE_ZH=zh_CN-huayan-medium. The handler maps language → instance from config,
so adding more languages is just another instance + env pair (no code change).