# DSD-neo Configuration System This document describes the configuration file system in `dsd-neo`, covering file format, options, profiles, validation, and CLI integration. The goal is to let users avoid re-entering common options on every run while preserving strict backward compatibility with existing CLI / environment workflows. --- ## Goals - **Persist user preferences** such as input source, output backend, decode mode, trunking basics, and UI behavior. - **Non-breaking behavior** for existing users: - If no config file exists, behavior is identical to current releases. - CLI arguments and environment variables always take precedence over config file values. - **Simple INI-style format** that's easy to read and edit. - **Future-proof**: Config files are versioned; unknown keys/sections are ignored on load (use `--validate-config` to report warnings). - **Validation** with helpful diagnostics including line numbers. - **User experience enhancements**: - Template generation (`--dump-config-template`). - Path expansion (`~`, `$VAR`, `${VAR}`). - Profile support for switching between configurations. - Include directive for modular configs. --- ## Config File Format We use a minimal INI-style format: - Sections: `[section-name]` - Key-value pairs: `key = value` - Comments: lines starting with `#` or `;` - Values: - Strings (unquoted or double-quoted). - Integers (parsed with `strtol`). - Booleans (`true` / `false`, `yes` / `no`, `on` / `off`, `1` / `0`). - Paths (support `~` and `$VAR` expansion). - Frequencies (support K/M/G suffix, e.g., `851.375M`). Design principles: - **Whitespace is ignored** around keys, the `=` sign, and values. - **Case-insensitive booleans** (e.g., `True`, `YES`, `0`). - **Unknown sections/keys are ignored** during normal startup; `--validate-config` reports them as warnings. - A top-level `version` key defines the config schema version. Example: ```ini version = 1 [input] source = "rtl" # pulse / rtl / rtltcp / soapy / file / tcp / udp rtl_device = 0 rtl_freq = "851.375M" # supports K/M/G suffix or raw Hz [output] backend = "pulse" # pulse / null ncurses_ui = true # map to -N / use_ncurses_terminal [alerts] enabled = false # map to -a / call alert toggle voice_start = true voice_end = true data = true [trunking] enabled = true chan_csv = "~/dsd-neo/dmr_t3_chan.csv" # path expansion supported group_csv = "$HOME/dsd-neo/group.csv" allow_list = true [mode] decode = "auto" # auto / p25p1 / p25p2 / dmr / nxdn48 / ... ``` --- ## Path Expansion Configuration values of type `PATH` support shell-like expansion: - `~` expands to the user's home directory (`$HOME` on Unix, `%USERPROFILE%` on Windows). - `$VAR` expands to environment variable `VAR`. - `${VAR}` expands to environment variable `VAR` (braced form). - Missing variables expand to empty string (no error). Path expansion is applied to: - Explicit config paths passed through `--config`, a single positional `*.ini`, `DSD_NEO_CONFIG`, and interactive config load/save prompts - `[input] file_path` - `[trunking] chan_csv` - `[trunking] group_csv` - `[trunk_scan] targets_csv` - `[logging] event_log` - `[logging] frame_log` - `[logging] p25_sm_log` - `[recording] per_call_wav_dir` - `[recording] static_wav` - `[recording] raw_wav` - Include directive paths --- ## Profile Support Profiles allow defining multiple named configurations in a single file. Use `--profile NAME` to activate a specific profile. ### Syntax Profiles are defined using `[profile.NAME]` sections with dotted key syntax: ```ini version = 1 [input] source = "pulse" [mode] decode = "auto" [trunking] enabled = false [profile.p25_trunk] # Dotted syntax: section.key = value mode.decode = "p25p1" trunking.enabled = true trunking.chan_csv = "~/p25/channels.csv" [profile.local_dmr] input.source = "rtl" input.rtl_device = 0 input.rtl_freq = "446.5M" input.rtl_gain = 30 mode.decode = "dmr" [profile.scanner] mode.decode = "auto" output.ncurses_ui = true ``` ### Usage ```bash # Load base config dsd-neo --config config.ini # Load base config with profile overlay dsd-neo --config config.ini --profile p25_trunk # List available profiles dsd-neo --config config.ini --list-profiles ``` The ncurses Config menu also supports `Load Profile...`, which lists profiles from the active config path and applies the selected overlay to the running session. ### Behavior - Base config is loaded first. - Profile keys override base config values. - If `--profile NAME` is specified but profile doesn't exist, an error is returned. - Profile sections do not affect base loading (they are skipped unless `--profile` is specified). --- ## Include Directive Config files can include other files using the `include` directive: ```ini # Main config file include = "/etc/dsd-neo/system.ini" include = "~/.config/dsd-neo/local.ini" include = "site-overrides.ini" # relative to this config file's directory version = 1 [input] source = "rtl" # overrides anything from includes ``` ### Rules - Include directives must appear **before** any section headers. - Paths support expansion (`~`, `$VAR`, `${VAR}`). - Maximum include depth: 3 (to prevent infinite recursion). - Circular includes are detected and silently skipped. - Included files are processed first; main file values override includes. - Profile sections in included files are ignored (profiles are only read from the main config file). --- ## Config Discovery and Locations Config loading is **opt-in**. By default, no config file is loaded unless explicitly requested. ### Enabling Config - CLI: `--config` (uses default path) or `--config /path/to/config.ini` - Convenience: `dsd-neo /path/to/config.ini` (single positional `*.ini`) is treated as `--config /path/to/config.ini`. - Environment: `DSD_NEO_CONFIG=/path/to/config.ini` - Explicit paths may be absolute, relative to the current working directory, or use `~`/environment expansion. When both are present, CLI wins: 1. `--config PATH` (explicit path) 2. Default path (when `--config` is passed without a path; this ignores `DSD_NEO_CONFIG`) 3. `DSD_NEO_CONFIG` If the file cannot be read or parsed: - Log a warning (path + reason). - Proceed without applying any user config values (defaults + env + CLI). - If config loading was enabled (`--config` or `DSD_NEO_CONFIG`), autosave is still enabled for decode runs and the effective settings are written back on exit (this can create the file). - If `--profile NAME` is specified and that profile is missing, startup fails instead of falling back silently. ### Default Path When `--config` is passed without a path argument, the default per-user config location is used: - On Unix-like systems: - If `$XDG_CONFIG_HOME` is set: - `${XDG_CONFIG_HOME}/dsd-neo/config.ini` - Else: - `${HOME}/.config/dsd-neo/config.ini` - On Windows: - `%APPDATA%\dsd-neo\config.ini` If the default file does not exist, it will be created when settings are saved (DSD-neo autosaves on exit whenever config loading is enabled). Autosave is disabled when an explicit `--profile NAME` is loaded successfully, because saving the effective config would flatten the profile overlay back into the base file and remove profile sections. --- ## Precedence Rules For the keys covered by this user config system: 1. **Built-in defaults** 2. **User config file** (explicit or default path, when enabled) 3. **CLI arguments** (highest priority) Environment variables are separate advanced runtime knobs (some CLI flags set `DSD_NEO_*` env vars). Most are not persisted in the user config file, but a small subset is exposed as config keys for convenience (for example `[input] auto_ppm`, `[dsp] iq_balance`, `[dsp] iq_dc_block`). --- ## Configuration Reference **[input] section:** | Key | Type | Description | Default | |-----|------|-------------|---------| | `source` | ENUM | Input source type (`pulse|rtl|rtltcp|soapy|file|tcp|udp`) | `pulse` | | `pulse_source` | STRING | PulseAudio source device | (empty) | | `pulse_input` | STRING | Deprecated alias for `pulse_source` | (empty) | | `rtl_device` | INT (0-255) | RTL-SDR device index | `0` | | `rtl_freq` | FREQ | RTL-SDR frequency | `851.375M` | | `rtl_gain` | INT (0-49) | RTL-SDR gain in dB | `0` | | `rtl_ppm` | INT (-1000-1000) | Frequency correction | `0` | | `rtl_bw_khz` | INT (4-48) | DSP bandwidth | `48` | | `rtl_sql` | INT (-100-0) | Squelch level | `0` | | `rtl_volume` | INT (1-3) | RTL monitor/non-symbol gain multiplier | `2` | | `auto_ppm` | BOOL | Enable carrier/error-based RTL auto-PPM correction | `false` | | `rtl_auto_ppm` | BOOL | Deprecated alias for `auto_ppm` | `false` | | `rtltcp_host` | STRING | RTL-TCP hostname | `127.0.0.1` | | `rtltcp_port` | INT (1-65535) | RTL-TCP port | `1234` | | `soapy_args` | STRING | SoapySDR device selection args (from SoapySDRUtil `--find`/`--probe`) | (empty) | | `soapy_settings` | STRING | SoapySDR driver settings (`key=value`, `rx:key=value`) | (empty) | | `file_path` | PATH | Input file path (WAV/BIN/RAW/SYM) | (empty) | | `file_sample_rate` | INT (8000-192000) | File sample rate (WAV/RAW) | `48000` | | `tcp_host` | STRING | TCP PCM input host | `127.0.0.1` | | `tcp_port` | INT (1-65535) | TCP PCM input port | `7355` | | `udp_addr` | STRING | UDP bind address | `127.0.0.1` | | `udp_port` | INT (1-65535) | UDP port | `7355` | **[output] section:** | Key | Type | Description | Default | |-----|------|-------------|---------| | `backend` | ENUM | Audio output backend (`pulse|null`) | `pulse` | | `pulse_sink` | STRING | PulseAudio sink device | (empty) | | `pulse_output` | STRING | Deprecated alias for `pulse_sink` | (empty) | | `ncurses_ui` | BOOL | Enable ncurses UI | `false` | **[mode] section:** | Key | Type | Description | Default | |-----|------|-------------|---------| | `decode` | ENUM | Decode mode preset | `auto` | | `demod` | ENUM | Demodulator path | `auto` | **[trunking] section:** | Key | Type | Description | Default | |-----|------|-------------|---------| | `enabled` | BOOL | Enable trunking | `false` | | `chan_csv` | PATH | Channel map CSV | (empty) | | `group_csv` | PATH | Group list CSV | (empty) | | `allow_list` | BOOL | Use as allow list | `false` | | `tune_group_calls` | BOOL | Follow group calls | `true` | | `tune_private_calls` | BOOL | Follow private calls | `true` | | `tune_data_calls` | BOOL | Follow data calls | `false` | | `tune_enc_calls` | BOOL | Follow encrypted calls | `true` | **[trunk_scan] section:** | Key | Type | Description | Default | |-----|------|-------------|---------| | `enabled` | BOOL | Enable single-tuner trunk scan | `false` | | `targets_csv` | PATH | Scan target list CSV | (empty) | | `idle_dwell_ms` | INT (250-600000) | Default idle dwell per target | `3000` | | `activity_hold_ms` | INT (250-600000) | Conventional DMR activity hold | `1200` | **[logging] section:** | Key | Type | Description | Default | |-----|------|-------------|---------| | `event_log` | PATH | Event history log file path | (empty) | | `frame_log` | PATH | Frame trace log file path | (empty) | | `p25_sm_log` | PATH | P25 state-machine health log file path | (empty) | **[alerts] section:** | Key | Type | Description | Default | |-----|------|-------------|---------| | `enabled` | BOOL | Enable audible call-alert beeps | `false` | | `call_alert` | BOOL | Deprecated alias for `enabled` | `false` | | `voice_start` | BOOL | Beep when a voice call starts | `true` | | `start` | BOOL | Deprecated alias for `voice_start` | `true` | | `voice_end` | BOOL | Beep when a voice call ends | `true` | | `end` | BOOL | Deprecated alias for `voice_end` | `true` | | `data` | BOOL | Beep when a data call is logged | `true` | **[recording] section:** | Key | Type | Description | Default | |-----|------|-------------|---------| | `per_call_wav` | BOOL | Enable per-call decoded voice WAV output | `false` | | `per_call_wav_dir` | PATH | Per-call WAV output directory | `./WAV` | | `static_wav` | PATH | Static decoded voice WAV output file | (empty) | | `raw_wav` | PATH | Raw (48 kHz) audio WAV output file | (empty) | | `rdio_mode` | ENUM | rdio export mode: off/dirwatch/api/both | `off` | | `rdio_system_id` | INT | rdio-scanner numeric system ID | `0` | | `rdio_api_url` | STRING | rdio API base URL | `http://127.0.0.1:3000` | | `rdio_api_key` | STRING | rdio API key | (empty) | | `rdio_upload_timeout_ms` | INT | rdio API timeout per call in ms | `5000` | | `rdio_upload_retries` | INT | rdio API upload attempts per call | `1` | | `rdio_api_delete_after_upload` | BOOL | Delete per-call WAV after successful API-only upload | `false` | Note: `per_call_wav` and `static_wav` are mutually exclusive (same as `-P` vs `-w` on the CLI). For RAM-backed rdio API staging, set `per_call_wav_dir` to a tmpfs/RAM-disk path and enable `rdio_api_delete_after_upload`. DirWatch modes keep files for watcher ingestion. Rdio API uploads do not follow HTTP redirects. Configure `rdio_api_url` as the final trusted HTTP/HTTPS endpoint. **[dsp] section:** | Key | Type | Description | Default | |-----|------|-------------|---------| | `iq_balance` | BOOL | Enable RTL IQ balance (image suppression) | `false` | | `iq_dc_block` | BOOL | Enable RTL I/Q DC blocker | `false` | Note: The defaults shown match the generated template (`--dump-config-template`). Missing keys generally mean “leave the engine default unchanged”; some input sources require specific keys to actually switch the input at startup (see Notes on Input Sources). --- ## Validation The config system validates files and reports issues with line numbers: - **Error**: Invalid enum value, type mismatch, parse failure - **Warning**: Unknown key or section, integer out of range - **Info**: Deprecated key usage (key still works) ```bash # Validate a config file dsd-neo --validate-config /path/to/config.ini # Validate with strict mode (warnings become errors) dsd-neo --validate-config /path/to/config.ini --strict-config ``` Exit codes: - `0`: No errors (may have warnings) - `1`: Errors present - `2`: Only warnings (strict mode only) Diagnostics are only printed when you run `--validate-config`. During normal startup, DSD-neo logs a notice when a config is loaded and warns if loading fails; unknown keys are ignored without warnings. --- ## Template Generation Generate a commented config template with all options: ```bash dsd-neo --dump-config-template > config.ini ``` Output format: ```ini # DSD-neo configuration template # Generated by: dsd-neo --dump-config-template # # Uncomment and modify values as needed. # Lines starting with # are comments. # # Precedence: CLI arguments > environment variables > config file > defaults version = 1 [input] # Input source type # Allowed: pulse|rtl|rtltcp|soapy|file|tcp|udp # source = "pulse" # RTL-SDR device index (0-based) # Range: 0 to 255 # rtl_device = 0 # RTL-SDR frequency (supports K/M/G suffix) # Frequency (supports K/M/G suffix) # rtl_freq = "851.375M" ... ``` --- ## Notes on Input Sources - **RTL-SDR (`source = "rtl"`)**: Uses the `rtl_*` keys for frequency, gain, PPM correction, bandwidth, squelch, monitor gain, and auto-PPM. Omitted values use sensible defaults. To switch the input to RTL at startup, set at least `rtl_freq` (and optionally `rtl_device`). Digital RTL decode uses direct DSP output: FSK feeds centered discriminator samples into the sample-domain symbolizer, while CQPSK feeds symbol-rate samples. `rtl_volume` affects only the separate monitor/non-symbol audio path. - **RTL-TCP (`source = "rtltcp"`)**: Uses `rtltcp_host`/`rtltcp_port` for the network endpoint, plus the same `rtl_*` tuning keys. To switch the input to RTL-TCP at startup, set at least `rtltcp_host`. - **SoapySDR (`source = "soapy"`)**: - Requires SoapySDR 0.8.1 or newer when the backend is enabled. - Uses `soapy_args` for device selection only (same semantics as CLI `-i soapy[:args]`). - CLI also supports optional shorthand tuning: `-i soapy[:args]:freq[:gain[:ppm[:bw[:sql[:vol]]]]]`. - Reuses existing `rtl_*` tuning keys (`rtl_freq`, `rtl_gain`, `rtl_ppm`, `rtl_bw_khz`, `rtl_sql`, `rtl_volume`) so trunking and retune behavior remains unchanged. - Digital decode uses the same FSK discriminator and CQPSK symbol contracts as RTL USB, RTL-TCP, and IQ replay. - `rtl_volume` remains a monitor/direct-output gain key; it does not scale RTL-family digital decode samples. - `rtl_device` and `rtltcp_*` endpoint keys are not used in Soapy mode. - Set `rtl_freq` explicitly for predictable startup frequency with non-RTL radios. - If frequency resolves to `0`, radio startup fails with `Please specify a frequency.` - Verify Soapy install and plugin discovery with `SoapySDRUtil --info`. - Use `SoapySDRUtil --find` / `SoapySDRUtil --probe=""` to discover valid `soapy_args`. - **PulseAudio (`source = "pulse"`)**: Use `pulse_source` to specify a particular input device. The older `pulse_input` key is accepted as an alias. - **TCP (`source = "tcp"`)**: Set `tcp_host` (port optional) to switch the input to TCP PCM audio (raw PCM16LE mono). Sample rate uses the global `-s` CLI setting (default 48000). - **UDP (`source = "udp"`)**: Set `udp_addr` (port optional) to switch the input to UDP PCM audio (PCM16LE datagrams). Sample rate uses the global `-s` CLI setting (default 48000). - **File (`source = "file"`)**: Set `file_path` to switch the input to a file input (WAV/BIN/RAW/SYM). Use `file_sample_rate` for PCM16 WAV/RAW inputs that are not 48 kHz (symbol capture formats ignore it). ### Decode Modes The `decode` key in `[mode]` configures the frame types and modulation. Supported values: `auto`, `p25p1`, `p25p2`, `dmr`, `nxdn48`, `nxdn96`, `x2tdma`, `ysf`, `dstar`, `edacs_pv`, `dpmr`, `m17`, `tdma`, `analog`. Compatibility aliases are also accepted: `p25p1_only`, `p25p2_only`, `edacs`, `provoice`, and `analog_monitor`. The optional `demod` key selects a demodulator path (`auto`, `c4fm`, `gfsk`, `qpsk`). When set, it locks demodulator selection similarly to the `-m*` CLI modulation options. If you choose RTL/RTLTCP/Soapy input and omit specific tuning fields, DSD-neo falls back to its built-in radio defaults: center frequency 850 MHz, DSP bandwidth 48 kHz, and monitor gain multiplier 2. The template still shows 851.375M as the example frequency. ### Soapy Config Usage ```ini [input] source = "soapy" soapy_args = "driver=sdrplay,serial=123456" soapy_profile = "sdrplay" soapy_stream_format = "auto" soapy_settings = "rfnotch_ctrl=true,dabnotch_ctrl=true,biasT_ctrl=false,agc_setpoint=-30,rfgain_sel=4" soapy_gains = "IFGR:35" soapy_bandwidth_hz = 200000 # Soapy tuning reuses rtl_* keys rtl_freq = "851.375M" rtl_gain = 22 rtl_ppm = -2 rtl_bw_khz = 24 rtl_sql = 0 rtl_volume = 2 ``` If you omit `soapy_args`, DSD-neo uses the default Soapy device args (equivalent to `-i soapy`). For multiple identical devices, prefer including a stable selector like `serial=...`. Soapy-specific keys are optional: profiles default to `auto`, stream format defaults to `auto`, `soapy_settings` is applied only when present, named gains are only applied when `soapy_gains` is present, and `soapy_bandwidth_hz = 0` leaves bandwidth selection to the driver. `soapy_settings` accepts comma/semicolon-separated `key=value` items for device settings, plus `rx:key=value` or `rx0:key=value` for RX channel 0 settings. Startup fails if an item is malformed, the scope is unknown, the key/value is rejected by Soapy metadata, or the driver rejects `writeSetting`. ### Soapy Troubleshooting - If device discovery is empty, run `SoapySDRUtil --find` first and verify SoapySDR 0.8.1 or newer plus your hardware module are installed. - If Soapy devices/modules are not discovered, confirm plugin discovery path via `SOAPY_SDR_PLUGIN_PATH`. - Driver capabilities differ by hardware: some devices may not support PPM correction, bandwidth selection, or manual gain range controls. - Native SDRplay/Airspy APIs are not used by this backend; driver-specific controls are exposed through Soapy settings when the installed Soapy module provides them. - Expected sample-rate and gain behavior can vary by driver; requested values may be quantized/clamped to supported ranges. - See `docs/soapysdr.md` for a full non-RTL setup flow. ### Trunking When `[trunking] enabled = true`: - Trunking is activated for the selected mode. - CSV paths (`chan_csv`, `group_csv`) are passed to the decoder. - CSV paths in the config are applied the same as passing `-C`/`-G` and are loaded when trunking is enabled. - CSV formats and examples are documented in `docs/csv-formats.md` and `examples/`. - If you start DSD-neo with any CLI args and you do not explicitly set trunking or scan mode (`-T`/`-Y`), trunking inherited from the config is disabled for that run. When `[trunk_scan] enabled = true`: - `targets_csv` is required and must use the target format in `docs/csv-formats.md`. - Global `[trunking] chan_csv` is rejected; each trunk target must name its own `chan_csv` if it needs a channel map. - The group policy remains global, so `[trunking] group_csv`, `allow_list`, and tune controls apply uniformly across all scan targets. - One tuner is rotated across targets. Calls on systems that are not currently parked can be missed. - Runtime still needs a retuning path, either RTL-family input opened by DSD-neo or rigctl tuning. IQ replay is rejected. - Full user workflow, examples, and troubleshooting: `docs/trunk-scan.md`. Example: ```ini [input] source = "rtl" rtl_freq = "851.0125M" rtl_gain = 22 rtl_bw_khz = 48 [mode] decode = "tdma" [trunking] group_csv = "~/radio/group.csv" [trunk_scan] enabled = true targets_csv = "~/radio/trunk_scan_targets.csv" idle_dwell_ms = 3000 activity_hold_ms = 1200 ``` Config/CLI interaction: - `--validate-config` reports an error when `trunk_scan.enabled = true` lacks `targets_csv`. - `--validate-config` reports an error when trunk scan and `[trunking] chan_csv` are both enabled. - If trunk scan is inherited from a config file, one-off CLI arguments that select another input, mode, channel map, file/replay input, trunking mode, or legacy scan mode disable the inherited scan for that run. UI-only flags such as `-N` and trunk-scan timing overrides keep the inherited scan enabled. - Explicit profile runs preserve the profile's trunk scan settings and disable autosave for that process, like other profile-based runs. --- ## Startup Behavior 1. Config is loaded early, before CLI argument parsing. 2. Config values are applied, then CLI arguments override them. 3. One-shot commands (`--dump-config-template`, `--validate-config`, `--list-profiles`, `--print-config`) execute and exit immediately. Before `--print-config` renders, Soapy shorthand input specs are normalized into `soapy_args` plus shared `rtl_*` tuning keys. Explicit `soapy_settings` values are rendered under their normalized key. 4. If no CLI args and no config is loaded, the interactive bootstrap wizard runs. 5. When a config is loaded: interactive bootstrap is skipped unless `--interactive-setup` is specified. ### File Sample Rate When using `source = "file"` with a non-48 kHz sample rate: - Set `file_sample_rate` to match your input file. - Symbol timing is adjusted automatically. --- ## Interactive Bootstrap The interactive bootstrap wizard (`--interactive-setup`) guides you through selecting input, mode, trunking, and UI options. ### Auto-Saving - When the wizard completes, settings are automatically saved to the config path (default or explicit). - Auto-save only occurs when config is enabled (`--config`, `DSD_NEO_CONFIG`, or a single positional `*.ini`). - Outside the wizard, the app also auto-saves the effective settings at exit whenever a config path is in use. To avoid having your file rewritten, run without `--config`, without `DSD_NEO_CONFIG`, and without a single positional `*.ini`. - Explicit profile runs (`--profile NAME`) disable autosave for that process. ### Config and CLI Interaction - A no-arg run only loads a config if you enable it via `DSD_NEO_CONFIG`. - If config is enabled and loads successfully and no other CLI args are provided: skip the wizard, use config settings. - If CLI args are provided: config is loaded first, then CLI overrides it. - CLI: - `--interactive-setup` forces running the wizard even if a config exists; the resulting setup is automatically saved to the current config path. --- ## CLI Flags Reference ### Config Loading | Flag | Description | |------|-------------| | `--config [PATH]` | Enable config loading; optionally specify path (uses default if omitted) | | `--print-config` | Print effective config (as INI) and exit | ### Template and Validation | Flag | Description | |------|-------------| | `--dump-config-template` | Print commented template with all options | | `--validate-config [PATH]` | Validate config and report diagnostics | | `--strict-config` | Treat warnings as errors (with `--validate-config`) | ### Profiles | Flag | Description | |------|-------------| | `--profile NAME` | Load named profile (overrides base config) | | `--list-profiles` | List all profile names in config file | ### Bootstrap | Flag | Description | |------|-------------| | `--interactive-setup` | Force interactive wizard even if config exists | --- ## Versioning and Compatibility - Config files use `version = 1` at the top. - If `version` is missing, defaults to 1. - Unknown keys are ignored on load; `--validate-config` reports them as warnings. - This allows newer binaries to add options without breaking older configs. --- ## ncurses UI Config Menu When running with the ncurses UI (`ncurses_ui = true` or `-N`), the **Config** menu provides: - **Save Config (Current)**: Save current settings to the active config path (loaded via `--config` or **Load Config...**). - **Save Config (Default)**: Save current settings to the default config path. - **Save Config As...**: Save to a custom path. - **Load Config...**: Load a config file and apply it to the running session. ### Live Reload The following can be changed without restarting: - PulseAudio input/output device - RTL-SDR, RTL-TCP, and Soapy tuning parameters (frequency, gain, PPM, etc.) - TCP/UDP connection parameters - File input path Switching between different input types (e.g., Pulse → RTL) requires a restart to take full effect. --- ## Summary - INI-based config with clear precedence (CLI > env > config > defaults). - Platform-specific default paths when config loading is explicitly enabled. - Validation with line-number diagnostics. - Template generation for discoverability. - Path expansion (`~`, `$VAR`, `${VAR}`) for portability. - Profile support for switching configurations. - Include directive for modular configs. - Interactive bootstrap can persist user choices automatically.