No description
  • Rust 98.1%
  • Shell 1.9%
Find a file
microchipster 5f8aecac04 update
2026-06-30 11:33:37 -07:00
apps update 2026-06-30 11:33:37 -07:00
crates update 2026-06-30 11:33:37 -07:00
docs/addressed/todos checkpoint 2026-03-26 11:07:42 -07:00
tests refactor for source size 2026-04-28 11:50:50 -07:00
.gitignore checkpoint 2026-03-26 11:07:42 -07:00
Cargo.lock checkpoint 2026-03-26 11:07:42 -07:00
Cargo.toml checkpoint 2026-03-26 11:07:42 -07:00
install.sh better adding ergonomics 2026-03-27 19:27:16 -07:00
README.md update 2026-06-30 11:33:37 -07:00
ROADMAP.md checkpoint 2026-04-28 10:13:00 -07:00
rust-toolchain.toml checkpoint 2026-03-26 11:07:42 -07:00
TODO.md checkpoint 2026-04-28 10:13:00 -07:00
TUTORIAL.md updates 2026-04-24 17:47:44 -07:00

whoshome

whoshome is a Linux-first presence detector for Bluetooth and passive Wi-Fi devices. It runs best as a long-lived local daemon and gives you a CLI plus a full-screen TUI for:

  • seeing who is probably home right now
  • labeling every device you can identify
  • grouping devices under higher-level entities like roommate0
  • reviewing timelines, patterns, and signal strength history
  • exposing derived signals like residents-absent, unexpected-present, and watchlist-absent
  • firing local hooks when arrivals, departures, or signals change

binaries

  • whoshomed: background scanner, API server, hook runner
  • whoshome: CLI and TUI client

quick install

./install.sh

That script:

  • installs whoshome and whoshomed with cargo install --path . --force
  • creates a starter config in ~/.cache/whoshome/config.toml if one does not exist
  • starts a service for whoshomed
    • systemd --user when Wi-Fi capture is disabled
    • a root-managed system service (whoshomed-$USER.service) when Wi-Fi capture is enabled and passwordless sudo is available

Useful follow-up commands:

systemctl --user status whoshomed.service
sudo systemctl status whoshomed-$USER.service
journalctl --user -u whoshomed.service -f
sudo journalctl -u whoshomed-$USER.service -f
whoshome status
whoshome signals
whoshome tui

runtime model

The intended setup is:

  • whoshomed always running in the background
  • whoshome talking to the daemon over the local Unix socket when available
  • clean fallback to direct local storage access if the daemon socket is unavailable

The daemon stores state in:

  • ~/.cache/whoshome/state.db
  • ~/.cache/whoshome/config.toml
  • ~/.cache/whoshome/sock
  • ~/.cache/whoshome/logs/

starter config behavior

The installer creates a conservative starter config:

  • Bluetooth enabled on hci0
  • passive Wi-Fi disabled by default

When Wi-Fi capture is already enabled in your config, ./install.sh now also tries to grant CAP_NET_ADMIN and CAP_NET_RAW to the installed whoshomed binary, plus CAP_NET_ADMIN to /usr/sbin/ip and /usr/sbin/iw, because the current monitor-mode workflow shells out to those tools to switch interface mode.

This is intentional. Passive Wi-Fi monitor mode usually needs extra privileges or adapter-specific setup, while Bluetooth is much more likely to work cleanly in a user service.

If Wi-Fi capture is enabled and passwordless sudo is available, ./install.sh prefers a dedicated system service so the daemon can hold the privileges needed for monitor mode and packet capture while still running as your user. If that path is unavailable, the practical options are:

  • give the installed daemon binary CAP_NET_ADMIN and CAP_NET_RAW
  • give /usr/sbin/ip and /usr/sbin/iw CAP_NET_ADMIN while using the current helper flow
  • run the daemon in a more privileged service model
  • keep Wi-Fi disabled and use Bluetooth-only collection

For example, after installing to ~/.cargo/bin/whoshomed:

sudo setcap cap_net_admin,cap_net_raw+ep "$HOME/.cargo/bin/whoshomed"
sudo setcap cap_net_admin+ep /usr/sbin/ip /usr/sbin/iw
./install.sh

Re-running cargo install --path . --force replaces the daemon binary, so you would need to re-apply setcap to whoshomed after reinstalling. The capabilities on /usr/sbin/ip and /usr/sbin/iw persist until you change or replace those binaries. The simplest path is usually to re-run ./install.sh, which will refresh the service model and helper capabilities for you.

To enable Wi-Fi later, edit ~/.cache/whoshome/config.toml and re-run ./install.sh. The installer will choose the appropriate service model and restart the daemon for you.

./install.sh

common commands

whoshome status
whoshome devices --unknown
whoshome label AA:BB:CC:DD:EE:FF phone-roommate0
whoshome onboard AA:BB:CC:DD:EE:FF phone-roommate0 --entity roommate0 --entity-kind person --kind phone --entity-tag resident
whoshome onboard AA:BB:CC:DD:EE:FF phone-roommate0 --also-device 11:22:33:44:55:66 --entity roommate0 --kind phone --alias roommate0-phone
whoshome alias add phone-roommate0 alex-phone
whoshome entity create roommate0 --kind person
whoshome attach alex-phone roommate0 --kind phone
whoshome tag add roommate0 resident
whoshome note set alex-phone "Usually appears after 18:00"
whoshome history roommate0 --hours 24
whoshome stats phone-roommate0 --days 7
whoshome signals
whoshome kinds
source <(whoshome completion bash)
whoshome hooks list
whoshome db prune
whoshome tui

whoshome kinds prints the built-in vocab for:

  • entity kinds: person, device, household, room, place, neighbor, vehicle, pet
  • device kinds: phone, laptop, tablet, watch, earbuds, headphones, speaker, tv, console, tracker, keys, bag, accessory, appliance, vehicle
  • suggested tags and supported hook events

Entity kinds, device kinds, and hook events are validated in the CLI and TUI. Tags are deliberately freeform, but the built-in tag list is what completion and the default signal policy understand out of the box.

shell completion

whoshome can generate dynamic shell completion that includes:

  • built-in entity kinds and device kinds
  • built-in tags and hook events
  • known raw ids, labels, aliases, entities, hooks, and signals from your live cache/daemon state

Examples:

source <(whoshome completion bash)
whoshome completion zsh > ~/.zfunc/_whoshome
whoshome completion fish > ~/.config/fish/completions/whoshome.fish

After loading completion, fields like --kind, --entity-kind, --tag, show, attach, onboard, and even raw observed ids become completable.

onboarding workflows

Fastest CLI flow for a newly recognized device:

whoshome onboard AA:BB:CC:DD:EE:FF phone-roommate0 \
  --entity roommate0 \
  --entity-kind person \
  --kind phone \
  --device-tag personal \
  --entity-tag resident

AA:BB:CC:DD:EE:FF here means a raw observed identifier from whoshome devices --unknown or the TUI inbox. It can be either a Bluetooth MAC or a Wi-Fi MAC.

If you know both radio identifiers for the same physical device, you can onboard them together:

whoshome onboard AA:BB:CC:DD:EE:FF phone-roommate0 \
  --also-device 11:22:33:44:55:66 \
  --entity roommate0 \
  --kind phone \
  --alias roommate0-phone \
  --entity-tag resident

That workflow:

  • sets the primary device label
  • auto-labels extra raw ids with derived suffixes like -bt or -wifi
  • optionally creates the entity if it does not exist yet
  • attaches all onboarded raw ids to the entity with an optional role like phone
  • applies device and entity tags in one shot

When Bluetooth or Wi-Fi advertisements include a recognizable human-readable device name, whoshome shows that name in the CLI and TUI lists. In practice this is often the fastest clue for onboarding because it may match what Android shows in Bluetooth or Wi-Fi settings.

The TUI has matching workflows:

  • o onboard the selected device with label | entity[:entity-kind] | member-kind | alias1,alias2 | extra-id1,extra-id2
  • l set the primary label
  • a add an alias
  • g attach the selected device to an entity
  • t add a tag
  • s snooze the selected device so it drops out of unexpected-present
  • b bulk-tag the unknown inbox with tag | count
  • n add or replace a note
  • u upsert a hook from the live dashboard
  • d toggle an existing hook with hook-name | on/off
  • y seed a canned hook template with template | hook-name
  • v cycle inspect views between overview, timeline, and rules/events

Start it with:

whoshome tui

hooks

Hooks run from the daemon on matching events. Hook event names are validated against whoshome kinds; hook commands are trusted shell commands executed with sh -c, a 30-second timeout, and WHOSHOME_* environment variables describing the event.

The daemon secures its Unix socket under the cache directory before serving. Treat access to that socket as administrative access to this local whoshome instance, because it can label devices, edit hooks, and change automation behavior.

Create one:

whoshome hooks add watchlist-absent-on watchlist_absent "notify-send WatchlistAbsent"

