Skip to content

Release v0.9.0

Latest

Choose a tag to compare

@github-actions github-actions released this 10 Apr 05:41
· 6 commits to main since this release

Released: 2026-04-10

This release adds Site Manager cloud fleet management, a full WiFi TUI dashboard, demo mode for PII-safe screen recordings, and a comprehensive end-to-end test suite running against a real controller. The command count grows from 27 to 28, and the documentation site migrates from VitePress to Zola.

🌟 Highlights

☁️ Site Manager Cloud Fleet Commands

New cloud command group (hosts, sites, devices, isp, sdwan, switch) provides fleet-wide visibility through the Ubiquiti Site Manager API at api.ui.com. A new SiteManagerClient in unifly-api handles cursor-based pagination and cloud rate-limit responses with Retry-After parsing. The cloud switch <site> subcommand fuzzy-matches site names and persists the new target in the active cloud profile.

📡 WiFi TUI Dashboard

A dedicated WiFi screen (key 9) with four sub-tabs surfaces RF health and client experience data directly in the TUI. The Overview tab shows AP health scores, client counts, and a toggleable channel map. Clients lists wireless clients with signal strength and expandable WiFi experience detail. Neighbors displays rogue/nearby APs with band filtering and sortable columns. Roaming renders per-client roam event timelines showing AP transitions and signal changes.

🎭 Demo Mode for PII Sanitization

New Sanitizer in crates/unifly/src/sanitizer.rs intercepts all entity payloads in the TUI data bridge before rendering, replacing personal names with cute deterministic aliases (Starling, Moonbeam, Cosmo...), WAN IPs with RFC 5737 documentation addresses (198.51.100.x), SSIDs with fun replacements ("The Promised LAN"), and optionally redacting MACs and ISP names. Activated via --demo, UNIFI_DEMO=1, or the [demo] config section.

✅ End-to-End Test Suite

A UniFi controller runs in Docker simulation mode during CI, and the CLI executes against it to verify session auth, device listing, output formatting, health subsystems, observability endpoints, destructive-command guards, and error paths. Over 30 e2e tests cover devices, stats, events, WiFi, VPN, NAT, settings, and client resolution errors.

🔧 Interactive Cloud Setup Wizard

unifly config cloud-setup validates a Site Manager API key, discovers accessible consoles and sites, and writes a ready-to-use cloud profile. The wizard prompts for API key entry (or detects UNIFI_API_KEY), lets users pick a console and site, and offers keyring, env var, or plaintext storage.

⚡ Lightweight Connect for One-Shot Commands

Controller::connect_lightweight() authenticates and sets up the session but defers the full data snapshot load. System commands (info, sysinfo, backup) now use this path, avoiding unnecessary network round-trips.

☁️ Cloud & Site Manager

  • New auth_mode = "cloud" end-to-end across CLI profiles, TUI onboarding, and settings screens — cloud profiles default the controller URL to https://api.ui.com, force system TLS defaults, disable WebSocket and session caching, and use longer polling intervals (60s refresh, 30s poll)
  • IntegrationClient detects cloud platform connections and surfaces ConsoleOffline / ConsoleAccessDenied errors with actionable guidance
  • Cloud connector routing uses /v1/connector/consoles/{host_id} prefix for Integration API tunneling
  • --host-id global flag and UNIFI_HOST_ID env var for CLI-level host targeting
  • cloud isp and cloud sdwan subcommands for ISP metrics and SD-WAN configuration visibility

🖥️ TUI Improvements

  • Device detail view now populates Ports, Radios, and Clients tabs with real data parsed from both Integration and Session API responses — ports show max speed, connector type, and PoE standard; radios show band, channel, width, standard, channel utilization, and TX retry rates
  • Firmware upgrade action (U key) with confirmation dialog in device detail
  • Screen key handlers now take priority over global handlers so device-specific bindings work correctly
  • Navigation range extended to keys 19 for the new WiFi screen
  • Notification TTL is now severity-aware — warnings and errors persist longer than informational toasts
  • Sponsor button replaces the old Donate/PayPal link with GitHub Sponsors URL

🔧 CLI & Config

  • --theme promoted from TUI-only to a global flag shared by CLI and TUI — resolution order: --themeUNIFLY_THEME env → config defaults.themesilkcircuit-neon
  • nat policies update merges only changed fields into the existing rule via the Session v2 API, eliminating the delete-and-recreate workaround
  • Non-interactive stdin detection — destructive commands (device remove, client forget, backup delete) now return a NonInteractiveRequiresYes error in CI/pipe contexts instead of blocking forever, guiding callers to pass --yes
  • config set normalizes auth_mode = "legacy" to "session" on write for backward compatibility
  • Error help text across AuthFailed, NoCredentials, Unsupported, ProfileNotFound, and NoConfig now references both config init (local) and config cloud-setup (Site Manager)

