Skip to content

feat: multi-profile config storage (desktop + Android)#1057

Open
dazzling-no-more wants to merge 1 commit intotherealaleph:mainfrom
dazzling-no-more:feature/profile
Open

feat: multi-profile config storage (desktop + Android)#1057
dazzling-no-more wants to merge 1 commit intotherealaleph:mainfrom
dazzling-no-more:feature/profile

Conversation

@dazzling-no-more
Copy link
Copy Markdown
Contributor

Summary

Adds a multi-profile system so users can keep several complete config snapshots side by side (e.g. one Apps Script setup + one Full tunnel setup) and switch between them without re-typing deployment IDs, auth keys, or tuning knobs. Available on both the desktop egui UI and the Android Compose UI; the on-disk format is identical so a profiles.json from desktop is bit-for-bit importable into Android (and vice versa).

The runtime is unchanged — the proxy server, tunnel client, and MITM still read a single config.json. Profiles live in a separate
profiles.json next to it, and switching a profile rewrites config.json to the chosen snapshot before reloading the form.

Originally requested in a community feature ask: #1044

What's in it

Storage layer (src/profiles.rs,
ProfileStore.kt)

  • New profiles.json with an active pointer and a list of
    {name, config} snapshots. Atomic temp-file + rename writes on
    every mutation; no pre-delete window where the previous file could
    be lost to a failed rename.
  • Identical on-disk schema on Rust and Kotlin sides.

Desktop UI (src/bin/ui.rs)

  • Profile bar above the Mode section: selector dropdown, "Save as
    profile…", "Manage…".
  • Manage window has rename / duplicate / delete with an inline
    "Confirm delete?" row — profile data may be the user's only saved
    copy, so deletion isn't one-click.
  • Selector + Save-as both disabled while the proxy is running (the
    running task holds a cloned Config; rewriting config.json under
    it would cause runtime/disk drift).

Android UI (ProfileBar.kt)

  • Same selector / save / manage surfaces, wired into HomeScreen.
  • Honours ui_lang on switch: a profile that changes the locale
    fires the same onLangChange path as the top-bar toggle, so RTL/LTR
    flips correctly.
  • All new user-visible strings localised in both values/strings.xml
    and values-fa/strings.xml.

Design invariants

These are documented at the top of both profiles.rs and ProfileStore.kt and pinned by tests on both sides:

  1. Raw snapshot preservation. A profile's config is written to
    config.json byte-for-byte. Unknown fields (fronting_groups,
    exit_node, future Rust-side keys) survive the round-trip.
    Config::extras (Rust, via #[serde(flatten)]) and
    MhrvConfig.extrasJson (Android, via a MODELLED_KEYS filter)
    carry the bytes through the in-memory form too — so even after a
    user edits a field, the unknown keys are re-emitted on the next
    save.
  2. active == "name" means profiles[name].config equals the
    live config.json.
    Save-as writes both files (config first,
    profiles second). Deleting the active profile clears active = ""
    instead of jumping to a different profile. Regular form edits
    also clear active since the form no longer matches any saved
    snapshot.
  3. Persist before in-memory state changes. All mutations clone,
    save, and only commit the clone back to in-memory state on
    success. A failed disk write leaves both the live UI and the
    on-disk file unchanged.
  4. Load failure is loud. A corrupt profiles.json is renamed
    aside to profiles.json.corrupt-<nanos> and surfaces a startup
    toast. If the backup rename also fails, all profile writes are
    disabled for the session so we can't clobber the recoverable
    bytes. Read I/O failures (permissions, locked) get the same
    write-gate treatment.

@github-actions github-actions Bot added the type: feature feat: PR — auto-applied by release-drafter label May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feature feat: PR — auto-applied by release-drafter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant