Documents
Configuration System
Configuration System
Type
Topic
Status
Published
Created
Feb 27, 2026
Updated
Apr 13, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Configuration System#

Overview#

The opnDossier Configuration System is a Viper-based layered configuration management system that provides flexible, hierarchical configuration through multiple sources. The system implements a strict four-level precedence model where command-line flags take highest priority, followed by environment variables, configuration files, and finally built-in defaults. This architecture enables developers to maintain persistent settings in configuration files while allowing easy overrides through environment variables for deployment scenarios and temporary adjustments via CLI flags.

Built on the spf13/viper configuration library and integrated with the Cobra CLI framework, the system provides comprehensive validation, structured error reporting, and specialized configuration management commands. The configuration layer is responsible for configuration file parsing (YAML), environment variable processing with the OPNDOSSIER_ prefix, command-line flag integration, and configuration precedence management.

The system supports both flat and nested configuration structures for backward compatibility, includes format aliases for common output types, enforces mutually exclusive flag constraints, and provides dedicated commands (config init, config show, config validate) for configuration file management and validation.

Architecture and Design#

Core Technology Stack#

The configuration system is built on two primary frameworks:

Configuration Precedence Model#

The system implements a strict four-level precedence hierarchy:

PrioritySourceDescriptionOverride Behavior
1 (Highest)CLI FlagsDirect command-line argumentsOverrides all other sources
2Environment VariablesVariables with OPNDOSSIER_ prefixOverrides config file and defaults
3Configuration FileYAML file at ~/.opnDossier.yamlOverrides defaults only
4 (Lowest)Built-in DefaultsHardcoded sensible defaultsUsed when no other source provides value

This precedence ensures that CLI flags always override environment variables and config files, making it easy to temporarily override settings for specific runs.

Design Principles#

The configuration system follows standard precedence ordering used by most CLI tools, where environment variables can override configuration file settings for deployment flexibility and security. This design enables:

  • Development flexibility - Use configuration files for persistent settings
  • Deployment adaptability - Override settings via environment variables in containerized environments
  • Debugging convenience - Temporarily override any setting with CLI flags
  • Security - Keep sensitive values in environment variables rather than files

Configuration Sources#

Configuration Files#

The default configuration file location is ~/.opnDossier.yaml. You can specify a custom location using the --config flag.

Basic Configuration Example:

# ~/.opnDossier.yaml

# Output settings
verbose: false
quiet: false
format: markdown
wrap: 120

# Display settings
display:
  width: 120
  pager: false
  syntax_highlighting: true

# Logging settings
logging:
  level: info
  format: text

# Export settings
export:
  format: markdown
  directory: ""
  backup: false

# Validation settings
validation:
  strict: false
  schema_validation: false

Using Custom Configuration File:

opndossier --config /path/to/custom/config.yaml convert config.xml

Environment Variables#

All configuration options can be set using environment variables with the OPNDOSSIER_ prefix.

Naming Convention#

Environment variables follow this pattern:

  1. Add OPNDOSSIER_ prefix
  2. Convert key to uppercase
  3. Replace hyphens (-) and dots (.) with underscores (_)

Transformation Examples:

Configuration KeyEnvironment Variable
verboseOPNDOSSIER_VERBOSE
input_fileOPNDOSSIER_INPUT_FILE
no_progressOPNDOSSIER_NO_PROGRESS
display.widthOPNDOSSIER_DISPLAY_WIDTH
logging.levelOPNDOSSIER_LOGGING_LEVEL
export.formatOPNDOSSIER_EXPORT_FORMAT

Value Formats#

Boolean Values:

Boolean environment variables accept various formats:

  • True values: true, TRUE, True, 1
  • False values: false, FALSE, False, 0
export OPNDOSSIER_VERBOSE=true
export OPNDOSSIER_QUIET=false

List Values:

List values use comma-separated strings:

export OPNDOSSIER_SECTIONS="system,network,firewall,dhcp"

String and Numeric Values:

export OPNDOSSIER_FORMAT=json
export OPNDOSSIER_WRAP=120
export OPNDOSSIER_LOGGING_LEVEL=debug

