A self-hosted real-time relay server for notifications and voice over WebSocket.
Project description
Chaka
Chaka means "bridge" in Quechua.
Chaka is a self-hosted, real-time relay server for notifications and voice over a single WebSocket. Sender clients publish notification payloads (or stream push-to-talk audio); receiver clients get them in real time. It is a small, dependency-light FastAPI application with a server-rendered admin UI for managing access tokens, channels, and logs.
It is transport-agnostic about what you relay — any JSON notification payload and any binary audio stream. The reference clients are Android apps (a notification forwarder and a receiver), but any WebSocket or HTTP client that follows the protocol works.
Features
- Real-time notification fan-out over WebSocket, with an HTTP
POST /api/notifyalternative for server-side sources. - Missed-message replay — the server tracks
last_delivered_atper token and replays up to 100 missed notifications on reconnect (single batched frame). - Push-to-talk voice — named voice channels carried on the same WebSocket as binary frames, with a per-channel single-transmitter lock, mute, and live peer presence.
- Token-based auth with four independent permissions:
can_send,can_receive,can_talk,can_hear. - Admin UI (HTTP Basic) — token CRUD and permissions, live connected-client monitoring, voice channels, notification history with per-token delivery/ack tracking, and log tailing.
- Operational niceties — rotating file logs, an optional push heartbeat to any status-page/uptime monitor (Uptime Kuma, Healthchecks.io, …), optional Sentry error reporting.
See PROTOCOL.md for the full wire protocol (frames, permissions, close codes).
Architecture
senders (WS can_send / HTTP POST /api/notify)
│
▼
┌─────────────────────────────────────────────┐
│ Chaka (FastAPI, single process) │
│ • /ws WebSocket endpoint │
│ • ConnectionManager (in-memory, asyncio) │
│ • admin UI + REST API (Jinja2 templates) │
└─────────────────────────────────────────────┘
│ persist │ broadcast
▼ ▼
MySQL receivers (WS can_receive) / voice peers (can_talk/can_hear)
ConnectionManager(chaka/manager.py) holds all live WebSocket connections and per-channel voice state in memory, guarded by a singleasyncio.Lock. One connection per token is enforced.- Persistence is for tokens, notification history, delivery/ack records, connection events, and voice-session metadata — not for live routing, which is in-memory.
- The server is designed to run single-process (
--workers 1). Cross-process/cross-instance fan-out is not implemented.
Tech stack
| Concern | Choice |
|---|---|
| Language / runtime | Python 3.11+ |
| Web framework | FastAPI + Uvicorn |
| Realtime | websockets (via FastAPI WebSocket) |
| ORM / DB driver | SQLAlchemy 2.0 (async) + aiomysql (MySQL) |
| Migrations | Alembic |
| Templates | Jinja2 (server-rendered admin UI) |
| Validation | Pydantic 2 |
| Monitoring (optional) | Push heartbeat (Uptime Kuma-compatible), Sentry |
Note: the project is written and tested against MySQL (
aiomysql). SQLAlchemy's async layer could in principle target another backend (e.g. PostgreSQL viaasyncpg) by changingDATABASE_URLand the driver, but that is untested here and migrations have only been exercised on MySQL.
Configuration
All configuration is via environment variables (a .env file is loaded automatically). Copy .env.example to .env and edit:
| Variable | Required | Purpose | Default |
|---|---|---|---|
DATABASE_URL |
yes | SQLAlchemy async DB URL | mysql+aiomysql://user:pass@localhost:3306/chaka |
ADMIN_USER |
yes | Admin UI Basic-auth user | admin |
ADMIN_PASSWORD |
yes | Admin UI Basic-auth password — change this | changeme |
LOG_FILE |
no | Rotating application log path | ./chaka.log |
LOG_MAX_BYTES |
no | Log rotation size (5 MB) | 5242880 |
LOG_BACKUP_COUNT |
no | Rotated log files kept | 5 |
HEARTBEAT_URL |
no | Push-heartbeat URL (status-page/uptime monitor); blank disables it | (disabled) |
HEARTBEAT_INTERVAL |
no | Heartbeat interval (seconds) | 60 |
HEARTBEAT_LOG_FILE |
no | Heartbeat log path | ./heartbeat.log |
SENTRY_DSN |
no | Sentry DSN; blank disables Sentry | (disabled) |
SENTRY_TRACES_SAMPLE_RATE |
no | Sentry traces sample rate | 0.2 |
TITLE |
no | App title (admin UI / OpenAPI) | Chaka |
WEBSOCKET_PATH |
no | WebSocket route path | /ws |
STATIC_DIR |
no | Override the static-assets directory | (bundled) |
TEMPLATES_DIR |
no | Override the admin-templates directory | (bundled) |
Install & run (from PyPI)
pip install chaka
chaka init # copy static/templates here, write .env, run migrations
# edit .env — set DATABASE_URL and admin credentials
chaka serve # serves on http://127.0.0.1:8000
chaka init scaffolds a customizable server in the current directory. If you
don't need to edit the bundled assets you can skip it — just provide the config
(above), run chaka db upgrade to create the schema, and chaka serve.
Open the admin UI, log in with ADMIN_USER / ADMIN_PASSWORD, and create a
token on the Tokens tab (the value is shown once). Point a client at
ws://HOST:PORT/ws?token=YOUR_TOKEN.
CLI
chaka serve [--host H] [--port P] run the server
chaka init [--path DIR] copy assets + write .env + migrate
chaka db upgrade [--revision REV] apply migrations
Use as a library
Build the app with the create_app factory and adapt it by composition —
inject your own collaborators, no subclassing required:
from chaka.factory import create_app
from chaka.application import Settings
app = create_app() # env-driven
app = create_app(Settings(title="Acme Relay")) # explicit config
app = create_app(manager=MyManager(), routers=[(my_router, "/api")])
create_app returns a ChakaApp (an ASGI app with .run()), so
uvicorn mymodule:app works. For deeper changes, subclass
ChakaApplicationFactory and override a build step (get_manager,
get_handler, default_routers, _make_logger, …):
from chaka.factory import ChakaApplicationFactory
class MyFactory(ChakaApplicationFactory):
def get_manager(self, voice_log):
return MyManager(voice_log=voice_log)
app = MyFactory().create_app()
Develop from a clone
git clone https://github.com/puentesarrin/chaka.git
cd chaka
python3 -m venv venv
source venv/bin/activate
pip install -e ".[dev]" # runtime deps + ruff, pytest
cp .env.example .env
# edit .env — set DATABASE_URL and admin credentials
alembic upgrade head # or: chaka db upgrade
uvicorn main:app --host 0.0.0.0 --port 8000 --reload # or: chaka serve
Lint and format (config in pyproject.toml, [tool.ruff]):
ruff check . # lint
ruff format . # format (single quotes, 120 cols)
Tests
The suite is pure-unit and mock-based — no database or network required (the
in-memory backend, NullVoiceLog, fake repositories, and factory injection make
every layer testable in isolation):
pip install -e ".[dev]"
pytest # run the suite
pytest --cov=chaka --cov-report=term-missing # with coverage
Config lives in pyproject.toml ([tool.pytest.ini_options], asyncio_mode = "auto"
so async tests need no decorator). CI (.github/workflows/ci.yml) runs ruff +
pytest on Python 3.11 and 3.12 for every push and pull request.
Migrations
chaka db upgrade # installed: apply latest
alembic upgrade head # from a clone: apply latest
alembic revision --autogenerate -m "describe change" # after model changes (clone)
Production deployment (systemd + nginx)
The deploy/ directory contains templates:
deploy/chaka.service— systemd unit (expects the app at/opt/chakawith a venv at/opt/chaka/venvand achakaservice user; adjust to taste).deploy/nginx.conf— TLS reverse proxy with the WebSocket upgrade headers and a longproxy_read_timeoutfor/ws. Replacechaka.example.comwith your domain.
# install into a venv at /opt/chaka and set up the DB:
python3 -m venv /opt/chaka/venv
/opt/chaka/venv/bin/pip install chaka
# create /opt/chaka/.env with DATABASE_URL + admin credentials, then:
/opt/chaka/venv/bin/chaka db upgrade
# service:
sudo cp deploy/chaka.service /etc/systemd/system/chaka.service
sudo systemctl daemon-reload
sudo systemctl enable --now chaka
sudo systemctl status chaka
sudo journalctl -u chaka -f
# TLS + reverse proxy
sudo certbot --nginx -d chaka.example.com
sudo cp deploy/nginx.conf /etc/nginx/sites-available/chaka
sudo ln -s /etc/nginx/sites-available/chaka /etc/nginx/sites-enabled/chaka
sudo nginx -t && sudo systemctl reload nginx
Run the server bound to 127.0.0.1 behind nginx. Use a single worker — the connection manager is in-process, so multiple workers would not share connections. chaka serve runs one worker; if you invoke uvicorn directly, pass --workers 1.
Status & limitations
- Single-process only. No cross-process/cross-instance fan-out.
- Single admin account (HTTP Basic) governing all tokens; no per-user accounts.
- Best-effort delivery — notifications are persisted and replayed on reconnect (up to 100), but there is no guaranteed/ack-driven retransmission; voice audio is relayed live and not stored.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file chaka-0.1.0.tar.gz.
File metadata
- Download URL: chaka-0.1.0.tar.gz
- Upload date:
- Size: 49.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e417a2e21d6ce29b9a4545f997ca2ebdb23f88cd8b7f64d38a4e4a4485bd9bb8
|
|
| MD5 |
a4bb95541e213270fde48eeb726fa29c
|
|
| BLAKE2b-256 |
b2d9320343f09e8a111a44fd6e1015129d690193f121335256e52a2c3703dbe5
|
Provenance
The following attestation bundles were made for chaka-0.1.0.tar.gz:
Publisher:
publish.yml on puentesarrin/chaka
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chaka-0.1.0.tar.gz -
Subject digest:
e417a2e21d6ce29b9a4545f997ca2ebdb23f88cd8b7f64d38a4e4a4485bd9bb8 - Sigstore transparency entry: 2044146094
- Sigstore integration time:
-
Permalink:
puentesarrin/chaka@eb4661226906c7b818a98ab334a719cc42581ffe -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/puentesarrin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@eb4661226906c7b818a98ab334a719cc42581ffe -
Trigger Event:
release
-
Statement type:
File details
Details for the file chaka-0.1.0-py3-none-any.whl.
File metadata
- Download URL: chaka-0.1.0-py3-none-any.whl
- Upload date:
- Size: 59.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1eebc116c4298f8bf0d4d0e0f8b23e66971de84b308562086427ccc5ed4f9031
|
|
| MD5 |
0b6e7417efd72ac8a3c11a701b7d44ec
|
|
| BLAKE2b-256 |
2d8656b08d6554e31e485c6f99d290bdd6f4773c8081ead786c7ca14dda1359f
|
Provenance
The following attestation bundles were made for chaka-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on puentesarrin/chaka
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chaka-0.1.0-py3-none-any.whl -
Subject digest:
1eebc116c4298f8bf0d4d0e0f8b23e66971de84b308562086427ccc5ed4f9031 - Sigstore transparency entry: 2044146111
- Sigstore integration time:
-
Permalink:
puentesarrin/chaka@eb4661226906c7b818a98ab334a719cc42581ffe -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/puentesarrin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@eb4661226906c7b818a98ab334a719cc42581ffe -
Trigger Event:
release
-
Statement type: