Skip to content

feat(desktop): snapshot and restore the OS proxy around system/pac - #218

Merged
loss-and-quick merged 1 commit into
mainfrom
feat/os-proxy-snapshot-restore
Jul 14, 2026
Merged

feat(desktop): snapshot and restore the OS proxy around system/pac#218
loss-and-quick merged 1 commit into
mainfrom
feat/os-proxy-snapshot-restore

Conversation

@loss-and-quick

Copy link
Copy Markdown
Owner

Summary

Close the two OS-proxy gaps documented in #65's reviewer notes: clear blanked whatever proxy the OS had (wiping one the user configured by hand), and a hard GUI crash left the OS pointing at a dead local port until the next start. Neither is matched by v2rayN — its clear is an unconditional disable on all three OSes and it has no crash recovery — so this is a step past parity, not catch-up.

Ownership record. The first apply in system/pac mode snapshots the current per-layer OS proxy state into a versioned os-proxy-backup.json in the user datadir (survives a reboot); its presence marks the OS proxy as ours. From there:

  • clear — record present → restore every layer from the snapshot, delete the record; absent → no-op (a proxy we didn't set is never touched); corrupt/unknown-version → the old blanket disable, then delete.
  • re-apply (restart, profile switch) — record present → apply only, never re-snapshot (that would capture our own settings).
  • crash recovery — at GUI start, right after the platform is built and before the Service, clear_os_proxy() runs once: the helper has already reaped the data-path when the GUI died, so a surviving record is by definition an orphan and the user's proxy is restored before anything else happens.
  • Partial restore failure degrades a layer to OFF, never half-restored; an unresolvable datadir degrades to the old blanket clear.

Per layer: Windows snapshots/restores the four WinINET values (ProxyEnable, ProxyServer, ProxyOverride, AutoConfigURL) via RegGetValueW; Linux snapshots the gsettings proxy keys (quote-stripped), the six kioslaverc keys via kreadconfig6/5, and the live-session values of the 8 proxy env vars from systemctl --user show-environment (our environment.d file is ours by name — just removed). The decision logic (snapshot-vs-apply, restore-vs-blank-vs-noop) is pure and unit-tested; the RunEvent::Exit and Service lifecycle call sites are untouched — they route through the same two functions and inherit the semantics.

Affected layer

  • frontend/ — React Web UI
  • crates/ · src-tauri/ — Rust core / backend / Tauri desktop
  • module/ — Android installable zip (thin launcher over the Rust daemon)
  • scripts/ — build / release helpers
  • CI / .github/
  • Docs only

Verification

Rust (crates/ · src-tauri/):

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace — one failure: net::tests::tcp_ping_fails_on_unreachable_host, the known environment flake (an active VPN tun on the dev machine captures the TEST-NET-1 connect); fails identically on main
  • No codegen drift (no settings/type changes; frontend/src/generated untouched)

New tests (9): record round-trip incl. the version field, absent→noop / corrupt→blank / wrong-version→blank, snapshot-only-when-no-record; gsettings quote-stripping, show-environment filtering incl. values containing =.

Windows #[cfg(windows)] (RegGetValueW reads) compiles only under the Windows CI job — every windows-sys 0.59 signature and constant was checked against the vendored crate source.

Checklist

  • Title is a scoped Conventional Commit; commits are logically split
  • No build artifacts committed (module/bin/<abi>/, geoip/geosite, built module/webroot/, src-tauri/gen/ — all gitignored on purpose)
  • Generated frontend/src/generated/ was regenerated from Rust, not hand-edited (untouched)
  • If user-visible strings changed: i18n/en.ts and every locale file updated (no user-visible strings changed)
  • Renames touching the project id were grepped in all case forms (kasumi-proxy, Kasumi Proxy, camelCase)

Notes for reviewers

  • The startup sweep is just clear_os_proxy() — the record's absence after a clean exit makes it a natural no-op, so there is no separate "recovery" code path to reason about.
  • Windows values absent at snapshot time restore to the neutral default (ProxyEnable=0, empty strings) rather than being deleted with RegDeleteKeyValueW — behaviourally identical for proxy-off, smaller diff.
  • A record that exists but can't be read (I/O error, not corruption) reads as absent → no-op. In a user-owned datadir this is effectively unreachable; the alternative (blanket clear) would wipe a hand-configured proxy on the failure of an unrelated read.
  • A gsettings key whose get fails at snapshot time is skipped and therefore not restored (that one key keeps our value). Schema-present-but-get-failing is another effectively-unreachable corner; noted for completeness.

The system/pac proxy modes overwrote the OS proxy with a blanket disable on
clear, wiping any proxy the user had configured by hand, and a hard GUI crash
left the OS proxy untouched until the next start.

Record the pre-existing per-layer OS proxy state in a versioned ownership file
(os-proxy-backup.json, in the datadir so it survives a reboot) on the first
apply. Clearing restores every layer from that snapshot and drops the record;
with no record the current proxy isn't ours and is left alone. A corrupt or
unknown-version record falls back to the old blanket disable. At GUI start a
surviving record is an orphan from an unclean exit, so the setup path restores
the user's proxy before bringing the data-path up.
@github-actions github-actions Bot added the backend Rust core/backend/desktop (crates/, src-tauri/) label Jul 14, 2026
@loss-and-quick
loss-and-quick merged commit dd93b1c into main Jul 14, 2026
9 checks passed
@loss-and-quick
loss-and-quick deleted the feat/os-proxy-snapshot-restore branch July 14, 2026 07:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Rust core/backend/desktop (crates/, src-tauri/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant