- Launcher now only injects --interface/--host/--port when the matching GRANIAN_* env var is unset, so every granian option (GRANIAN_LOOP, GRANIAN_WORKERS, GRANIAN_HTTP, GRANIAN_HOST, GRANIAN_PORT, ...) can be configured via granian's standard GRANIAN_* environment variables - Add the rloop event loop as a runtime dependency so GRANIAN_LOOP=rloop works out of the box (previously failed with a missing 'rloop' package)
kaya-rbcs
A simplified Python clone of RBCS (Remote Build Cache Server) built on the Kaya web framework.
It implements only the two core cache endpoints — GET and PUT — persisting
values to memcached. Authentication, RBAC, rate limiting, TRACE health checks,
compression and TLS are intentionally left out.
Behavior
The cache key is the request path relative to a configurable URL prefix
(default /), normalized (.. segments are resolved). Values are stored in
memcached together with the Content-Type and Content-Disposition metadata
sent on upload, mirroring RBCS' CacheValueMetadata.
| Request | Response |
|---|---|
PUT /<key> |
201 Created, body = key, Content-Type: text/plain |
GET /<key> (hit) |
200 OK, body = value, stored Content-Type / Content-Disposition |
GET /<key> (miss) |
404 Not Found, empty body |
GET/PUT outside prefix |
400 Bad Request |
Installation
pip install -e . # + kaya-core, aiomcache
pip install -e .[server] # additionally installs granian + kaya-rsgi
Requires Python 3.10+ and a running memcached.
Running
Start the server with the rbcs-server console script or python -m kaya_rbcs:
rbcs-server
Configuration is read from environment variables:
| Variable | Default | Description |
|---|---|---|
RBCS_HOST |
127.0.0.1 |
Bind address |
RBCS_PORT |
8080 |
Bind port |
RBCS_PATH_PREFIX |
/ |
URL prefix that maps to the cache |
RBCS_MEMCACHE_HOST |
127.0.0.1 |
Memcached host |
RBCS_MEMCACHE_PORT |
11211 |
Memcached port |
RBCS_KEY_PREFIX |
(unset) | String appended to each cache key |
RBCS_DIGEST |
(unset) | Hash the key with this algorithm (e.g. sha256) |
RBCS_MAX_AGE |
7d |
Value TTL (s/m/h/d) |
Configuring Granian
The server runs on Granian via
its RSGI interface. Any granian option can be configured with its standard
GRANIAN_* environment variables (see granian --help), for example:
GRANIAN_LOOP=rloop GRANIAN_WORKERS=4 GRANIAN_HTTP=2 rbcs-server
Every granian CLI option has a matching GRANIAN_* variable
(GRANIAN_LOOP, GRANIAN_WORKERS, GRANIAN_HTTP, GRANIAN_TASK_IMPL,
GRANIAN_RUNTIME_THREADS, GRANIAN_BLOCKING_THREADS, GRANIAN_LOG_LEVEL,
GRANIAN_METRICS, GRANIAN_WS, ...).
When a GRANIAN_* variable and the corresponding RBCS_* variable are both
set, the granian one wins:
GRANIAN_HOSToverridesRBCS_HOST(default127.0.0.1)GRANIAN_PORToverridesRBCS_PORT(default8080)GRANIAN_INTERFACEoverrides the defaultrsgiinterface
The Docker image ships the rloop event loop, so GRANIAN_LOOP=rloop works
out of the box:
docker run -e GRANIAN_LOOP=rloop -e RBCS_MEMCACHE_HOST=memcached kaya-rbcs
The app is also a plain ASGI application, so it can be served by any ASGI
server, e.g. daphne kaya_rbcs.app:app.
Usage example
curl -X PUT -H 'Content-Type: application/octet-stream' \
--data-binary @build-output.tar http://localhost:8080/my-module/build
curl http://localhost:8080/my-module/build -o build-output.tar
Tests
python -m unittest discover -s tests
Tests use an in-process fake memcached (text protocol), so they run offline with no external dependencies.
Layout
src/kaya_rbcs/
├── __init__.py # package exports + `rbcs-server` entry point
├── __main__.py # Granian (RSGI) launcher
├── config.py # env-based configuration
├── store.py # MemcacheStore: value+metadata encoding, key processing
└── app.py # create_app(): KayaApp with GET/PUT recursive routes
tests/
├── fake_memcached.py
└── test_app.py