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:

  1. Parse and detect — The loader extracts schema_version from the TOML file
  2. Create backup — The original file content is preserved as *.bak.<version> in src/config/file.rs
  3. Apply sequential migrations — Handler functions transform the configuration in-memory
  4. 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 bindings map 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:

  1. Prefix stripping for receiver: and raw: prefixes
  2. Collision detection and duplicate route folding
  3. 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 .bak file 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_version from 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →