MIKODES VOX docs
v2.0.0
Live demo Get help
● Start · About 30 minutes

Installation
three ways in.

Docker on a Linux server, the installer script with systemd, or a virtual environment by hand. Then the single-user first run, or SaaS mode. A reference of every command closes the page.

01Choose a way

WayWhenStatus
Docker — docker compose up -dA server, the fastest startVerified build, start, health check, onboarding and check in the container
deploy/install.sh + systemdA server without Docker, or your own Linux computer (PC actions then work on your files)Installer verified in a clean Debian container; units passed systemd-analyze verify but Unverified on a real server
By hand (venv + pip)DevelopmentSame steps as the installer

02Docker

One image with Python 3.11 and Node 20, no key inside. It runs as user vox (uid 10001) with a read-only root file system; only app/data (the named volume vox-data) is writable.

cp deploy/vox.env.example deploy/vox.env && chmod 600 deploy/vox.env   # optional: keys can also go in the console
docker compose up -d --build
docker compose logs vox | grep "VOX admin"           # console URL with its token
docker compose run --rm vox node src/run.js           # the 12 onboarding questions
docker compose run --rm vox python -m voice check     # what is missing, no network, no key
Set VOX_ADMIN_TOKEN

Without a fixed VOX_ADMIN_TOKEN (16+ characters) in deploy/vox.env, a new random console token is printed to the container log at every start, where anyone who can read Docker logs sees it.

Other ports

VOX_ADMIN_PORT=9766 VOX_MEDIA_PORT=9765 docker compose up -d

These are Compose variables (defaults 8766 and 8765), not VOX settings. The health check moves with them.

What does not work in Docker

  • PC actions and desktop clean-up work on the container's files, not on the user's computer. For that, use the user unit (below).
  • MCP servers over stdio (uvx, npx): those tools are not in the image. Use HTTP MCP servers or extend the image.
  • The spoken terminal briefing (say is macOS only). It is printed.

SaaS mode in Docker

The shipped compose file runs single-user mode. For SaaS, override the command and the health check, for example in docker-compose.override.yml:

services:
  vox:
    command: ["python", "-m", "voice", "saas", "--host", "127.0.0.1", "--port", "8080",
              "--serve-host", "127.0.0.1", "--serve-port", "8765"]
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request as u; u.build_opener(u.ProxyHandler({})).open('http://127.0.0.1:8080/api/csrf', timeout=4)"]
docker compose run --rm vox python -m voice saas migrate
docker compose run --rm vox python -m voice saas create-operator --email you@example.com --role owner
Unverified

This SaaS override is derived from the code; it has not been run as a container.

03Installer script (no Docker)

deploy/install.sh                 # options: --python python3.11 · --dev (also installs the test tools)

It checks Python ≥ 3.11 and Node ≥ 20, installs @anthropic-ai/sdk from the lock file (npm ci --omit=dev --ignore-scripts), creates app/.venv, installs app/voice/requirements.txt, installs and verifies NLTK punkt_tab, runs python -m voice check and prints the next steps. It never asks for, reads or stores a key. Running it again is safe.

04By hand

cd app
python3 -m venv .venv
. .venv/bin/activate
pip install -r voice/requirements.txt
python -m voice setup-nltk                   # sentence-splitter data; without it the bot is silent
python -m voice check

setup-nltk options: --dir D (install elsewhere, then set NLTK_DATA to that directory), --from-zip Z (offline, from a zip downloaded elsewhere; the zip is pinned by SHA-256), --force.

Tests (offline, no keys): pip install -r voice/requirements-dev.txt && python -m pytest -q.

05Service units (systemd)

UnitWhen
deploy/systemd/mikodes-vox.serviceA server: system user vox, code in /opt/mikodes-vox, keys in /etc/mikodes-vox/vox.env (0640) or in the console, hardened (ProtectSystem=strict, writes only app/data).
deploy/systemd/mikodes-vox.user.serviceYour own Linux computer with systemctl --user: runs as you, so PC actions work on your files.

