Building & Testing
Everything you need to hack on ferrixd.
Toolchain
The workspace pins its Rust version in rust-toolchain.toml; with rustup, the right compiler is selected automatically:
git clone https://github.com/josunlp/ferrixd
cd ferrixd
cargo build # debug
cargo build --release # optimized (release profile strips symbols)No system libraries needed for the default build — SQLite is bundled (rusqlite/bundled) and the WASM interpreter (wasmi) is pure Rust.
Run a development server from the checkout:
cargo run -p ferrixd -- run --devThe quality gates
Exactly what CI runs — all four must pass:
cargo test # unit + integration tests
cargo clippy --all-targets -- -D warnings # lints are errors
cargo fmt --check # formatting
cargo deny check # license/advisory/dependency auditTwo lint policies deserve a call-out, because they're unusual and non-negotiable:
unsafe_code = "forbid"at the workspace level — code that needsunsafeneeds a different design.panic!/unwrap/expectare clippy errors in the data path — useResultand structured errors. Tests are exempt.
Testing philosophy
Unit tests live beside their modules; integration tests drive real connections (including TLS) against an in-process server.
Conformance: capabilities are implemented against the IRCv3 spec text. Running the irctest suite against a local build is a useful external check, but it is not (yet) wired into CI.
Fuzzing the protocol parser (nightly toolchain):
shcargo install cargo-fuzz cargo +nightly fuzz run parse_messageThe harness asserts the parser never panics and respects its length budgets on arbitrary input. If you touch
ferrix-protocol, run the fuzzer for a while before opening a PR.
Load testing
The loadtest/ crate (excluded from the workspace build) is the connection-density generator behind the 100k figure:
cd loadtest
cargo run --release -- --helpIt opens tens of thousands of registered connections with configurable join/message behavior and reports latency and throughput. Methodology notes live in loadtest/'s README. The headline result: ~100,000 concurrent connections on an 8-core host at ~1.38 GB RSS (~13.8 KB per connection), scaling linearly with connection count.
Repository layout for contributors
| Path | What lives there |
|---|---|
crates/ferrix-protocol/ | wire model, parser, encoder — dependency-light, fuzz-facing |
crates/ferrixd/src/ | the daemon (module map in Architecture) |
fuzz/ | cargo-fuzz targets |
loadtest/ | density load generator (own crate, excluded) |
scripts/ | installer scripts (POSIX sh — must stay dash/BusyBox/Termux-compatible — and PowerShell) |
docs/ | this documentation (VitePress) |
.github/workflows/ | ci.yml (gates above) and release.yml (7-target build matrix) |
Working on the docs
cd docs
npm install
npm run dev # live-reload dev server
npm run build # what CI/Pages runs; also checks internal linksCutting a release
- Bump
versionin the workspaceCargo.toml([workspace.package]). - Commit and push, then publish a GitHub Release tagged
vX.Y.Z(gh release create vX.Y.Z --generate-notes, or the GitHub UI). The tag must equal the crate version — the workflow refuses mismatches. Publishing the Release is what triggers the build; a bare tag push does not. release.ymlbuilds all 13 targets (musl statics + BSD/Android viacross, native macOS/Windows), generates SHA-256 checksums, attaches them to the release, and pushes a multi-arch container image to ghcr.io. Aworkflow_dispatchrun is a dry run: builds everything, publishes nothing.
Conventions
- Match the existing style;
rustfmtsettles formatting arguments. - Error handling:
thiserror-style structured errors in library code,anyhowat the CLI boundary. - New capabilities: implement against the IRCv3 spec text, add integration tests, and cover negotiation (LS/REQ/ACK/NAK) — not just the happy path.
- New limits: every bound needs a defined consequence, a log line, and — if clients can hit it — a metric.