7c8ea919c4d30486b53fc256fb54c52e5a9c5ca8
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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>
|
||
|
|
fa452f00b1 |
fix(cli,aura-gui): v3.4.2 — break the infinite-tunnel loop + drop dead GUI handle
Two coupled bugs from the macOS half-Internet-route fix (v3.4.1) put together. ## 1) Infinite tunnel loop: server IP routed through itself v3.4.1 made `default = "VPN"` actually win against the host's pre-existing default by installing `0.0.0.0/1` and `128.0.0.0/1` via the TUN. Both half-Internet routes are strictly more specific than `0.0.0.0/0`, so outbound traffic finally went through Aura. Side-effect: the server's outer IP (187.77.67.17) falls inside `128/1`. That means Aura's own encrypted ciphertext to 187.77.67.17:443 also matched the new route → re-entered the TUN → was about to be re-encrypted and shipped to … itself. The kernel held the existing TCP socket on en0 for a few seconds (sticky source), so the connection survived briefly. As soon as anything triggered a re-route resolution (TCP retransmit on a different socket, cover-traffic, new cipher frame), the socket flipped to utun4 and the data plane died — exactly the "Aura умирает через пару секунд" the user reported. Fix: before calling `OsRouteGuard::install`, scan the dial config for outer-endpoint IPs (the primary `server_addr` plus any `[client] bridges` entries) and inject them into `SplitRoutes::direct_hosts`. The existing macOS plan turns `direct_hosts` into `route add -host <ip> <gateway>` — a /32 bypass via the original LAN gateway, more specific than `128/1`, so the kernel routes the ciphertext via en0 even after the half-Internet routes are in. No recursion, no flap, no death. Only applied when `default = "VPN"` (the only mode where the bypass is needed). Linux doesn't need it — the `metric 50` default-via-TUN doesn't override more-specific kernel routes. ## 2) Dead GUI handle wedges the Connect button `connect()` in lib.rs refused with "already running" whenever the `Option<ClientHandle>` was `Some`, regardless of whether the child was still alive. So when the aura-client died from bug #1 (within ~2 s of Connect), the UI was permanently stuck — the only escape was quitting and relaunching the GUI. Now `connect()` checks `prev.is_alive()` first. If the previous handle is dead, we reap it (calling `kill()` to consume the handle's drop path) and spawn a fresh one transparently. Reconnect-after-crash now Just Works. This also matches what a sensible "Connect" button does on every other VPN GUI: clicking it when something looks stuck should make progress, not demand a quit-and-relaunch dance. ## Verification - `cargo test -p aura-cli --lib os_routes` — 21/21 ok - `cargo build --release` — green - Rebuilt /Applications/Aura.app against both fixes - Server-side aura.service restarted to clear the leftover pool reservation the dead session never released (see v3.5 task #52 for the auto-cleanup follow-up) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
f68a61f760 |
fix(tunnel,aura-gui): macOS TUN auto-assign + admin-access check
Two bugs found in the GUI's first end-to-end test: ## #41 was incomplete — `Some("")` is not the same as `None` for tun-rs The agent's earlier #41 fix passed `""` to `Configuration::tun_name()` expecting the tun crate to treat empty as "let the kernel auto-assign". It doesn't. Looking at tun-0.8.9/src/platform/macos/device.rs: if !tun_name.starts_with("utun") { return Err(Error::InvalidName); } An empty string fails `starts_with("utun")` so the create errors out before the kernel is ever consulted. The auto-assign branch ONLY triggers when `config.tun_name` is `None` — which requires us to skip the `.tun_name()` call entirely, not pass a sentinel value. Fix: split the builder chain so `.tun_name()` is only called when the sanitized name is non-empty. The kernel now correctly auto-picks the next free `utunN` for the standard provisioned `tun_name = "aura0"` config. User-visible symptom this resolves: the GUI's Connect button consistently died with `failed to create TUN device 'aura0'` followed by an InvalidName chain, even though aura was running as root. ## check_admin_access tested the wrong command shape `check_admin_access` ran `sudo -n <aura> --help` and inferred the sudoers entry was installed iff that succeeded. But our sudoers entry is scoped to `<aura> client *` — `<aura> --help` does NOT match, so even when the entry was correctly installed and Connect was already working, the yellow "One-time setup needed" banner stayed up forever. Switched to `sudo -n -l <aura>` which lists matching sudoers entries for the binary path itself. Returns 0 iff ANY entry covers it without a password — works regardless of the per-command scope. ## Verification - `cargo test -p aura-tunnel --lib tun` — all 3 sanitize / create tests pass - Rebuilt `target/release/aura` and `/Applications/Aura.app` against the fixes - Confirmed via `sudo -n -l /Users/xah30/AuraVPN/target/release/aura` that the installed sudoers entry is detectable by the new check 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
dbee9d8b93 |
fix(aura-gui): rename product to 'Aura' + uppercase SETENV in sudoers
Two bugs visible after user's first Install-admin-access click:
1) `visudo -c` rejected the fragment because sudoers tags must be UPPERCASE.
We wrote `setenv:` (lowercase) which sudoers does not recognise as a tag
and treats as a command path, producing a syntax error at column 29 of
/etc/sudoers.d/aura-gui:2 — and worse, the broken file stayed on disk
so every subsequent `sudo` complained about the syntax error too (sudo
still functions but the warning is noise).
Fix: drop the `setenv:` tag entirely. We never needed it — the GUI only
passes RUST_LOG to the child via env(), which `sudo -E` would forward
but we deliberately chose not to (smaller surface). Removing the tag
also removes the failure mode.
2) Product rename per user feedback ("переименуй пакет на просто Aura"):
- tauri.conf.json `productName` and window title: `aura-gui` -> `Aura`
- bundle now produces /Applications/Aura.app and Aura_0.1.0_aarch64.dmg
- identifier `ru.undergr0und.aura` was already correct, no change
- sudoers file is now /etc/sudoers.d/aura (was aura-gui), so the success
message and revert hint are updated accordingly
The internal MacOS/ binary is still named `aura-gui` (Tauri uses the Cargo
crate name there) — not user-visible, only the dev internals see it.
Manual cleanup also performed on the dev host:
- /Applications/aura-gui.app removed
- /etc/sudoers.d/aura-gui (the broken fragment from the first failed
install attempt) removed via `osascript ... with administrator
privileges` so sudo is no longer logging syntax errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
1635190797 |
feat(aura-gui): privilege escalation via sudo + one-click NOPASSWD installer
The v0.1 GUI's Connect button was broken in practice: the Tauri app launched from /Applications runs as the desktop user, so `Command::new(aura).spawn()` started aura without root. aura died in ms with EPERM at TUN creation, faster than the 1.5 s status poller could catch — the UI just silently flipped back to "disconnected" with no clue. ## Fix * `cli_proc::spawn_client` now prepends `sudo -n` on Unix. After spawn it blocks for 1.5 s and checks `try_wait`; if the child already exited, it reads the stderr ring's last 20 lines and returns an anyhow Error with that tail + a hint list of common causes. The Tauri command surfaces it to the frontend's `error` state where the UI renders it as a multi-line `<pre>` block instead of the previous single-line text. * `ClientHandle::kill` no longer uses `Child::kill` (SIGKILL) on its sudo parent — that would have left aura orphaned with the TUN lingering. Sends SIGTERM to sudo, which sudo forwards to aura, giving the inner `OsRouteGuard::Drop` 2 s to run cleanup. Falls back to SIGKILL only after the grace period. ## One-click NOPASSWD installer Two new Tauri commands plus a UI banner: * `check_admin_access` — runs `sudo -n aura --help` and returns whether the sudoers entry is in place. Used by the React side to decide whether to show the banner. * `install_sudoers_admin` — runs `osascript ... with administrator privileges` which surfaces the native macOS auth dialog, then writes `/etc/sudoers.d/aura-gui` scoped to `<aura> client *` only (not arbitrary aura invocations), runs `visudo -c` for syntax validation, and reports success or the syntax error. The frontend shows a yellow "One-time setup needed" banner above the profile list whenever `adminReady === false`. Clicking the button pops the Mac password dialog once; from then on Connect is a single click with no prompt. ## UI feedback * "Connecting…" disabled state on the Connect button while spawn_client's 1.5 s wait is in progress * Errors render as monospace `<pre>` so the multi-line stderr tail is readable * `.error` and `.admin-banner` CSS classes added to App.css 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
40b38beb11 |
feat(aura-gui): v0.1 Tauri-based desktop client — system tray + profile manager + admin status
New crate (kept out of the cargo workspace so the protocol-side check/test cycle stays fast): a Tauri 2 + React 19 + TypeScript desktop app that runs in the system tray and manages `aura client` for the user. The clash-verge replacement we settled on instead of trying to shoehorn AuraVPN's L3 IP-tunnel into a clash-verge L4 outbound. ## What's wired - **Profile manager** — `aura-gui/src-tauri/src/profiles.rs`. App-data layout (`~/Library/Application Support/ru.undergr0und.aura/profiles/<id>/` on macOS, the equivalent on Linux + Windows). `import_profile_from_tgz` accepts the same bundle shape `aura provision-client` emits, detects flat vs single-dir layouts, and refuses overwrites unless the operator deletes first. `delete_profile` refuses symlinks. - **Connection control** — `cli_proc.rs`. Spawns `aura client --config <profile>/client.toml --admin-socket /tmp/aura-admin-<uid>-<profile>.sock`, captures stderr into a bounded in-memory ring (200 lines) for the UI to tail, kills via `Child::kill` on disconnect. Per-profile / per-uid socket paths so two GUIs (or two profiles) don't collide. - **Live status** — `admin.rs`. Tiny JSON-line client for the v3.3 admin socket. Polled by the React app every 1.5 s: peer id, rx/tx packets, default action, rule count. Falls back gracefully (admin_error in the response) when the handshake hasn't completed yet. - **System tray** — `lib.rs` `setup` callback. Three-item menu (Open AuraVPN / Disconnect / Quit). The window's close button hides to the tray instead of exiting — the app keeps running so the VPN stays connected; the user explicitly chooses Quit. - **Frontend** — `src/App.tsx`. Single-page layout: profile list (with badge for missing files), connect/disconnect button per profile, status table, collapsible logs panel, binary-path picker at the bottom. Dark-mode CSS by default; the same look as a typical WireGuard / Tailscale-style tray app. ## What's deferred for v0.2 - Auto-start at login (launchd plist / systemd user unit / Windows Run key) - Code signing + notarization - Persisting the aura binary path between sessions - Per-profile route overrides editor - Live log streaming (today the frontend polls the ring buffer) - Admin status query on Windows (today's `admin.rs` Unix-only; Windows path returns a clear "not supported yet" error) - Polkit / authorization-services prompt for the TUN-needs-root step (today the operator has to launch the GUI from a privileged context, e.g. `sudo open -a aura-gui` on macOS) ## Workspace hygiene Cargo workspace at the repo root now has `exclude = ["aura-gui"]` so the protocol crates' `cargo check --workspace` / `cargo test --workspace` don't pull in the tauri + wry + webview dep graph. The GUI builds standalone from `aura-gui/` via `npm run tauri build`. ## Validation - `cd aura-gui/src-tauri && cargo check` — green - `cd aura-gui/src-tauri && cargo clippy -- -D warnings` — clean - `cd aura-gui/src-tauri && cargo fmt --check` — clean - `cd aura-gui && npm run build` — frontend tsc + vite build succeeds - Full `npm run tauri dev` not exercised in this session (would open a real window) — should work; if it breaks the surface area is small enough that next session fixes it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |