No description
Find a file
Breadway d3c1e19ba3 Release v2.1.0: backend test seam, captive-portal detection, JSON status, robustness
Features:
- Introduce a Backend trait + System impl so flow/status/watch can be unit
  tested against a fake; add 11 connect-state-machine tests.
- Captive-portal detection: status::connectivity returns Online/Portal/Offline;
  surfaced in status, JSON, connect notes, and a dedicated watch state.
- `status --json` for bars/scripts; `profile add`/`profile remove`; detect now
  scores by number of in-range markers.

Robustness:
- Pin LC_ALL=C/LANG=C on child processes for locale-independent parsing.
- Atomic config/state writes (temp + rename); 0600 config never world-readable.
- Transient PSK file written to $XDG_RUNTIME_DIR when available.

Fixes (from prior audit):
- Feed Wi-Fi PSK to nmcli via stdin/passwd-file, never argv.
- mask() no longer panics on multi-byte passwords.
- Connectivity check requires HTTP 204 (no captive-portal false positives).
- nmcli NAME,TYPE parsing handles escaped colons.
- Strip CIDR suffix from displayed IP; PKGBUILD/Cargo version aligned (2.1.0).
2026-06-23 12:13:34 +08:00
.forgejo/workflows Use REGISTRY_TOKEN (scoped write:package) for registry publish 2026-06-13 22:55:42 +08:00
.github/workflows Release v2.1.0: backend test seam, captive-portal detection, JSON status, robustness 2026-06-23 12:13:34 +08:00
packaging/arch Release v2.1.0: backend test seam, captive-portal detection, JSON status, robustness 2026-06-23 12:13:34 +08:00
src Release v2.1.0: backend test seam, captive-portal detection, JSON status, robustness 2026-06-23 12:13:34 +08:00
tests Release v2.1.0: backend test seam, captive-portal detection, JSON status, robustness 2026-06-23 12:13:34 +08:00
.gitignore Initial commit: breadcrumbs — profile-driven Wi-Fi + Tailscale state machine 2026-05-19 11:52:46 +08:00
bakery.toml fix: move tailscale/sudo/xdg-utils to optional_system_deps 2026-06-11 13:38:06 +08:00
breadcrumbs.example.toml Release v2.1.0: backend test seam, captive-portal detection, JSON status, robustness 2026-06-23 12:13:34 +08:00
Cargo.lock Release v2.1.0: backend test seam, captive-portal detection, JSON status, robustness 2026-06-23 12:13:34 +08:00
Cargo.toml Release v2.1.0: backend test seam, captive-portal detection, JSON status, robustness 2026-06-23 12:13:34 +08:00
LICENSE Initial commit: breadcrumbs — profile-driven Wi-Fi + Tailscale state machine 2026-05-19 11:52:46 +08:00
README.md Release v2.1.0: backend test seam, captive-portal detection, JSON status, robustness 2026-06-23 12:13:34 +08:00

breadcrumbs

A profile-aware Wi-Fi state machine for Linux with Tailscale exit-node management and a self-healing watch daemon.

breadcrumbs sits on top of NetworkManager (nmcli) and manages your Wi-Fi based on location profiles. Switch between home, work, school, or any other context with a single command — it handles scanning, connecting, DNS pinning, and Tailscale setup automatically.

Features

  • Profile-based connection management — define ordered network priority lists per location
  • Bootstrap + Tailscale gating — connect to an interim network first, bring up Tailscale, then move to the target network
  • Self-healing watch daemon — monitors for drops, auto-recovers, reacts within seconds via nmcli monitor
  • Auto-detection — scans visible SSIDs and guesses your location from config-defined markers (picks the profile with the most markers in range)
  • Captive-portal detection — distinguishes a real connection from a sign-in page and surfaces the portal URL instead of falsely reporting "online"
  • Secure credential handling — passwords fed to nmcli out-of-band (via stdin with --ask, or a 0600 passwd-file), never in argv/ps; config stored at 0600
  • Machine-readable statusbreadcrumbs status --json for bars/scripts
  • Desktop notifications via notify-send (optional)
  • systemd user service generation via breadcrumbs install-service

Requirements

  • Linux with NetworkManager (nmcli in $PATH)
  • Rust toolchain (to build from source)
  • tailscale (optional — only needed if any profile sets tailscale = true)
  • notify-send (optional — for desktop notifications)
  • curl (optional — used for connectivity checks, falls back to ping)

Installation

