Thanks for your interest in contributing! Whether it's a bug report, feature idea, or code contribution — it all helps make unifly better.
Open a GitHub Issue with:
- unifly version (
unifly --version) - Controller model and firmware version
- Steps to reproduce
- Expected vs. actual behavior
- Relevant error output (use
-vvvfor full debug logging)
Open a GitHub Issue describing:
- The use case — what problem does this solve?
- How you'd expect it to work (CLI syntax, TUI behavior, etc.)
- Whether it relates to the Integration API, Legacy API, or both
Check the ROADMAP.md first — it might already be planned.
- Rust 1.86+ (edition 2024) — install via rustup
- Nightly rustfmt —
rustup component add rustfmt --toolchain nightly - A UniFi Network controller for integration testing (Cloud Key, Dream Machine, or self-hosted)
git clone https://github.com/hyperb1iss/unifly.git
cd unifly
cargo build --workspace
cargo test --workspacecargo run -p unifly -- devices list
cargo run -p unifly-tuicrates/
unifly-api/ # Async HTTP/WebSocket transport — Integration + Legacy API clients
unifly-core/ # Controller lifecycle, DataStore, entity models, reactive streams
unifly-config/ # Profile management, keyring integration, TOML config
unifly/ # CLI binary — clap command routing, output formatting
unifly-tui/ # TUI binary — 8-screen ratatui dashboard, charts, SilkCircuit theme
Dependency chain: unifly-api <- unifly-core <- unifly-config <- CLI / TUI binaries.
See the README for the full architecture overview.
cargo +nightly fmt --allThe project uses nightly rustfmt with a custom rustfmt.toml (100-char max width, field init shorthand, try shorthand).
cargo clippy --workspace --all-targets -- -D warningsThe workspace has opinionated clippy configuration — pedantic lints enabled. Key rules:
unsafe_code = "forbid"— no unsafe code, periodunwrap_used = "deny"— use?,.ok(),.unwrap_or(), or proper error handlingpedantic = "deny"— clippy pedantic lints are enforced
- Error types use
thiserrorwithmiettefor rich diagnostics - Async runtime is
tokio; all public async APIs areSend + Sync - Entity IDs use the
EntityIdenum (Uuid|Legacy) for dual-API compatibility - Secrets are wrapped in
secrecy::SecretString— never log or display credentials
- Fork the repository
- Branch from
main(feature/my-featureorfix/my-fix) - Implement your changes with tests where applicable
- Ensure CI passes locally:
cargo +nightly fmt --all --check cargo clippy --workspace --all-targets -- -D warnings cargo test --workspace - Open a PR targeting
main - Describe what changed and why — link the relevant issue if one exists
PRs are reviewed for correctness, style consistency, and architectural fit. Don't worry about perfection on the first pass — feedback is collaborative, not adversarial.
By contributing, you agree that your contributions will be licensed under the Apache License 2.0, the same license covering the project.