Historical bootstrap prompt. Describes how this infra was originally stood up. Updated after the 2026-05-19 cutover (#36) which retired the separate ZeroClaw/RPi host and consolidated all services onto a single Docker host. The firmware-provisioning steps still require the build-from-source flow in
SETUP.md.
Paste this into your terminal:
claude --prompt-file ./session-prompt.md
I need you to set up infrastructure on a single Linux Docker host for an M5Stack StackChan robot. You'll be SSHing from this workstation to the target via Tailscale. Read the CLAUDE.md in this directory first for full architecture context.
A self-hosted voice pipeline with four Docker containers on one host:
- xiaozhi-esp32-server — ASR (FunASR SenseVoiceSmall) + TTS (Piper/EdgeTTS) + WebSocket voice gateway for StackChan.
- dotty-pi — the pi coding agent: the voice-tool brain. Runs
qwen3.5:4bon a local llama-swap instance; invoked viadocker exec -iby thePiVoiceLLMprovider. Seedotty-pi/README.md. - dotty-behaviour — perception event bus, ambient consumers, greeter, calendar. FastAPI on
:8090. Seedotty-behaviour/README.mdandscripts/deploy-behaviour.sh. - bridge.py (admin dashboard) — HTMX admin UI on
:8080. Runs as a container on the same host.
All four containers run on the same machine (the Docker host). There is no separate brain host or RPi.
- Run
tailscale status(if you use Tailscale) to find the hostname and IP for the Docker host. - SSH into the Docker host. Find its LAN IP (not Tailscale IP) — StackChan will need this because it's on WiFi, not Tailnet. Check
ip addrorhostname -I. Also confirm Docker is available and pick a directory for the install (e.g./opt/xiaozhi-server/or/srv/xiaozhi-server/). - Check whether a local model backend is already running — llama-swap at
:8080/health(or Ollama at:11434/api/tags). If not, set up Ollama as the simpler single-binary option (seecookbook/run-fully-local.md) or llama-swap if you need concurrent voice+coding model sets.
On the Docker host:
- Create the directory structure at your chosen install path (e.g.
/opt/xiaozhi-server/) with subdirs:data/,models/SenseVoiceSmall/,tmp/. - Clone this repo (
dotty-stackchan) into the install path or copy the relevant files. Themake setupwizard handles most substitution. - Download the SenseVoiceSmall ASR model (
model.pt, ~250 MB) intomodels/SenseVoiceSmall/. Try ModelScope first:https://www.modelscope.cn/models/iic/SenseVoiceSmall/resolve/master/model.pt. If that's slow, use HuggingFace:https://huggingface.co/FunAudioLLM/SenseVoiceSmall/resolve/main/model.pt. Verify the file is >200 MB after download. - Create
data/.config.yamlwith:selected_module.ASR: FunASRLocalselected_module.LLM: PiVoiceLLMselected_module.TTS: LocalPiper(orEdgeTTSif you don't want offline TTS)selected_module.VAD: SileroVAD- PiVoiceLLM container name:
dotty-pi(thedocker exectarget) - A system prompt that identifies as a desktop robot assistant. Enforce emoji-first responses. Keep TTS-friendly (short sentences).
- VAD silence duration 700 ms (so it doesn't cut off slow speakers)
- Use the actual LAN IP for
<XIAOZHI_HOST>, not a placeholder.
- Bring up the xiaozhi-esp32-server container via
docker compose up -d(fromdocker-compose.yml.templateaftermake setupsubstitutes your placeholders). - Check the container's internal directory structure before writing volume mounts. Run
docker run --rm ghcr.io/xinnan-tech/xiaozhi-esp32-server:server_latest ls /opt/xiaozhi-server/(or wherever the app lives) to confirm internal paths. The mount targets must match where the app actually loads providers from. - Tail the logs and confirm you see the WebSocket and OTA addresses in the output.
Follow dotty-pi/README.md for build and run instructions. Key points:
- The container idles via
sleep infinity; voice turns invoke pi on demand viadocker exec -i. - Model target for the outer agent loop:
qwen3.5:4b(in the llama-swapvoicematrix set). Do not useqwen3.6:27bhere — it evicts the voice model set and causes cold-reload latency on everythink_hardescalation. - Mount
persona/,memory/brain.db, and thedotty-pi-extextension directory as documented indotty-pi/README.md.
Follow dotty-behaviour/README.md for build and run instructions. The scripts/deploy-behaviour.sh helper handles the deploy step. Key points:
- Runs in
network_mode: hoston port:8090. - xiaozhi-server talks to it on
http://<XIAOZHI_HOST>:8090(the LAN IP, not loopback — xiaozhi-server is on bridge networking so its loopback resolves to itself). - Set
VISION_BRIDGE_URLenv var in the xiaozhi-server compose tohttp://<XIAOZHI_HOST>:8090.
The bridge.py dashboard container starts as part of the main compose stack. It serves the HTMX admin UI on :8080/ui and a health endpoint at :8080/health. No separate deployment step beyond docker compose up -d.
After all containers are up:
- Check the OTA endpoint from a LAN machine:
curl -s http://<XIAOZHI_HOST>:8003/xiaozhi/ota/ # Expect: OTA接口运行正常...
- Check the dashboard health:
curl -s http://<XIAOZHI_HOST>:8080/health # Expect: {"status":"ok", ...}
- Check dotty-behaviour health:
curl -s http://<XIAOZHI_HOST>:8090/health # Expect: {"status":"ok", ...}
- Tail the xiaozhi-server logs to confirm the WebSocket endpoint is listening.
When everything is confirmed working, print a clear summary:
=== STACKCHAN SETUP COMPLETE ===
OTA URL (enter this in StackChan's Advanced Settings):
http://X.X.X.X:8003/xiaozhi/ota/
WebSocket endpoint:
ws://X.X.X.X:8000/xiaozhi/v1/
Admin dashboard:
http://X.X.X.X:8080/ui
dotty-behaviour (perception bus):
http://X.X.X.X:8090/health
When StackChan arrives:
1. Flash open firmware built from https://github.com/m5stack/StackChan
(see SETUP.md for the current build/flash flow — stock firmware
ships with BLE+cloud provisioning, not the SoftAP flow that some
older guides describe).
2. First boot: device POSTs to the OTA URL, gets redirected to the
WebSocket endpoint, and connects automatically.
- Use
microif you need to interactively edit files (not nano, not vim). - Don't install anything on the local workstation — everything happens via SSH to the remote machine.
- All IPs in config files must be real LAN IPs discovered at runtime, not Tailscale IPs (StackChan isn't on the Tailnet).
- If any step fails, diagnose from the logs before retrying. Don't just re-run blindly.
- The xiaozhi-esp32-server Docker image's internal directory structure may differ from the repo layout. Inspect the container before writing volume mounts.
- Be conservative about commands not documented in the component READMEs — link to
dotty-pi/README.mdanddotty-behaviour/README.mdrather than inventing invocations.