breadcrumbs/breadcrumbs.example.toml
Breadway 037c6e54c9 Harden breadcrumbs: fix real bugs, restructure as lib, stop storing PSKs twice
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.
2026-07-22 06:58:47 +08:00

49 lines
1.8 KiB
TOML

# breadcrumbs configuration template.
#
# Copy to ~/.config/breadcrumbs/breadcrumbs.toml and fill in real values, OR
# just run breadcrumbs once (it generates a skeleton) and then use
# `breadcrumbs add` / `breadcrumbs edit` to fill in your networks.
# The real breadcrumbs.toml is gitignored and never committed.
#
# Saved networks (SSID + optional local password) live in a separate file,
# networks.toml, in the same directory — not here. See
# networks.example.toml for its format; in practice you never hand-edit it,
# `breadcrumbs add` / `scan` / `forget` manage it for you. This file is just
# settings + the location profiles built from those saved networks.
[settings]
dns = "1.1.1.1"
nmcli_wait = 8
exit_node = "my-exit-node" # Tailscale hostname of your preferred exit node
default_profile = "away"
watch_interval = 12
connectivity_url = "http://connectivitycheck.gstatic.com/generate_204"
ping_host = "1.1.1.1"
# Location state machine. Switch with: breadcrumbs profile set <name>
#
# detect_ssids: list any SSIDs that reliably indicate you are at this location.
# `breadcrumbs detect` scans for visible networks and switches to the first
# profile whose detect_ssids list contains a match. Profiles without
# detect_ssids are skipped during detection; the default_profile is used as
# the final fallback.
[profiles.away]
networks = ["HomeWifi"]
tailscale = false
include_all_known = true
# No detect_ssids: "away" is the catch-all fallback (set as default_profile).
[profiles.home]
networks = ["HomeWifi"]
tailscale = false
include_all_known = false
detect_ssids = ["HomeWifi"]
[profiles.work]
bootstrap = "WorkGuest" # connect here first so Tailscale can come up
networks = ["CorpWifi"]
tailscale = true
exit_node = "my-exit-node"
include_all_known = false
detect_ssids = ["CorpWifi", "WorkGuest"]