feat: multi-profile config storage (desktop + Android)#1057
Open
dazzling-no-more wants to merge 1 commit intotherealaleph:mainfrom
Open
feat: multi-profile config storage (desktop + Android)#1057dazzling-no-more wants to merge 1 commit intotherealaleph:mainfrom
dazzling-no-more wants to merge 1 commit intotherealaleph:mainfrom
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.jsonfrom 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 separateprofiles.jsonnext to it, and switching a profile rewritesconfig.jsonto 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)
profiles.jsonwith anactivepointer and a list of{name, config}snapshots. Atomic temp-file + rename writes onevery mutation; no pre-delete window where the previous file could
be lost to a failed rename.
Desktop UI (src/bin/ui.rs)
profile…", "Manage…".
"Confirm delete?" row — profile data may be the user's only saved
copy, so deletion isn't one-click.
running task holds a cloned
Config; rewritingconfig.jsonunderit would cause runtime/disk drift).
Android UI (ProfileBar.kt)
HomeScreen.ui_langon switch: a profile that changes the localefires the same
onLangChangepath as the top-bar toggle, so RTL/LTRflips correctly.
values/strings.xmland
values-fa/strings.xml.Design invariants
These are documented at the top of both
profiles.rsandProfileStore.ktand pinned by tests on both sides:configis written toconfig.jsonbyte-for-byte. Unknown fields (fronting_groups,exit_node, future Rust-side keys) survive the round-trip.Config::extras(Rust, via#[serde(flatten)]) andMhrvConfig.extrasJson(Android, via aMODELLED_KEYSfilter)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.
active == "name"meansprofiles[name].configequals thelive
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
activesince the form no longer matches any savedsnapshot.
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.
profiles.jsonis renamedaside to
profiles.json.corrupt-<nanos>and surfaces a startuptoast. 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.