Files
woggioni-opencode-agent c7d75f6899
CI / Build and push docker image (push) Successful in 2m52s
Support standard GRANIAN_* env vars and the rloop event loop
- 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)
2026-08-03 05:28:04 +00:00

117 lines
4.3 KiB
Markdown

# 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 /<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
```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
```