ShipVoice
Primer / Part A · Appendices

Appendix C: Environment Setup Recipes

Copy-paste recipes for the four most common deployment configurations. Each recipe assumes you have Python 3.11+, uv for package management, and API keys for your chosen providers.

Recipe 1: Local Development with Console Mode

Console mode connects your laptop’s microphone and speakers directly to the agent, bypassing LiveKit and telephony entirely. This is the fastest way to iterate on prompts and tool logic.

# Clone and install
git clone https://github.com/your-org/voiceclaw-agents.git
cd voiceclaw-agents/main-agent
uv sync

# Set environment variables
cp .env.example .env
# Edit .env with your API keys:
#   OPENAI_API_KEY=sk-...
#   DEEPGRAM_API_KEY=...
#   CARTESIA_API_KEY=...

# Run in console mode
uv run python main.py console

Console mode starts the STT-LLM-TTS pipeline using your default audio devices. Speak into your microphone and the agent responds through your speakers. No LiveKit server, no SIP, no phone numbers required.

Tips:

  • Use a headset to avoid echo feedback between speakers and microphone.
  • Console mode does not support multiple concurrent “calls.” It is single-session only.
  • All tool calls execute normally, so make sure your backend is accessible if your tools hit APIs.

Recipe 2: Self-Hosted LiveKit + SIP on Hetzner VPS

For production telephony you need a LiveKit server with SIP support. This recipe uses a single Hetzner VPS (CPX31 or higher: 4 vCPU, 8GB RAM) running LiveKit and the SIP bridge via Docker Compose.

# docker-compose.yml
services:
  livekit:
    image: livekit/livekit-server:latest
    ports:
      - '7880:7880' # HTTP
      - '7881:7881' # WebSocket
      - '7882:7882' # ICE/TCP
      - '50000-50200:50000-50200/udp' # ICE/UDP range
    volumes:
      - ./livekit.yaml:/etc/livekit.yaml
    command: ['--config', '/etc/livekit.yaml']
    restart: unless-stopped

  sip:
    image: livekit/sip:latest
    network_mode: host
    environment:
      - SIP_PORT=5060
      - LIVEKIT_URL=ws://localhost:7880
      - LIVEKIT_API_KEY=${LIVEKIT_API_KEY}
      - LIVEKIT_API_SECRET=${LIVEKIT_API_SECRET}
    restart: unless-stopped
# livekit.yaml
port: 7880
rtc:
  port_range_start: 50000
  port_range_end: 50200
  tcp_port: 7882
  use_external_ip: true
keys:
  your-api-key: your-api-secret

Setup steps:

  1. Provision a Hetzner VPS with Ubuntu 22.04. Ensure ports 5060/UDP (SIP), 7880-7882/TCP, and 50000-50200/UDP are open in the firewall.
  2. Install Docker and Docker Compose.
  3. Copy the files above and set LIVEKIT_API_KEY and LIVEKIT_API_SECRET in a .env file.
  4. Run docker compose up -d.
  5. Point DNS records: livekit.yourdomain.com and sip.yourdomain.com to the VPS IP.
  6. Configure your Telnyx SIP trunk to send calls to sip.yourdomain.com:5060.

SSL: For WebSocket connections, place an Nginx or Caddy reverse proxy in front of LiveKit port 7880 with a TLS certificate.

Recipe 3: LiveKit Cloud

LiveKit Cloud is the managed alternative. No servers to maintain, automatic scaling, and built-in SIP support.

  1. Create an account at cloud.livekit.io.
  2. Create a project. Note your API key, API secret, and WebSocket URL (e.g., wss://your-project.livekit.cloud).
  3. In the project dashboard, navigate to SIP and create an inbound trunk with your Telnyx SIP credentials.
  4. Create a dispatch rule that routes incoming calls to your agent.
# Set environment variables for your agent
export LIVEKIT_URL=wss://your-project.livekit.cloud
export LIVEKIT_API_KEY=APIxxxxxxx
export LIVEKIT_API_SECRET=xxxxxxxxxxxxxxxxxxxxxxx

# Deploy your agent (it connects outbound to LiveKit Cloud)
uv run python main.py start

With LiveKit Cloud, your agent process connects outbound to the cloud service. There is no inbound port to open. The agent can run anywhere: a laptop, a VPS, a container on Fly.io.

Recipe 4: CI/CD with GitHub Actions

This workflow runs linting, type checking, unit tests, and integration tests on every push and pull request. Integration tests use DeepSeek instead of OpenAI to reduce cost.

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv python install 3.11
      - run: uv sync
        working-directory: main-agent
      - run: uv run ruff check src/
        working-directory: main-agent
      - run: uv run mypy src/
        working-directory: main-agent

  test-unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv python install 3.11
      - run: uv sync
        working-directory: main-agent
      - run: uv run pytest tests/unit/ -q --cov=src --cov-fail-under=80
        working-directory: main-agent

  test-integration:
    runs-on: ubuntu-latest
    needs: [lint, test-unit]
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv python install 3.11
      - run: uv sync
        working-directory: main-agent
      - run: uv run pytest tests/integration/ -v
        working-directory: main-agent
        env:
          DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
          CI: true

  deploy:
    runs-on: ubuntu-latest
    needs: [test-integration]
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: superfly/flyctl-actions/setup-flyctl@master
      - run: flyctl deploy --remote-only
        working-directory: main-agent
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

Key decisions in this pipeline:

  • Lint and unit tests run in parallel for speed. Integration tests wait for both to pass.
  • Integration tests use DEEPSEEK_API_KEY instead of OPENAI_API_KEY. The test fixtures detect the CI environment variable and swap the LLM provider.
  • Deploy only on main: The deploy job only runs when changes are merged to main, not on pull requests.
  • Coverage enforcement: Unit tests fail if coverage drops below 80%.
  • uv for dependency management: Faster than pip, deterministic lockfile, built-in Python version management.

This primer is the theory. ShipVoice is the code: a LiveKit boilerplate with per-minute billing, auth, telephony, and deploy already wired in.

Get founding access