How OpenLogi Handles Schema Migrations for Older Configuration Versions (v1, v2, v3)
OpenLogi automatically migrates older configuration schemas (v1, v2, v3) to the current version at load time by detecting the stored schema_version, applying sequential in-memory transformations, and persisting the updated file with automatic backup protection.
The OpenLogi project uses a load-time migration pipeline that transparently upgrades user configuration files without requiring manual intervention. When Config::load_from_path parses a TOML configuration, it reads the schema_version field and executes version-specific migration functions until the in-memory representation matches the current SCHEMA_VERSION constant.
How the Migration Pipeline Works
The migration system follows a four-step process defined in crates/openlogi-core/src/config.rs:
- Parse and detect — The loader extracts
schema_versionfrom the TOML file - Create backup — The original file content is preserved as
*.bak.<version>insrc/config/file.rs - Apply sequential migrations — Handler functions transform the configuration in-memory
- Persist and cleanup — The migrated configuration is written back, and the backup is removed on success
If the file already matches SCHEMA_VERSION, the loader skips migration entirely for optimal performance.
Version-Specific Migration Details
v1 → v2: Gesture Binding Restructure
The first major migration addresses a fundamental change in how button and gesture bindings are stored.
Before v2: Configurations used a flat list structure for single-gesture entries.
After v2: Bindings moved to a structured GestureMap with per-button organization.
The migrate_owner_locked_gestures function in src/config/settings.rs handles this transformation:
- Creates the new
bindingsmap structure - Migrates legacy single-gesture fields into the per-button format
- Preserves all user custom bindings while removing obsolete fields
v2 → v3: Transport-Agnostic Device Keys
This migration removes transport-specific prefixes from device identifiers.
Before v3: Device keys included transport prefixes like receiver:046d:... or raw:...
After v3: Keys use transport-agnostic direct: style identifiers.
The migrate_transport_scoped_keys function in src/config/identity.rs performs:
- Prefix stripping for
receiver:andraw:prefixes - Collision detection and duplicate route folding
- Host-switch reference updates to match the new key format
v3 → Current: Direction Normalization
Post-v3 migrations handle smaller schema adjustments through helper functions like migrate_thumbwheel_native_direction in src/config/settings.rs. These normalize thumbwheel direction flags and apply current default values to older configurations.
Backup and Safety Mechanisms
OpenLogi protects against migration failures through a robust backup strategy implemented in src/config/file.rs:
- Pre-migration backup — Raw content copied to
*.bak.<version>before any modification - Atomic replacement — New configuration written to temporary location, then moved
- Backup retention — Original backup preserved only until successful write completion
- Failure recovery — User can manually restore from
.bakfile if migration fails
Code Examples
Automatic Migration at Load Time
use openlogi_core::config::Config;
use std::path::Path;
// Load a user config – old versions are migrated automatically.
let path = Path::new("/home/user/.config/openlogi/config.toml");
match Config::load_from_path(&path) {
Ok(cfg) => {
println!("Config loaded (schema {}); {} devices configured.",
cfg.schema_version, cfg.devices.len());
// `cfg` is now in the current schema; you can safely use the new API.
}
Err(e) => eprintln!("Failed to load config: {}", e),
}
Manual Migration for Testing
// Manually force a migration (useful in tests):
let mut cfg = Config::load_from_path(&path).unwrap();
if cfg.schema_version < Config::CURRENT_SCHEMA {
cfg.migrate_owner_locked_gestures(); // v1 → v2
cfg.migrate_transport_scoped_keys(); // v2 → v3
cfg.migrate_thumbwheel_native_direction(); // v3 → v4+
}
Key Source Files
| File | Migration Role |
|---|---|
crates/openlogi-core/src/config.rs |
Top-level loader with version detection and migration orchestration |
crates/openlogi-core/src/config/file.rs |
File I/O, backup creation, and atomic write operations |
crates/openlogi-core/src/config/settings.rs |
migrate_owner_locked_gestures (v1 → v2) and thumbwheel normalization |
crates/openlogi-core/src/config/identity.rs |
migrate_transport_scoped_keys (v2 → v3) and route folding |
crates/openlogi-core/src/config/tests.rs |
Verification tests including migrates_v1_button_and_gesture_bindings |
crates/openlogi-core/src/config/device.rs |
Compatibility shim for pre-v2 device entry folding |
Summary
- Automatic detection — OpenLogi reads
schema_versionfrom every configuration file at load time - Sequential transformation — Migrations run in order (v1 → v2 → v3 → current) until the schema matches
SCHEMA_VERSION - Memory-safe operations — All transformations occur in-memory; file writes only happen after successful migration
- Protected by backups — Every migration creates a versioned backup that persists until atomic write completion
- Zero user intervention — Legacy configurations upgrade transparently without manual steps or data loss
Frequently Asked Questions
What happens if a migration fails halfway through?
The backup file created in src/config/file.rs preserves the original configuration. Since migrations are purely in-memory until the final write, a failure during transformation leaves the disk file untouched. Only successful migration completion triggers the atomic file replacement and backup removal.
Can I prevent automatic migration and keep my old configuration format?
No — OpenLogi requires configurations to match the current SCHEMA_VERSION for API compatibility. The design intentionally lacks a "read-only legacy mode" because newer features depend on current schema structures. The backup file (*.bak.<version>) allows manual recovery if needed.
How do I verify that my v1 or v2 configuration migrated correctly?
Check the schema_version field in your saved configuration file after running OpenLogi once. The test suite in crates/openlogi-core/src/config/tests.rs includes specific validations like migrates_v1_button_and_gesture_bindings that demonstrate expected transformation behavior for regression testing.
Are migrations reversible?
No — migrations are forward-only. The backup mechanism in src/config/file.rs provides the only rollback path: manually replace your current configuration with the *.bak.<version> file and restart with an older OpenLogi version that recognizes that schema version.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →