Skip to content

feat: TOML config file support ('~/.config/all-smi/config.toml') #192

Description

@inureyes

Summary

Introduce a user-level TOML configuration file so operators can persist common settings (hostfile path, update interval, alert thresholds, $/kWh, theme) without retyping CLI flags on every invocation. Provide a well-defined precedence chain (CLI flag > env var > config file > compiled default), plus all-smi config print and all-smi config init helpers.

Motivation

all-smi has accumulated many run-time options across the three subcommands (local, view, api) and will acquire more as upcoming features land (alerts, energy pricing, record defaults, themes, agentless SSH). All of them currently live on the CLI. For a power user managing a fleet, retyping flags each session is friction; for a team, sharing a config file is far simpler than sharing a shell alias. A config file also provides the single natural home for the [alerts] and [energy] sections referenced by the threshold-alerts issue and the energy-accounting issue.

Current state

  • src/common/config.rs contains compile-time constants in AppConfig/EnvConfig.
  • No config file reader exists.
  • CLI flags are the only user-facing input.

Proposed design

File location

Standard platform locations, with --config <path> always overriding.

  • Linux: $XDG_CONFIG_HOME/all-smi/config.toml (fallback ~/.config/all-smi/config.toml)
  • macOS: ~/Library/Application Support/all-smi/config.toml (fallback ~/.config/all-smi/config.toml accepted for parity)
  • Windows: %APPDATA%\all-smi\config.toml

Schema

# all-smi config — all fields optional; omitted fields fall back to the built-in default.

[general]
default_mode = "local"            # "local" | "view" | "api"
theme = "auto"                    # "auto" | "light" | "dark" | "high-contrast" | "mono"
locale = "en"

[local]
interval_secs = 2                 # 0 = adaptive default

[view]
hostfile = "~/.config/all-smi/hosts.csv"
hosts = []
interval_secs = 0                 # 0 = adaptive based on host count

[api]
port = 9090
socket = false                    # bool or path
processes = false
interval_secs = 3

[alerts]
enabled = true
temp_warn_c = 80
temp_crit_c = 90
util_idle_pct = 5
util_idle_warn_mins = 15
hysteresis_c = 2
bell_on_critical = false
webhook_url = ""

[energy]
price_per_kwh = 0.12
currency = "USD"
show_cost = true
wal_path = "~/.cache/all-smi/energy-wal.bin"

[display]
color_scheme = "default"          # "default" | "colorblind" | "mono"
gauge_style = "blocks"            # "blocks" | "braille"
show_led_grid = true

[record]
output_dir = "~/.cache/all-smi/records"
compress = "zstd"                 # "zstd" | "gzip" | "none"

[snapshot]
default_format = "json"
default_pretty = true

Precedence

From highest to lowest priority:

  1. Explicit CLI flag (e.g., --port 9091).
  2. Environment variable (ALL_SMI_API_PORT=9091).
  3. Config file.
  4. Compiled default.

Implementation: parse CLI into a struct with Option<T> fields, merge env into same struct, merge config file, finally fill remaining None with defaults. Keep the merge order explicit; document precedence in the error message when a conflict is ambiguous.

Helpers

  • all-smi config init [--force] writes a commented example config to the default path. Does not overwrite unless --force.
  • all-smi config print [--format toml|json] prints the final merged effective configuration. Useful for debugging flag interaction.
  • all-smi config validate [<path>] parses a config file and reports errors (with line/column numbers from the toml crate). Returns exit 0 on valid, 2 on parse error.

Env var mapping

Canonical pattern: ALL_SMI_<SECTION>_<KEY> (upper-snake). Examples:

  • ALL_SMI_VIEW_HOSTFILE=/etc/hosts.csv
  • ALL_SMI_ALERTS_TEMP_CRIT_C=95
  • ALL_SMI_ENERGY_PRICE_PER_KWH=0.18

Env var parsing uses the same schema; unknown names produce warnings in config print.

Implementation plan

Files to add / modify:

  • New src/common/config_file.rsSettings root struct, typed sections, serde derives, validators, load(path: Option<Path>) returning the merged Settings.
  • Add toml = "0.9" (or latest compatible) to Cargo.toml, gated by the cli feature alongside clap since config is a runtime concern.
  • src/common/config.rs — refactor constants into Defaults and expose both the Defaults and the loaded Settings for call sites. All mutable runtime behaviors pull from Settings.
  • src/cli.rs:
    • Add global --config <path> argument.
    • Add new subcommand Config(ConfigArgs) with Init/Print/Validate variants.
    • Rewire LocalArgs/ViewArgs/ApiArgs so interval, hostfile, hosts, port, etc. are Option<T> and fall back through the precedence chain.
  • src/main.rs — after parsing CLI, load config, merge env, build a single Settings handle passed to each mode entry point.
  • New src/common/paths.rs — helpers for XDG / macOS / Windows config paths, ~ expansion.
  • Each subcommand entry (local/view/api/snapshot/doctor/record) reads its parameters from Settings with --flag overrides — update all entry points accordingly.
  • src/api/server.rs, src/view/runner.rs, src/view/data_collection/* — consume Settings rather than passing around ad-hoc args.

Acceptance criteria

  • A fresh install works with no config file (defaults apply).
  • all-smi config init creates a commented config at the default path; does not overwrite without --force.
  • all-smi config print shows the merged effective config. With --format json, output is valid JSON.
  • all-smi config validate reports line/column for TOML parse errors and semantic errors (e.g., theme = "rainbow").
  • CLI flags override config; env vars override config but not flags. Documented and tested.
  • Malformed config prints a clear error to stderr and exits 2 — does not silently apply partial config.
  • ~ expansion and $XDG_CONFIG_HOME/%APPDATA% all resolve correctly on the respective platforms.
  • Backward compat: every existing CLI flag in local, view, api continues to work with identical semantics.
  • Unit tests cover: precedence (CLI > env > file > default), malformed sections, unknown keys (warn in print, reject in validate --strict), path expansion.
  • README gains a "Configuration" section; DEVELOPERS.md documents the Settings refactor.

Edge cases & non-goals

  • Unknown keys: print warns, validate without --strict accepts them (forward compat); validate --strict rejects.
  • Schema version: include schema_version = 1 at the top level; raise an error for unsupported future versions rather than silently loading.
  • Config reload: out of scope — restart to pick up changes. Document this explicitly.
  • Secret handling: webhook_url may contain tokens. print redacts by default (webhook_url = "<redacted>"); --show-secrets prints fully.
  • Non-goal: JSON or YAML support. TOML only for consistency and human-editability.
  • Non-goal: per-user merged configs (system-wide + user). Keep just user-level for v1; a /etc/all-smi/config.toml fallback can follow later.

Soft dependencies

  • The alerts/filter issue and the energy issue assume [alerts] and [energy] sections; they can ship independently with CLI/env overrides, but gain ergonomics once this lands.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions