pihole
Drive the home Pi-hole over its v6 REST API (/api/..., auth via X-FTL-SID). Pi-hole logs
every DNS query, so it answers "what does this device talk to on the internet" — per-device egress,
the payoff after isolating IoT onto its own VLAN. Pure Python 3 stdlib, no deps: python3 on the
host is enough.
Setup (once)
- In the overlay config (non-secret): set
PIHOLE_HOST(base URL/host of the Pi-hole; scheme +:portoptional) in<overlay>/config/home-assistant.vars(override its root with$AXON_HOME_ROOT). The tool fails cleanly, naming a missing/TODO-key, until it's filled in. - In Bitwarden (secret only): an item
home-assistant/pihole(override with$PIHOLE_BW_ITEM) with the custom fieldPIHOLE_PASSWORD(the admin/API password). Provision via Axonsetup-secret.sh. The item may not exist yet — the tool fails cleanly, naming the missing field, until it does. - Point
bwat the vault once:bw config server <vault-url>. - On the network: the Fritz!Box DHCP DNS must point at the Pi for whole-house coverage, and
Pi-hole must run in
network_mode: hostso per-client source IPs survive (Docker NAT would hide them and break per-device egress).
Run
Unlock Bitwarden interactively, then call (the tool reads $BW_SESSION, never the master password):
export BW_SESSION=$(bw unlock --raw)
scripts/pihole status # overview + blocking state
scripts/pihole egress <device-ip> # what one device phones home to
scripts/pihole top-domains 20
scripts/pihole top-clients
scripts/pihole block-off 300 # pause blocking 5 min
scripts/pihole block-on
scripts/pihole deny telemetry.example.com
scripts/pihole allow updates.example.com
scripts/pihole --help (or <command> --help) lists every command and its args.
Egress allow-listing (turn isolation into containment)
After isolating an IoT device, lock it to only its needed vendor domains: review egress <ip>,
then deny everything else (or build a per-device allowlist in the UI). A compromised camera then
can't exfiltrate or pivot — DNS for anything but its vendor is refused.
Gotchas
$BW_SESSIONmust be set and unlocked. The tool refuses to run without it rather than hanging on a master-password prompt.bw unlock --rawon bitwarden-cli v2026.2.0 can emit an empty string in non-interactive shells (upstream issue #19649) — pin a known-goodbwif the session comes back empty.- Vaultwarden with a self-signed cert:
export NODE_EXTRA_CA_CERTS=/path/to/ca.pembeforebw, orbwitself hangs before this tool is even reached. - Credentials never print. To debug the fetch, check that
PIHOLE_HOSTis set inhome-assistant.varsand the bw item's field name matchesPIHOLE_PASSWORDexactly — a missing key/field is reported by name, never by value. - Sessions expire. The tool re-auths per invocation (
POST /api/auth→sid→X-FTL-SID); no session state is cached across runs. - Query
statustells reached-vs-blocked.GRAVITY/DENYLIST/REGEX= blocked;FORWARDED/CACHE/ALLOWLIST= the device actually reached that domain. Anything not blocked is real egress.
Examples
"What is this IoT device phoning home to?"
→ pihole egress <device-ip> → recent client / status / domain rows → scan the non-blocked ones
for unexpected vendors/telemetry.
"Is ad-blocking on, and how much is it catching?"
→ pihole status → blocking: true/false + today's blocked count and percentage.
"Pause blocking for a flaky app, then re-enable."
→ pihole block-off 300 (5 min) … pihole block-on.