Comprehensive Environment Variable Reference#

The system supports environment variables for all flat and nested configuration options:

Basic Settings:

  • OPNDOSSIER_VERBOSE
  • OPNDOSSIER_QUIET
  • OPNDOSSIER_FORMAT
  • OPNDOSSIER_WRAP
  • OPNDOSSIER_MINIMAL
  • OPNDOSSIER_NO_PROGRESS

Display Settings:

  • OPNDOSSIER_DISPLAY_WIDTH
  • OPNDOSSIER_DISPLAY_PAGER
  • OPNDOSSIER_DISPLAY_SYNTAX_HIGHLIGHTING

Export Settings:

  • OPNDOSSIER_EXPORT_FORMAT
  • OPNDOSSIER_EXPORT_DIRECTORY
  • OPNDOSSIER_EXPORT_BACKUP

Logging Settings:

  • OPNDOSSIER_LOGGING_LEVEL
  • OPNDOSSIER_LOGGING_FORMAT

Validation Settings:

  • OPNDOSSIER_VALIDATION_STRICT
  • OPNDOSSIER_VALIDATION_SCHEMA_VALIDATION

Validate Command Settings:

  • OPNDOSSIER_JSON_OUTPUT - Output validation errors in JSON format (validate command only)

Command-Line Flags#

Global Flags#

Global flags are available on all commands:

FlagShortDefaultDescription
--config""Custom config file path
--verbose-vfalseEnable debug-level logging
--quiet-qfalseSuppress all output except errors
--color"auto"Color output mode (auto, always, never)
--no-progressfalseDisable progress indicators
--timestampsfalseInclude timestamps in log output
--minimalfalseMinimal output mode

Example Usage:

# Enable verbose logging
opndossier --verbose convert config.xml

# Use custom config file
opndossier --config ~/configs/production.yaml convert config.xml

# Suppress progress bars
opndossier --no-progress convert config.xml

Command-Specific Flags#

Convert Command:

Command-specific flags for convert:

FlagShortDefaultDescription
--output-o""Output file path (default: stdout)
--format-f"markdown"Output format
--forcefalseForce overwrite without prompt
--section[]Sections to include
--wrap-1Text wrap width (-1=auto, 0=off)
--no-wrapfalseDisable text wrapping
--comprehensivefalseGenerate comprehensive reports
--include-tunablesfalseInclude all system tunables in report output (markdown, text, HTML only; JSON/YAML always include all tunables)

Display Command:

Command-specific flags for display:

FlagShortDefaultDescription
--theme""Rendering theme (auto, dark, light, none)
--section[]Sections to include
--wrap-1Text wrap width
--no-wrapfalseDisable text wrapping

Validate Command:

Command-specific flags for validate:

FlagShortDefaultDescription
--json-outputfalseOutput validation errors in JSON format

Validation and Constraints#

Mutually Exclusive Flags#

The system enforces mutually exclusive constraints on certain flag combinations:

Verbose and Quiet:

--verbose and --quiet are mutually exclusive. This constraint is enforced using Cobra's native MarkFlagsMutuallyExclusive() feature:

# Error: these flags cannot be used together
opndossier --verbose --quiet convert config.xml
# Error: if any flags in the group [verbose quiet] are set none of the others can be

Wrap and No-Wrap:

--wrap and --no-wrap are mutually exclusive. This is validated using manual flag checking:

# Error: these flags cannot be used together
opndossier convert config.xml --wrap 120 --no-wrap
# Error: --no-wrap and --wrap flags are mutually exclusive

Global Flag Constraints#

Various constraints are applied to global flag values:

Color Mode:

--color must be one of: auto, always, never

# Valid
opndossier --color always convert config.xml

# Invalid
opndossier --color invalid convert config.xml
# Error: invalid color "invalid", must be one of: auto, always, never

Theme:

--theme must be one of: auto, dark, light, none

Note: The theme flag is command-specific to the display command, not a global flag.

Format:

--format must be one of: markdown, md, json, yaml, yml, text, txt, html, htm

Format Aliases#

The system supports format aliases for common formats. Format aliases are defined as constants and normalized via the normalizeFormat() function:

