Exposes the already-complete irtt_server::Server runtime as a real command-line applet. This is CLI orchestration only; no protocol, session or runtime behavior is added or changed.
Adds a server feature to irtt-cli, enabling optional irtt-server and tokio dependencies.
Makes server part of the default feature set, so a normal install now ships a working server.
Adds a standalone irtt-server binary target, gated on that feature.
Makes irtt-rs server ... and argv0 == irtt-server reach the same parser and runner; the dispatcher's help now reports the applet as available rather than unconditionally unavailable.
Maps CLI policy options directly onto ServerConfig builders, starting from ServerConfig::default() so library defaults are never restated in clap.
Runs one reusable irtt_server::Server on one current-thread Tokio runtime with one explicitly bound socket.
Reuses the existing process-wide shutdown flag installed by the shared signal handler; no second handler and no change to the client/TUI signal model.
CLI surface
irtt-server --bind 127.0.0.1:2112
irtt-rs server --bind 192.0.2.10:2112 \
--max-sessions 512\
--idle-timeout 30s
One listener per process. Serving a second address or family means a second process.
The bind address is required and parsed as a SocketAddr; host names are not resolved and there is no implicit wildcard default.
Wildcard binds are accepted, but reply source-address selection is then left to the kernel: per-packet destination-address handling for multi-homed hosts remains future work, and the help text says so.
The actual bound endpoint is printed to stderr on startup, which also resolves --bind 127.0.0.1:0.
Ctrl-C returns from Server::run normally: no close packets, exit status 0.
Zero is preserved as meaningful operator policy where ServerConfig defines it (--min-interval 0, --idle-timeout 0, --burst 0, --max-sessions 0, --max-packet-length 0). --max-duration is positive-only; omitting it is how "no maximum" is expressed, so a zero maximum is rejected rather than silently treated as absence.
The feature boundary is real: --no-default-features --features server builds the server applet without irtt-client, irtt-stats, ratatui or crossterm, and a bare --no-default-features build still produces the irtt-rs dispatcher, which reports the server applet as needing the server feature.
Binaries: --no-default-features → irtt-rs; + --features server → irtt-rs, irtt-server; default → irtt-rs, irtt-cli, irtt-server; --all-features → those plus irtt-tui.
Scope exclusions
No protocol or server-core behavior changes; irtt-proto, irtt-client and irtt-server production code are untouched. Not in this change: DSCP socket policy, the full ServerFill policy, pktinfo/source-address selection, daemonization, config files, a logging framework, hostname resolution, and multi-listener orchestration.
Verification
All of the following ran and passed on this branch:
cargo check -p irtt-cli --no-default-features --features server --all-targets --locked
cargo test -p irtt-cli --no-default-features --features server --locked
cargo test -p irtt-cli --locked and cargo test -p irtt-cli --all-features --locked
cargo tree for the server-only and bare-dispatcher builds, confirming irtt-server + tokio present and no client/stats/TUI dependencies leaking in
Manual smoke: irtt-server --bind 127.0.0.1:0 printed the resolved ephemeral endpoint and exited 0 on SIGINT; irtt-rs server --help and irtt-server --help produce byte-identical output.
Tests added: server argument parsing (required bind, IPv4/IPv6, policy mapping, zero-policy values, unit-less durations, zero --max-duration), a drift guard asserting that a bind-only invocation yields exactly ServerConfig::default(), feature-aware subcommand and argv0 dispatch tests, a standalone-binary help test, and one runtime smoke test that binds 127.0.0.1:0 with shutdown already requested and asserts run_server returns Ok.
One incidental fix: the new bin shares its name with the irtt-server library crate, which made cargo doc --workspace emit an output-filename-collision warning, so the bin target sets doc = false.
## Summary
Exposes the already-complete `irtt_server::Server` runtime as a real command-line applet. This is CLI orchestration only; no protocol, session or runtime behavior is added or changed.
- Adds a `server` feature to `irtt-cli`, enabling optional `irtt-server` and `tokio` dependencies.
- Makes `server` part of the default feature set, so a normal install now ships a working server.
- Adds a standalone `irtt-server` binary target, gated on that feature.
- Makes `irtt-rs server ...` and `argv0 == irtt-server` reach the same parser and runner; the dispatcher's help now reports the applet as available rather than unconditionally unavailable.
- Maps CLI policy options directly onto `ServerConfig` builders, starting from `ServerConfig::default()` so library defaults are never restated in clap.
- Runs one reusable `irtt_server::Server` on one current-thread Tokio runtime with one explicitly bound socket.
- Reuses the existing process-wide shutdown flag installed by the shared signal handler; no second handler and no change to the client/TUI signal model.
## CLI surface
```sh
irtt-server --bind 127.0.0.1:2112
```
```sh
irtt-rs server --bind 192.0.2.10:2112 \
--max-sessions 512 \
--idle-timeout 30s
```
Options: `--bind`, `--hmac`, `--max-sessions`, `--max-packet-length`, `--min-interval`, `--burst`, `--idle-timeout`, `--max-duration`.
- One listener per process. Serving a second address or family means a second process.
- The bind address is required and parsed as a `SocketAddr`; host names are not resolved and there is no implicit wildcard default.
- Wildcard binds are accepted, but reply source-address selection is then left to the kernel: per-packet destination-address handling for multi-homed hosts remains future work, and the help text says so.
- The actual bound endpoint is printed to stderr on startup, which also resolves `--bind 127.0.0.1:0`.
- `Ctrl-C` returns from `Server::run` normally: no close packets, exit status 0.
Zero is preserved as meaningful operator policy where `ServerConfig` defines it (`--min-interval 0`, `--idle-timeout 0`, `--burst 0`, `--max-sessions 0`, `--max-packet-length 0`). `--max-duration` is positive-only; omitting it is how "no maximum" is expressed, so a zero maximum is rejected rather than silently treated as absence.
## Feature model
- `default = ["client", "server", "stats"]`; `full = ["client", "server", "stats", "tui"]`.
- The TUI stays optional.
- The feature boundary is real: `--no-default-features --features server` builds the server applet without `irtt-client`, `irtt-stats`, `ratatui` or `crossterm`, and a bare `--no-default-features` build still produces the `irtt-rs` dispatcher, which reports the server applet as needing the `server` feature.
Binaries: `--no-default-features` → `irtt-rs`; `+ --features server` → `irtt-rs`, `irtt-server`; default → `irtt-rs`, `irtt-cli`, `irtt-server`; `--all-features` → those plus `irtt-tui`.
## Scope exclusions
No protocol or server-core behavior changes; `irtt-proto`, `irtt-client` and `irtt-server` production code are untouched. Not in this change: DSCP socket policy, the full `ServerFill` policy, pktinfo/source-address selection, daemonization, config files, a logging framework, hostname resolution, and multi-listener orchestration.
## Verification
All of the following ran and passed on this branch:
- `cargo fmt --all -- --check`
- `cargo check --workspace --all-targets --all-features --locked`
- `cargo test --workspace --all-targets --all-features --locked` (634 tests)
- `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings`
- `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps`
- `cargo check -p irtt-cli --no-default-features --all-targets --locked`
- `cargo check -p irtt-cli --no-default-features --features server --all-targets --locked`
- `cargo test -p irtt-cli --no-default-features --features server --locked`
- `cargo test -p irtt-cli --locked` and `cargo test -p irtt-cli --all-features --locked`
- `cargo tree` for the server-only and bare-dispatcher builds, confirming `irtt-server` + `tokio` present and no client/stats/TUI dependencies leaking in
Manual smoke: `irtt-server --bind 127.0.0.1:0` printed the resolved ephemeral endpoint and exited 0 on `SIGINT`; `irtt-rs server --help` and `irtt-server --help` produce byte-identical output.
Tests added: server argument parsing (required bind, IPv4/IPv6, policy mapping, zero-policy values, unit-less durations, zero `--max-duration`), a drift guard asserting that a bind-only invocation yields exactly `ServerConfig::default()`, feature-aware subcommand and `argv0` dispatch tests, a standalone-binary help test, and one runtime smoke test that binds `127.0.0.1:0` with shutdown already requested and asserts `run_server` returns `Ok`.
One incidental fix: the new bin shares its name with the `irtt-server` library crate, which made `cargo doc --workspace` emit an output-filename-collision warning, so the bin target sets `doc = false`.
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
### 💡 Codex Review
Here are some automated review suggestions for this pull request.
**Reviewed commit:** `a672f78e86`
<details> <summary>ℹ️ About Codex in GitHub</summary>
<br/>
[Your team has set up Codex to review pull requests in this repo](https://chatgpt.com/codex/cloud/settings/general). Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
</details>
Recognize the .exe suffix before dispatching the server
On Windows, this new binary reaches the shared dispatcher with an argv[0] basename such as irtt-server.exe, but detect_applet_from_argv0 recognizes only irtt-server; the subsequent starts_with("irtt-") branch therefore rejects every invocation as an unknown applet before Clap or the server runner can execute. Strip the platform executable suffix or recognize the .exe applet names so the advertised server applet works on Windows.
**<sub><sub></sub></sub> Recognize the .exe suffix before dispatching the server**
On Windows, this new binary reaches the shared dispatcher with an `argv[0]` basename such as `irtt-server.exe`, but `detect_applet_from_argv0` recognizes only `irtt-server`; the subsequent `starts_with("irtt-")` branch therefore rejects every invocation as an unknown applet before Clap or the server runner can execute. Strip the platform executable suffix or recognize the `.exe` applet names so the advertised server applet works on Windows.
AGENTS.md reference: [AGENTS.md:L68-L68](https://github.com/Lochnair/irtt-rs/blob/a672f78e861770e339bdff16082986078cb82508/AGENTS.md#L68-L68)
Useful? React with 👍 / 👎.
Fixed in 565d7fb: applet_basename now strips the platform EXE_SUFFIX (case-insensitively, no-op on Unix), so irtt-server.exe dispatches correctly — the same bug also broke irtt-cli.exe, irtt-tui.exe and even the irtt-rs.exe dispatcher itself, which all fell into the unknown-applet branch, and Windows is a shipped target per dist-workspace.toml.
Fixed in 565d7fb: `applet_basename` now strips the platform `EXE_SUFFIX` (case-insensitively, no-op on Unix), so `irtt-server.exe` dispatches correctly — the same bug also broke `irtt-cli.exe`, `irtt-tui.exe` and even the `irtt-rs.exe` dispatcher itself, which all fell into the unknown-applet branch, and Windows is a shipped target per `dist-workspace.toml`.
_🤖 Addressed by [Claude Code](https://claude.com/claude-code)_
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Exposes the already-complete
irtt_server::Serverruntime as a real command-line applet. This is CLI orchestration only; no protocol, session or runtime behavior is added or changed.serverfeature toirtt-cli, enabling optionalirtt-serverandtokiodependencies.serverpart of the default feature set, so a normal install now ships a working server.irtt-serverbinary target, gated on that feature.irtt-rs server ...andargv0 == irtt-serverreach the same parser and runner; the dispatcher's help now reports the applet as available rather than unconditionally unavailable.ServerConfigbuilders, starting fromServerConfig::default()so library defaults are never restated in clap.irtt_server::Serveron one current-thread Tokio runtime with one explicitly bound socket.CLI surface
Options:
--bind,--hmac,--max-sessions,--max-packet-length,--min-interval,--burst,--idle-timeout,--max-duration.SocketAddr; host names are not resolved and there is no implicit wildcard default.--bind 127.0.0.1:0.Ctrl-Creturns fromServer::runnormally: no close packets, exit status 0.Zero is preserved as meaningful operator policy where
ServerConfigdefines it (--min-interval 0,--idle-timeout 0,--burst 0,--max-sessions 0,--max-packet-length 0).--max-durationis positive-only; omitting it is how "no maximum" is expressed, so a zero maximum is rejected rather than silently treated as absence.Feature model
default = ["client", "server", "stats"];full = ["client", "server", "stats", "tui"].--no-default-features --features serverbuilds the server applet withoutirtt-client,irtt-stats,ratatuiorcrossterm, and a bare--no-default-featuresbuild still produces theirtt-rsdispatcher, which reports the server applet as needing theserverfeature.Binaries:
--no-default-features→irtt-rs;+ --features server→irtt-rs,irtt-server; default →irtt-rs,irtt-cli,irtt-server;--all-features→ those plusirtt-tui.Scope exclusions
No protocol or server-core behavior changes;
irtt-proto,irtt-clientandirtt-serverproduction code are untouched. Not in this change: DSCP socket policy, the fullServerFillpolicy, pktinfo/source-address selection, daemonization, config files, a logging framework, hostname resolution, and multi-listener orchestration.Verification
All of the following ran and passed on this branch:
cargo fmt --all -- --checkcargo check --workspace --all-targets --all-features --lockedcargo test --workspace --all-targets --all-features --locked(634 tests)cargo clippy --workspace --all-targets --all-features --locked -- -D warningsRUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-depscargo check -p irtt-cli --no-default-features --all-targets --lockedcargo check -p irtt-cli --no-default-features --features server --all-targets --lockedcargo test -p irtt-cli --no-default-features --features server --lockedcargo test -p irtt-cli --lockedandcargo test -p irtt-cli --all-features --lockedcargo treefor the server-only and bare-dispatcher builds, confirmingirtt-server+tokiopresent and no client/stats/TUI dependencies leaking inManual smoke:
irtt-server --bind 127.0.0.1:0printed the resolved ephemeral endpoint and exited 0 onSIGINT;irtt-rs server --helpandirtt-server --helpproduce byte-identical output.Tests added: server argument parsing (required bind, IPv4/IPv6, policy mapping, zero-policy values, unit-less durations, zero
--max-duration), a drift guard asserting that a bind-only invocation yields exactlyServerConfig::default(), feature-aware subcommand andargv0dispatch tests, a standalone-binary help test, and one runtime smoke test that binds127.0.0.1:0with shutdown already requested and assertsrun_serverreturnsOk.One incidental fix: the new bin shares its name with the
irtt-serverlibrary crate, which madecargo doc --workspaceemit an output-filename-collision warning, so the bin target setsdoc = false.💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:
a672f78e86ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
@@ -0,0 +1,5 @@use std::process::ExitCode;fn main() -> ExitCode {irtt_cli::entry::main()On Windows, this new binary reaches the shared dispatcher with an
argv[0]basename such asirtt-server.exe, butdetect_applet_from_argv0recognizes onlyirtt-server; the subsequentstarts_with("irtt-")branch therefore rejects every invocation as an unknown applet before Clap or the server runner can execute. Strip the platform executable suffix or recognize the.exeapplet names so the advertised server applet works on Windows.AGENTS.md reference: AGENTS.md:L68-L68
Useful? React with 👍 / 👎.
@@ -0,0 +1,5 @@use std::process::ExitCode;fn main() -> ExitCode {irtt_cli::entry::main()Fixed in
565d7fb:applet_basenamenow strips the platformEXE_SUFFIX(case-insensitively, no-op on Unix), soirtt-server.exedispatches correctly — the same bug also brokeirtt-cli.exe,irtt-tui.exeand even theirtt-rs.exedispatcher itself, which all fell into the unknown-applet branch, and Windows is a shipped target perdist-workspace.toml.🤖 Addressed by Claude Code