legion-settings

Configuration management module for the LegionIO framework. Loads settings from JSON files, directories, and environment variables. Provides a unified Legion::Settings[:key] accessor used by all other Legion gems.

Version: 1.3.27

Installation

gem install legion-settings

Or add to your Gemfile:

gem 'legion-settings'

Usage

require 'legion/settings'

Legion::Settings.load                          # loads defaults, env, DNS bootstrap, and nearest .legionio.env
Legion::Settings.load(config_dir: './settings') # also loads all .json files in the directory

Legion::Settings[:client][:hostname]
Legion::Settings.dig(:transport, :connection, :host)

[] and dig will auto-load settings on first access, and implicit access follows the same overlay/project-env/base precedence as explicit load.

Config Loading

Legion::Settings.load only consumes the paths you pass via config_file, config_dir, or config_dirs.

If a caller wants the canonical Legion search directories, use Legion::Settings::Loader.default_directories:

  1. ~/.legionio/settings
  2. /etc/legionio/settings on Unix-like systems
  3. %APPDATA%\\legionio\\settings on Windows when APPDATA is present

LegionIO uses those directories during daemon boot. Library consumers can choose to pass any directory set they want.

Each Legion module registers its own defaults via merge_settings during startup, and the nearest .legionio.env file is merged on top of base settings. Request overlays applied through with_overlay take highest precedence.

Hot Reload

Legion::Settings.reload! re-reads the config files that were previously loaded, reapplies module defaults and the nearest .legionio.env, re-resolves secret references, and returns a hash describing the changed keys.

changes = Legion::Settings.reload!

changes
# {
#   "llm.default_model" => { old: "old-model", new: "new-model" }
# }

Callbacks run only when changes are detected:

Legion::Settings.on_reload do |changes|
  Legion::Settings.logger.info("Settings changed: #{changes.keys.join(', ')}")
end

watch! installs a SIGHUP handler when the platform supports it. Repeated signals are coalesced through one background reload worker, so rapid SIGHUP bursts do not create unbounded reload threads.

Legion::Settings.watch! do |changes|
  Legion::Settings.logger.info("Reloaded #{changes.size} setting(s)")
end

# Later, from a shell:
# kill -HUP <daemon_pid>

On platforms without HUP, watch! logs and returns without raising. Direct reload! remains available for API endpoints, tests, or environments that use a different process-control mechanism.

Project Environment Overrides

When present, the nearest .legionio.env file is loaded after base settings and module defaults. Dot notation maps to nested settings:

llm.default_model=claude-sonnet
cache.driver=redis

Hot reload picks up changes to this file as part of the same reload! flow.

Secret Resolution

Settings values can reference external secret sources using URI syntax. Three schemes are supported:

Scheme Format Resolution
vault:// vault://path/to/secret#key Reads static KV secrets from HashiCorp Vault via Legion::Crypt
env:// env://ENV_VAR_NAME Reads from environment variable
lease:// lease://name#key Reads from dynamic Vault leases via Legion::Crypt::LeaseManager

Array values act as fallback chains — the first non-nil result wins:

{
  "transport": {
    "connection": {
      "password": ["vault://secret/data/rabbitmq#password", "env://RABBITMQ_PASSWORD", "guest"]
    }
  }
}

Call Legion::Settings.resolve_secrets! to resolve all URIs in-place. In the LegionIO boot sequence this is called automatically after Legion::Crypt.start. The env:// scheme works even when Vault is not connected.

Legion::Settings.resolve_secrets!
# All vault://, env://, and lease:// references are now replaced with their resolved values

Schema Validation

Types are inferred automatically from default values. Optional constraints can be added:

Legion::Settings.merge_settings('mymodule', { host: 'localhost', port: 8080 })
Legion::Settings.define_schema('mymodule', { port: { required: true } })
Legion::Settings.validate!  # raises ValidationError if any settings are invalid

# In development, warn instead of raising:
# Set LEGION_DEV=true or Legion::Settings.set_prop(:dev, true)
# validate! will warn through the configured logger instead of raising

Logging Defaults

The logging key includes a transport sub-section that controls whether log events are forwarded over the message bus:

{
  "logging": {
    "level": "info",
    "format": "text",
    "log_file": "./legionio/logs/legion.log",
    "log_stdout": true,
    "trace": true,
    "async": true,
    "include_pid": false,
    "transport": {
      "enabled": true,
      "forward_logs": true,
      "forward_exceptions": true
    }
  }
}

When transport.enabled is true, log events and unhandled exceptions are published to the AMQP bus so a central log consumer can aggregate them.

Requirements

  • Ruby >= 3.4
  • legion-json

License

Apache-2.0