AliasCanonical Format
mdmarkdown
ymlyaml
txttext
htmhtml

Example Usage:

# These are equivalent
opndossier convert config.xml --format markdown
opndossier convert config.xml --format md

# These are equivalent
opndossier convert config.xml --format yaml
opndossier convert config.xml --format yml

Wrap Width Constraints#

The --wrap flag has specific constraints. Constants define the recommended range:

  • Recommended range: 40-200 columns (ideal: 80-120)
  • Special values:
    • -1 - Auto-detect terminal width
    • 0 - Disable wrapping
    • Positive integers - Specific column width

Values outside 40-200 trigger warnings but are still accepted. Values less than -1 trigger errors.

# Recommended range (no warning)
opndossier convert config.xml --wrap 120

# Outside recommended range (warning issued)
opndossier convert config.xml --wrap 250
# Warning: wrap width 250 is outside recommended range [40, 200]

# Invalid value (error)
opndossier convert config.xml --wrap -5
# Error: invalid wrap width -5: must be -1 (auto-detect), 0 (no wrapping), or positive

Validation Error Messages#

The system provides clear validation error messages when constraints are violated:

Validation ErrorSolution
"invalid format"Use: markdown, md, json, yaml, yml, text, txt, html, or htm
"invalid theme value"Use: light, dark, auto, none, or custom
"invalid log level"Use: debug, info, warn, or error
"wrap width must be >= -1"Use -1 (auto), 0 (no wrap), or positive number
"mutually exclusive flags"Remove one of the conflicting flags

Configuration Management Commands#

config init#

The config init command generates a template configuration file.

Basic Usage:

# Create default config at ~/.opnDossier.yaml
opndossier config init

Advanced Options:

# Create config at specific path
opndossier config init --output /path/to/config.yaml

# Overwrite existing config
opndossier config init --force

config show#

The config show command displays the current effective configuration, showing the merged result of all configuration sources according to precedence rules.

Basic Usage:

# Show all configuration values (styled terminal output)
opndossier config show

JSON Output for Scripting:

# Show config in JSON format
opndossier config show --json

This command is useful for:

  • Debugging configuration issues
  • Verifying which settings are active
  • Understanding precedence behavior
  • Generating configuration documentation

config validate#

The config validate command validates configuration files, checking for syntax errors, invalid values, and constraint violations.

Basic Usage:

# Validate default config file
opndossier config validate

# Validate specific config file
opndossier config validate --config /path/to/config.yaml

Common Validation Errors:

The system provides detailed validation error information:

ErrorCauseSolution
"invalid theme value"Theme not in allowed listUse: light, dark, auto, none, or custom
"invalid format"Format not recognizedUse: markdown, md, json, yaml, or yml
"invalid log level"Log level not validUse: debug, info, warn, or error
"wrap width must be >= -1"Wrap value out of boundsUse -1 (auto), 0 (no wrap), or positive

Implementation Architecture#

Configuration Struct Design#

The configuration uses a hybrid design with both flat fields for backward compatibility and nested structs with mapstructure tags for Viper integration:

type Config struct {
    // Flat fields (backward compatible)
    InputFile string `mapstructure:"input_file"`
    OutputFile string `mapstructure:"output_file"`
    Verbose bool `mapstructure:"verbose"`
    Quiet bool `mapstructure:"quiet"`
    Format string `mapstructure:"format"`
    Theme string `mapstructure:"theme"`
    WrapWidth int `mapstructure:"wrap"`
    Sections []string `mapstructure:"sections"`
    JSONOutput bool `mapstructure:"json_output"`
    Minimal bool `mapstructure:"minimal"`
    NoProgress bool `mapstructure:"no_progress"`

    // Nested configuration sections
    Display DisplayConfig `mapstructure:"display"`
    Export ExportConfig `mapstructure:"export"`
    Logging LoggingConfig `mapstructure:"logging"`
    Validation ValidationConfig `mapstructure:"validation"`
}

type DisplayConfig struct {
    Width int `mapstructure:"width"`
    Pager bool `mapstructure:"pager"`
    SyntaxHighlighting bool `mapstructure:"syntax_highlighting"`
}

