# greentic-config
Enterprise configuration loader for Greentic hosts. This crate resolves configuration from defaults, user-level config, project config, environment variables, and CLI overrides with clear precedence and provenance tracking.
## Features
- Loads canonical schema from `greentic-config-types` (no secrets).
- Precedence: CLI > env > project > user > defaults.
- Provenance tracking:
- `ResolvedConfig` includes per-field source (`default|user|project|env|cli`).
- `ResolvedConfigDetailed` includes per-leaf source plus origin (file path / env var name / `cli`).
- Validation hooks for dev-only fields, path sanity, and unsafe combinations.
- Explain output (string/JSON) for operators.
- Deployer defaults resolved here so deploy tools never hard-code routing domains.
## Operator semantics
- `--config <path>` / `ConfigResolver::with_config_path(...)` is strict and deterministic: if the file does not exist, resolution fails.
- If `--config` is set, it replaces project discovery (`<project_root>/.greentic/config.toml`) but keeps the same precedence position (project layer).
## Services and events mapping
- Services transports (e.g., `services.runner.kind/url`) are outbound client settings (HTTP/NATS/noop).
- Services bindings (`services.*.service`) describe how this host listens/advertises (bind_addr/port/public_base_url/metrics), non-secret only.
- Events knobs (`events.reconnect`, `events.backoff`) get defaults (reconnect enabled, max_retries=50, backoff initial=250ms, max=30s, multiplier=2.0, jitter=true) and validation (offline + remote endpoint is rejected; backoff sanity enforced).
- No secrets in config; route auth material via your secrets backend.
## Environment variables
Only explicitly supported `GREENTIC_*` variables are mapped (stable, documented surface area):
| `GREENTIC_SCHEMA_VERSION` | `schema_version` |
| `GREENTIC_ENVIRONMENT_ENV_ID` | `environment.env_id` |
| `GREENTIC_ENVIRONMENT_DEPLOYMENT` | `environment.deployment` |
| `GREENTIC_ENVIRONMENT_CONNECTION` | `environment.connection` |
| `GREENTIC_ENVIRONMENT_REGION` | `environment.region` |
| `GREENTIC_PATHS_GREENTIC_ROOT` | `paths.greentic_root` |
| `GREENTIC_PATHS_STATE_DIR` | `paths.state_dir` |
| `GREENTIC_PATHS_CACHE_DIR` | `paths.cache_dir` |
| `GREENTIC_PATHS_LOGS_DIR` | `paths.logs_dir` |
| `GREENTIC_SERVICES_EVENTS_URL` | `services.events.url` |
| `GREENTIC_SERVICES_RUNNER_KIND` | `services.runner.kind` |
| `GREENTIC_SERVICES_RUNNER_URL` | `services.runner.url` |
| `GREENTIC_SERVICES_RUNNER_BIND_ADDR` | `services.runner.service.bind_addr` |
| `GREENTIC_SERVICES_RUNNER_PORT` | `services.runner.service.port` |
| `GREENTIC_SERVICES_RUNNER_PUBLIC_BASE_URL` | `services.runner.service.public_base_url` |
| `GREENTIC_SERVICES_RUNNER_METRICS_ENABLED` | `services.runner.service.metrics.enabled` |
| `GREENTIC_SERVICES_RUNNER_METRICS_BIND_ADDR` | `services.runner.service.metrics.bind_addr` |
| `GREENTIC_SERVICES_RUNNER_METRICS_PORT` | `services.runner.service.metrics.port` |
| `GREENTIC_SERVICES_RUNNER_METRICS_PATH` | `services.runner.service.metrics.path` |
| `GREENTIC_SERVICES_DEPLOYER_KIND` | `services.deployer.kind` |
| `GREENTIC_SERVICES_DEPLOYER_URL` | `services.deployer.url` |
| `GREENTIC_SERVICES_DEPLOYER_BIND_ADDR` | `services.deployer.service.bind_addr` |
| `GREENTIC_SERVICES_DEPLOYER_PORT` | `services.deployer.service.port` |
| `GREENTIC_SERVICES_DEPLOYER_PUBLIC_BASE_URL` | `services.deployer.service.public_base_url` |
| `GREENTIC_SERVICES_DEPLOYER_METRICS_ENABLED` | `services.deployer.service.metrics.enabled` |
| `GREENTIC_SERVICES_DEPLOYER_METRICS_BIND_ADDR` | `services.deployer.service.metrics.bind_addr` |
| `GREENTIC_SERVICES_DEPLOYER_METRICS_PORT` | `services.deployer.service.metrics.port` |
| `GREENTIC_SERVICES_DEPLOYER_METRICS_PATH` | `services.deployer.service.metrics.path` |
| `GREENTIC_SERVICES_EVENTS_TRANSPORT_KIND` | `services.events_transport.kind` |
| `GREENTIC_SERVICES_EVENTS_TRANSPORT_URL` | `services.events_transport.url` |
| `GREENTIC_SERVICES_EVENTS_TRANSPORT_BIND_ADDR` | `services.events_transport.service.bind_addr` |
| `GREENTIC_SERVICES_EVENTS_TRANSPORT_PORT` | `services.events_transport.service.port` |
| `GREENTIC_SERVICES_EVENTS_TRANSPORT_PUBLIC_BASE_URL` | `services.events_transport.service.public_base_url` |
| `GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_ENABLED` | `services.events_transport.service.metrics.enabled` |
| `GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_BIND_ADDR` | `services.events_transport.service.metrics.bind_addr` |
| `GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_PORT` | `services.events_transport.service.metrics.port` |
| `GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_PATH` | `services.events_transport.service.metrics.path` |
| `GREENTIC_SERVICES_SOURCE_KIND` | `services.source.kind` |
| `GREENTIC_SERVICES_SOURCE_URL` | `services.source.url` |
| `GREENTIC_SERVICES_SOURCE_BIND_ADDR` | `services.source.service.bind_addr` |
| `GREENTIC_SERVICES_SOURCE_PORT` | `services.source.service.port` |
| `GREENTIC_SERVICES_SOURCE_PUBLIC_BASE_URL` | `services.source.service.public_base_url` |
| `GREENTIC_SERVICES_SOURCE_METRICS_ENABLED` | `services.source.service.metrics.enabled` |
| `GREENTIC_SERVICES_SOURCE_METRICS_BIND_ADDR` | `services.source.service.metrics.bind_addr` |
| `GREENTIC_SERVICES_SOURCE_METRICS_PORT` | `services.source.service.metrics.port` |
| `GREENTIC_SERVICES_SOURCE_METRICS_PATH` | `services.source.service.metrics.path` |
| `GREENTIC_SERVICES_PUBLISH_KIND` | `services.publish.kind` |
| `GREENTIC_SERVICES_PUBLISH_URL` | `services.publish.url` |
| `GREENTIC_SERVICES_PUBLISH_BIND_ADDR` | `services.publish.service.bind_addr` |
| `GREENTIC_SERVICES_PUBLISH_PORT` | `services.publish.service.port` |
| `GREENTIC_SERVICES_PUBLISH_PUBLIC_BASE_URL` | `services.publish.service.public_base_url` |
| `GREENTIC_SERVICES_PUBLISH_METRICS_ENABLED` | `services.publish.service.metrics.enabled` |
| `GREENTIC_SERVICES_PUBLISH_METRICS_BIND_ADDR` | `services.publish.service.metrics.bind_addr` |
| `GREENTIC_SERVICES_PUBLISH_METRICS_PORT` | `services.publish.service.metrics.port` |
| `GREENTIC_SERVICES_PUBLISH_METRICS_PATH` | `services.publish.service.metrics.path` |
| `GREENTIC_SERVICES_METADATA_KIND` | `services.metadata.kind` |
| `GREENTIC_SERVICES_METADATA_URL` | `services.metadata.url` |
| `GREENTIC_SERVICES_METADATA_BIND_ADDR` | `services.metadata.service.bind_addr` |
| `GREENTIC_SERVICES_METADATA_PORT` | `services.metadata.service.port` |
| `GREENTIC_SERVICES_METADATA_PUBLIC_BASE_URL` | `services.metadata.service.public_base_url` |
| `GREENTIC_SERVICES_METADATA_METRICS_ENABLED` | `services.metadata.service.metrics.enabled` |
| `GREENTIC_SERVICES_METADATA_METRICS_BIND_ADDR` | `services.metadata.service.metrics.bind_addr` |
| `GREENTIC_SERVICES_METADATA_METRICS_PORT` | `services.metadata.service.metrics.port` |
| `GREENTIC_SERVICES_METADATA_METRICS_PATH` | `services.metadata.service.metrics.path` |
| `GREENTIC_SERVICES_OAUTH_BROKER_KIND` | `services.oauth_broker.kind` |
| `GREENTIC_SERVICES_OAUTH_BROKER_URL` | `services.oauth_broker.url` |
| `GREENTIC_SERVICES_OAUTH_BROKER_BIND_ADDR` | `services.oauth_broker.service.bind_addr` |
| `GREENTIC_SERVICES_OAUTH_BROKER_PORT` | `services.oauth_broker.service.port` |
| `GREENTIC_SERVICES_OAUTH_BROKER_PUBLIC_BASE_URL` | `services.oauth_broker.service.public_base_url` |
| `GREENTIC_SERVICES_OAUTH_BROKER_METRICS_ENABLED` | `services.oauth_broker.service.metrics.enabled` |
| `GREENTIC_SERVICES_OAUTH_BROKER_METRICS_BIND_ADDR` | `services.oauth_broker.service.metrics.bind_addr` |
| `GREENTIC_SERVICES_OAUTH_BROKER_METRICS_PORT` | `services.oauth_broker.service.metrics.port` |
| `GREENTIC_SERVICES_OAUTH_BROKER_METRICS_PATH` | `services.oauth_broker.service.metrics.path` |
| `GREENTIC_RUNTIME_MAX_CONCURRENCY` | `runtime.max_concurrency` |
| `GREENTIC_RUNTIME_TASK_TIMEOUT_MS` | `runtime.task_timeout_ms` |
| `GREENTIC_RUNTIME_SHUTDOWN_GRACE_MS` | `runtime.shutdown_grace_ms` |
| `GREENTIC_RUNTIME_ADMIN_SECRETS_EXPLAIN_ENABLED` | `runtime.admin_endpoints.secrets_explain_enabled` |
| `GREENTIC_TELEMETRY_ENABLED` | `telemetry.enabled` |
| `GREENTIC_TELEMETRY_EXPORTER` | `telemetry.exporter` |
| `GREENTIC_TELEMETRY_ENDPOINT` | `telemetry.endpoint` |
| `GREENTIC_TELEMETRY_SAMPLING` | `telemetry.sampling` |
| `GREENTIC_NETWORK_PROXY_URL` | `network.proxy_url` |
| `GREENTIC_NETWORK_TLS_MODE` | `network.tls_mode` |
| `GREENTIC_NETWORK_CONNECT_TIMEOUT_MS` | `network.connect_timeout_ms` |
| `GREENTIC_NETWORK_READ_TIMEOUT_MS` | `network.read_timeout_ms` |
| `GREENTIC_SECRETS_KIND` | `secrets.kind` |
| `GREENTIC_SECRETS_REFERENCE` | `secrets.reference` |
| `GREENTIC_DEV_DEFAULT_ENV` | `dev.default_env` |
| `GREENTIC_DEV_DEFAULT_TENANT` | `dev.default_tenant` |
| `GREENTIC_DEV_DEFAULT_TEAM` | `dev.default_team` |
| `GREENTIC_EVENTS_RECONNECT_ENABLED` | `events.reconnect.enabled` |
| `GREENTIC_EVENTS_RECONNECT_MAX_RETRIES` | `events.reconnect.max_retries` |
| `GREENTIC_EVENTS_BACKOFF_INITIAL_MS` | `events.backoff.initial_ms` |
| `GREENTIC_EVENTS_BACKOFF_MAX_MS` | `events.backoff.max_ms` |
| `GREENTIC_EVENTS_BACKOFF_MULTIPLIER` | `events.backoff.multiplier` |
| `GREENTIC_EVENTS_BACKOFF_JITTER` | `events.backoff.jitter` |
Example snippet:
```toml
[services.runner]
kind = "http"
url = "https://runner.greentic.local"
[services.runner.service]
bind_addr = "0.0.0.0"
port = 8080
public_base_url = "https://runner.greentic.local"
[services.runner.service.metrics]
enabled = true
bind_addr = "127.0.0.1"
port = 9090
path = "/metrics"
[services.deployer]
kind = "nats"
url = "nats://nats.greentic.local:4222"
[services.events]
url = "https://events.greentic.local"
[runtime.admin_endpoints]
secrets_explain_enabled = true
```
## Status
- Schema and loader implemented; CLI binary behind the `cli` feature (`greentic-config show|explain|validate`).
- Defaults are non-secret and filesystem/network safe.
## Deployer defaults
- `deployer.base_domain` defaults to `deploy.greentic.ai` and is used when generating deployment URLs / routing domains.
- Provenance is tracked like other fields; override via config layers instead of baking domains into deployer code.