Configuration
Configuration lives in etc/config.yaml by default; if the file is absent, that layer is skipped and built-in defaults plus environment variables apply. Loading is otherwise fail-loud: a mistyped field or a non-compliant secret fails startup instead of running degraded.
How configuration loads
Section titled “How configuration loads”Configuration is three layers, each overriding the previous:
- Built-in defaults (in code; e.g.
server.addris:8080,cache.driverismemory). etc/config.yaml— point elsewhere withGORTEXA_CONFIG=/path/to/config.yaml; the layer is skipped when the file is absent.- Environment variables, in the form
GORTEXA_<SECTION>__<FIELD>(double underscore between section and field).
When the same field is set in more than one layer, the environment variable wins. Real secrets are injected only through environment variables — never written into config.yaml, never committed.
config.yaml
Section titled “config.yaml”server: addr: ":8080" shutdown_timeout: 20s enable_cors: true cors_origins: [] # CORS allowlist; enable_cors is only the master switch — origins not listed here get no CORS headers openapi: true reflection: false # gRPC reflection off by default
auth: jwt_secret: "dev-only-insecure-secret-change-me-please" issuer: "gortexa" # audience: "myapp" # optional: stamps aud into signed tokens and requires it — isolates services sharing a secret ttl: 1h
db: dsn: "" # PostgreSQL DSN (Secret, masked in logs); inject via env: GORTEXA_DB__DSN max_conns: 10
cache: driver: "memory" # "memory" (default, process-local) or "redis"
mq: driver: "nats" # "nats" (core, at-most-once) or "jetstream" (durable, at-least-once) url: "" # comma-separated servers, e.g. "nats://a:4222,nats://b:4222" group_id: "" # empty = fanout; non-empty = load-balance
log: level: "info" format: "json"
observ: service_name: "gortexa" service_version: "dev" # tracing_otlp: "localhost:4317" # OTLP trace endpoint; empty disables # metrics_otlp: "localhost:4317" # OTLP metrics endpoint; empty disables (rate-limit/circuit-breaker metrics need it) # logs_otlp: "localhost:4317" # OTLP log endpoint; empty disables otlp_insecure: true # local docker compose only; never true for remote sample_ratio: 1.0 genai_capture_content: false # when true, MCP tool-call arguments are recorded in telemetry # genai_mask_fields: ["password", "token", "secret", "authorization", "api_key"]Cache: memory and redis
Section titled “Cache: memory and redis”cache.driver selects the backend. The default memory needs no external service; redis is the only driver that reads addr and the fields below it. Any value other than memory or redis fails startup with cache: unsupported driver.
cache: driver: "memory"Process-local, no external dependency. Good for single-instance deployments and local development.
cache: driver: "redis" addr: "localhost:6379" password: "" # inject via env: GORTEXA_CACHE__PASSWORD db: 0
# The client tunables below are all optional; omit to use the built-in default dial_timeout: 5s # default 5s read_timeout: 3s # default 3s write_timeout: 3s # default 3s pool_size: 10 # default 10; must not be negativeOn redis, the framework’s built-in zero-dependency RESP client connects — no third-party redis package.
Each tunable’s zero value means “use the built-in default”, so set only the one you need to change:
| Field | Default | Purpose |
|---|---|---|
dial_timeout |
5s | Timeout for establishing a connection |
read_timeout |
3s | Timeout for reading a single reply |
write_timeout |
3s | Timeout for writing a single command |
pool_size |
10 | Connection pool size; 0 uses the default, negative is rejected |
Database (PostgreSQL)
Section titled “Database (PostgreSQL)”db.dsn is the pgx connection string (a Secret field, always masked in logs); db.max_conns defaults to 10. Inject the real DSN via an environment variable:
GORTEXA_DB__DSN=postgres://user:pass@pgbouncer:6432/appThe pool is PgBouncer-safe: QueryExecModeExec with statement and description caches disabled, so it works behind PgBouncer in transaction-pooling mode without prepared-statement errors.
The development flow: schema goes in db/migrations/, SQL in db/queries/, and make sqlc generates type-safe query code into internal/storage/db/. deploy/docker-compose.yaml already ships postgres and PgBouncer for local verification.
Environment overrides
Section titled “Environment overrides”Any field can be overridden with an environment variable, using GORTEXA_<SECTION>__<FIELD>:
GORTEXA_SERVER__ADDR=:9090GORTEXA_AUTH__JWT_SECRET=<real-secret>GORTEXA_CACHE__DRIVER=redisGORTEXA_CACHE__ADDR=redis.internal:6379GORTEXA_SERVER__REFLECTION=trueSecret rules
Section titled “Secret rules”- The placeholder
dev-only-insecure-secret-change-me-pleaseis refused at startup — it cannot ship by accident. - JWT secrets shorter than 32 bytes are also refused.
- Services sharing a secret and the default issuer accept each other’s tokens — set a distinct
auth.audienceper service to isolate them (a token minted for A is rejected by B). - Secret fields such as the redis password and the MQ URL (which can embed credentials) are masked in logs and error output.
- Real secrets are injected via environment variables, never committed.
MQ semantics
Section titled “MQ semantics”mq.group_id selects the delivery mode, identically on both drivers:
| group_id | NATS (core) | JetStream | Semantics |
|---|---|---|---|
| Empty (default) | Subscribe | Ephemeral consumer | Fanout: every subscriber receives each message |
| Non-empty | QueueSubscribe | Durable consumer (named after group_id, acked only after the handler succeeds) | Load-balance: one handler per message |
The drivers differ in the delivery guarantee: core NATS is at-most-once (no redelivery — a message whose handler fails is gone), while driver: "jetstream" is at-least-once — publish blocks on the server’s storage ack and a handler error triggers a delayed redelivery, so handlers must be idempotent. JetStream needs the server started with -js; the framework lazily creates a stream per topic (24h retention), and an operator may pre-create a stream with different retention — the framework adopts it and never rewrites an existing stream.
Diagnostics
Section titled “Diagnostics”gortexa doctor # check the Go toolchain and proto tools