How to Migrate TOML Configuration Files in OpenLogi
OpenLogi automatically upgrades legacy TOML configuration files to the current schema version (currently 7) when they are loaded, creating a backup of the original before applying transformations.
OpenLogi stores user preferences and per-device state in a TOML file located at ~/.config/openlogi/config.toml. As the project evolves, the configuration schema changes, but the built-in migration pipeline ensures backward compatibility by transforming older files into the latest format without manual intervention.
Understanding the Configuration Schema Versioning
The migration system centers around a schema version constant defined in the core crate. In crates/openlogi-core/src/config.rs, the constant SCHEMA_VERSION (currently set to 7) represents the latest supported configuration format. When Config::load_from_path reads a TOML file, it compares the file's stored schema version against this constant to determine which migrations to apply.
The loading process begins with openlogi_core::config::ConfigFile::load, which parses the raw TOML and returns both the configuration structure and the original schema version. This occurs in crates/openlogi-core/src/config/file.rs at line 107. If the loaded version is older than SCHEMA_VERSION, the system triggers an automatic upgrade sequence.
Built-In Migration Functions
OpenLogi's migration logic applies a series of targeted transformations to bring older configurations up to date. These migrations handle breaking changes introduced across different versions.
migrate_owner_locked_gestures (v4 to later)
The migrate_owner_locked_gestures function (found in crates/openlogi-core/src/config.rs at line 387) removes the historic "gesture-owner lock" mechanism introduced in schema version 4. It rewrites gesture configurations from the legacy locked format to the modern per-button format, ensuring that gesture mappings remain functional after the upgrade.
migrate_transport_scoped_keys (v5 to later)
Introduced in version 5, transport-scoped keys prefixed with direct: required special handling. The migrate_transport_scoped_keys function (line 458 in config.rs) strips these prefixes from device keys and folds them into the device's links map while preserving the identifier fragments. This consolidation simplifies the device linking structure while maintaining backward compatibility with older configuration files.
migrate_thumbwheel_native_direction (v7)
For the version 7 schema, migrate_thumbwheel_native_direction (line 534 in config.rs) normalizes thumb-wheel scroll defaults. It converts legacy explicit-direction defaults to the canonical native direction representation, ensuring consistent scrolling behavior across different hardware generations.
Device-Level Migrations
Beyond global migrations, DeviceConfig in crates/openlogi-core/src/config/device.rs (see line 84) handles device-specific transformations. These include merging older button_bindings and gesture_bindings maps from schema version 2, and managing the legacy RawDeviceConfig shim from version 1. These changes ensure that per-device settings migrate correctly even when the global schema remains unchanged.
Backup and Safety Mechanisms
Before applying any transformations, OpenLogi preserves the original configuration to prevent data loss. When ConfigFile detects that migrations are necessary, it writes the pre-migration content to config.toml.bak in the same directory before overwriting the original file with the migrated version. This backup logic is implemented in crates/openlogi-core/src/config/file.rs at line 175.
The migration process is atomic in the sense that the file is only overwritten after the backup completes. If the migration fails or produces unexpected results, users can restore their previous configuration by manually renaming the .bak file.
Programmatic Migration Examples
You can trigger migrations programmatically using the OpenLogi core library. The following examples demonstrate how to load and migrate configurations in Rust applications.
Loading and Migrating via the Config API
use std::path::Path;
use openlogi_core::config::Config;
/// Load (and automatically migrate) the user configuration.
fn load_config() -> anyhow::Result<Config> {
// Resolve the platform-standard config directory via `openlogi_core::paths`.
let cfg_path = openlogi_core::paths::config_dir()?.join("config.toml");
// `load_from_path` parses the file, runs all migrations, and writes back a
// migrated copy (with a .bak backup of the original).
let cfg = Config::load_from_path(&cfg_path)?;
println!("Config schema version after load: {}", cfg.schema_version);
Ok(cfg)
}
Dry-Run Migration Testing
For testing or inspection purposes, you can load and migrate a configuration without writing changes to disk:
use std::path::Path;
use openlogi_core::config::ConfigFile;
fn dry_run_migration(path: &Path) -> anyhow::Result<()> {
// Load the file but keep it in memory; no backup or overwrite is performed.
let (cfg, original_version) = ConfigFile::load(path, /*dry_run=*/true)?;
println!("Original version: {}", original_version);
println!("Migrated version: {}", cfg.schema_version);
Ok(())
}
Key Source Files for Migration Logic
Understanding the migration architecture requires familiarity with these core files:
crates/openlogi-core/src/config.rs— Defines theConfigstruct,SCHEMA_VERSIONconstant, and the primary migration functions (migrate_owner_locked_gestures,migrate_transport_scoped_keys,migrate_thumbwheel_native_direction).crates/openlogi-core/src/config/file.rs— Handles TOML serialization and deserialization, backup creation, and orchestrates the migration sequence viaConfigFile::load.crates/openlogi-core/src/config/device.rs— ContainsDeviceConfigand legacyRawDeviceConfighandling for device-specific migration logic.crates/openlogi-core/src/config/settings.rs— Stores per-device and global settings (lighting, SmartShift, etc.) that may be affected by schema changes.
Summary
- Automatic upgrades: OpenLogi detects schema version mismatches automatically when loading
~/.config/openlogi/config.tomland applies necessary migrations. - Safety first: The system creates a
.bakbackup of the original file before writing migrated content, preventing data loss. - Current schema: Version 7 is the latest schema, supporting modern thumb-wheel direction handling and consolidated transport keys.
- API access: Developers can trigger migrations programmatically using
Config::load_from_pathor test migrations in-memory usingConfigFile::loadwithdry_run=true.
Frequently Asked Questions
When does OpenLogi migrate my configuration files?
Migrations run automatically whenever the configuration is loaded, which occurs during GUI startup (openlogi-desktop), CLI commands (such as openlogi-cli list), or programmatic calls to Config::load_from_path. The migration happens transparently before the application presents any interface or returns the configuration object.
Can I prevent OpenLogi from modifying my original TOML file?
Yes, you can perform a dry-run migration using ConfigFile::load(path, true), which parses and migrates the configuration in memory without creating a backup or overwriting the original file. However, normal application usage always writes the migrated version back to disk to ensure consistency between the file and the running system.
What happens if a migration fails midway?
OpenLogi writes the backup file (config.toml.bak) before beginning any modifications to the original. If the migration process fails, the original file remains unchanged (or can be restored from the backup). The application will typically return an error rather than leaving the configuration in a partially migrated state, as the transformations are applied in memory before the final write operation.
How do I migrate configurations from schema version 1 or 2?
Legacy configurations from versions 1 and 2 are fully supported through the device-level migration logic in device.rs. The system automatically merges old button_bindings and gesture_bindings maps into the current format and handles the RawDeviceConfig shim transparently. Simply place the legacy file in the expected location, and OpenLogi will upgrade it to schema version 7 on the next startup.
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 →