Files
AuraVPN/aura-gui
xah30 1f82bc41c0 feat(cli,aura-gui): v3.4.4 — graceful Shutdown via admin socket (#1)
Closes the long-standing "GUI Disconnect button doesn't actually kill aura"
bug. The previous kill path sent SIGTERM to sudo (our direct child) and
hoped sudo's signal forwarding would propagate to the aura child running
as root; in practice this is unreliable when the parent has no controlling
terminal (which Tauri-spawned children don't), so aura would survive the
"Disconnect" click with the TUN still up and the OS routes still installed.

## Implementation

Adds a `Shutdown` admin-socket request. The aura-cli main loops
(`client::run` and `server::run`) now `tokio::select!` between their normal
work (router.run() / accept loop) and a `tokio::sync::Notify` carried on
the shared `AdminState`. When an admin client posts `{"cmd":"shutdown"}`
the handler calls `state.shutdown.notify_one()`, the select! second arm
fires, the work future is dropped, `OsRouteGuard::Drop` rolls back the
installed system routes, and the process exits with `Ok(())` — clean exit
code 0, kernel reaps the TUN device, no orphan.

The whole round-trip is sub-500 ms in practice (the slow step is the
`route delete` invocations on macOS).

## What changed

* `aura-cli/src/admin.rs`: `Request::Shutdown` variant, `AdminState.shutdown:
  Arc<Notify>` field, handler that calls `notify_one()` + returns `Response::ok()`.
* `aura-cli/src/client.rs`: clones `admin_state.shutdown` before spawning the
  admin server task, then `tokio::select!`s between `router.run()` and
  `shutdown.notified()`. Whichever finishes first ends the function; OsRouteGuard
  Drop runs after.
* `aura-cli/src/server.rs`: same pattern around the `MultiServer::accept` loop —
  graceful exit on admin Shutdown leaves the accept loop, breaks, and the
  router_task is aborted on function return.
* `aura-cli/src/main.rs`: `aura shutdown --admin-socket <path>` subcommand for
  CLI control (also useful from launchd/systemd post-stop hooks).
* `aura-gui/src-tauri/src/admin.rs`: new `send_shutdown(path)` helper; factored
  out `round_trip()` for the common write-line + read-line pattern. Windows
  stub returns "not implemented".
* `aura-gui/src-tauri/src/cli_proc.rs`: `ClientHandle::kill` now tries admin
  Shutdown first (3 s poll for graceful exit), then SIGTERM to sudo (2 s),
  then SIGKILL as last resort. The admin path needs no sudo because the
  socket is already chmod 0666 from v3.4.1.

## Test

New `admin::tests::shutdown_request_fires_notify` unit test: spawns a
notified() waiter, calls `handle_request(Request::Shutdown)`, asserts the
waiter wakes within 200 ms. Combined with the existing 5 admin tests, all 6
pass.

`cargo test --workspace` — all green, `cargo clippy --workspace --all-targets
-- -D warnings` — clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 22:18:00 +03:00
..

aura-gui — desktop client for AuraVPN

A Tauri 2 + React TypeScript app that runs in the system tray. It's the GUI front-end for the existing aura CLI: import a provisioned bundle (.tgz), pick a profile, hit Connect, watch the live tunnel status. No clash-verge replacement and no protocol patching — just a thin manager around the existing CLI.

Status

v0.1 (MVP) — scaffolding + core flows. Working:

  • Profile list / import / delete (drop in a provision-client .tgz and you're set)
  • Connect / Disconnect (spawns / kills aura client per profile)
  • Live status panel (peer, tx/rx packets, default action, rules) via admin socket
  • System tray with Open / Disconnect / Quit menu
  • Close button hides to tray (app stays alive in background)

Deferred for v0.2:

  • Auto-start at login (launchd plist / systemd user unit / Windows Run key)
  • Code signing + notarization (macOS) / Authenticode (Windows)
  • Per-profile route overrides editor
  • Live log streaming (currently polled, frontend tails the in-memory ring)
  • Admin status query on Windows (uses Unix sockets today; need named pipe support)

Layout

aura-gui/
├── src-tauri/          (Rust 2 backend, separate Cargo manifest)
│   ├── src/
│   │   ├── lib.rs      (Tauri commands + tray + window plumbing)
│   │   ├── profiles.rs ([app_data]/profiles/ I/O + .tgz import)
│   │   ├── cli_proc.rs (spawns aura client + stderr ring buffer)
│   │   └── admin.rs    (JSON-line admin socket client)
│   ├── Cargo.toml
│   └── tauri.conf.json
├── src/                (React TS frontend)
│   ├── App.tsx
│   └── App.css
├── package.json
└── README.md           (this file)

The src-tauri/ crate is intentionally excluded from the workspace at the repo root (workspace.exclude = ["aura-gui"]) so cargo check --workspace from the project root keeps checking just the protocol crates and doesn't pull tauri/wry/webview into every CI run.

Build

# Backend deps come down with cargo at build time
cd aura-gui
npm install                       # ~10 s, downloads vite + React 19
npm run build                     # frontend tsc + vite build → dist/
npm run tauri build               # full bundle: .dmg / .deb / .msi / .AppImage

For dev:

npm run tauri dev

The first build downloads ~200 MB of native deps (tauri, wry, webview) — subsequent builds are fast (incremental).

Profile storage

Per-platform app-data dir:

OS Path
macOS ~/Library/Application Support/ru.undergr0und.aura/profiles/
Linux ~/.config/AuraVPN/profiles/
Windows %APPDATA%\AuraVPN\profiles\

Each profile is a directory with the same shape as aura provision-client emits:

profiles/<id>/
├── client.toml
├── ca.crt
├── client.crt
├── client.key
└── bridges.signed   (optional, v3.3+)

The id is the basename of the imported .tgz (e.g. client-1.tgzprofiles/client-1/).

Aura binary path

The GUI shells out to aura client for each connection. It defaults to:

  1. /Users/xah30/AuraVPN/target/release/aura if present (dev convenience),
  2. /usr/local/bin/aura on Unix,
  3. C:\Program Files\AuraVPN\aura.exe on Windows.

Change it at runtime via the "Change…" button at the bottom of the window. The setting is session-only for now (persisting it to a config file is a v0.2 todo).

Sudo / admin privileges

aura client creates a TUN device, which needs root on Unix and Administrator on Windows. Currently the GUI does not run with elevated privileges — the operator must launch it from a privileged shell, or via sudo open -a aura-gui on macOS, etc.

v0.2 will add a polkit / authorization-services prompt for the privileged step.

Why not just patch clash-verge?

We thought about it. AuraVPN is an L3 IP-tunnel (like WireGuard); clash-verge / mihomo / sing-box outbounds are L4 per-flow proxies (like Trojan / VLESS / Hysteria). Bridging the two requires either a user-space TCP/IP stack inside the outbound (gVisor) or extensive mihomo patching. Neither was a small lift, and a self-contained tray app turned out to be the shortest path to "vpn that always-on in a clash-verge-ish UX".

A v0.3 stretch goal is to ship a local SOCKS5 listener alongside the TUN, so clash-verge users who already use SOCKS5 outbounds can point at AuraVPN as a SOCKS5 proxy. That requires the gVisor netstack — separate piece of work.