Getting Started
This guide covers installation, configuration, and running your first Letopis instance.
Prerequisites
| Dependency | Minimum version | Notes |
|---|---|---|
| Go | 1.25 | Required to build from source |
| MongoDB | 7.0 | One database per tenant is provisioned automatically |
| Redis | 6.0 | Used for the durable event queue, idempotency keys, and rule-cache invalidation |
| Docker | any | Optional; used for the compose stacks and integration tests |
Install
Prebuilt binaries
Download an archive for your platform (linux/darwin/windows, amd64/arm64) from GitHub Releases. Each archive bundles the letopis binary with LICENSE, NOTICE, config.example.yaml, and docker-compose.deps.yml.
From source
git clone https://github.com/max-trifonov/letopis.git
cd letopis
make build # produces bin/letopis
The binary embeds the version, commit hash, and build date via ldflags; make build sets them from git describe automatically.
Docker
A pre-built image is published at ghcr.io/max-trifonov/letopis. To build locally:
make docker # tags ghcr.io/max-trifonov/letopis:latest
Two compose flavours are provided:
Development stack
docker compose -f docker-compose.dev.yml up --build
Builds the image from the working tree and brings up MongoDB, Redis, and the service together.
Production stack
cp config.example.yaml config.yaml # edit before starting
docker compose up -d
Uses the published image. MongoDB and Redis are included for convenience; in production you will typically point mongodb.uri and redis.addr at your managed infrastructure.
Configuration
Letopis is configured by a YAML file. The binary searches for config.yaml in:
- The path given by
--config(highest priority) - The current working directory
- The directory containing the binary
The config file is required; the server will not start without it.
After the file is loaded, any LETOPIS_* environment variable overrides its corresponding key:
| Environment variable | Config key |
|---|---|
LETOPIS_ROLE |
role |
LETOPIS_HTTP_ADDR |
server.http.addr |
LETOPIS_GRPC_ADDR |
server.grpc.addr |
LETOPIS_MONGODB_URI |
mongodb.uri |
LETOPIS_REDIS_ADDR |
redis.addr |
LETOPIS_REDIS_PASSWORD |
redis.password |
LETOPIS_REDIS_DB |
redis.db |
LETOPIS_LOG_LEVEL |
log.level |
Minimal configuration
The minimal production config must set:
| Section | Key fields |
|---|---|
mongodb.uri |
Connection URI for the MongoDB cluster |
redis.addr |
Redis address |
tenants |
One entry per tenant with id and at least one API key (SHA-256 hash preferred) |
webhooks.secrets |
Named signing secrets referenced by rules |
See config.example.yaml for the full reference.
Starting the server
./bin/letopis serve
By default the role is all — HTTP + gRPC + worker in one process. This is correct for development and small installs. For production you can split:
./bin/letopis serve --role=api # HTTP + gRPC only
./bin/letopis serve --role=worker # async event processor only
Your first write
# Set your API key from config.yaml
TOKEN=hm_dev_plaintext
# Ingest a state change (server computes the diff)
curl -s -X POST http://localhost:8080/api/v1/collections/crm.deals/entities/deal-1/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"state": {"title": "Acme Corp", "amount": 5000, "stage": "prospect"}}'
Expected response (strict mode returns 201; default durable returns 202 with a ticket):
{
"ticket_id": "tkt_01hx...",
"status": "accepted"
}
Read it back
# Current state
curl -s http://localhost:8080/api/v1/collections/crm.deals/entities/deal-1/state \
-H "Authorization: Bearer $TOKEN"
# Full history
curl -s http://localhost:8080/api/v1/collections/crm.deals/entities/deal-1/history \
-H "Authorization: Bearer $TOKEN"
System endpoints
| Endpoint | Purpose |
|---|---|
GET /healthz |
Liveness probe |
GET /readyz |
Readiness — aggregated dependency checks |
GET /metrics |
Prometheus metrics |
GET /version |
Build info (version, commit, date) |
gRPC :9090 |
letopis.v1.SystemService, standard health, reflection |
Development
make test # go test -race ./...
make test-integration # requires Docker — integration + e2e (testcontainers)
make lint # golangci-lint + buf lint
make proto # regenerate gRPC bindings
make bench # diff/pipeline CPU benchmarks