git clone https://github.com/Breadway/breadcrumbs
cd breadcrumbs
cargo build --release
# Copy to somewhere on your PATH:
cp target/release/breadcrumbs ~/.local/bin/

Configuration

On first run, breadcrumbs creates ~/.config/breadcrumbs/breadcrumbs.toml with default profiles. Copy breadcrumbs.example.toml as a starting point and fill in your real network credentials:

cp breadcrumbs.example.toml ~/.config/breadcrumbs/breadcrumbs.toml
breadcrumbs edit   # opens in $EDITOR

Config paths respect $XDG_CONFIG_HOME and $XDG_STATE_HOME.

Config structure

[settings]
dns = "1.1.1.1"          # DNS server pinned on every connection
nmcli_wait = 8           # seconds to wait for nmcli connect
exit_node = "myhostname" # default Tailscale exit node
default_profile = "away"
watch_interval = 12      # seconds between health checks (minimum 4)
connectivity_url = "http://connectivitycheck.gstatic.com/generate_204"
ping_host = "1.1.1.1"

[[networks]]
ssid = "MyHomeNetwork"
password = "hunter2"
hidden = false

[profiles.home]
networks = ["MyHomeNetwork"]  # priority-ordered SSIDs
tailscale = false
include_all_known = false
detect_ssids = ["MyHomeNetwork"]  # used by `breadcrumbs detect`

[profiles.work]
bootstrap = "GuestWifi"   # connect here first before requiring Tailscale
networks = ["CorpWifi"]
tailscale = true
exit_node = "jump-host"   # per-profile override
detect_ssids = ["CorpWifi", "Corp-5G"]

Profiles

Each profile defines:

Key Description
networks Ordered list of SSIDs to try. First available wins.
tailscale If true, Tailscale must be healthy before moving to a target network.
bootstrap SSID to connect to first (e.g. guest Wi-Fi that allows Tailscale traffic).
exit_node Tailscale exit node for this profile (overrides settings.exit_node).
include_all_known After the priority list, also try every other known network.
detect_ssids Any visible SSID in this list marks this profile as a candidate for breadcrumbs detect.

Usage

breadcrumbs [--profile <name>] <command>
Command Description
status [--json] Show current Wi-Fi / Tailscale health (default); --json for scripts
init Run the full connect sequence for the active profile
watch [--no-initial] Self-healing daemon: monitors and auto-recovers drops
profile get Print the active profile
profile set <name> Switch profile (and apply it, unless --no-apply)
profile list List all profiles
profile add <name> [--detect <ssid>]… Create a new (empty) profile, optionally with detection markers
profile remove <name> Delete a profile (core home/work/away are protected)
detect [--apply] Guess profile from visible networks; optionally apply it
add <ssid> [password] Add or update a saved network
forget <ssid> Remove a network from config and NetworkManager
scan [--to <profile>] Interactive scan, pick, connect and save
list [--show-passwords] Show config: settings, networks, profiles
edit Open config in $EDITOR, validate on exit
doctor [--full] Quick connectivity and Tailscale diagnostics
cd [--shell] Print (or cd into) the config directory
install-service [--no-enable] Install and optionally enable systemd user unit

Examples

# Check current state
breadcrumbs

# Switch to the "work" profile and connect
breadcrumbs profile set work

# Run as a daemon in the foreground (use install-service for persistent use)
breadcrumbs watch

# Override profile for one run without persisting
breadcrumbs --profile home init

# Add a new network and attach it to a profile
breadcrumbs add "CoffeeShop5G" --to away

# Detect and switch profile based on visible networks
breadcrumbs detect --apply

# Install and start the systemd watcher service
breadcrumbs install-service

Watch daemon

breadcrumbs watch is the recommended way to run breadcrumbs for daily use. It:

  1. Polls health every watch_interval seconds (adaptive backoff on repeated failures)
  2. Reacts immediately to link-state changes via nmcli monitor
  3. Runs flow::run (the connect state machine) on any detected drop
  4. Handles profile changes live — re-reads config and state on every tick

Install as a systemd user service:

breadcrumbs install-service
# or manually:
systemctl --user enable --now breadcrumbs.service

Tailscale integration

For profiles with tailscale = true:

  1. Connects to the bootstrap SSID (if configured)
  2. Ensures the Tailscale daemon is running; opens a browser login if needed
  3. Sets the configured exit node with tailscale set --exit-node=<node>
  4. Only moves to the target network once Tailscale is healthy

If Tailscale needs interactive login, the auth URL is opened automatically and the watch daemon stays on the bootstrap network until authentication completes.

License

MIT