# How OpenLogi Handles Schema Migrations for Older Configuration Versions (v1, v2, v3)

> OpenLogi automatically handles schema migrations for v1, v2, v3 configurations at load time. Learn how it transforms and backs up your files.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-08-31

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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

```rust
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

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) | Top-level loader with version detection and migration orchestration |
| [`crates/openlogi-core/src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/file.rs) | File I/O, backup creation, and atomic write operations |
| [`crates/openlogi-core/src/config/settings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/settings.rs) | `migrate_owner_locked_gestures` (v1 → v2) and thumbwheel normalization |
| [`crates/openlogi-core/src/config/identity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/identity.rs) | `migrate_transport_scoped_keys` (v2 → v3) and route folding |
| [`crates/openlogi-core/src/config/tests.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/tests.rs) | Verification tests including `migrates_v1_button_and_gesture_bindings` |
| [`crates/openlogi-core/src/config/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.