Files
agents-marketplace/deploy/simulation/README.md
jelisaveta-talbot 3f3bc72680 deploy: add apex-simulation host stack (Traefik + PiperTTS + voice extras)
Stand up a second deploy target on apex-simulation dedicated to
"simulation"-style supplier capabilities, starting with CPU-only PiperTTS
served via openedai-speech-min behind Traefik on :80.

- deploy/simulation/docker-compose.traefik.yml: Traefik latest with
  introspection API, settles on Docker daemon 29.x API negotiation
- deploy/simulation/docker-compose.tts-piper.yml: openedai-speech-min
  with curl-based healthcheck (image lacks wget, which Docker would have
  marked unhealthy and Traefik would filter from the docker provider)
- deploy/simulation/deploy.sh: rsync-based deploy mirroring the
  apex-dashboard pattern; selective --traefik-only / --tts-only flags
- deploy/simulation/install-voice-extras.sh + voice-mappings.py:
  idempotent post-deploy step that downloads en_US-lessac-high voice
  files into the bind-mount and registers a "lessac" alias in
  voice_to_speaker.yaml; survives a host wipe without manual steps
- deploy/simulation/README.md: full host runbook
- .gitignore: exclude data/ recursively, *.log, .vitest-cache, .DS_Store,
  __pycache__/, *.pyc, docs/*.zip
2026-05-06 18:59:20 +02:00

5.4 KiB
Raw Permalink Blame History

apex-simulation host runbook

A second deploy target dedicated to "simulation"-style supplier capabilities (starting with CPU-only PiperTTS as audio.synthesize.v1). Independent of apex-dashboard so heavy local models can come and go without affecting the marketplace's chain-follower / buyer-app.

Layout

deploy/simulation/
├── docker-compose.traefik.yml      # Standalone Traefik on :80
├── docker-compose.tts-piper.yml    # PiperTTS behind OpenAI-compat shim
├── deploy.sh                       # rsync from laptop → apex-simulation, compose up
└── data/                           # bind-mount target (created on host; gitignored)
    └── tts-piper/{voices,config}/  # downloaded Piper voices persist here

Prereqs

On the laptop: SSH alias apex-simulation resolving to the new host (see ~/.ssh/config). rsync available locally.

On apex-simulation (one-shot):

# As root (or via sudo) — create the deploy user:
adduser --disabled-password --gecos "" deploy
usermod -aG docker deploy
sudo -u deploy mkdir -m 700 /home/deploy/.ssh
# Paste your laptop's public key:
sudo -u deploy tee /home/deploy/.ssh/authorized_keys >/dev/null <<'EOF'
ssh-ed25519 AAAA…  your-laptop-key
EOF
sudo -u deploy chmod 600 /home/deploy/.ssh/authorized_keys

# Optional but recommended — disable password SSH:
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl reload sshd

Cloudflare DNS

Add an A record tts-piper.vector.testnet.apexfusion.org → apex-simulation public IP. Orange cloud (proxied) is fine — CF terminates TLS, forwards HTTP to Traefik on :80.

If CF later flags the path with a 502, mirror the existing Skip mp-buyers on testnet rule for tts-piper.… (see deploy/CLOUDFLARE_WAF_BYPASS.md). TTS bodies are short text so this is unlikely, but the runbook is there if needed.

Deploy

From the laptop, repo root:

# First time (and every subsequent change):
bash deploy/simulation/deploy.sh

deploy.sh does:

  1. mkdir -p the bind-mount target dirs on the remote.
  2. rsync only deploy/simulation/* to /home/deploy/marketplace-simulation/deploy/simulation/ on apex-simulation. The voices dir (data/) is excluded — Piper downloads voices on first run.
  3. docker network create traefik-net (idempotent).
  4. docker compose ... up -d for Traefik then TTS.
  5. Print container status + last 20 lines of TTS logs.

Selective re-deploys:

bash deploy/simulation/deploy.sh --traefik-only
bash deploy/simulation/deploy.sh --tts-only

Voice quality

The compose file defaults to PRELOAD_MODEL=en_US-lessac-high — Piper's highest-quality American-English single-speaker voice (~60 MB). Other top-tier options:

Model Description Size
en_US-lessac-high Female, very natural, default 60 MB
en_GB-alan-high British male 65 MB
en_US-ryan-high American male 60 MB
en_US-libritts_r-medium Multi-speaker (904 voices) 75 MB

Browse the rest at https://huggingface.co/rhasspy/piper-voices/tree/main. To switch, edit PRELOAD_MODEL in docker-compose.tts-piper.yml, then:

bash deploy/simulation/deploy.sh --tts-only

Verify TTS end-to-end

From any laptop that resolves the CF hostname:

curl -s -X POST https://tts-piper.vector.testnet.apexfusion.org/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tts-1-hd",
    "input": "Hello from the local agents marketplace. PiperTTS, served via openedai-speech, signed via the future bonded escrow.",
    "voice": "alloy",
    "response_format": "wav"
  }' \
  -o /tmp/out.wav

aplay /tmp/out.wav     # or `mpv /tmp/out.wav` / `paplay /tmp/out.wav`

The OpenAI shim maps the six standard voice names (alloy, echo, fable, onyx, nova, shimmer) onto the bundled Piper voices. The model field is informational — Piper has no model picker beyond voice.

Health probes

# Traefik (returns 404 with no host = alive)
curl -i http://<server-ip>:80

# TTS service via Traefik
curl -s https://tts-piper.vector.testnet.apexfusion.org/v1/models | jq .

Wiring as a marketplace supplier (later)

Once the audio is sound, this becomes a second supplier deployment:

  1. Add a marketplace-supplier-tts compose on apex-simulation (clone of deploy/testnet/docker-compose.supplier.yml, swap OLLAMA_URL → http://marketplace-tts-piper:8000, advertise capability audio.synthesize.v1).
  2. New wallet (SUPPLIER_PRIV_KEY_HEX) so the bond is independent of the chat supplier.
  3. pnpm tx:post-advert against this host's wallet to publish the capability. The buyer Dashboard then sees two suppliers — one for llm.text.generate.v1, one for audio.synthesize.v1.

Out of scope here — file under a future UX-supplier-tts task.

Troubleshooting

Symptom Likely cause Fix
404 on the CF hostname Traefik up but no router matches Container labels missed; bash deploy.sh --tts-only
502 on first call Cold start downloading the Piper voice (30–90 s) Wait & retry, or pre-warm with a short curl
Audio file is 0 bytes Voice model failed to download (no outbound network from host?) ssh apex-simulation 'docker logs marketplace-tts-piper' — look for huggingface fetch errors
Want a different voice Change PRELOAD_MODEL in docker-compose.tts-piper.yml bash deploy/simulation/deploy.sh --tts-only