List them:

whoshome hooks list

Recent daemon events are also queryable directly:

whoshome events --limit 20

Two appearance-inference events are also emitted for first-seen raw ids when the system has enough signal to make a guess:

  • device_returning_guess: this new raw id probably matches a previously seen device that has come back with a different identifier
  • device_new_guess: this raw id looks genuinely new

Those events include a confidence score and the best prior-match candidate when one exists, so they can be used for notifications or manual triage.

The TUI rules view supports the same loop without leaving the dashboard:

  • u create or replace a hook with name | event_type | shell command
  • d toggle an existing hook on or off
  • y create a canned desktop notification hook from a template like desktop-unexpected | notify-unexpected
  • tab completes structured composer fields using built-in kinds/tags/events and your current devices, entities, and hooks
  • repeated tab or right cycles forward through fuzzy-matched candidates, and shift-tab or left cycles backward
  • ctrl-n accepts the current completion and jumps to the next structured field when the workflow supports it

When a TUI composer is open, Tab starts a completion session, applies the current candidate, and shows a richer suggestion strip with short descriptions in the popup. Matching is fuzzy, so partial fragments like rm0, veh, or watch can still surface useful candidates. Repeated Tab/Right and Shift-Tab/Left step through the available values, and Ctrl-N accepts the current completion and moves you to the next field.

Useful built-in hook templates now include:

  • desktop-unexpected
  • desktop-watchlist
  • desktop-residents-absent
  • desktop-returning-device
  • desktop-new-device

Hook commands receive these environment variables:

  • WHOSHOME_HOOK_NAME
  • WHOSHOME_EVENT_TYPE
  • WHOSHOME_SUBJECT_KIND
  • WHOSHOME_SUBJECT_KEY
  • WHOSHOME_EVENT_ID
  • WHOSHOME_EVENT_AT
  • WHOSHOME_EVENT_PAYLOAD_JSON

derived signals

Current built-in signals:

  • residents-absent
  • unexpected-present
  • watchlist-absent

Inspect them with:

whoshome signals
whoshome query signal:watchlist-absent

Signal policy is configurable in ~/.cache/whoshome/config.toml under [signals]. Useful knobs include:

  • residents_absent_tags and residents_absent_fallback_to_any_entity
  • unexpected_present_tags, unexpected_include_unknown_unlabeled, and unexpected_ignore_tags
  • watchlist_absent_block_tags and watchlist_absent_fallback_tags

Example:

[signals]
residents_absent_tags = ["resident"]
residents_absent_fallback_to_any_entity = false
unexpected_present_tags = ["suspicious"]
unexpected_include_unknown_unlabeled = true
unexpected_ignore_tags = ["ignore", "external", "snoozed"]
watchlist_absent_block_tags = ["watchlist"]
watchlist_absent_fallback_tags = ["resident", "guest"]

storage retention

whoshome stores compact evidence, not a packet archive. The daemon automatically applies retention during normal writes:

  • raw sightings are stored at most once every 5 minutes per device and retained for 14 days
  • per-device presence is materialized as current state instead of sampled forever; entity and signal samples are retained for 7 days
  • daemon events are retained for 14 days, with a global cap

To clean up an existing oversized cache and reclaim disk space after pruning, stop whoshomed and run:

whoshome db prune

Use whoshome db prune --no-compact if the daemon is still running and you only want to delete rows outside retention without running SQLite VACUUM.

Wi-Fi noise filtering

Passive monitor-mode Wi-Fi is inherently noisy, so whoshome now drops several obvious low-value observations before they ever hit storage:

  • only selected management frames are kept (association_request, probe_request, authentication, disassociation)
  • beacons, probe responses, data frames, control frames, broadcast senders, multicast MACs, and all-zero MACs are ignored
  • repeated sightings for the same raw id are coalesced to at most one stored record every 5 minutes
  • especially low-value 00:00:* Wi-Fi junk is filtered out of the unknown inbox, recent events, and unexpected-present
  • weak one-off unlabeled Bluetooth sightings are also suppressed from unexpected-present until they look more substantial

This keeps unexpected-present, the unknown inbox, and the event stream much more usable while still preserving genuinely useful nearby-device evidence.

notes

  • Bluetooth and passive Wi-Fi are probabilistic signals. whoshome tries to expose confidence and evidence, not fake certainty.
  • The first time you install as a user service, you may want lingering enabled if you want scanning to continue after logout:
sudo loginctl enable-linger "$USER"