Wire up on-demand deep-check: API route + dashboard button
POST /api/devices/:ip/deep-check runs deep-check-device.sh on the CT122 host via SSH (reaches its own LAN IP), returns mDNS/SSDP/port scan results. "Deep check" button on unknown device rows in the dashboard shows results inline below the row. Verified end-to-end via SSH before wiring into the API: correctly identified Home Assistant via SSDP (friendlyName/manufacturer/model), and confirmed both a shell-injection attempt and an out-of-subnet IP get rejected cleanly by the forced command's input validation. Closes #15 (all four pieces: OUI, mDNS, manual labels, deep check). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,8 +23,10 @@ periodic jobs like the rclone backups on CT105.
|
||||
|
||||
## Pieces
|
||||
|
||||
- `scripts/discover-devices.sh` — ping sweep + `ip neigh show` parse, writes
|
||||
`/opt/homelab-monitor/data/devices-raw.json` (`[{ip, mac, state}]`).
|
||||
- `scripts/discover-devices.sh` — ping sweep + `ip neigh show` parse, then an
|
||||
`avahi-resolve` mDNS reverse lookup per discovered IP (parallel, 2s timeout each so
|
||||
one non-mDNS device can't stall the run). Writes
|
||||
`/opt/homelab-monitor/data/devices-raw.json` (`[{ip, mac, state, mdnsHostname?}]`).
|
||||
- `deploy/systemd/homelab-monitor-discover.{service,timer}` — runs the script every
|
||||
5 minutes on the CT122 host (not in Docker).
|
||||
- `apps/api/src/discovery/index.ts` — reads that JSON file each poll cycle, labels
|
||||
@@ -33,17 +35,69 @@ periodic jobs like the rclone backups on CT105.
|
||||
- `GET /api/devices` — serves devices seen in the last 24h (older entries are dropped
|
||||
rather than shown as stale-known, since there's no per-device up/down tracking).
|
||||
|
||||
## Identifying unknown devices
|
||||
|
||||
Three passive/config-driven layers, plus one on-demand active one:
|
||||
|
||||
1. **MAC OUI vendor lookup** (`mac-oui-lookup` npm package, `apps/api/src/routes/devices.ts`)
|
||||
— computed on every read from the MAC prefix, no storage needed. Already resolves
|
||||
most smart-home gear to a vendor (e.g. "Amazon Technologies Inc.", "Ring LLC",
|
||||
"Tuya Smart Inc." — a very common IoT chipset vendor).
|
||||
2. **mDNS hostname** — see `discover-devices.sh` above, stored in `devices.mdns_hostname`.
|
||||
3. **Manual labels** — `device_labels` table, keyed by **MAC** (survives DHCP IP
|
||||
changes, unlike `knownDevices` in `config/hosts.yaml` — see "Known limitation"
|
||||
below). `PUT`/`DELETE /api/devices/:mac/label`, inline-editable in the dashboard's
|
||||
Name column. Manual label > `knownDevices` config name > mDNS hostname (shown as an
|
||||
*italic hint*, not treated as "known" — nobody's actually confirmed it yet).
|
||||
4. **On-demand deep check** — admin-triggered, single device, not automatic (avoids the
|
||||
noise/risk of doing this for the whole subnet on every poll). See below.
|
||||
|
||||
### Deep check
|
||||
|
||||
`scripts/deep-check-device.sh` runs, on request, against one target IP: mDNS resolve,
|
||||
a targeted SSDP/UPnP query (many smart-home devices announce a `friendlyName` /
|
||||
`manufacturer` / `modelName` this way — confirmed working against Home Assistant),
|
||||
reverse DNS, and a small curated TCP port scan (21,22,23,80,443,554,5000,8000,8008,
|
||||
8009,8060,8080,8443,9100,32400,62078) with an HTTP title/server grab on anything open.
|
||||
Bounded timeouts throughout, finishes in well under 15s.
|
||||
|
||||
Same multicast-needs-real-network-access constraint as the ping sweep applies, so this
|
||||
also runs on the CT122 **host**, not in the container — but unlike the scheduled
|
||||
discovery script, this is triggered on demand from `POST /api/devices/:ip/deep-check`.
|
||||
The API reaches it via SSH into CT122's own LAN IP (192.168.1.103), using the same
|
||||
`monitor_ed25519` key as `SshHostCollector` but a **separate `authorized_keys` entry on
|
||||
CT122 itself**. That entry's forced command is *parameterized*: forced commands ignore
|
||||
whatever the client literally requests, but OpenSSH still exposes it via
|
||||
`$SSH_ORIGINAL_COMMAND`, which the script reads and validates strictly (regex-anchored
|
||||
`192.168.1.<1-254>`, never passed to a shell) before using it — confirmed a shell
|
||||
injection attempt (`; rm -rf / #`) and an out-of-subnet IP both get rejected cleanly.
|
||||
|
||||
`deepCheck:` in `config/hosts.yaml` declares the target host (CT122 itself);
|
||||
`apps/api/src/discovery/deepCheck.ts` does the SSH round-trip and JSON parsing.
|
||||
|
||||
Requires `avahi-utils` and `miniupnpc` installed on the CT122 host (`apt-get install
|
||||
avahi-utils miniupnpc` — a one-time host package install, not part of any deploy
|
||||
script, so re-provisioning CT122 from scratch would need to redo this step).
|
||||
|
||||
## Deploying/updating
|
||||
|
||||
```bash
|
||||
scp scripts/discover-devices.sh homelab-monitor:/opt/homelab-monitor/scripts/
|
||||
scp scripts/discover-devices.sh scripts/deep-check-device.sh homelab-monitor:/opt/homelab-monitor/scripts/
|
||||
scp deploy/systemd/homelab-monitor-discover.* homelab-monitor:/etc/systemd/system/
|
||||
ssh homelab-monitor "chmod +x /opt/homelab-monitor/scripts/discover-devices.sh && \
|
||||
ssh homelab-monitor "chmod +x /opt/homelab-monitor/scripts/*.sh && \
|
||||
systemctl daemon-reload && systemctl enable --now homelab-monitor-discover.timer"
|
||||
```
|
||||
|
||||
## Known limitation
|
||||
(In practice, `git pull` on CT122 already updates the script files at their deployed
|
||||
path — the above is only needed for the systemd units or a from-scratch setup.)
|
||||
|
||||
`knownDevices` in `config/hosts.yaml` is matched by **IP**, not MAC — fine as long as
|
||||
DHCP reservations don't change, but a device losing its reservation would show up as
|
||||
"unknown" until the config is updated by hand.
|
||||
## Known limitations
|
||||
|
||||
- `knownDevices` in `config/hosts.yaml` is matched by **IP**, not MAC — fine as long as
|
||||
DHCP reservations don't change, but a device losing its reservation would show up as
|
||||
"unknown" until the config is updated by hand. (Manual labels don't have this
|
||||
problem — they're keyed by MAC.)
|
||||
- Deep check's SSDP/port-scan often finds nothing for cloud-connected devices (Ring,
|
||||
Echo) that deliberately minimize their LAN footprint — OUI vendor + mDNS are the
|
||||
primary identification layers for those; deep check helps most for devices that run
|
||||
a local web UI or SSDP responder (smart TVs, media devices, printers, Home Assistant).
|
||||
|
||||
Reference in New Issue
Block a user