# kaya-rbcs A simplified Python clone of [RBCS](https://gitea.woggioni.net/woggioni/rbcs) (Remote Build Cache Server) built on the [Kaya](https://gitea.woggioni.net/woggioni/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 /` | `201 Created`, body = key, `Content-Type: text/plain` | | `GET /` (hit) | `200 OK`, body = value, stored `Content-Type` / `Content-Disposition` | | `GET /` (miss) | `404 Not Found`, empty body | | `GET/PUT` outside prefix | `400 Bad Request` | ## Installation ```bash 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`: ```bash 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](https://github.com/emmett-framework/granian) via its RSGI interface. Any granian option can be configured with its standard `GRANIAN_*` environment variables (see `granian --help`), for example: ```bash 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_HOST` overrides `RBCS_HOST` (default `127.0.0.1`) - `GRANIAN_PORT` overrides `RBCS_PORT` (default `8080`) - `GRANIAN_INTERFACE` overrides the default `rsgi` interface The Docker image ships the `rloop` event loop, so `GRANIAN_LOOP=rloop` works out of the box: ```bash 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 ```bash 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 ```bash 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 ```