EN

Devices

Work in progress — content may be incomplete.

One person, one phone, one filter — devices are how Warden knows whose query it just answered.

What this is

A device is a single endpoint on your LAN — a phone, laptop, console, smart TV, IoT bulb — pinned to Warden by its IP address, its MAC address, or both. Each device can carry its own profile, override one rule on top of a group’s profile, or piggyback on a subnet default.

This page covers when to add a device entry, every identifying and classifying field, and the CLI / TUI / API surfaces that read and write them. For the wider picture of how queries are resolved end-to-end, see the filtering process. For per-field constraints, see the TOML reference.

When you reach for it

Add a [[devices]] entry when you need one of these:

  • A profile that follows a specific person across MAC randomization (kids’ phones, teenager laptops).
  • A per-device exception on top of a profile — for example, the “kids” profile blocks YouTube but the eldest’s iPad needs it for school.
  • A name in the TUI / query log that’s friendlier than 192.168.1.107.
  • A target for the block / quiet shortcuts (“ground the iPad until 21:00”).

You don’t need a device entry for every host. Unmapped hosts fall through to the subnet ([[subnets]]) or the global default_profile, which is exactly the right behaviour for a guest VLAN or a fleet of identical IoT bulbs.

Schema

A [[devices]] entry has three layers: identity (how Warden matches the device), classification (how it appears in the TUI and CLI), and overlay (per-device exceptions on top of the resolved profile).

Identity dimensions

Warden matches a device by comparing the source IP of a DNS query against these signatures. Either signature is enough — IP and MAC combine as OR, not AND, so configuring both lets the device be reached after a DHCP rebind or a MAC randomization.

SignatureTOML fieldFormatCaveats
Static IPipIPv4 (10.10.1.50) or IPv6 (fe80::1); single valueDevice loses identity if the lease is reassigned. Pin the lease on your router, or pair with mac.
Primary MACmacAA:BB:CC:DD:EE:FFResolved through the live ARP table. Stale entries are skipped. iOS Private Wi-Fi and Android randomize per-network — use mac_aliases for those.
MAC aliasesmac_aliasesList of AA:BB:CC:DD:EE:FFAny one match identifies the device. Useful for randomizing phones; capture each new MAC from the TUI’s unmapped list.

If [server].enforce_client_mac = true (the default) and the live ARP table shows a MAC that doesn’t match mac or any mac_aliases, the device is downgraded out of its direct profile and treated as unmapped — the subnet or default_profile applies. The downgrade is ergonomic, not cryptographic: a determined attacker with physical access can spoof any MAC, but it stops a curious teenager from changing their static IP to escape the kids profile.

Field reference

FieldTypeRequiredDefaultPurpose
idstring, [a-z0-9_-], ≤64 bytesyesStable cross-reference key used by groups, schedules, IPC, stats.
display_namestringyesHuman-facing name shown in the TUI, query log, audit entries.
ipIPv4 or IPv6one of ip/mac requiredStatic pin. Either ip or mac (or both) must be set.
macXX:XX:XX:XX:XX:XXone of ip/mac requiredPrimary MAC. Verified against ARP.
mac_aliaseslist of MACsno[]Additional MACs for randomizing phones. Any match identifies the device.
profileprofile idnounsetDirect profile assignment. Wins over groups, subnets, default.
groupslist of group idsno[]Memberships. Highest-priority group’s profile applies if profile is unset.
ownerstring, ≤64 bytesnounsetWho owns the device (e.g. "Edoardo"). TUI group-by axis.
device_typestring, ≤64 bytesnounsetFree-form label ("iPhone personale", "Smart TV"). Legacy alias device still loads.
departmentstring, ≤64 bytesnounsetLogical grouping ("famiglia", "home-office"). TUI group-by axis.
tagslist of strings, [a-z0-9_-], ≤32 charsno[]Free-form filter labels. Up to 16 tags. No effect on resolution.
notesstring, ≤256 bytesnounsetOperator-only annotation. Never sent over IPC.
allow_ruleslist of admin-rule idsno[]Per-device allow overlay — domains let through only for this device.
deny_ruleslist of admin-rule idsno[]Per-device deny overlay — additional blocks on top of the profile.
override_profile_denyboolnofalseWhen true, an allow_rules entry may override a profile-level deny on the same domain. Surfaced in the TUI Resolver tab.

