Car Voice for Hermes

Install the bridge by hand

What backend/install.py does, step by step, for when you want to do it yourself or adapt it (a machine without systemd, other paths). The paths and the unit are the installer’s, so you can run the installer later to update; it keeps your bridge.env, unit and tokens. The examples use voice.example.com for the bridge’s public address; use your own.

  1. Switch on the Hermes API server
  2. Install the bridge
  3. Configure the bridge
  4. Run the bridge
  5. Make the bridge reachable over HTTPS
  6. Pair your phone
  7. Updating

1. Switch on the Hermes API server

As in setup step 1: API_SERVER_ENABLED=true and a long random API_SERVER_KEY in ~/.hermes/.env, then restart hermes gateway.

2. Install the bridge

The bridge needs Python 3.11 or newer (and, on Debian and Ubuntu, the python3-venv package).

sudo git clone https://github.com/Vantix-Wealth-Labs/hermes-car-voice.git /opt/hermes-car-voice
cd /opt/hermes-car-voice
sudo python3 -m venv .venv
sudo .venv/bin/pip install -r backend/requirements.lock
sudo useradd --system --home-dir /var/lib/hermes-car-voice --shell /usr/sbin/nologin carvoice

requirements.lock pins every package. It includes faster-whisper for speech recognition on the machine itself; the first start downloads the Whisper model (a few hundred MB).

3. Configure the bridge

Create /etc/hermes-car-voice/bridge.env, readable only by root:

sudo mkdir -p /etc/hermes-car-voice
sudo install -m 600 /dev/null /etc/hermes-car-voice/bridge.env
sudoedit /etc/hermes-car-voice/bridge.env

with:

# Where the bridge listens. A tunnel or proxy on the same machine forwards to it.
HERMES_BRIDGE_HOST=127.0.0.1
HERMES_BRIDGE_PORT=8787
HERMES_BRIDGE_DATA_DIR=/var/lib/hermes-car-voice
# Access tokens for phones; `pair` (step 6) adds them.
HERMES_BRIDGE_TOKENS_FILE=/var/lib/hermes-car-voice/tokens.json

# Hermes API server from step 1.
HERMES_API_BASE_URL=http://127.0.0.1:8642
HERMES_API_ALLOW_INTERNAL_HTTP=true
HERMES_API_KEY=<the API_SERVER_KEY from step 1>

# Language for older app versions that do not send one. The app sends the
# phone's language with every question.
HERMES_BRIDGE_LANGUAGE=en

# Speech recognition: local Whisper on this machine ...
HERMES_BRIDGE_STT_PROVIDER=local
HERMES_BRIDGE_STT_MODEL=small
# ... or any OpenAI-compatible transcription service (much faster on a small CPU).
# Your recorded questions then go to that service.
# HERMES_BRIDGE_STT_PROVIDER=openai
# HERMES_BRIDGE_STT_API_BASE=https://api.groq.com/openai/v1
# HERMES_BRIDGE_STT_API_KEY=<key>
# HERMES_BRIDGE_STT_API_MODEL=whisper-large-v3-turbo

This is the file the installer writes. systemd reads it (EnvironmentFile=); do not load it into a shell with source, which breaks JSON values. Optional settings:

Variable Default What it does
HERMES_BRIDGE_TTS_VOICE_<LANG> built in for en, nl, de, fr, es, it The Edge TTS voice per language, e.g. HERMES_BRIDGE_TTS_VOICE_EN=en-GB-SoniaNeural. Other languages use the English voice unless you set one.
HERMES_BRIDGE_SPEECH_MAX_CHARS_SHORT 700 Safety net on the length of a short spoken reply.
HERMES_BRIDGE_SPEECH_MAX_CHARS 3000 Safety net on a full spoken reply. Only a longer reply is cut, at a sentence, and only then does the voice say the full text is in the app.
HERMES_VOICE_INSTRUCTIONS / HERMES_VOICE_INSTRUCTIONS_FULL built in Replace the instruction the bridge gives Hermes for short or full spoken replies.
HERMES_API_MODEL hermes-agent Sent as the run’s model. Leave it unless you need another model; see “Per-request model selection” in the Hermes API server documentation.
TELEGRAM_BOT_TOKEN, TELEGRAM_DESTINATIONS off Optional: send the full text of long replies to a Telegram chat. TELEGRAM_DESTINATIONS maps a phone name from the tokens file to a chat id, e.g. {"phone":"123456789"}.

After a change: sudo systemctl restart hermes-car-voice-bridge.

4. Run the bridge

Create /etc/systemd/system/hermes-car-voice-bridge.service (the installer writes the same unit):

[Unit]
Description=Car Voice bridge
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=carvoice
StateDirectory=hermes-car-voice
WorkingDirectory=/opt/hermes-car-voice/backend
EnvironmentFile=/etc/hermes-car-voice/bridge.env
ExecStart=/opt/hermes-car-voice/.venv/bin/python -m hermes_voice_bridge serve
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now hermes-car-voice-bridge
curl -s http://127.0.0.1:8787/readyz

/readyz answers {"status":"ready","hermes":"ok","stt":"ready"} once Hermes answers and the Whisper model is loaded; "stt":"loading" means the model is still downloading or loading. "hermes":"unreachable" means the API server from step 1 is not running, or HERMES_API_BASE_URL is wrong. /readyz does not check the key: a wrong key shows up on the first question, as “Hermes rejected the bridge key.” in the app. Logs: journalctl -u hermes-car-voice-bridge. They never contain tokens, audio or what you said.

To check the configuration without starting the bridge:

sudo systemd-run --pipe --wait --quiet -p EnvironmentFile=/etc/hermes-car-voice/bridge.env -p User=carvoice \
  -p WorkingDirectory=/opt/hermes-car-voice/backend \
  /opt/hermes-car-voice/.venv/bin/python -m hermes_voice_bridge check

5. Make the bridge reachable over HTTPS

See setup step 3. With Tailscale, run on the bridge machine:

sudo tailscale serve --bg --https=443 http://127.0.0.1:8787

6. Pair your phone

On the bridge machine, with the bridge’s public address from step 5:

sudo systemd-run --pipe --wait --quiet -p EnvironmentFile=/etc/hermes-car-voice/bridge.env -p User=carvoice \
  -p WorkingDirectory=/opt/hermes-car-voice/backend \
  /opt/hermes-car-voice/.venv/bin/python -m hermes_voice_bridge pair --origin https://voice.example.com --name phone

pair creates a random access token, adds it to the tokens file (mode 0600) and prints a QR code plus the address and token as text. The running bridge picks up the new token by itself. Use --replace to renew the token of an existing name; the old token then stops working. Treat the token like a password. sudo python3 /opt/hermes-car-voice/backend/install.py --pair --origin https://voice.example.com runs the same command. Then scan the code in the app as in setup step 4.

7. Updating

cd /opt/hermes-car-voice && sudo git pull
sudo .venv/bin/pip install -r backend/requirements.lock
sudo systemctl restart hermes-car-voice-bridge

Or, the same in one step: cd /opt/hermes-car-voice && sudo git pull && sudo python3 backend/install.py. Hosts without a git checkout can update from a bundle with backend/deploy/upgrade_bridge.sh; see the operator runbook.