🐛 Bug Fixes

  • Platform detection rewritten — both /api/login and /api/auth/login are probed before deciding, fixing misclassification of recent standalone controllers that return 401 from the UniFi OS endpoint
  • NAT policy creationfilter_type corrected to use only NONE/ADDRESS_AND_PORT (controller rejects ADDRESS/PORT); interface fields now resolve Integration UUIDs to Session _id strings; rule_index auto-increments from existing rules instead of hardcoding 0
  • API-key 401 classificationSessionAuth enum distinguishes cookie vs API-key session clients; a rejected API key now returns InvalidApiKey instead of a misleading "session expired" message
  • macOS config path — uses ~/.config/unifly/ (XDG) instead of ~/Library/Application Support/unifly/ with automatic migration on first run
  • Tracing to stderr — CLI tracing subscriber output redirected from stdout to stderr so warn-level messages no longer corrupt JSON/YAML pipe output
  • Wi-Fi observability parsing — roam events extract from nested parameters.DEVICE_FROM.name instead of flat fields; WiFi experience uses wlan_band; channels redesigned from per-radio rows to country-level regulatory data; format_radio_band handles both stat/sta and wifiman band code families
  • VPN IPsec SA 404 — controllers with no VPN tunnels return 404 for stat/ipsec-sa; vpn status now catches this and shows an empty list
  • Client uplink MAC — resolution uses ap_mac for wireless and sw_mac for wired clients regardless of wireless info presence
  • Session events — API-key session clients treat 404 on stat/event as optional-missing; events watch gated on cookie-backed session auth

♻️ Refactoring

  • convert.rs (2,189 lines) decomposed into convert/ module directory with 13 per-entity submodules — no behavioral changes
  • Full legacysession rename across Rust modules, config keys, TUI labels, CLI error strings, and documentation — LegacyClientSessionClient, Error::LegacyApiError::SessionApi, ensure_legacy_accessensure_session_access, etc.
  • ensure_session_access promoted from a private function in events.rs to the shared util/access.rs module
  • CLI dispatch split into dispatch() and dispatch_extended() to stay under clippy's cognitive-complexity threshold

📝 Documentation

  • Documentation site migrated from VitePress to Zola — eliminates the Node.js build dependency; new SilkCircuit theme with dark/light modes, glassmorphism nav, modular SCSS token system, Elasticlunr search, lazy Mermaid rendering with theme-aware re-renders, and responsive three-column layout
  • llms.txt and llms-full.txt generated from Zola content at build time via docs/scripts/gen-llms-txt.sh
  • Prettier configuration (.prettierrc, .prettierignore) integrated into justfile for consistent non-Rust file formatting
  • GitHub issue templates (bug report, feature request), PR template, CODE_OF_CONDUCT.md, and SECURITY.md added
  • AGENTS.md and SKILL.md updated with 28-command inventory, cloud auth mode, WiFi screen, and corrected auth guidance

🔧 CI/CD

  • End-to-end workflow triggers on push-to-main and pull requests (was manual/weekly-only)
  • E2e pipeline overlaps Docker controller startup with Rust compilation for faster runs
  • AUR package publishing automated in the release pipeline via KSXGitHub/github-actions-deploy-aur
  • ClawHub skill publish job runs after GitHub Release creation
  • Version-files automation patches .claude-plugin/plugin.json, .cursor-plugin/plugin.json, and skills/unifly/SKILL.md on release

💥 Breaking Changes

  • auth_mode = "legacy" renamed to "session" — Existing config files with auth_mode = "legacy" are accepted as a backward-compatible alias and normalized to "session" on next config set write. No manual migration required, but new configs should use "session".
  • macOS config path changed — Config and cache now resolve via XDG (~/.config/unifly/, ~/.cache/unifly/) instead of ~/Library/Application Support/unifly/. Automatic migration runs on first launch; if it fails, a message instructs you to move files manually.

📋 Upgrade Notes

  • If you have auth_mode = "legacy" in your config, it will continue working but will be rewritten to "session" on next config write
  • macOS users: config files are auto-migrated from ~/Library/Application Support/unifly/ to ~/.config/unifly/ on first run
  • Scripts using destructive commands without --yes in non-interactive contexts (CI, pipes) will now fail with exit code 1 and a message pointing to the -y flag
  • Cloud profiles can be created with unifly config cloud-setup — existing local profiles are unaffected
  • The --theme flag is now global; if you were passing it to tui specifically, it still works but can now be used with any command

💜 Contributors

A huge thank you to the community contributor who helped shape this release:

  • @TickTockBent (Wes) landed #10, the nat policies update subcommand — replacing the old delete-and-recreate dance with a proper merge against the Session v2 API. A much nicer story for anyone scripting NAT changes. Thank you! 💜

Want to help shape the next release? The issue tracker is open, and PRs are always welcome.