# Configuration

> The TOML config file, every server key, and the environment variable that overrides each one.

okuri reads an optional TOML file plus the environment. Point the server at
a file with `okuri serve --config okuri.toml` or `OKURI_CONFIG=okuri.toml`.
The file is optional: defaults plus the environment is a supported way to
run.

Precedence is **defaults, then file, then environment**. The environment
wins because it is the layer a scheduler controls, while the file is the
layer an image bakes in.

Unknown keys are rejected, so a typo fails at startup instead of silently
keeping a default.

The repository ships a fully commented reference at
[`okuri.example.toml`](https://github.com/okuri-oss/okuri/blob/main/okuri.example.toml).

## `[store]`

| key | default | env | notes |
| --- | --- | --- | --- |
| `bucket` | none, **required** | `OKURI_S3_BUCKET` | the bucket every queue lives in |
| `endpoint` | provider default | `OKURI_S3_ENDPOINT` | override for MinIO, R2, and other S3-compatible stores |
| `region` | none | `OKURI_S3_REGION` | the region to sign for |
| `access_key` | none | `OKURI_S3_ACCESS_KEY` | leave unset with `secret_key` to use the provider credential chain |
| `secret_key` | none | `OKURI_S3_SECRET_KEY` | see above |

## `[server]`

| key | default | env |
| --- | --- | --- |
| `admin_addr` | `0.0.0.0:9090` | `OKURI_ADMIN_ADDR` |
| `grpc_addr` | `0.0.0.0:8080` | `OKURI_GRPC_ADDR` |
| `drain_deadline_ms` | `30000` | `OKURI_DRAIN_DEADLINE_MS` |
| `readiness_probe_interval_ms` | `5000` | `OKURI_READINESS_PROBE_INTERVAL_MS` |
| `receive_poll_interval_ms` | `500` | `OKURI_RECEIVE_POLL_INTERVAL_MS` |
| `receive_max_wait_ms` | `20000` | `OKURI_RECEIVE_MAX_WAIT_MS` |
| `receipt_handle_key` | none, **required** | `OKURI_RECEIPT_HANDLE_KEY` |
| `receipt_handle_verify_keys` | `[]` | `OKURI_RECEIPT_HANDLE_VERIFY_KEYS` |
| `namespace` | `default` | `OKURI_NAMESPACE` |
| `api_keys` | none, **required** | `OKURI_API_KEYS` |
| `sqs_credentials` | none, **required** | `OKURI_SQS_CREDENTIALS` |
| `sqs_endpoint` | `http://localhost:8080` | `OKURI_SQS_ENDPOINT` |
| `sqs_region` | `us-east-1` | `OKURI_SQS_REGION` |
| `sequencer_enabled` | `false` | `OKURI_SEQUENCER_ENABLED` |
| `sequencer_ttl_ms` | `15000` | `OKURI_SEQUENCER_TTL_MS` |
| `sequencer_renew_interval_ms` | `5000` | `OKURI_SEQUENCER_RENEW_INTERVAL_MS` |

### The required keys

Three keys have no default and reject empty values, because these
failures are better discovered at startup than at the first request.

**`receipt_handle_key`** signs receipt handles: 32 bytes in base64, from
`head -c 32 /dev/urandom | base64`. Every node in a fleet carries the same
one, so a handle issued by whichever node served the receive is honored by
whichever node the load balancer sends the ack to.
`receipt_handle_verify_keys` holds keys that no longer sign but are still
honored, which is what makes key rotation possible: put the new key in
`receipt_handle_key`, leave the old one in the verify list for at least the
longest visibility timeout plus a grace period, then drop it.

**`api_keys`** are the bearer keys the native gRPC and HTTP/JSON surfaces
accept, each scoped to a namespace:

```toml
[[server.api_keys]]
namespace = "default"
key = "..."
```

As an environment variable, `OKURI_API_KEYS` takes comma-separated
`namespace:key` pairs.

**`sqs_credentials`** are the SigV4 credentials the SQS surface accepts,
separate from the API keys and never derived from them:

```toml
[[server.sqs_credentials]]
namespace = "default"
access_key_id = "..."
secret = "..."
```

As an environment variable, `OKURI_SQS_CREDENTIALS` takes comma-separated
`namespace:access-key-id:secret` triples.

### Namespaces

`namespace` scopes both storage and signatures. Two deployments pointed at
one bucket under different namespaces share the bucket and nothing else,
even where they use the same queue names: messages, segments, leases,
tombstones, and purge watermarks are all namespace-scoped. Namespace names follow the queue-name rule:
`[a-z0-9_-]`, at most 64 bytes.

### SQS addressing

`sqs_endpoint` is the root of every queue URL the compat surface hands
out. It is the address clients use to reach this node, which is a
different thing from the address this node uses to reach its bucket: a
node behind a load balancer has to advertise the balancer's address.
`sqs_region` is the region every ARN names. Both settings exist separately
from `[store]` for that reason.

## Per-queue configuration

Queue-level settings live in the queue's manifest object in the bucket, not
in the server config: `visibility_timeout_ms`, `retention_ms`, and
`max_receive_count`. They are exposed through SQS `GetQueueAttributes` and
`SetQueueAttributes` as well as the native API.

Queue names everywhere follow one rule: non-empty, at most 64 bytes,
`[a-z0-9_-]` only. The `-dlq` suffix is reserved, so a queue that wants a
reachable dead-letter queue is limited to 60 bytes of name.
