# Configuration

Service configuration via environment variables (no config files)

Zever reads all service configuration from **environment variables only**. There are **no config files** (`zever.yaml`, `.yml`, `.json` are ignored).

## Quick start

Layers (later wins): `defaults < .env < .env.local < process environment`.

- **defaults**: `config.Default()` — zero-infra adapters (sqlite, slog, stdhttp, memory caches, etc.)
- **`.env`**: project-committed example + local overrides (gitignored)
- **`.env.local`**: machine-local overrides (gitignored, higher priority)
- **process environment**: highest priority, no file I/O

```sh
DB_ADAPTER=postgres
DB_DSN=postgres://app:secret@db:5432/app
DB_MAX_CONNS=20
CACHE_ADAPTER=redis
CACHE_ADDR=redis:6379
AUTH_ADAPTER=jwt
AUTH_JWT_SECRET=prod-secret-at-least-32-bytes-long
```

## Resolve from env

```go
env, err := config.FromEnv()        // reads .env, .env.local, process env
resolved, err := config.Resolve(env) // typed Config
```

Or in one step: `config.LoadEnv()` (same as `FromEnv`).

```go
cfg, err := config.LoadEnv()
container.New(cfg, app.Providers()...)
```

## Map environment variables to keys

Env names carry no prefix: `_`, uppercased.

- Default instance: `SERVICE_FIELD` (e.g., `DB_ADAPTER=postgres`)
- Named instance: `SERVICE__NAME__FIELD` (e.g., `DB__ANALYTICS__DSN=...`)

Matching ignores case and `_`, so `MAX_RETRIES` matches `MaxRetries`. One nesting level splits at the first `_`: `AUTH_JWT_SECRET` sets `Options.JWT.Secret`.

Durations accept `"5s"` strings in env only; files store `time.Duration` as integer nanoseconds.

## Default adapters (zero-infra)

| Service | Default Adapter |
|---------|-----------------|
| db | sqlite |
| cache | memory |
| queue | memory |
| log | slog |
| router | stdhttp |
| ... | (see `config.Default()`) |

## Migration from v0.7

| v0.7 | v0.8 |
|------|------|
| `zever.yaml` | `.env` + `wiring.go` |
| `config.Load("")` | `config.FromEnv()` / `config.LoadEnv()` |
| `ServiceConfig.Adapter` | wired in `wiring.go` via `container.Provide` |
| `DB_MAXCONNS` | `DB_MAXCONNS` (same) |
| `DB__ANALYTICS__DSN` | `DB__ANALYTICS__DSN` (same format) |
| `zever doctor --config` | `zever doctor` (no flag) |

## Legacy file warning

If `zever.yaml`/`.yml`/`.json` exists in the working directory, `config.FromEnv()` prints a one-line warning:

```
config: zever.yaml found in working directory: ignored; see migration guide in config/README.md
```

The file is **ignored entirely** — no values are read from it.

## Generated env template

`zever new` and `zever add` generate `.env.example` with every env key, default value, and comment. This is the authoritative source for what env vars each service accepts.

## Redact config before printing

Raw option maps may hold secrets. `(*Config).RedactedServices` (built on `Redact`) is the sole display path: error text names services and fields only, and `zever config show` prints one `<service>  adapter=<x>` header per service plus its masked options.

```go title="main.go"
for name, sc := range cfg.RedactedServices() {
    fmt.Println(name, sc.Adapter, sc.Options)
}
```

`Default()` belongs in local dev and tests: its crypto key is deterministic and public. Supply `AUTH_JWT_SECRET`, the crypto key, and provider keys from env or a mounted file in production.

## Know the defaults

Defaults pick the zero-infra adapter per service (`db` sqlite, `cache`/`queue`/`session` memory, `storage` local, `log` slog, `observability` stdout, `payment`/`billing` stub, `secrets` env with prefix `ZEVER`). Two need keys: `ai` defaults to `anthropic` and requires an API key, and `mailer` defaults to `log` with a dummy `localhost:25`. The full table is the [adapters matrix](https://zenta-dev.github.io/zever/reference/adapters-matrix/).

## Troubleshooting

<details>
<summary>Unknown field error</summary>

A pre-rename flat spelling. Rename it to snake_case (e.g., `maxconns` → `max_conns`).
</details>

<details>
<summary>Unknown field under a wired service prefix</summary>

`config.Resolve` fails with a did-you-mean hint. Check the service's options struct.
</details>

<details>
<summary>`DB_MAXCONNS=abc` returns `InvalidOptionsError`</summary>

A resolved field failed to parse. Set an integer. Unset variables are ignored, bad values are not.
</details>

<details>
<summary>`AUTH_TOKEN` does nothing</summary>

Env is best-effort: `AUTH` is known but the remainder matches no field. Set `AUTH_JWT_SECRET`.
</details>

`Default()` belongs in local dev and tests: its crypto key is deterministic and public. Supply `AUTH_JWT_SECRET`, the crypto key, and provider keys from env or a mounted file in production.