The combined allow_rules + deny_rules cap is 64 entries (soft, validator warning) / 128 (hard, refused). Source: src/config/schema/device.rs.

Examples

A minimal entry — pin one host, point it at a profile:

/etc/purge-warden/devices.d/family.toml
toml
[[devices]]
id = "kids-tablet"
display_name = "iPad dei bambini"
ip = "10.10.1.50"
mac = "11:22:33:44:55:66"
profile = "kids"

A device that follows a teenager across MAC randomization, classified for the TUI, with one school-only allow rule on top of the kids profile:

/etc/purge-warden/devices.d/family.toml
toml
[[devices]]
id = "anna-iphone"
display_name = "iPhone di Anna"
ip = "10.10.1.107"
mac = "AA:BB:CC:DD:EE:FF"
mac_aliases = [
  "22:33:44:55:66:77",
  "33:44:55:66:77:88",
]
profile = "kids"
owner = "Anna"
device_type = "iPhone"
department = "famiglia"
tags = ["mobile", "school"]

allow_rules = ["anna-allow-classroom"]
override_profile_deny = false

A device with no direct profile — its profile comes from the highest-priority group it belongs to:

/etc/purge-warden/devices.d/iot.toml
toml
[[devices]]
id = "kitchen-bulb"
display_name = "Lampadina cucina"
mac = "FF:EE:DD:CC:BB:AA"
groups = ["iot-lite"]
device_type = "Smart bulb"
department = "iot"
Tip
For more than ~20 devices, split the config across devices.d/*.toml files (one per family member, room, or VLAN). The master config.toml pulls them in via includes. Diffs stay readable, and warden device add auto-routes new entries to the right file.

CLI

Every mutating subcommand triggers a hot reload of the running daemon (atomic config swap, no restart). Read-only subcommands (list, show) work without the daemon.

CommandWhat it does
warden device listPrint configured devices from the on-disk config.
warden device list --liveLive per-device stats from the running daemon (queries, blocked %, last seen, resolved profile).
warden device show <id>Render one device entry as key/value pairs.
warden device add <id> --ip <ip> [--mac <m>] [--profile <p>] [--owner <o>] [--device-type <t>] [--department <d>] [--tags a,b] [--display-name <n>] [--into <file>]Create a new device. At least one of --ip / --mac is required.
warden device set <id> <field> <value>Mutate a single field. Supported: ip, mac, profile, display_name, owner, device, department, notes, tags, groups. Use none to clear a nullable field.
warden device remove <id>Delete a device. Refused if any schedule still references it.
warden device block <id>Set the device’s profile to blocked (auto-created if missing).
warden device unblock <id> [--profile <p>]Restore the device to a profile (defaults to default).
warden device quiet <id> --for 30mOne-shot temporary block via a self-expiring schedule. Accepts --for 1h30m or --until <RFC 3339>.
warden device allow <device-id> <domain>Add a per-device allow rule for one domain. Refused with RULE_REFUSED_OVERRIDE if the device’s profile denies the same domain and override_profile_deny = false.
warden device deny <device-id> <domain>Add a per-device deny rule for one domain.
warden device rules <device-id> pruneDrop dangling allow_rules / deny_rules entries that no longer point at a real admin rule (LIST_PRUNE_WARN).

Common patterns:

bash
# pin a new tablet
sudo warden device add kids-tablet \
  --ip 10.10.1.50 --mac 11:22:33:44:55:66 \
  --profile kids --owner Daisy --device-type iPad

# bedtime override
sudo warden device quiet kids-tablet --for 12h

# carve a per-device exception
sudo warden device allow anna-iphone classroom.example.com

# audit live activity
warden device list --live

Source: src/cli/commands/devices.rs, src/cli/mod.rs:474.

TUI

Press 3 (or g d) in the TUI to jump to the Devices tab. The view is master/detail: configured devices and live-observed unmapped hosts merged into one list, with a detail card on the right showing vendor, cache stats, MAC aliases, and effective profile.

Key bindings on the Devices tab:

  • a — open the Add modal (form fields mirror warden device add).
  • eEdit the focused configured device.
  • dDelete with y/n confirm. Refused if a schedule still references it.
  • Enter on an unmapped row — promote it: the Add modal opens pre-filled with the observed IP + MAC; you supply id and display_name.
  • G — cycle group-by axis: none → ownerdepartmentprofile.
  • / (or j/k) — navigate; group headers are skipped.

The list polls the daemon every 5 s via the IPC DeviceStats command. Source: src/tui/tabs/devices.rs.

REST API

Live device stats are exposed over the local APIs.

GET /api/devices — Bearer-token authenticated. Returns a JSON array, one entry per device that has hit the daemon since the last snapshot:

json
[
  {
    "name": "kids-tablet",
    "ip": "10.10.1.50",
    "queries": 4321,
    "blocked": 187,
    "blocked_pct": 4.3,
    "cache_hits": 2890,
    "profile": "kids",
    "last_seen": 1714820400
  }
]

The legacy path /api/clients is deprecated; responses carry Deprecation and Sunset headers pointing to /api/devices.

IPC

Over the Unix socket: IpcCommand::DeviceStats { token: None } returns IpcResponse::DeviceList { clients: Vec<DeviceStatEntry> }. Same fields as REST. Read-only, no token required. Source: src/api/handlers.rs, src/ipc/protocol.rs.

Decision precedence

When a query arrives, Warden walks five levels in this order. The first match wins.

  1. Direct device profile. Source IP matches a device’s ip (or MAC matches via ARP), and the device sets profile = "...".
  2. Active schedule. A [[schedules]] entry whose time window covers now targets this device or one of its groups.
  3. Group profile. Device has no direct profile but belongs to one or more [[groups]]. The highest-priority group wins; same-priority conflicts are validator errors.
  4. Subnet longest-prefix match. Source IP isn’t in [[devices]] at all but falls inside a [[subnets]] CIDR. Longest prefix wins.
  5. Global fallback. [server].default_profile. If unset, unmapped queries are answered with REFUSED.

override_profile_deny, allow_rules, and deny_rules apply inside the resolved profile — they are an overlay, not a separate level.

If enforce_client_mac = true and ARP shows a MAC mismatch, the device is downgraded to level 4 (subnet) for that query. The schedule, group, and direct-profile slots are skipped. Source: src/profiles/resolver.rs.

Security notes

  • MAC addresses are stored in plaintext in config.toml and devices.d/*.toml. They are not hashed.
  • The query log records client_ip and client_name (the device’s id). It does not record MAC addresses.
  • The audit log records device CRUD events — adds, removes, field changes, allow/deny overlays — with the invoking uid, action, target id, and whether override_profile_deny was used. It does not record MACs.
  • MAC enforcement is ergonomic, not cryptographic. It stops casual IP-changing, not a determined attacker with physical access. See the threat model for the precise guarantee.
  • Notes (notes = "...") stay local. They are never serialised over IPC or REST.

Troubleshooting

The wrong profile is being applied. Ask the resolver directly:

bash
warden resolve 10.10.1.50
# Level 1: device 'kids-tablet' → profile 'kids'

If the level shown is not the one you expected, walk up the chain — was the MAC randomized? did the lease move? is enforce_client_mac triggering a downgrade?

A phone that worked yesterday now drops to the subnet profile. Almost always MAC randomization. Check the TUI Devices tab for the unmapped row sharing the same IP — it shows the new MAC. Promote it (Enter) or add it to mac_aliases:

bash
sudo warden device set anna-iphone mac_aliases "[AA:BB:CC:DD:EE:FF,22:33:44:55:66:77]"

A device pinned by IP only loses its profile after a router reboot. DHCP reassigned the lease. Either pin the lease on the router, or add mac to the entry so identity follows the hardware.

warden device allow is refused with RULE_REFUSED_OVERRIDE. The device’s profile already denies that domain and override_profile_deny is false. Either flip the flag and re-run, or add the allow rule at the profile level:

bash
sudo warden device set anna-iphone override_profile_deny true
sudo warden device allow anna-iphone classroom.example.com

A dual-stack host gets the wrong profile over IPv6. The current schema stores one ip per device. If your host has separate v4 and v6 addresses, pin the v6 via a [[subnets]] entry covering its IPv6 CIDR, or rely on mac (which works regardless of address family, as long as the device sits on the same L2 segment as Warden).

For more, see troubleshooting.

See also

  • Profiles — what a profile contains (blocklists, rules, block response).
  • Groups — assign one profile to many devices at once.
  • Subnets — fallbacks for unmapped hosts.
  • Schedules — time-based profile swaps.
  • Admin rules — the rule entries allow_rules / deny_rules reference.
  • TOML reference — every field, every constraint.
  • CLI reference — the full warden device … surface.