When config.json schema changes between versions, register a migration function in src/config/migrations.js. On startup, loadSiteConfig() detects the stored version, runs all migrations forward, and writes the result back. Users never see the migration — it runs silently and the rest of the app only ever sees the current schema. Includes: - v0 → v1: normalize dns from string to object - v1 → v2: add dns.provider field (default 'technitium') - Forward compat: configs from future versions left untouched - Idempotent: re-running on already-migrated config is a no-op - Safe: no user data is removed during migration 21 unit tests covering edge cases: null input, forward compat, corrupt JSON, missing parent dirs, idempotency, full migration chain.