Configuration Initialization Timing#
Configuration Initialization Timing is a critical architectural pattern in CipherSwarmAgent that governs how the agent loads, merges, and validates configuration settings from multiple sources during startup. This topic is essential for operators deploying the agent in secure, airgapped environments where configuration must be reliable, predictable, and resilient to filesystem constraints.
CipherSwarmAgent uses Viper, a popular configuration management library, to merge settings from four sources in priority order: command-line flags (highest), environment variables, YAML configuration files, and built-in defaults (lowest). The challenge arises because Viper performs this merging after the application registers its default values but before the agent validates and finalizes its runtime configuration. A common but dangerous mistake is attempting to compute one configuration value from another during the initial default-setting phase—this causes derived values to ignore user-specified overrides, leading to incorrect paths, timeouts, or other critical misconfiguration.
To solve this timing problem, CipherSwarmAgent implements a two-phase initialization architecture. Phase 1 (SetDefaultConfigValues()) registers only simple, literal default values before any configuration files are read. Phase 2 (SetupSharedState()) runs after all configuration sources have been merged, at which point the agent computes derived values (like file paths based on a data directory), validates numeric ranges, and clamps invalid settings to safe defaults with warning messages. This design ensures that operators' configuration choices are always respected while the agent maintains fail-safe operation in restricted environments like read-only filesystems, containerized deployments, or secure lab systems where persistent configuration writes may fail.
Configuration Initialization Sequence#
Complete Bootstrap Flow#
CipherSwarmAgent's configuration initialization follows a precisely ordered sequence:
1. Go runtime executes init() in cmd/root.go
├─ Lines 41-159: Define CLI flags and bind to Viper keys
├─ Line 37: Register cobra.OnInitialize(config.InitConfig) callback
└─ Line 161: Call config.SetDefaultConfigValues()
└─ Register Viper defaults WITHOUT deriving dependent paths
2. main() calls cmd.Execute()
└─ Cobra framework triggers OnInitialize callback
└─ config.InitConfig() executes
├─ Read config file (if exists)
├─ Read environment variables
└─ Merge with CLI flags and defaults
3. agent.StartAgent() executes
├─ Validate required config (api_url, api_token)
├─ Line 53: Call config.SetupSharedState()
│ ├─ Read final merged Viper config
│ ├─ Derive dependent paths (files_path, zap_path)
│ ├─ Validate numeric values with clamping
│ └─ Populate agentstate.State struct
└─ Call lib.GetAgentConfiguration() to fetch server config
├─ Retrieve device lists, update intervals, hashcat settings from API
└─ Store atomically via lib.SetConfiguration()
4. Runtime goroutines access configuration concurrently
├─ Heartbeat goroutine: lib.GetConfiguration() for AgentUpdateInterval
├─ Agent-loop goroutine: lib.GetConfiguration() for device lists
└─ All goroutines: agentstate.State for paths, timeouts, credentials
Viper Configuration Precedence#
Viper merges configuration from multiple sources with the following precedence (highest to lowest):
- Command-line flags (highest priority)
- Environment variables
- Configuration file (YAML)
- Default values (lowest priority)
Each flag is defined using Cobra and bound to a Viper key with the same name. When a key is accessed via viper.Get*(), Viper returns the value from the highest-precedence source that has set it.
SetDefaultConfigValues Function#
The SetDefaultConfigValues function is defined in lib/config/config.go lines 191-217 and registers default values for all configuration keys:
// SetDefaultConfigValues sets default configuration values.
func SetDefaultConfigValues() {
cwd, err := os.Getwd()
cobra.CheckErr(err)
viper.SetDefault("data_path", filepath.Join(cwd, "data"))
viper.SetDefault("gpu_temp_threshold", DefaultGPUTempThreshold)
viper.SetDefault("always_use_native_hashcat", false)
viper.SetDefault("hashcat_path", "")
viper.SetDefault("sleep_on_failure", DefaultSleepOnFailure)
viper.SetDefault("always_trust_files", false)
// files_path and zap_path are derived from data_path in SetupSharedState
// when not explicitly set (avoids eagerly reading data_path before config is loaded).
viper.SetDefault("extra_debugging", false)
viper.SetDefault("status_timer", DefaultStatusTimer)
viper.SetDefault("heartbeat_interval", DefaultHeartbeatInterval)
// ... [additional defaults] ...
}
Configuration Default Constants#
All default values are defined as exported constants in lib/config/config.go lines 15-36, serving as the single source of truth:
const (
// DefaultGPUTempThreshold is the GPU temperature threshold in Celsius.
DefaultGPUTempThreshold = 80
// DefaultSleepOnFailure is the sleep duration after task failure.
DefaultSleepOnFailure = 60 * time.Second
// DefaultStatusTimer is the status update interval in seconds.
DefaultStatusTimer = 10
// DefaultHeartbeatInterval is the heartbeat interval.
DefaultHeartbeatInterval = 10 * time.Second
// DefaultTaskTimeout is the task timeout (long-running tasks are expected).
DefaultTaskTimeout = 24 * time.Hour
// DefaultDownloadMaxRetries is the max download retry attempts.
DefaultDownloadMaxRetries = 3
// DefaultDownloadRetryDelay is the base delay between download retries.
DefaultDownloadRetryDelay = 2 * time.Second
// DefaultInsecureDownloads controls TLS certificate verification for downloads.
DefaultInsecureDownloads = false
// DefaultMaxHeartbeatBackoff is the max heartbeat backoff multiplier (caps at 64x).
DefaultMaxHeartbeatBackoff = 6
)
These constants are referenced by both CLI flag defaults and SetDefaultConfigValues, ensuring consistency across the codebase.
The Viper Default Value Computation Pitfall#
GOTCHAS.md explicitly documents the critical timing pitfall:
SetDefaultConfigValuesruns before config files/env vars are loaded. Never derive defaults from other viper keys (e.g.,viper.GetString("data_path")) — they only return the registered default, not user overrides. Derive inSetupSharedStateinstead.
Why deriving defaults from other Viper keys fails:
// ❌ WRONG - This will only see the default value of "data_path"
func SetDefaultConfigValues() {
viper.SetDefault("data_path", "./data")
// BUG: This reads data_path BEFORE user overrides are loaded
viper.SetDefault("files_path", filepath.Join(viper.GetString("data_path"), "files"))
}
The problem:
SetDefaultConfigValuesexecutes duringinit(), before config files/env vars are processed- At this point,
viper.GetString("data_path")only returns the just-registered default value"./data" - User overrides to
data_path(via CLI flag--data_path=/custom/path, environment variable, or config file) haven't been loaded yet - The derived
files_pathdefault is computed from the wrongdata_pathvalue - Even if the user sets
data_path=/custom/path,files_pathwould still default to./data/filesinstead of/custom/path/files
The solution:
The inline comment at lines 202-203 explains the correct approach:
files_path and zap_path are derived from data_path in SetupSharedState when not explicitly set (avoids eagerly reading data_path before config is loaded).
Notice that files_path and zap_path are not registered as defaults in SetDefaultConfigValues. Instead, they are computed at runtime in SetupSharedState.
SetupSharedState Function#
SetupSharedState is defined in lib/config/config.go lines 79-189 and is called from agent.StartAgent() at line 53, after all configuration sources have been merged.
Derived Path Computation#
SetupSharedState computes derived paths at lines 101-112:
// SetupSharedState configures the shared state from configuration values.
func SetupSharedState() {
// Set the API URL and token
agentstate.State.URL = viper.GetString("api_url")
agentstate.State.APIToken = viper.GetString("api_token")
dataRoot := viper.GetString("data_path")
agentstate.State.DataPath = dataRoot
agentstate.State.PidFile = filepath.Join(dataRoot, "lock.pid")
agentstate.State.HashcatPidFile = filepath.Join(dataRoot, "hashcat.pid")
agentstate.State.CrackersPath = filepath.Join(dataRoot, "crackers")
// ✅ CORRECT - Derived paths computed at runtime
agentstate.State.FilePath = viper.GetString("files_path")
if agentstate.State.FilePath == "" {
agentstate.State.FilePath = filepath.Join(dataRoot, "files")
}
agentstate.State.ZapsPath = viper.GetString("zap_path")
if agentstate.State.ZapsPath == "" {
agentstate.State.ZapsPath = filepath.Join(dataRoot, "zaps")
}
agentstate.State.HashlistPath = filepath.Join(dataRoot, "hashlists")
agentstate.State.PreprocessorsPath = filepath.Join(dataRoot, "preprocessors")
agentstate.State.ToolsPath = filepath.Join(dataRoot, "tools")
agentstate.State.OutPath = filepath.Join(dataRoot, "output")
agentstate.State.RestoreFilePath = filepath.Join(dataRoot, "restore")
agentstate.State.BenchmarkCachePath = filepath.Join(dataRoot, "benchmark_cache.json")
// ... [continue populating state] ...
}
Key behaviors:
- Check for explicit user values first:
viper.GetString("files_path")returns the user's explicit setting (if any) - Compute default only if unset: If empty, derive from
data_path - Respect precedence: User can override
data_pathORfiles_pathindependently
Test cases in config_test.go lines 233-283 verify this behavior:
- Default
data_pathcorrectly derivesfiles_pathandzap_path - Custom
data_pathoverrides are honored for derived paths - Explicit
files_pathorzap_pathsettings override derivation
Configuration Validation#
SetupSharedState validates numeric and duration fields at lines 165-186, clamping invalid values to defaults with warnings:
// Validate numeric/duration config fields — clamp to defaults with a warning.
agentstate.State.DownloadMaxRetries = viper.GetInt("download_max_retries")
if agentstate.State.DownloadMaxRetries < 1 {
agentstate.Logger.Warn("download_max_retries must be >= 1, using default",
"configured", agentstate.State.DownloadMaxRetries, "default", DefaultDownloadMaxRetries)
agentstate.State.DownloadMaxRetries = DefaultDownloadMaxRetries
}
agentstate.State.DownloadRetryDelay = viper.GetDuration("download_retry_delay")
agentstate.State.TaskTimeout = viper.GetDuration("task_timeout")
if agentstate.State.TaskTimeout <= 0 {
agentstate.Logger.Warn("task_timeout must be > 0, using default",
"configured", agentstate.State.TaskTimeout, "default", DefaultTaskTimeout)
agentstate.State.TaskTimeout = DefaultTaskTimeout
}
agentstate.State.MaxHeartbeatBackoff = viper.GetInt("max_heartbeat_backoff")
if agentstate.State.MaxHeartbeatBackoff < 0 {
agentstate.Logger.Warn("max_heartbeat_backoff must be >= 0, using default",
"configured", agentstate.State.MaxHeartbeatBackoff, "default", DefaultMaxHeartbeatBackoff)
agentstate.State.MaxHeartbeatBackoff = DefaultMaxHeartbeatBackoff
}
agentstate.State.SleepOnFailure = viper.GetDuration("sleep_on_failure")
Validation philosophy:
- Clamp, don't fail: Invalid values are replaced with defaults, not rejected
- Log warnings: Users are informed of the misconfiguration
- Continue execution: The agent starts successfully with corrected values
- Fail-safe defaults: Default values are always valid and safe
This approach is appropriate for an agent designed to run in airgapped environments where interactive configuration debugging may not be feasible.
Configuration Access Patterns#
CipherSwarmAgent supports three patterns for accessing configuration after initialization, each serving different use cases:
Direct Viper Access#
dataPath := viper.GetString("data_path")
Advantages: Simple and direct
Disadvantages:
- Couples code to Viper library
- No centralized validation
- Requires checking validity at every call site
Use case: Reading configuration during initialization in SetupSharedState
AgentState Access#
dataPath := agentstate.State.DataPath
Advantages:
- Values are pre-validated in
SetupSharedState - Computed/derived values are available
- More testable (can mock
agentstate.State) - Type-safe access (struct fields vs string keys)
- Decouples application code from Viper
Use case: Accessing agent operational settings (paths, timeouts, thresholds) from any goroutine
Atomic Configuration Access (Required for Server Configuration)#
cfg := lib.GetConfiguration()
backendDevices := cfg.Config.BackendDevices
updateInterval := cfg.Config.AgentUpdateInterval
Advantages:
- Race-free concurrent access from multiple goroutines
- Returns immutable snapshot, ensuring consistent reads
- Atomic updates via
lib.SetConfiguration() - No locking overhead for readers
Disadvantages:
- Slightly more verbose than direct global access
- Returns a copy, not a reference (intentional for safety)
Use case: Accessing server-provided configuration (device lists, update intervals, hashcat settings) from heartbeat and agent-loop goroutines
Implementation: The agent stores server configuration in an atomic.Value to prevent race conditions. Two goroutines access this configuration concurrently: the heartbeat loop (which may reload configuration from the server) and the main agent loop (which reads device lists and intervals). The atomic accessor pattern replaced a previous plain global variable that had race conditions detected by go test -race.
// ✅ CORRECT - Atomic configuration access
func startAgentLoop(ctx context.Context) {
cfg := lib.GetConfiguration() // Atomic read returns snapshot
sleepTime := time.Duration(cfg.Config.AgentUpdateInterval) * time.Second
// ... use cfg ...
}
// ✅ CORRECT - Atomic configuration update
func fetchAgentConfig(ctx context.Context) error {
fetchedCfg := lib.GetConfiguration() // Read current
fetchedCfg.Config.UseNativeHashcat = true // Modify snapshot
lib.SetConfiguration(fetchedCfg) // Atomic write
return nil
}
// ❌ WRONG - Race condition with plain global variable (old pattern)
var Configuration agentConfiguration // REMOVED in PR #171
func oldPattern() {
// Two goroutines reading/writing this simultaneously = data race
interval := Configuration.Config.AgentUpdateInterval
}
Recommendation:
- Use
agentstate.Statefor agent operational settings (paths, timeouts, API credentials) - Use
lib.GetConfiguration()for server-provided configuration (device lists, hashcat settings, update intervals) - Never access Viper directly outside of
SetupSharedStateandInitConfig
Configuration File Persistence#
CipherSwarmAgent occasionally needs to persist configuration changes back to disk, such as when discovering the Hashcat binary location at runtime.
Initial Config Creation with SafeWriteConfig#
if err := viper.ReadInConfig(); err == nil {
agentstate.Logger.Info("Using config file", "config_file", viper.ConfigFileUsed())
} else {
agentstate.Logger.Warn("No config file found, attempting to write a new one")
if err := viper.SafeWriteConfig(); err != nil && err.Error() != "config file already exists" {
agentstate.Logger.Error("Error writing config file", "error", err)
}
}
Error handling pattern:
- Use
SafeWriteConfig()to avoid overwriting existing configs - Ignore "config file already exists" error (race condition benign)
- Log other errors at ERROR level
- Non-fatal: Agent continues even if write fails
Runtime Config Updates with WriteConfig#
agentstate.Logger.Info("Found Hashcat binary", "path", binPath)
agentstate.State.HashcatPath = binPath
viper.Set("hashcat_path", binPath)
if err := viper.WriteConfig(); err != nil {
agentstate.Logger.Warn("Failed to persist hashcat path to config; path will be lost on restart",
"error", err, "hashcat_path", binPath)
}
return nil
Error handling pattern:
- Use
WriteConfig()to update existing config file - Log failures at WARN level with descriptive message
- Explain the consequence (value lost on restart)
- Non-fatal: Function returns successfully even if write fails
- Runtime state is still updated (
agentstate.State.HashcatPath)
Non-Fatal Write Philosophy#
Both examples demonstrate CipherSwarmAgent's consistent philosophy of treating configuration write failures as non-fatal warnings. This design decision is appropriate for an agent that may run in:
- Read-only filesystems
- Container environments with ephemeral storage
- Restricted permission environments
- Airgapped labs with strict security policies
The agent prioritizes availability (continue running with correct runtime state) over persistence (ensure config survives restart).
Common Pitfalls and Best Practices#
Pitfall 1: Computing Derived Values in SetDefaultConfigValues#
Problem:
// ❌ WRONG
func SetDefaultConfigValues() {
viper.SetDefault("data_path", "./data")
viper.SetDefault("files_path", filepath.Join(viper.GetString("data_path"), "files"))
}
Why it fails: User overrides to data_path haven't been loaded yet; viper.GetString("data_path") returns only the default
Solution:
// ✅ CORRECT - In SetupSharedState() instead
func SetupSharedState() {
dataPath := viper.GetString("data_path") // Now fully merged
filesPath := viper.GetString("files_path")
if filesPath == "" {
filesPath = filepath.Join(dataPath, "files")
}
agentstate.State.FilesPath = filesPath
}
Pitfall 2: Accessing Configuration Before SetupSharedState#
Problem:
func someEarlyInit() {
// May return incomplete/incorrect value
fmt.Println(viper.GetString("data_path"))
}
Solution:
func properInit() {
config.SetupSharedState() // Ensure config is complete
// Safe to access after this point
fmt.Println(agentstate.State.DataPath)
}
Pitfall 3: Forgetting to Validate Numeric Values#
Problem:
// May panic or use invalid value
retries := viper.GetInt("download_max_retries")
Solution:
// Validate in SetupSharedState with clamping
retries := viper.GetInt("download_max_retries")
if retries < 1 {
agentstate.Logger.Warn("invalid retries, using default", "value", retries)
retries = DefaultDownloadMaxRetries
}
agentstate.State.DownloadMaxRetries = retries
Pitfall 4: Treating Config Write Failures as Fatal#
Problem:
// ❌ WRONG - Kills agent on write failure
if err := viper.WriteConfig(); err != nil {
return fmt.Errorf("fatal: %w", err)
}
Solution:
// ✅ CORRECT - Log and continue
if err := viper.WriteConfig(); err != nil {
agentstate.Logger.Warn("config write failed; changes lost on restart", "error", err)
}
// Continue execution
Pitfall 5: Concurrent Access to Configuration Without Atomics#
Problem:
// ❌ WRONG - Race condition between goroutines
var Configuration agentConfiguration
func heartbeatLoop() {
// Goroutine 1: reads configuration
interval := Configuration.Config.AgentUpdateInterval
}
func reloadConfig() {
// Goroutine 2: writes configuration
Configuration = newConfig
}
Why it fails: Multiple goroutines reading/writing the same variable simultaneously causes data races detected by go test -race
Solution:
// ✅ CORRECT - Use atomic.Value for race-free access
var configuration atomic.Value
func init() {
configuration.Store(agentConfiguration{})
}
func GetConfiguration() agentConfiguration {
return configuration.Load().(agentConfiguration)
}
func SetConfiguration(cfg agentConfiguration) {
configuration.Store(cfg)
}
func heartbeatLoop() {
cfg := GetConfiguration() // Atomic read
interval := cfg.Config.AgentUpdateInterval
}
func reloadConfig() {
SetConfiguration(newConfig) // Atomic write
}
Best Practices Summary#
- Set simple defaults in
SetDefaultConfigValues: Useviper.SetDefault()with literal values or exported constants - Compute derived values in
SetupSharedState: Never callviper.Get*()insideSetDefaultConfigValues - Validate in
SetupSharedState: All validation runs after config is fully merged - Use atomic accessors for server configuration: Always call
lib.GetConfiguration()andlib.SetConfiguration()for concurrent access - Use
agentstate.Statefor agent settings: Access operational settings (paths, timeouts, credentials) viaagentstate.State - Treat writes as non-fatal: Log errors but don't block execution
- Document defaults as constants: Maintain single source of truth in
lib/config/config.go - Test configuration precedence: Verify CLI flags override env vars, env vars override config files, etc.
- Test with race detector: Run
go test -raceto catch concurrent access bugs
Architecture Diagrams#
Initialization Sequence Diagram#
The following diagram illustrates the configuration initialization timing and data flow through the three phases:
Configuration Precedence Flow#
This diagram shows how configuration values flow through Viper's precedence hierarchy:
Deployment Considerations for Airgapped Environments#
CipherSwarmAgent's configuration architecture is specifically designed for deployment in airgapped, high-security laboratory environments where password cracking operations must run without Internet connectivity. These constraints significantly influence how configuration initialization is implemented.
Read-Only Filesystems#
Many secure environments mount application directories as read-only to prevent unauthorized modifications. The agent's non-fatal write error handling ensures continuous operation even when WriteConfig() fails. For example, when the agent discovers the Hashcat binary location and attempts to persist it to configuration, a write failure logs a warning but doesn't interrupt cracking operations. On the next restart, the agent simply rediscovers the binary location—a minor performance cost that ensures the agent never fails due to filesystem restrictions.
Container and Orchestrated Deployments#
Containerized deployments (Docker, Kubernetes) frequently use ephemeral storage where written configuration files are lost when containers restart. This pattern actually aligns well with the agent's architecture:
- Environment variables for deployment-specific settings: API tokens, server URLs, and data paths can be injected via container environment variables
- Mounted configuration files for static settings: GPU temperature thresholds, timeout values, and operational parameters can be provided as read-only mounted volumes
- Runtime discovery over persistence: The agent rediscovers system resources (GPUs, Hashcat binary) on each startup rather than depending on persisted configuration
- Validation at startup:
SetupSharedState()catches configuration errors early, logging warnings for invalid values before cracking operations begin
This design ensures that containerized agents are stateless and can be scaled horizontally across GPU clusters without coordination.
Configuration Discovery and Graceful Degradation#
The agent performs runtime discovery of system resources to adapt to diverse hardware configurations:
Hashcat Binary Detection: The agent searches standard paths for native Hashcat installations. If found, it updates agentstate.State.HashcatPath immediately and attempts to persist this via WriteConfig(). If persistence fails (read-only filesystem, permission denied), the agent logs a warning but continues with the discovered path for the current session. On restart, the agent rediscovers the binary automatically.
GPU Device Enumeration: The agent detects available GPUs and validates against gpu_temp_threshold to protect hardware. These hardware characteristics are not persisted—they're discovered fresh on each startup to handle dynamic hardware changes in lab environments.
Network Connectivity: In airgapped environments, the agent must be pre-configured with the API URL of the CipherSwarm coordinator server within the isolated network. The configuration system validates that api_url is set during startup, failing fast if this required setting is missing rather than attempting to reach external services.
Example Deployment Patterns#
The following examples demonstrate how to configure CipherSwarmAgent for different airgapped deployment scenarios.
Environment Variable Configuration (Containerized Deployment):
# Docker/Kubernetes deployment - secrets via environment
export API_URL="https://cipherswarm.internal.lab"
export API_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
export DATA_PATH="/mnt/agent-data"
export GPU_TEMP_THRESHOLD="85"
./cipherswarm-agent
This approach is ideal for container orchestration where secrets are injected via platform mechanisms (Kubernetes secrets, Docker secrets) and configuration can be templated per deployment.
Config File Configuration (Bare Metal Deployment):
# /etc/cipherswarm/agent.yaml
api_url: https://cipherswarm.internal.lab
api_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
data_path: /opt/cipherswarm/data
gpu_temp_threshold: 85
task_timeout: 48h
download_max_retries: 5
./cipherswarm-agent --config /etc/cipherswarm/agent.yaml
This pattern suits traditional server deployments where configuration files can be version-controlled and distributed via configuration management tools (Ansible, Puppet, etc.).
Hybrid Configuration (Recommended for Production):
# /etc/cipherswarm/agent.yaml - Static operational settings
api_url: https://cipherswarm.internal.lab
gpu_temp_threshold: 85
task_timeout: 48h
heartbeat_interval: 15s
status_timer: 5
download_max_retries: 5
download_retry_delay: 3s
always_trust_files: false
enable_additional_hash_types: true
# Sensitive token and deployment-specific paths via environment and CLI
export API_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
./cipherswarm-agent \
--config /etc/cipherswarm/agent.yaml \
--data_path /mnt/raid/agent-data \
--debug
Why this approach is recommended:
- Security: Sensitive credentials never touch disk; they're provided via environment variables that can be managed by secret management systems
- Flexibility: Static configuration (timeouts, thresholds) is version-controlled; deployment-specific settings (paths, debug flags) are provided at runtime
- Precedence utilization: Leverages Viper's precedence model—CLI flags override environment variables override config files—allowing operators to temporarily override settings without editing files
- Auditability: Config files show the intended operational parameters; environment variables and CLI flags show deployment-specific overrides
Real-World Lab Deployment Example:
A typical secure password recovery lab might deploy multiple agents across a GPU cluster:
# Node 1: High-performance cracking node with 8x RTX 4090 GPUs
export API_TOKEN="node1-token"
./cipherswarm-agent \
--config /etc/cipherswarm/base-config.yaml \
--data_path /nvme/agent-node1 \
--gpu_temp_threshold 80 \
--task_timeout 72h
# Node 2: General-purpose node with 4x RTX 3080 GPUs
export API_TOKEN="node2-token"
./cipherswarm-agent \
--config /etc/cipherswarm/base-config.yaml \
--data_path /nvme/agent-node2 \
--gpu_temp_threshold 75 \
--task_timeout 24h
# Node 3: Development/testing node with single GPU
export API_TOKEN="dev-node-token"
./cipherswarm-agent \
--config /etc/cipherswarm/base-config.yaml \
--data_path /tmp/agent-dev \
--debug \
--extra_debugging \
--force_benchmark_run
Each node shares a common base configuration file but uses environment variables and CLI flags for node-specific settings. This pattern scales to dozens of nodes while maintaining consistent operational parameters.
Related Configuration Topics#
- Agent State Management: The
agentstatepackage and its role in centralizing runtime state - Logger Configuration: How the logger is initialized from configuration in
initLogger() - Hashcat Integration: Configuration of native vs embedded Hashcat selection
- Device Detection: GPU configuration and temperature threshold enforcement
- Network Configuration: API client setup with timeout and retry configuration
Relevant Code Files#
| File | Purpose | Key Lines |
|---|---|---|
cmd/root.go | CLI command definition, flag binding, initialization sequence | 37-162 (init function) |
lib/config/config.go | Default constants, SetDefaultConfigValues, SetupSharedState, InitConfig | 15-36 (constants), 79-189 (SetupSharedState), 191-217 (SetDefaultConfigValues) |
lib/agent/agent.go | Agent startup sequence, SetupSharedState invocation, concurrent config access | 42-54 (StartAgent), 237-308 (heartbeat/agent loops) |
lib/agentClient.go | Atomic configuration accessors (GetConfiguration, SetConfiguration) | 26-53 (atomic.Value initialization and accessors) |
agentstate/agentstate.go | Shared state structure definition | 14-64 (State struct) |
lib/crackerUtils.go | Runtime config persistence example (Hashcat path) | 25-34 (setNativeHashcatPath) |
lib/config/config_test.go | Configuration initialization test cases | 233-283 (derived path tests) |
GOTCHAS.md | Documented configuration pitfalls | 36-38 (Configuration section) |
References#
- Viper Configuration Library - Go configuration management with precedence and merging
- Cobra CLI Framework - Go CLI framework used for command structure and flag parsing