The install procedure is in the header of each file. Both run python -m voice up and send SIGINT on stop, which up treats like Ctrl+C. For SaaS mode change ExecStart to python -m voice saas --host 127.0.0.1 --port 8080 --serve-host 127.0.0.1 --serve-port 8765. The console URL with its token is in journalctl (not printed when VOX_ADMIN_TOKEN is set).

06First run: single-user

  1. Answer the 12 questionsnode src/run.js from app/. Options: --answers file.json (scripted), --no-speak. This step never calls anyone.
  2. Start everythingpython -m voice up. It prints one line, VOX admin http://127.0.0.1:8766/#token=…, once the console really listens.
  3. Enter the keysOpen that URL, go to Setup and enter the keys (Configuration). The media server starts only when all seven required keys and the NLTK data are present, so restart up after entering them.
  4. Verify your own numberProfile → Verify number (how). Until then even a call to yourself is treated as a third-party call.
  5. Place the first callpython -m voice dial --purpose "morning briefing", or the Dial section of the console.

07First run: SaaS mode

cd app && . .venv/bin/activate
python -m voice saas migrate                                   # create / upgrade data/saas/vox.db
python -m voice saas create-operator --email you@example.com --role owner
python -m voice saas --host 127.0.0.1 --port 8080 --serve-host 127.0.0.1 --serve-port 8765

One process serves the public site (/), each customer's console (/app/) and the operator panel (/op/) on port 8080, runs the multi-tenant worker, and starts the media server on port 8765 when every required key is present. Ctrl+C stops everything. Put a TLS proxy in front (Deployment) and continue with Operator panel.

Option of python -m voice saasDefaultMeaning
--host127.0.0.1address of the web app; publish it through the proxy
--port8080port of the web app
--insecure-httpoffcookies without the Secure flag and no HSTS. Only for development on your own machine.
--no-worker / --no-serveoffdo not run the job worker / never start the media server
--serve-host / --serve-port0.0.0.0 / 8765media server address; use 127.0.0.1 behind a proxy
--interval / --mail-every5 s / 5 minworker round and mail check interval
--data-dirapp/datathe data directory (also for saas migrate and saas create-operator); VOX_DATA_DIR does the same

The database is <data>/saas/vox.db (0600) and each customer lives in <data>/tenants/<tenant_id>/. Do not run up and saas on the same data directory at the same time.

08Command reference

CommandWhat it doesExit codes
python -m voice checkprofile, own number, rings, disclosure sentence, barge-in settings and every missing key, without network0 all set · 2 something missing
python -m voice serve [--host] [--port 8765]media server only2 missing keys
python -m voice dial --purpose "…" [--to +…] [--ring self|third_party|cold] [--lang en|sk]places a call; without --to it calls the number in the profile0 accepted · 1 refused by the provider · 2 keys / bad number / cold
python -m voice admin [--host 127.0.0.1] [--port 8766]console only—
python -m voice worker [--once] [--interval 5] [--mail-every 5]job worker only0
python -m voice up [--no-serve] [--port 8766] [--admin-host] [--serve-host] [--serve-port] …single-user mode: console + worker (+ media server)0 Ctrl+C · 1 a service stopped · 2 bad admin token
python -m voice saas [options]SaaS modeas up
python -m voice saas migrateapplies every migration in app/voice/saas/migrations/ in name order0 · 2 migration error
python -m voice saas create-operator --email E [--name N] [--role owner|staff] [--password-stdin]creates an operator account for /op/; the password is typed twice (or piped), never an argument, never printed0 · 2
python -m voice mail-auth [--no-browser]single-user: connect Gmail once (read-only)2 Google libraries missing
python -m voice calendar-auth [--allow-create] [--no-browser]single-user: connect Google Calendar; read-only unless --allow-create2 Google libraries missing
python -m voice setup-nltk [--dir D] [--from-zip Z] [--force]installs NLTK punkt_tab—
python -m voice local-check [--lang en|sk]Piper speaks a test sentence, whisper.cpp transcribes it back (only the local engines you chose)0 works / none chosen · 2 an engine fails
python -m voice wakeword-test FILE.wavruns your own openWakeWord model over a 16 kHz mono recording0 ran · 2 off / missing

serve, admin, worker, up and saas also take --data-dir PATH. --help works on every command.