Custos server can be configured using a YAML configuration file instead of environment variables. This guide explains how to set up and use the configuration system.
config/custos.yaml./custos
The server will automatically load the configuration file. If the config file is not found at the resolved path, the server exits with an error — make sure config/custos.yaml exists (or override CONFIG_PATH).
By default, the server looks for the configuration file at config/custos.yaml. You can customize this location using the CONFIG_PATH environment variable:
CONFIG_PATH=/etc/custos/config.yaml ./custos
The core section contains essential server settings:
core: database: url: "admin:admin@tcp(localhost:3306)/custos?parseTime=true&charset=utf8mb4" api: port: 8080 log_level: "info"
go-sql-driver/mysql driver). Required.The verifier and caller resolver read this block. The loader refuses to boot when either OIDC field is empty; the cache TTL is capped at 60s.
core: auth: oidc: issuer: "${OIDC_ISSUER_URL}" audience: "${OIDC_AUDIENCE}" cache_ttl: "30s"
| Key | Default | Notes |
|---|---|---|
core.auth.oidc.issuer | (required) | OIDC issuer URL. The verifier discovers JWKS from <issuer>/.well-known/openid-configuration. Empty → boot fails with core.auth.oidc.issuer is required. |
core.auth.oidc.audience | (required) | Expected aud claim. Usually the client_id registered with the IdP. Empty → boot fails with core.auth.oidc.audience is required. |
core.auth.cache_ttl | 30s | TTL of the in-process caller + privilege cache, keyed by OIDC sub. Any time.Duration string (s, m). Zero or negative → 30s. Values above 60s are capped at 60s at boot and a WARN is logged. |
Public routes (those that bypass JWT verification) are declared in code via router.Public(...). There is no YAML allowlist.
The connectors section defines which connectors are enabled and their settings.
connectors: slurm-mapper: type: "slurm-association-mapper" enabled: true slurm_api: url: "https://slurm-api.example.com" version: "0.0.38" username: "slurm_admin" token: "${SLURM_TOKEN}"
slurm-usage-monitor: type: "slurm-usage-monitor" enabled: true slurm_api: url: "https://slurm-api.example.com" version: "0.0.38" username: "slurm_admin" token: "${SLURM_TOKEN}" cluster_id: "slurm-cluster"
comanage-provisioner: type: "comanage-identity-provisioner" enabled: true registry: url: "https://comanage.example.org" co_id: 1 api_user: "comanage_api_user" api_key: "${COMANAGE_API_KEY}" unix_cluster: id: 10 person_id_type: "eppn" provisioning: custos_cluster_id: "cluster-001" default_shell: "/bin/bash" homedir_prefix: "/home/" http_timeout: "30s"
amie-processor: type: "amie-processor" enabled: true credentials: base_url: "https://amie.xsede.org" site_code: "XSEDE" api_key: "${AMIE_API_KEY}" cluster: id: "cluster-001" polling: poll_interval: "30s" worker_interval: "5s" poller_enabled: true timeouts: connect_timeout: "5s" read_timeout: "20s"
The configuration parser supports environment variable substitution using the ${VAR_NAME} syntax. This allows you to:
core: database: url: "${DATABASE_URL}"
In your shell:
export DATABASE_URL="admin:admin@tcp(localhost:3306)/custos?parseTime=true&charset=utf8mb4" ./custos
If an environment variable referenced in the config file is not set, it will remain unexpanded:
token: "${MISSING_TOKEN}" # Will not be replaced if MISSING_TOKEN is not set
A small set of process-level knobs aren‘t in the YAML — they’re read directly from the environment at boot.
| Variable | Default | Purpose |
|---|---|---|
CONFIG_PATH | config/custos.yaml | Override the config file location. |
DB_MAX_OPEN_CONNS | 25 | Maximum open database connections. |
DB_MAX_IDLE_CONNS | 5 | Maximum idle database connections. |
The configuration supports any number of connectors with any names. Each connector is identified by its type field which determines which loader is used:
connectors: connector-name: # Can be any identifier type: "slurm-association-mapper" # Determines which loader to use enabled: true # Connector-specific configuration
To disable a connector, set enabled: false:
connectors: slurm-mapper: type: "slurm-association-mapper" enabled: false # Rest of configuration is still required but will be ignored
When a connector is disabled:
slurm-association-mapper - SLURM Association Mapperslurm-usage-monitor - SLURM Usage Monitorcomanage-identity-provisioner - COmanage Identity Provisioneramie-processor - AMIE ProcessorNew connector types can be added by:
internal/connectors/loader.goconnectorLoaders mapThe configuration is validated during parsing. Common issues:
core.database.url is missing, the server will fail to start${VAR} will be left as-is if not setConfiguration loading is logged at the INFO level:
{
"time": "2026-06-12T10:30:45Z",
"level": "INFO",
"msg": "loaded config",
"path": "config/custos.yaml"
}
Connector loading logs indicate which connectors are enabled or disabled:
{
"time": "2026-06-12T10:30:46Z",
"level": "INFO",
"msg": "loading SLURM Association Mapper connector"
}
To add a new connector type:
LoadConnector functioninternal/connectors/loader.go to import the new connectorconnectorLoaders map:connectorLoaders := map[string]func(context.Context, *sqlx.DB, *events.Bus, *service.Service, *sync.WaitGroup, *http.ServeMux, *config.ConnectorConfig) error{ "my-new-connector": mynewconnector.LoadConnector, // ... existing connectors }
No code changes are required elsewhere — the configuration system is fully extensible.
See config/custos.yaml for a complete example configuration with all connectors.