type LoggingConfig struct {
    Level string `mapstructure:"level"`
    Format string `mapstructure:"format"`
}

Configuration Loading Process#

Three loading functions provide increasing flexibility:

  1. LoadConfig(cfgFile string) - Default loading with Viper
  2. LoadConfigWithFlags(cfgFile string, flags *pflag.FlagSet) - Binds CLI flags for proper precedence
  3. LoadConfigWithViper(cfgFile string, v *viper.Viper) - Core implementation with custom Viper instance

Viper Initialization:

The LoadConfigWithViper function sets up the configuration system:

func LoadConfigWithViper(cfgFile string, v *viper.Viper) (*Config, error) {
    // Set up environment variable handling
    v.SetEnvPrefix("OPNDOSSIER")
    v.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
    v.AutomaticEnv()

    // Configure config file settings
    if cfgFile != "" {
        v.SetConfigFile(cfgFile)
    } else {
        home, _ := os.UserHomeDir()
        v.AddConfigPath(home)
        v.SetConfigType("yaml")
        v.SetConfigName(".opnDossier")
    }

    // Read config file if it exists
    v.ReadInConfig()

    // Set default values
    v.SetDefault("format", "markdown")
    v.SetDefault("wrap", -1)
    v.SetDefault("logging.level", "info")
    // ... more defaults

    // Unmarshal into Config struct
    cfg := &Config{}
    v.Unmarshal(cfg)

    return cfg, nil
}

Nested Environment Variable Bindings:

Explicit nested env bindings are required because Viper's AutomaticEnv() doesn't automatically resolve nested keys:

// Bind environment variables for nested configuration keys
nestedEnvBindings := map[string]string{
    "display.width": "DISPLAY_WIDTH",
    "display.pager": "DISPLAY_PAGER",
    "display.syntax_highlighting": "DISPLAY_SYNTAX_HIGHLIGHTING",
    "export.format": "EXPORT_FORMAT",
    "logging.level": "LOGGING_LEVEL",
    "logging.format": "LOGGING_FORMAT",
    "validation.strict": "VALIDATION_STRICT",
}
for key, envSuffix := range nestedEnvBindings {
    v.BindEnv(key, "OPNDOSSIER_"+envSuffix)
}

Validation System#

The Validator struct performs comprehensive validation:

type Validator struct {
    errors *MultiValidationError
    config *Config
}

func (v *Validator) Validate() *MultiValidationError {
    v.validateFlags()
    v.validateInputFile()
    v.validateOutputFile()
    v.validateTheme()
    v.validateFormat()
    v.validateWrapWidth()
    v.validateDisplayConfig()
    v.validateExportConfig()
    v.validateLoggingConfig()
    v.validateValidationConfig()

    if v.errors.HasErrors() {
        return v.errors
    }
    return nil
}

Validation Types:

  1. Enum validation - Checks against valid value lists
  2. Range validation - Ensures numeric bounds
  3. File path validation - Verifies file existence
  4. Directory validation - Checks directory accessibility

Valid values are defined as package constants:

var ValidLogLevels = []string{"debug", "info", "warn", "error"}
var ValidThemes = []string{"light", "dark", "auto", "none", "custom", ""}
var ValidFormats = []string{"markdown", "md", "json", "yaml", "yml", "text", "txt", "html", "htm", ""}

CLI Integration with Cobra#

The root command uses a two-path initialization strategy in PersistentPreRunE to optimize startup time:

Lightweight Path (for version, help, completion):

Creates minimal context without config loading

Full Path (for normal commands):

Performs complete initialization:

  1. Load config with flag binding: config.LoadConfigWithFlags()
  2. Determine log level from verbose/quiet flags
  3. Initialize logger with proper configuration
  4. Validate global flags
  5. Create CommandContext with Config and Logger
  6. Set context on command for subcommand access

CommandContext Pattern#

The CommandContext provides explicit dependency injection, eliminating global state:

type CommandContext struct {
    Config *config.Config
    Logger *logging.Logger
}

