breadcrumbs now speaks `org.freedesktop.NetworkManager` on the system
bus directly (new `zbus` dependency) — no `nmcli` subprocesses for
connect, scan, status, or the watch loop.
Why:
- Wi-Fi PSKs and 802.1x passwords no longer touch a command line. They
travel inside `AddAndActivateConnection2` / `Update2` settings
payloads, so they are never visible to other local users via
`/proc/<pid>/cmdline`. This fully supersedes the earlier
"feed the PSK to `nmcli --ask` on stdin" approach.
- The watch loop reacts to real `Device.StateChanged` / connectivity
signals instead of parsing `nmcli monitor` text.
- Connect waits on the device actually reaching the ACTIVATED state
rather than trusting `nmcli --wait`.
Config: `settings.nmcli_wait` is renamed to `connect_wait`; the old key
is still accepted via `#[serde(alias)]`. `status.rs` loses its private
`ipv4()` nmcli helper in favour of `nm::ipv4_address`. `util::run_with_stdin`
stays (tailscale still uses it) but no longer carries secrets.
First connect (and reuse-with-password) no longer puts the secret on
nmcli's command line, so it is not visible in /proc/<pid>/cmdline.
networks.toml stays 0600; the local copy is still cleared after first
success.
Bug fixes:
- mask() panicked on multi-byte UTF-8 passwords (byte-slicing a char
boundary); now masks by char count and never echoes a real character
- `cd --shell` interpolated the config path into a shell -c string via
Debug formatting, which doesn't neutralize shell metacharacters; now
passed as a positional shell argument instead
- connecting to open (no-password) networks failed because an empty PSK
was always sent to nmcli, which nmcli treats as secured-with-no-password
instead of open; the password arg is now omitted entirely when empty
- five nmcli terse-output parse sites used a raw splitn(2, ':'), which
mis-splits any device/connection name containing a literal ':'; unified
on the existing escape-aware field splitter
- watch's health classifier silently read a config-deleted profile as
"healthy" off a bare internet check instead of surfacing the misconfig
- the nmcli-monitor thread seeded its debounce clock with
`Instant::now() - 10s`, which panics on the monotonic clock near boot —
exactly when the generated systemd unit tends to start the watcher
Architecture:
- extracted src/lib.rs + src/app.rs so command logic can be exercised
in-process by tests instead of only by spawning the compiled binary
- added a Runner trait (src/util.rs) so subprocess calls can be faked in
tests; flow::run and watch::classify are now covered by real in-process
tests of the connect state machine and health transitions, not just
their pure helpers
- Wi-Fi passwords are no longer kept in breadcrumbs' config once
NetworkManager durably holds them: NetworkDef.password is now optional,
and a successful password-based connect clears + persists it
immediately, so it's never sent again on subsequent connects
- saved networks (SSID + optional local password) moved out of
breadcrumbs.toml into a separate networks.toml; old configs with
inline [[networks]] still load and migrate automatically on next save
- corrected a false README claim that passwords are never in nmcli argv
Test count: 20 -> 89 (52 unit, 24 CLI integration, 13 in-process
state-machine tests). Full clean run: cargo build/build --release/
test/clippy --all-targets, verified from a `cargo clean` rebuild.