// Usage in commands
func runCommand(cmd *cobra.Command, args []string) error {
    cmdCtx := GetCommandContext(cmd)
    if cmdCtx == nil {
        return errors.New("command context not initialized")
    }

    // Access configuration
    format := cmdCtx.Config.Format

    // Use logger
    cmdCtx.Logger.Info("running command", "format", format)

    return nil
}

This pattern provides:

  • Explicit dependencies (no hidden global state)
  • Easier testing through context injection
  • Clear data flow traceability
  • Better support for concurrent command execution

Best Practices#

Use Configuration Files for Persistent Settings#

Store frequently used settings in ~/.opnDossier.yaml for consistent behavior across sessions:

# Development configuration
verbose: true
format: markdown
wrap: 120

logging:
  level: debug
  format: text

display:
  syntax_highlighting: true
  width: 120

validation:
  strict: true

Use Environment Variables for Deployment#

For automated scripts and CI/CD pipelines, use environment variables to adapt configuration without modifying files:

#!/bin/bash
# CI/CD pipeline configuration
export OPNDOSSIER_QUIET=true
export OPNDOSSIER_VALIDATION_STRICT=true
export OPNDOSSIER_NO_PROGRESS=true
export OPNDOSSIER_FORMAT=json

opndossier convert config.xml -o report.json

Production Configuration Example:

Example production configuration:

verbose: false
quiet: false
minimal: true
no_progress: true
format: markdown

export:
  format: markdown
  backup: true
  directory: /var/reports/opnsense

logging:
  level: warn
  format: json

validation:
  strict: true

Use CLI Flags for One-off Overrides#

For temporary debugging or testing, use CLI flags:

# Debug a specific run
opndossier --verbose convert problematic-config.xml

# Test different output formats
opndossier convert config.xml --format json -o test.json

# Override configured wrap width
opndossier convert config.xml --wrap 80

Development vs. Production Configurations#

Development Configuration:

Example development configuration:

verbose: true

logging:
  level: debug
  format: text

validation:
  strict: true

display:
  syntax_highlighting: true
  width: 120

CI/CD Pipeline Configuration:

Example CI/CD configuration:

export OPNDOSSIER_QUIET=true
export OPNDOSSIER_VALIDATION_STRICT=true
export OPNDOSSIER_NO_PROGRESS=true

opndossier validate config.xml
opndossier convert config.xml -o report.md

Troubleshooting#

Configuration File Not Found#

Symptoms:

Configuration file is not being loaded or settings are not applied.

Solutions:

# Verify config file exists
ls -la ~/.opnDossier.yaml

# Check file permissions (should be readable)
chmod 644 ~/.opnDossier.yaml

# Explicitly specify config path
opndossier --config ~/.opnDossier.yaml convert config.xml

# Use verbose mode to see config file path
opndossier --verbose convert config.xml

Environment Variables Not Working#

Symptoms:

Environment variables are set but not being applied.

Solutions:

  1. Check prefix is correct:
# List all OPNDOSSIER environment variables
env | grep OPNDOSSIER
  1. Verify variable names match exactly:
# Correct
export OPNDOSSIER_VERBOSE=true

# Wrong (missing underscore)
export OPNDOSSIERVERBOSE=true
  1. For nested config, use underscore separator:
# Correct
export OPNDOSSIER_DISPLAY_WIDTH=120

# Wrong (using dot)
export OPNDOSSIER_DISPLAY.WIDTH=120

CLI Flags Not Overriding Config#

Symptoms:

CLI flags appear to be ignored, with config file values taking precedence.

Solutions:

  1. Verify flag syntax is correct:
# Check for typos
opndossier convert config.xml --format json # Correct
opndossier convert config.xml --fromat json # Wrong
  1. Global flags must come before the command:
# Correct
opndossier --config custom.yaml --verbose convert config.xml

# Wrong (global flags after command)
opndossier convert config.xml --config custom.yaml --verbose
  1. Check for flag validation errors:
# Invalid values will be rejected
opndossier convert config.xml --format invalid
# Error: invalid format "invalid", must be one of: markdown, md, json, yaml, yml, text, txt, html, htm

Debug Configuration Loading#

Use verbose mode to see configuration details:

opndossier --verbose --config /path/to/config.yaml convert config.xml

This displays:

  • Configuration file path being used
  • Detected environment variables
  • Final merged configuration values
  • Validation results

Use config show to see effective configuration:

opndossier config show
opndossier config show --json # For programmatic inspection

Complete Configuration Reference#

Configuration Options#

The system supports the following configuration options:

OptionTypeDefaultDescription
input_filestring""Default input file path
output_filestring""Default output file path
verbosebooleanfalseEnable verbose/debug logging
quietbooleanfalseSuppress all output except errors
formatstring"markdown"Output format (markdown, json, yaml, text, html)
themestring""Display theme (auto, dark, light, none)
wrapint-1Text wrap width (-1=auto, 0=off, >0=columns)
sectionsstring[][]Sections to include in output
no_progressbooleanfalseDisable progress indicators
minimalbooleanfalseMinimal output mode

Validate Command Options:

OptionTypeDefaultDescription
json_outputbooleanfalseOutput validation errors in JSON format (validate command only)

Nested Configuration#

Display Settings:

OptionTypeDefaultDescription
display.widthint-1Display width (-1=auto-detect)
display.pagerbooleanfalseEnable pager for output
display.syntax_highlightingbooleantrueEnable syntax highlighting

Export Settings:

OptionTypeDefaultDescription
export.formatstring"markdown"Export format
export.directorystring""Export directory path
export.backupbooleanfalseCreate backup before export

Logging Settings:

OptionTypeDefaultDescription
logging.levelstring"info"Log level (debug, info, warn, error)
logging.formatstring"text"Log format (text, json)

Validation Settings:

OptionTypeDefaultDescription
validation.strictbooleanfalseEnable strict validation mode
validation.schema_validationbooleanfalseEnable schema validation

Adding New Configuration Options#

Based on CONTRIBUTING.md guidance, follow these steps:

  1. Add to Config struct - Define field with mapstructure tag in internal/config/config.go
  2. Set default value - Use v.SetDefault() in LoadConfigWithViper
  3. Add CLI flag - Register flag in command's flag definitions
  4. Add validation - Implement validation in Validator methods
  5. Update documentation - Update README, configuration guides, and help text

Implementation File Reference#

File PathDescriptionKey Components
internal/config/config.goCore configuration managementConfig struct, LoadConfig functions, Viper setup, default values
internal/config/validation.goConfiguration validationValidator struct, enum/range/path validation, valid value constants
internal/config/errors.goValidation error typesFieldValidationError, MultiValidationError with structured messages
cmd/root.goRoot command and global flagsPersistentFlags, two-path initialization, global flag definitions
cmd/context.goDependency injection patternCommandContext, Get/SetCommandContext helpers
cmd/shared_flags.goShared flag definitionsMinWrapWidth, MaxWrapWidth constants, flag completion
cmd/convert.goConvert command implementationFormat aliases constants, normalizeFormat(), flag validation
~/.opnDossier.yamlDefault user configuration fileYAML configuration file location

Viper Configuration Library#

The Viper library by spf13 provides the foundation for opnDossier's configuration management. Viper supports:

  • Multiple configuration formats (JSON, TOML, YAML, HCL, etc.)
  • Reading from environment variables
  • Reading from remote config systems (etcd, Consul)
  • Live watching and re-reading of config files
  • Setting explicit values

Cobra CLI Framework#

The Cobra library by spf13 provides the CLI command structure. Cobra features:

  • POSIX-compliant flags (including short & long versions)
  • Nested subcommands
  • Global, local and cascading flags
  • Intelligent suggestions (did you mean?)
  • Automatic help generation
  • Shell completion (bash, zsh, fish, powershell)

CLI Architecture Patterns#

The configuration system demonstrates several important CLI architecture patterns:

  • Layered configuration with clear precedence - Standard pattern for CLI tools
  • Environment variable conventions - Uppercase with prefix and underscores
  • CommandContext dependency injection - Explicit dependencies over global state
  • Two-path initialization - Performance optimization for lightweight commands
  • Structured validation with helpful error messages - User-friendly error reporting