DeviceShelf Blog

Release history

Every DeviceShelf release on one page — newest first. Expand a version for the full notes.

Deutsch

← All posts

DeviceShelf 1.9.12 — Zyxel, and a router test that says what broke Zyxel is the eighteenth router brand and the second one verified against real hardware. The router test no longer blames your credentials when the box simply refuses a second login. And AI identification finally shows that it is working.

Zyxel's consumer routers speak neither TR-064 nor SNMP. That covers the AX/DX/EX/PX lines, VMG/EMG/PMG, and the LTE/NR/FWA cellular boxes. If one of them is your gateway, the router integration gave you nothing: no names from the DHCP table, no devices from other VLANs, no MAC for a host the scan only knows by its reply.

They speak their own JSON API instead. The new provider reads the router's client table through it, including whether an address is an ordinary lease, a fixed reservation, or configured on the device itself. After UniFi this is the second brand measured against real hardware rather than only against documented API shapes: an FWA505 on firmware V1.60. The rest of the family speaks the same interface but has not been in our hands, and that belongs in the sentence.

One session, and what happens when you keep it

These boxes allow exactly one admin session. Take it and fail to give it back, and the owner loses their own router menu until the inactivity timer expires.

We walked into that twice while building. The logout call only works as a POST. As a GET it cheerfully answers "success" and leaves the session standing. And our requests gave up after two seconds, while roughly one login in five needs longer than that just to accept the connection. Hanging up did not prevent the session from being created. It only meant we never saw its key and could no longer close it. Both fixed, measured over twelve consecutive rounds.

The test now says what is wrong

The second change follows from that. When a router refuses another login, the settings dialog used to say "no client list — check the credentials". In that situation every word was wrong. The box had answered, the password was right, and the user was sent hunting for a fault that did not exist.

The test now asks the provider for a reason before it passes judgement, and there is a verdict for this case: the router is busy, log out of its web interface. In all seven languages, on desktop, server and phone. Zyxel was the occasion; the logic applies to every brand whose detection works without logging in.

AI identification shows that it is running

AI identification probes the device itself before it asks the model, which takes a few seconds. During that time the app redraws the device drawer on every scan event, and the progress line went with it, along with the disabled button. It looked like a click that did nothing, so people clicked again and paid for two answers.

The indicator survives the redraw now, a second click during a running call is ignored, and the line sits where the answer will appear.

The phone also gained a button that opens a device's web interface when a web port is open. Same address rule as the desktop, so the two do not send you to different places.

DeviceShelf 1.9.11 — help that starts where you got stuck The in-app help stopped being a brochure. Help now sits next to the feature that needs it, the help view became a compact hub with a diagnostics button, and the MCP endpoint accepts IPv6 loopback.

The help view used to open with "What is DeviceShelf?". Whoever reads that has installed the app, has usually paid for it, and knows perfectly well what it is. What they actually wanted, in the moments they clicked Help, was an answer to a concrete failure: the scan shows nothing, live bandwidth is greyed out, the license will not activate.

Help where the problem is

Those answers now sit where the problem shows up. The bandwidth setting links its own setup guide when Npcap or BPF access is missing. The empty device list links the troubleshooting page. The license box links the activation guide. Each link opens the website section for exactly that situation, in the app's language.

The help view itself became a compact hub: how to scan, what the deep probes do, which features need elevated rights, how activation works, and a link list to the guides. The brochure cards are gone.

New there: a diagnostics button. It copies version, system, license state and whether packet capture is available, so a support mail can start with facts instead of a round of questions. Nothing is sent anywhere; it fills the clipboard and that is all. The app remains telemetry-free; the help links carry a plain URL tag so we can see on the website which help topic gets used, without the app reporting anything.

While wiring the button we found the diagnostics would have claimed packet capture is present on builds that do not contain it at all: the check asked only about the Npcap runtime, which is always "yes" outside Windows. It now asks both questions, compiled-in and runtime.

The MCP endpoint and IPv6

The server edition's MCP endpoint checks the Origin header before a browser page may talk to it. That check had no test, and writing the tests found a real defect: on a dual-stack machine, localhost resolves to ::1 first, the browser sends Origin: http://[::1]:port — and the allow-list only knew 127.0.0.1. The same access worked over IPv4 and failed over IPv6. Fixed, with the comparison kept strict so http://[::1].attacker.example still falls. The test was red against the unfixed code before it was green against the fix.

The macOS Help menu also grew direct entries for the setup guide, troubleshooting and support, and the Windows publisher warning, the Ubuntu .deb trap and the other common first-run walls are now documented on one page the app links to.

DeviceShelf 1.9.10 — nothing goes to a printer's raw port anymore A user reported that his Kyocera printed a page of junk characters on every scan cycle, all day long. We caused it, and in two places rather than one.

On printers, port 9100 is JetDirect, and it is not a protocol that answers questions. It is an intake: whatever arrives there gets printed. DeviceShelf sent a PJL query to that port to learn the model. On printers that do not parse PJL at that point, the query lands in the queue as a job.

The report came from a user with a Kyocera ECOSYS M6635cidn: one page of junk characters per scan cycle, all day, at his own cost in paper and toner.

Two writers, not one

The PJL query was the obvious half. The bigger one sat next to it. Service detection walked its entire plugin list across the same port: an HTTP GET, a Redis PING, a TLS hello, each of them a potential sheet of paper. Measured in the test, that is 40 connections per port, against the single message the PJL probe sent.

Both are blocked now, at the point every query has to pass rather than at the individual site. This covers 9100, 9101 and 9102, the same channel on multi-port devices, and 515, where LPD queues a job from malformed input just as happily.

Port 631 is not blocked. IPP speaks HTTP there, and a request is a question rather than a job.

What detection gives up

DeviceShelf still records whether the port is open; that is a real signal and costs no sent byte at all. Printers are still identified through the port pattern, SNMP, mDNS, IPP and the MAC vendor. What is lost is the exact model on printers that reveal it over PJL and nowhere else. Printing a sheet for that is not worth the price.

We had the same class of defect once before: until 1.9.2, service detection guessed credentials on port 22 and wrote itself into other people's authentication logs. The dividing line is the same both times: does a port answer questions, or does it accept work?

A note on the test

The first regression test covered the PJL probe only. A review before shipping pointed out that this left the second guard untested, and reproducing it confirmed the point: delete that guard and the whole suite stays green. The test now listens on a real port and counts connections, with a control that stops a zero from proving something that never happened in the first place.

DeviceShelf 1.9.9 — a scan that takes the network down now stops In a measurement on our own network the gateway went silent 23 seconds into a sustained scan, and the scan kept probing for 40 more minutes. Plus a server setting that has been displayed since late June and did nothing.

A scan asks every address in the subnet at once. On a shared medium that burst can push an already-loaded connection over the edge. Wi-Fi counts, so does a wireless link between two access points, so does Powerline. Until now, nothing stopped when it did.

Measured on our own home network: 23 seconds into a sustained scan the gateway stopped answering. The scan ran for another 40 minutes. That prolongs an outage instead of easing it. And it is the worse half of the problem: not the single scan, but that nobody ends it.

A running scan now watches the gateway and ends the run once it has stopped answering for about ten seconds. The brake only arms if the gateway answered beforehand: plenty of routers ignore pings to themselves by policy, and there an unconditional brake would abort every scan. That would be the worse fault. It applies at every intensity. Until now only the adaptive profile could pull back, and that was not the default.

For context: after a fix to the same network's radio configuration, the identical load did no harm at all. The scan was the trigger on an already-saturated channel, not the sole cause. Software that interrogates other people's networks should still notice when it is killing one.

A setting that did nothing

Three defects sat in the same place in the server edition.

Before every scan the service re-reads its settings. Doing so, it read the scan intensity from the desktop app's field while reading its own fields all around it. Both editions share one settings file; on a headless install that field is empty, so the throttle fell back to the normal values on every pass. Anyone who chose "gentle" to protect a fragile link never got it. The dashboard kept showing "gentle" while, measured, 128 concurrent probes ran instead of 32.

The same block also copied an outdated list of knobs. The heavy probes were missing from it, of all things: the deep service detection that opens around 30 connections for a single unclear port, and the TLS fingerprint with ten more. Precisely what the gentle profile exists to switch off.

And the function that applies an intensity called itself authoritative for every level in its own source, yet cleared one field in a single branch only. Switching away from the adaptive profile stayed adaptive.

Rather than extending the copy list, the intensity is now applied directly to the running options. A list that can drift is gone.

The default changes

A server install that never picked a level scans adaptively from 1.9.9 on, instead of normal. An unattended collector runs every few minutes, around the clock, on a network nobody described to it. The adaptive profile measures the gateway while it works and pulls back when the medium suffers. A level you picked yourself is left alone, including an explicit "normal".

The desktop default does not change. Someone is sitting there and triggers the scan themselves.

One small thing

An unreachable host no longer writes a line containing the word "FATAL" into the log. A sweep passes dozens of empty addresses, and the ping library in use reported every one of them that way while carrying on regardless. One server log held 115,000 such lines from three weeks.

DeviceShelf 1.9.8 — the server stops calling devices by their address On one real installation, 37 of 99 rows in the server dashboard were titled with a bare IP address, most of them for devices whose maker and type the scan had already identified. Two causes, one of them invisible from the outside.

A user put it as a feeling: "name detection in the server isn't as good, I often just see the IP address." Feelings about software are usually right about the symptom and wrong about the cause, so the first thing was to count. On that user's own network, 99 devices: 37 rows carried a bare address as their title, and 30 of those belonged to devices the scan had already identified by maker and type. Every Dell. Every Brother. The QNAP.

The obvious half was the ladder that turns a device into a row title. The desktop app and the phone have used the same one for a long time: your own name for it, then the device's own clean name, then the model, then maker plus type, and only when a device gives away nothing at all a stable "Unknown (0B7B)" keyed by the tail of its MAC address. The server dashboard stopped after the hostname and printed the address. It now walks the whole ladder, and it drops a local DNS zone while it is at it, so "udr7.local" is simply udr7.

The other half was harder to see, because it hid behind a field that looked filled in. The scanner works out a device's structured name parts while it enriches that device. Anything that skips enrichment never gets them: everything restored from storage when the server starts, devices enrichment passed over, and rows whose name only arrived later from ARP or the router's client list. The desktop app has always recomputed those parts when it serves a device. It is one line, described in its own source as a safety net. The server had no equivalent, so it served whatever the last enrichment happened to leave behind. Measured: the field was set on 6 of 99 devices where it should have been 56, and "udr7.local" was one of the devices it served as nameless. It recomputes them now on every update, including for devices streaming in during the very first scan.

What does not come back is a name the scanner threw away on purpose. An mDNS responder that answers with its own MAC address as its hostname is not telling you what it is called, and printing that string is worse than saying nothing; those devices are still shown by maker and type. There is a test for exactly that, because a recompute pass is precisely the kind of change that would quietly resurrect it.

After the change, that same network has no bare addresses left: 92 rows carry a real name, and seven read "Unknown" with the MAC tail, which is what a device that reveals neither a name nor a maker actually deserves.

The release carries a second piece of catching up. When the AI identifies a device, the desktop app and the phone fill in the fields you left empty — name, tags, comment, type. They never touch what you typed yourself, and they only do it at all when the model says it is confident. The server returned the answer and left every field blank, so the same device identified from the dashboard stayed unnamed. It fills them now, and the decision about what may be written and when now lives in one shared place instead of one copy per app. That is the only way two implementations stay honest about the word "confident". The dashboard shows the filled values the moment they are written rather than at the next scan, which on a quiet network can be minutes away.

DeviceShelf 1.9.7 — the installer says what it is waiting for Four reports from one user in one evening. The Windows installer waited in silence and without a limit, stopping the service took minutes, the dashboard stayed empty for a whole sweep after every start, and a TV in standby flapped between online and offline all day.

Yesterday's release taught the installer to wait. Waiting, it turns out, is only half the job: it also has to say so. The same user ran the update again, saw "Preparing (stopping any previous version) ..." and then nothing at all for the better part of a minute, and cancelled it. Twice, before letting it run through. An installer that goes quiet for forty-five seconds looks exactly like an installer that has crashed.

It now reports every five seconds how long it has been waiting and how long it is prepared to wait. The stop request itself got a ceiling too. deviceshelf-server -service stop blocks until the service manager answers, and a server wedged mid-scan never answers, so the wait budget only started after an already unbounded wait. Both ends are bounded now, which is the only way the step has a worst case anyone can name.

Underneath sat the real cause. Stopping the service waited for the running scan to unwind, and a port sweep runs far longer than the thirty seconds Windows gives a service to answer a stop request. Past that, the service manager refuses every further control message. That is the error that broke the updates in the first place. The service now saves its state and lets go after eight seconds. Measured on the machine that produced the report, with a scan deliberately running: thirteen seconds for the whole update against a current server, forty-six against an old one, and narrated the entire time.

The third report was about the dashboard. After a start it showed nothing until the full sweep had finished, which on a populated network is a minute or more of empty screen. Right after an install, that reads as a product that does not work. The first scan of a process now pushes devices into the list as it finds them, and carries the network along with them, the same way the desktop app has always behaved. Fifty-six devices after 1.6 seconds instead of nothing for two minutes.

The fourth is older and quieter. A device that answers ARP but neither ping nor any probed port was counted as present by the full sweep and as absent by the thirty-second presence check. Two verdicts that contradict each other forever produce a device that flaps forever: thirty-two events in eighty minutes for one LG TV in standby. The presence check now asks the same question the sweep asks, so a standby TV, a sleeping printer and anything behind a firewall that drops ICMP stay where they belong. Real absences are untouched: something that answers on neither layer is still reported offline.

For the desktop app there is one small thing. The header could say "not scanned" above a list of a hundred devices, because it only ever learned the network from a live scan event and never asked the backend for the one it already had. It asks now. And when there is genuinely nothing to ask for, the line says what is true instead: the list is the stored one, and this app has not swept anything yet.

DeviceShelf 1.9.6 — the Windows installer learns to wait Updating the Windows server failed whenever a scan happened to be running, and left the machine with a service marked for deletion. The installer asked the service to stop, ignored the refusal and copied anyway. It now waits, and it also stops printing a token that belongs to whoever once ran the server by hand.

A user reported that installing the Windows server "keeps giving me problems", with the log to prove it: the service could not accept control messages, the service was then marked for deletion, and the copy failed because the file was in use. Three errors in a row, one cause.

The installer asked the service to stop, ignored the answer, and copied the new binary immediately. But the service does not stop on request alone. A scan in flight keeps it in STOP_PENDING for ten seconds and more, Windows refuses further control requests while that lasts, and the running image holds the file. So the update failed at the last step, after the service registration was already gone: no working installation, and a service name that Windows still considers half-deleted.

It now waits. The stop is requested, then the installer polls once a second for up to 45 seconds until the binary is genuinely writable, and only then continues. If the service is truly wedged, the process is terminated — the same thing an administrator would do by hand — and if even that does not free the file, the installer stops and says so, because a half-written server binary is worse than an update you run again after a reboot. Copy and install are retried a few times, since Windows releases the file lock and the deleted service name a moment after the process exits. The uninstaller had the same gap and got the same treatment.

The other half was hiding behind it. The token the installer printed at the end was often not the token the service uses: deviceshelf-server -token searches the calling user's profile before the service's own, so on any machine where somebody once started the server by hand, that stale token got presented as "your token" — and it fails from every other device on the network. The same wrong lookup quietly made the update's token-preservation check compare a value against itself, so the safety net never fired. The installer now reads the service's own token file first.

Verified on the machine that produced the report: with a scan deliberately running, the update goes through, and the token shown at the end actually authenticates.

DeviceShelf 1.9.5 — sensitivity you can set, and a scan that is not an oracle A day of testing 1.9.4 against a real network found the other half of the flapping problem, a notification that introduced a named device by its manufacturer, and a restart summary shouting internal error levels at forty-five raw MAC addresses. All fixed, and the offline sensitivity is now a setting — globally and per device.

Yesterday's release stopped treating one lost ping as an outage. A day of testing against a real network showed that was only half the story.

The 30-second probe had learned patience, but the full scan had not: a device that fell out of a single sweep was still marked offline on the spot. And falling out of one sweep is easy: the test Pi here carries 24 virtual devices and gets scanned aggressively, and it "went offline" 31 times in one afternoon. The event log gave it away: offline at 14:22:19, online at 14:22:24. Five seconds. There was never an absence, just a missed round.

Both paths now feed one shared counter. A missed sweep counts as one unanswered check, the probe half a minute later may contradict it, and only a streak establishes an absence. A device that is really gone still gets caught in about ninety seconds, because failing probes reach the same streak. The event log names both the count and the last check ("no answer to 3 consecutive checks; last: full scan"), so the next person debugging their alerts reads the reason instead of guessing it.

How many checks make a streak is now yours to decide: Instant (one), Standard (three), Tolerant (six). That applies globally in the alert settings, and per device in its drawer, where "like global" is the default. The flaky camera gets Tolerant, the UPS gets Instant, everything else inherits. Below the two controls the dashboard now states their combined effect in plain terms ("alert at the earliest about 90 s after the last reply"), because two settings that both sound like "how fast" read like rivals until the arithmetic is visible.

Notifications also stopped wasting their words. The push about a device its owner had named "Einerda" used to introduce it as "Raspberry Pi Trading Ltd". The naming ladder consulted every measured name but never the user's, and the user's outranks them all everywhere else. The body repeated the title instead of adding the address. And the summary after a service restart listed forty-five alarms as "ERROR:" followed by a raw MAC each. All three now speak like the rest of the product: your name for the device first, facts the title cannot carry in the body, and levels only where a warning genuinely differs from an error.

One more thing testers will notice: the device drawer in the dashboard saves every change the moment it happens, like the desktop app and the settings page always did. The Save button is gone, and with it the question of whether anything was unsaved.

DeviceShelf 1.9.4 — a printer left in peace, a server that was never down A user mail carried two reports. His Brother printer lit up its display every five minutes, each time the server scan sent it an SNMP question. And his Unraid machine, which runs around the clock, kept appearing in ntfy as offline, then online again. Both were real. 1.9.4 adds a per-device switch that stops active probes entirely, and stops treating one lost ping as an outage.

A user mail carried two reports, and both turned out to be real.

His Brother MFC-J497DW woke its display every five minutes, exactly when the DeviceShelf server ran its scan. The cause is the SNMP question the scan asks every device. Most hardware answers it silently; some printers treat it as a reason to light up. There was no way to spare a device from that.

Now there is. Every device has a switch in its drawer: exclude from active scans. With it set, DeviceShelf sends that device nothing. No port scan, no SNMP question, no direct name queries. Passive discovery and the presence ping stay, so the device remains on the list and keeps being monitored. It just stops being interrogated.

The first version of that switch had a hole, and I would not have found it alone. An independent review of the change noticed that name resolution starts while the scan is still sweeping, at a moment when a row has an address but no MAC yet. A switch keyed on the MAC could not match there, and the printer would have been queried through exactly that path. The exclusion now also matches on the device's last known address. There is a test that watches the wire and fails if an excluded device receives a question.

The second report: his Unraid server, running 24/7, kept going offline in ntfy and coming back. The mechanism was embarrassingly simple. Between full scans, the server pings known devices every 30 seconds, and one unanswered ping (750 milliseconds of patience, once) counted as an outage under the default settings. A machine that briefly deprioritises ICMP, or a Wi-Fi hop that drops one packet, produced the exact pattern from his mail. An absence now needs three consecutive misses before it counts. A lost single ping no longer stains the uptime ledger. And every offline event states how it was established, "missed full scan" or "no reply to 3 probes", so if anything still flaps, the event log says where to look.

While proving all this with tests, a third defect surfaced: the per-device "mute notifications" switch, which the dashboard has offered all along, was stored by the server and then ignored at the moment of delivery. Muted devices notified anyway. That is fixed, with the semantics you would want: the timeline entry, the alarm state and the outage record stay. What you muted is the pushing, not the monitoring.

Also in 1.9.4: icon search now works in every language across the full set, and the picker can browse by group instead of search-only. Custom icons and custom types reach the server dashboard and the phone, and your own types are listed before the built-in ones. A machine with two network cards is one row instead of two, and a type you assigned survives that merging. The server writes its store with half the CPU it used before. A running packet capture stops by itself when nobody is watching it.

DeviceShelf 1.9.3 — icons that exist, and a gate that blocks A user asked for four device icons and found one. Not because the other three do not exist, but because the file holding them said "auto-generated" while nothing in the project could generate it — so it held 56 of Tabler's 5130 icons. That build step exists now, and the set went from 1023 to 2107. Accessibility also became a release gate; its first run found 21 real barriers, sixteen of them controls a screen reader could not name.

A user wrote in with four device types he could not find an icon for: a robot lawn mower, a robot vacuum, a coffee machine, a Thermomix. He also mentioned that tablets and phones drew the same picture.

I assumed the icons did not exist and started estimating an upload feature. They existed. The set ships Tabler and Fluent, both MIT-licensed, and Tabler alone has 5130 icons — of which we had included 56. The file holding them carries the header "AUTO-GENERATED", and nothing in the repository could generate it. Adding one icon meant hand-editing 2.3 MB of minified JSON, so nobody ever did.

The build step now exists. The set is 2107 icons, and the file grew by 0.3 MB, because the colourful half dominates it either way. Robot vacuum, lawn mower, coffee machine and kitchen appliance are built-in device types in all seven languages, on desktop, server and phone. "Tablet" had been pointing at a notebook 📓, which is why it looked like the phone; both now use icons that are actually of those devices.

Three things the generator refuses to do, each because of a way this goes wrong quietly:

It will not let an icon disappear. A device type you define yourself stores the icon's name. If an icon left the set, your row would show an empty square — data on your machine that we cannot see and cannot migrate. Every icon that was in the set stays in it, even against the filter. If upstream removes one, the build fails and names it rather than shipping the hole.

It excludes brand logos. Company marks are a legal question we have not answered, and shipping four hundred of them inside the app is exactly what that question is about. They also poisoned the search: typing "cup" matched a car brand.

It keeps the eagerly-loaded subset in step. The full set loads on demand; an icon a built-in type points at has to be in the small subset that loads immediately, or every device of that type shows an empty slot until you happen to open the type manager. My first attempt shipped exactly that, and nothing warned me. There is a test now.

Accessibility as a gate

Nothing ships from here without passing EN 301 549 clause 11 — the standard the European Accessibility Act and the German BFSG point at, which adopts WCAG 2.1 level AA word for word. Every view, in light and dark, plus the detail drawer, 200% zoom, the right-to-left layout, keyboard reach to every view and a visible focus ring.

Its first run found 21 real barriers. Sixteen were critical: switches and dropdowns that a screen reader announced as "checkbox" with no indication of what they did — in Settings, in Network, in Bandwidth, in Diagnostics. If you use a screen reader, those controls were unusable, and I did not know.

Five were contrast failures, including white text on the accent colour at 3.2:1 in dark mode where 4.5:1 is required. Those are fixed at the level of the colour tokens rather than per element, so the fix holds in both themes.

The gate almost shipped as a formality. It set the theme through a value the app does not read, so the dark half was measuring light colours and reporting the same four findings twice. It now seeds what the app actually reads and asserts the result, so it cannot quietly become a duplicate run again.

And it says on every run what it cannot do: automated rules reach about a third of real barriers. Whether a label is meaningful, whether the reading order makes sense, whether an announcement arrives at a useful moment — that needs a person with a keyboard and a screen reader. A third, measured on every view before every release, is worth a great deal more than a good intention. It is not the same as being accessible.

DeviceShelf 1.9.2 — a scanner that stopped knocking DeviceShelf was attempting real SSH logins on your own network. The service-detection library it uses does not stop at reading a banner; to find out whether password login is enabled it tried admin/admin against every port 22 it found, on every scan. Measured on a test machine, that was 146 to 237 failed-password entries an hour. Update. Also in this release, the macOS menu-bar icon became useful, and accessibility became a release gate.

A network scanner has one promise to keep: it looks, it does not touch. DeviceShelf broke that promise, and I want to say plainly how.

Service detection uses fingerprintx. Its SSH plugin does not stop at the banner a server sends unprompted. To report whether password authentication is enabled, it attempts a real login as admin/admin, then follows up with a keyboard-interactive round answering every prompt with password. DeviceShelf ran that against every port 22 on your network, on every scan.

What it cost, measured on a Raspberry Pi in the lab: 146 to 237 Failed password for admin entries per hour, around the clock, from a single running copy. OpenSSH 9.8 answers that with per-source penalties and starts refusing the scanning host — so you can be locked out of your own device by your own scanner. That happened three times while I was tracking this down, and I blamed the Pi twice before I looked at us. Any intrusion detection or fail2ban reads those entries as a brute-force attempt, because that is what they are.

The plugin is banned by name now. SSH identification survives, and this is the distinction that matters: a server states its version unprompted, before anyone has authenticated. Reading what a device volunteers is fingerprinting. Sending a password nobody asked you to send is guessing. Only the first one belongs in a scanner.

If you run 1.9.1 or older, update. If you have a device that started refusing your machine, this is likely why.

The menu bar

On macOS the icon used to be the letters "DS" in a fixed slot, and it did nothing except open the window. It now shows what a glance is for: how many devices were found and how many are online, how old the last scan is, and what a DeviceShelf Server on this machine is doing — with the address it serves on.

It also starts and stops that server. On macOS the server is a launch daemon with KeepAlive, which means killing it in Activity Monitor achieves nothing: launchd restarts it within a second. The only thing that works is launchctl bootout system/com.deviceshelf.server as root, and that is not knowledge an application should require. The button does it through the standard authentication prompt, and the command is shown next to it for anyone who prefers to type it.

Closing the window now hides it and leaves the app running behind that icon, which is the ordinary arrangement on macOS and the only one that makes a menu-bar icon worth having. Settings has one choice for it, and an option to start DeviceShelf at login.

Devices your scan can see but not touch

A device on another subnet, in a guest network, or behind a Wi-Fi extender answers a ping but leaves no ARP entry, so it arrived without a MAC address. Everything you can attach to a device is stored under its MAC: the name you gave it, the note, the tags, the favourite, the mute, Wake-on-LAN, the uptime history. Without one, the device was permanently uneditable, and nothing said why.

Your router knows that MAC — it is in the client list DeviceShelf already reads. It is now folded in, along with the name, on both desktop and server. The phone app has done this since it shipped, because iOS cannot ARP at all; this brings the other two in line. Where no MAC can be found even so, the app and the dashboard now say what is missing and what would supply it.

Accessibility as a gate

Nothing ships now without passing EN 301 549 clause 11, the standard the European Accessibility Act and the German BFSG point at, which adopts WCAG 2.1 level AA. Every view, in light and dark, plus the drawer, 200% zoom, the right-to-left layout and keyboard reach.

Its first run found 21 real barriers. Sixteen were critical: switches and dropdowns a screen reader announced as "checkbox" with no indication of what they did — in Settings, Network, Bandwidth, Diagnostics. Five were contrast failures, including white text on the accent colour at 3.2:1 in dark mode. All fixed.

The gate says on every run what it cannot do: automated rules reach about a third of real barriers. Whether a name is meaningful, whether the reading order makes sense, whether an announcement arrives at a useful moment — that still needs a person with a keyboard and a screen reader. But a third, measured on every view before every release, is a great deal better than a good intention.

Also

Switch topology reads the forwarding table before the CDP walk. On a TP-Link Omada SG2008P the CDP walk leaves the SNMP agent unable to answer for about four seconds, and the forwarding-table read used to run straight into that window: 45 entries when read first, zero when read after.

Update checks keep running while the app is open and mark an update as urgent when it is. A release like this one should not wait for someone to restart the app.

DeviceShelf 1.9.0 — reading what the network already says The DHCP fingerprints DeviceShelf was already collecting are now read on your own machine instead of being sent to an optional cloud. On 91 real devices the type went from 30 correct to 43 of 43, with the misclassifications gone, and the maker is known for 83 of them instead of 71. The device list also filters by online and offline, and the outage list finally says which day it is talking about.

Every device that asks for an address announces which DHCP options it wants, in its own order. Apple asks differently from Windows, and an embedded lamp asks for four options where a Mac asks for thirteen. DeviceShelf has recorded that list for a long time. It just never read it: the fingerprint went to Fingerbank if you had turned that on, and nowhere otherwise. Now it is evaluated on your machine, against rules that ship inside the app.

The device that gains most is the one that used to give up nothing. A phone with a private Wi-Fi address has a MAC that belongs to no manufacturer, so the usual lookup returns a blank, and it stayed blank no matter how long you looked at it. Its DHCP request list still says Apple.

Numbers, because a claim like that is worth nothing without them. There is now a corpus of 91 real devices in the repository, captured from networks I control and anonymised: phones, laptops, routers, access points, a switch, two NAS boxes, cameras, speakers, a TV, a fridge, printers, IoT bridges, servers. Each one carries what was actually observed and, where I could establish it independently, what it really is. Against that corpus the device type went from 30 correct to 43 out of 43 labelled, and the 11 misclassifications are gone. The maker is right for 32 of 33 instead of 21, and is known at all for 83 of the 91 instead of 71. The product name is right for 20 of 32 instead of 10.

Behind those numbers are things that were plainly wrong. My own Mac was a speaker, because it advertises AirPlay and the rule that noticed audio ran before the rule that noticed file sharing. Access points named AP were routers. Both Bose speakers were "web devices" because their only open port was a web server. A Samsung fridge was a television. A Windows machine sharing a printer was itself a printer. Two eufy cameras were generic smart-home gadgets, even though they advertise their model number.

The rules that fix this read three kinds of evidence: the DHCP request list and vendor class, the names and model strings devices publish over mDNS and SSDP, and SNMP product patterns below the enterprise arcs we already recognised. Each rule names its source, its licence and the date it was added, and a rule that has only a manufacturer prefix to go on is rejected when the file loads, because an OUI names a chip maker, not a device. Nothing here came from Fingerbank, Satori or Nmap. The old FingerBank database is open data, but its licence attaches duties to anything derived from it, so it is parked until that is properly checked rather than quietly used.

None of it adds a network connection. The rules are compiled into the binary, the evaluation is a string comparison, and Fingerbank stays exactly what it was: off unless you enter a key, and even then only when you ask for a lookup. Storing the key does not start anything.

The phone reads the same rule file. Literally the same file, copied into the app bundle, with a test that fails if the two ever differ by a byte. What it cannot do is the DHCP half, because a phone has no way to watch other devices' DHCP traffic, and the SNMP product walk. That is written down in the feature matrix rather than papered over.

Two smaller things you will notice sooner. The device list filters by online, offline or all, and remembers which you picked. And the outage list in the device drawer is grouped by day: it used to be a single unbroken run of bare times, so "Down 02:51" told you nothing about which night that was, and a device that flaps produced hundreds of rows. It now shows the last thirty, grouped by date, and says how many older ones it left out.

One correction that only shows up over time: the server used to overwrite a device type you had set by hand whenever fresh DHCP data arrived. It re-asserts your choice now, the way the desktop always did.

DeviceShelf 1.8.0 — the device list holds still A scan no longer empties the list before refilling it, and a device that stops answering stays in it marked offline instead of vanishing. Sorting by address is numeric again, a second click on a column reverses it exactly, and rows stop changing address between refreshes. The phone gains ascending and descending order, and now needs iOS 15.

A scan used to throw the whole list away and rebuild it. The screen went blank, then refilled, and anything that failed to answer that one time was gone, with no trace it had ever been there. Most of a network is the same from one scan to the next, so the list now stays and is updated in place. What a completed sweep swept over without hearing anything is marked offline, and that row stays. The list can say "was here, is not now" instead of quietly dropping it. A device that answers again comes back.

Three things it deliberately does not do. A scan judges only the addresses it actually covered, so a range scan of one subnet says nothing about a device on another. A scan you stopped judges nothing at all: it probed an unknown fraction of its range, and reading that as "everything else is gone" would grey out half the list. And a device that answered from a new address after a fresh DHCP lease leaves no ghost at the old one. Otherwise one machine would show twice, once offline at an address nothing is at any more.

Sorting works again. Ordering by address had become text ordering, which puts .100 before .9. Worse, the order was not total: rows the chosen column called equal came out in whatever order the sort left them, and that order changes whenever a source is switched on or off. On a list long enough, it visibly reshuffled for no reason. Every comparison now ends on something unique per row, so the answer is the same every time. The phone gains what it never had. Ascending and descending, remembered across launches, with the direction spoken to a screen reader and the icon flipped so you can see it without opening the menu.

Rows also stop changing address. A machine answering on several of its own adapters is one row, and which of its addresses that row kept was decided by the order the devices happened to arrive in: random on the desktop, scan-dependent on the phone. The same unchanged network therefore answered "the Mac is here" one moment and "over there" the next. Because the list is keyed by address, that is a row disappearing and another appearing somewhere else. The fold now says outright which addresses a row absorbed, so nothing has to be guessed, and a device that takes over one of those addresses gets its own row at once rather than waiting.

Rows without a favourite star line up with the rest. Devices a collector reported have nothing to toggle here, so they get a spacer where the star would be, and that spacer was nineteen pixels narrower than the star. Every such row's icon and name sat too far left.

The server dashboard picks its gateway by address now, rather than by whichever probe finished first. That choice decides whether the hosts behind a router answering ARP for them are rows of their own, so the same network could show a different number of rows between ticks, and a different number again from the desktop.

Behind all of it: the desktop, the server and the phone each carry their own copy of these rules, and they had drifted without a single test going red, because nothing compared them. Two shared descriptions now live in the repository, and every runtime checks itself against the same file in its own test suite. Where the two clients differ on purpose, the difference is written down and checked as well. A rule that differs deliberately and is not pinned looks exactly like one that drifted.

The phone now needs iOS 15. Apple stops accepting anything below it in spring 2027, and doing it while the user base is small costs the fewest people. What drops out is hardware that cannot run iOS 15 at all: iPhone 6, 6 Plus, 5s, iPad Air 1, iPad mini 2. Everything from the iPhone 6s onward stays.

DeviceShelf 1.7.9 — a router that refuses you now says why Every failed router connection used to blame the password, even when no password could have helped. The app now tells the failures apart, and the four brands you have to open up first say so under the picker. Seventeen brands were re-checked against recorded answers. Threshold checks save as you edit, run on the first tick after a restart, and record what they measured.

A router that refuses you now says why. The connection test had one answer for every failure, and it named the password first. That sent people down the wrong path: a GL.iNet owner changed his password twice, but his OpenWrt never exposes the interface we talk to, so no password could ever have worked. Unreachable, no endpoint, refused, no data and no brand matched are separate answers now. OpenWrt reports its own diagnosis; the other brands fall back to a reachability probe, which at least separates "no answer at all" from "answered and said no". A guessed diagnosis would be worse than an honest coarse one.

Four brands need the router opened up before anything can connect, and the picker now says so on the spot: ubus for OpenWrt, an API key rather than an account password for UniFi, the applications switch on a FRITZ!Box, www-ssl on MikroTik. The other thirteen show nothing rather than an empty box. The help text also stopped selling the feature short, and stopped promising switch ports for every brand, because only UniFi reports them. What you get is devices the scan cannot reach at all (sleeping phones, anything behind a firewall, other VLANs), the names from the router's DHCP table, and whether an address is a lease or a fixed reservation.

The phone says the same thing in the same words. It has its own router client and its own catalogue, so it had kept answering "check the credentials or the router type" for every failure. Someone holding both devices would have been told two different stories about one router.

All seventeen brands now have recorded answers on file, replayed through the Go and the Dart implementation alike, and that turned up three defects. A Peplink client that is not connected no longer counts as present. A Netgear device whose MAC cannot be read no longer disappears from the list. And a UniFi controller on Network 9.x reports the switch port it actually uses, rather than the field older firmware used.

Threshold checks work like every other setting now. They used to sit idle for a full monitor interval after each restart. At a fifteen-minute cadence that is fifteen minutes of a check which looks armed, measures nothing, and cannot raise the alarm it exists for. They also had a Save button nothing else has, so an edit you typed but did not confirm was thrown away, in the one place where losing an edit means an alarm quietly stops watching. Fields save as you leave them now, and the status line is read out to a screen reader.

A check also records what it measured. The result used to be evaluated and dropped, so a check row sat at a dash with a grey bar forever and reported itself up whatever it found. And in the monitoring list a check no longer poses as a device: someone asked about a device called Test that does not exist on any network, because a check pointing at localhost sat between wlan0 and a NAS, carrying a device icon and nothing to mark it. Checks have their own group now, their own icon, and their type written next to the name, in text, because colour alone reaches neither a screen-reader user nor anyone with a colour deficiency.

Desktop and server are 1.7.9, the mobile apps 1.3.9. Existing licences carry over; nothing in the data formats changed.

DeviceShelf 1.7.8 — the ranges you configure are the ranges it scans A subnet typed into the scan-range field used to change nothing at all; on the server it could even replace your own network. Both fixed, and the app now says when it dropped part of a scope. Alarms are muted instead of acknowledged, the macOS window keeps its toolbar on screen, and both device drawers work by keyboard and screen reader. The post also catches up on 1.7.4 to 1.7.7, which taught the list to show one row per machine.

Ranges you configure are finally scanned. A subnet typed into the scan-range field did nothing at all: the run finished in seconds and found nothing, with no hint that the field had been ignored. On the server the same setting had the opposite defect: a configured range replaced the machine's own network instead of adding to it, so entering a routed subnet made the server stop seeing the network it sits on. Both are fixed. And when a scope is too large or holds an entry that cannot be read, the app says so and names what it dropped, instead of quietly scanning less than you asked for.

The window no longer scrolls. On macOS the toolbar could ride off the top edge, taking the Scan button with it, and there was no scrollbar to bring it back. The dashboard also stops asking for a login you already typed.

Alarms are muted now rather than acknowledged. A muted alarm leaves the list instead of sitting in it, there is a mute-all, and the list stops reshuffling while you work in it. Both device drawers take keyboard input and read correctly to a screen reader, and closing one returns focus to the row you came from.

Four point releases went out between 1.7.3 and this one without a post, all on the same theme: one machine, one row. 1.7.4 started asking devices for their own names, so a phone on a private Wi-Fi address or a laptop with sharing switched off shows a name instead of a bare address, and the OS column says iOS, iPadOS, macOS, tvOS or Android where it used to lump them all together. 1.7.5 through 1.7.7 folded a machine's several addresses into a single row (a computer running virtual machines, a NAS answering from its container interfaces) and made that hold no matter who saw the device: the local scan, a collector, or both. Your names, comments and tags survive the folding, and a machine on your own network keeps Wake-on-LAN and its other actions even when a collector reports it too.

Desktop and server are 1.7.8, the mobile apps 1.3.8. Existing licences carry over; nothing in the data formats changed.

DeviceShelf 1.7.3 — one device list, and you pick whose network it shows The source pill in the toolbar now governs the device list instead of jumping to a second screen. Tick this computer's scan, any of your collectors, or several at once; origin becomes a column you can sort and filter by. The security analysis follows the same choice and says how many devices it could actually examine.

There used to be two device tables. The main one had configurable columns, filtering, favourites and sorting, and it showed this computer's scan. The second lived inside the remote view, had seven fixed columns and no actions, and clicking the source pill took you there. From 1.7.3 there is one table. The pill is a set of toggles: this computer's scan, any collector you have configured, any combination of them, with a filter box once the list passes eight entries. Picking a source changes the rows in place, every row carries where it came from, and that origin is a column you can sort and filter by. Turning the last source off falls back to the local scan instead of leaving an empty table with nothing to explain it. A device a collector reported stays view-only: waking it, scanning its ports or connecting to it would reach whatever holds that address on the network you are sitting on, not the machine you are reading about.

The security analysis follows the same choice. It had kept asking the backend for "the devices", meaning this computer's own scan, while the list was pointed at a collector. A report about one network sat above a list of another, with nothing to tell the reader which was which. Two counts in its summary were wrong as well. "Devices with port data" accepted any evidence at all, an mDNS service name or a certificate included, so a device nobody had port-scanned still counted as carrying port data, and a zero read as bad luck when the honest answer was that nothing had looked. Port-scanned devices are counted separately now, and the view names why there are none. The risk score also used to drop when you added a collector, because its devices had never been examined for ports and were counted in the denominator as though they had come back clean. Looking at more of your network cannot make it safer, so devices nobody examined are reported as skipped rather than quietly improving the figure.

On the server, "Update available" read the same whether a release tidied up a label or the version you are running is defective. 1.7.2 is the case in point: it shipped a CVE database missing all of 2025, so it reported fewer findings and looked healthier than it was. The manifest now carries that judgement in two independent fields: how much the new release matters, and which installs below it have a known defect. The notice says which of the two applies. The update panel used to offer docker compose pull with a footnote for the .deb, which left the macOS .pkg and the Windows service with no command at all. The server now works out how it was installed from what each package leaves behind and shows the one command that applies. The push notification uses the same logic, since an operator reading it over ntfy has no dashboard in front of them.

Pressing Scan on iPhone or iPad while not on Wi-Fi did nothing at all. The start path returned silently on a missing network, so the button could be pressed forever with no message, no error and no change on screen. The app now records why a scan could not start, says there is no local network to scan, and offers to try again; the header says it before the tap. A scan range holding no addresses is reported too, instead of ending quietly. Both apps also gained an indicator naming whose data is on screen: the local network, or the collector's name with its connection state.

Desktop and server are 1.7.3, the mobile apps 1.3.3. Existing licences carry over; nothing in the data formats changed.

DeviceShelf 1.7.2 — editing devices in the server dashboard The server dashboard can now set a device's type, tags, note, favourite and mute state, and say which machine a container runs on. Renaming no longer clears the note and type override, and one machine answering on several MAC addresses is one row again.

Until now the server dashboard could only rename a device. Everything else a person might want to record was stored, synced and displayed, yet could only be entered in the desktop app: the device type when detection guesses wrong, tags, a note, a favourite mark, muted notifications. 1.7.2 puts all of it in the dashboard's device drawer. Virtual devices get a host picker too. For a container behind NAT nothing on the network reveals which machine it runs on, so somebody stating it is the only answer there is; the server already honoured such a statement and simply had no way to take one.

Renaming a device in the dashboard used to clear its note and its type override. The dashboard sent only the new name, the server read the missing fields as emptied ones, and the metadata sync then carried that deletion to every other client. The endpoint now patches field by field: an omitted field is left alone, an explicitly emptied one is a deliberate clear. Two related defects came out with it. A statement about a container's host was filed under a differently spelled key than the same device's name and type, so forgetting the device removed one and left the other behind. And an unparseable address sent to the host endpoint was stored as given, matched nothing afterwards, and still reported success.

The device list no longer repeats itself. A NAS answering on its own network card plus a handful of container interfaces used to fill the dashboard with near-identical rows, because the server served the raw per-address inventory while only the desktop app folded them together. That folding now runs on the server as well, in the read path alone. Availability, alerts and history stay keyed per address, where they belong.

Checkboxes in the dashboard also stopped growing on their first click.

DeviceShelf 1.6.24 — router imports respect your scan range Devices imported from the router's client table or SNMP now stay inside the configured scan range, self-assigned 169.254.x addresses no longer appear as permanently-down ghosts, and the trial gives an honest heads-up before it ends.

When a router login is configured, DeviceShelf reads the router's own client table and adds hosts the local scan missed. Until now that import ignored the scan range set in the app. A Wi-Fi client that had failed to get a DHCP lease showed up under its self-assigned 169.254.x address: unreachable, 0% uptime, first seen on the nonsense date 01/01/1. 1.6.24 bounds the import to what your scan settings actually cover. If you configured a range or picked interfaces, only hosts inside that scope are adopted. Without a configured range the app uses every private LAN the machine sits on, so multi-homed setups keep their second segment.

Junk addresses are now rejected outright, from every import path. Router client tables and the ARP tables read over SNMP can contain self-assigned link-local rows (169.254.x, fe80::), loopback, multicast or 0.0.0.0. None of these is a real device. And since the monitor probes adopted addresses, a misreporting device could point probes at addresses that should never be targets. The server already blocked these address classes for SNMP targets; the same block now applies to adopted hosts, on desktop and server alike. Imported devices also get a real first-seen timestamp.

Ghost entries created by earlier versions are not deleted automatically. Remove them once and they will not come back.

The desktop app now warns before a trial runs out. On the last two trial days a dismissible notice with a buy link appears; previously the only purchase hint sat in the Settings panel. The expiry screen wording was corrected along the way: after the trial only scanning stops on desktop, and the text now says exactly that, including that collected data stays on your machine.

DeviceShelf 1.6.23 — the sync no longer eats your device names A full re-pull from a remote server could overwrite freshly written names, tags and types with stale empty records. The sync now merges by revision instead of replacing, and the monitor list learns sorting and filtering.

If the desktop app syncs device metadata with a server, certain events made it re-read the server's entire record feed: enabling the sync, re-adding a server, a server reinstall. The desktop applied every record from that feed as delivered. A record that predated your latest edits carried empty fields, and those empty fields replaced whatever you or the AI identification had just written. The AI result text survived, because it is not part of the sync. The name, tags, type and note it had filled in did not. From the outside it looked like the app forgetting your labels after a restart or a rescan.

The rebuilt pull path is deliberately clock-free, since comparing wall-clock timestamps across machines is how sync systems lose data. The desktop now remembers which server revision it has already seen and skips re-deliveries outright; a cursor reset carries no new information and can no longer touch anything. It also tracks, per server, which local change was last acknowledged. When local state has edits a server never saw, a pulled record merges field by field instead of replacing: queued edits stay untouched, a non-empty local name beats a stale empty one and gets pushed back to the server, and only genuinely newer server values are adopted. A deliberate clear made on another client still comes through, because it arrives with a newer revision.

Two smaller holes closed along the way. A sync conflict used to freeze the affected edit until you touched the field again; conflicted changes now re-base onto the server's current revision and retry, with a bound so a misbehaving server cannot loop them forever. And a transient failure reading the stored server list no longer causes an edit to be quietly skipped by the outgoing queue.

The fix is entirely on the desktop side. Servers need no update; 1.6.23 server builds exist for version parity only.

Separately, the monitoring list is now sortable and filterable. Devices with problems sort to the top by default, and a search box, sort options (name, uptime, type) and a down-only filter appear once the list has six or more entries. This applies to the local monitor and to remote server views alike.

DeviceShelf 1.6.22 — presence Home Assistant can actually use The export shipped presence as a binary sensor, which no person entity can read. It now also publishes a device tracker, one problem sensor per monitored check, and the vitals of every polled machine.

The Home Assistant export in 1.6.21 published each device as a binary sensor. That is an honest reading of what the scan measured, and it is fine on a dashboard. It also cannot drive anything: Home Assistant only attaches a device_tracker to a person, so "the phone answers on the LAN" never became "she is home", and none of the presence automations people already have could see it.

Every exported device now gets a tracker as well. Both read the same state topic, so the two views cannot disagree, and source_type says the presence was inferred from the network rather than from GPS. The binary sensor stays for dashboards and templates.

The second gap was services. Home Assistant can tell whether a machine answers a ping; it cannot tell whether the database on it is healthy, which is why people keep a second uptime tool running next to it. Each enabled check now becomes a problem sensor, reading the same alarm state that sends the notification. What turns red in HA is what triggers an alert, not a second opinion assembled elsewhere.

Then the numbers the server already collects. Each SNMP-polled machine appears as its own device with CPU, memory, disk, temperature and fan; Docker containers share one device with a running sensor each. "Notify me when the NAS disk passes 90%" is a two-line automation once the sensor exists, and impossible without it.

Some care went into what happens when a source goes quiet, because the obvious implementation is destructive. An empty list is ambiguous: Docker being switched off and the socket not answering for one cycle both arrive as zero containers. Treating the second one as "there are none" removes the entities from Home Assistant's registry, and they come back a minute later as new entities. The names you gave them, the areas you assigned and the dashboard cards pointing at them are gone. Sources now say whether they reported at all, and one that stayed silent keeps what it published.

The same applies to a failed poll. The SNMP layer reports minus one for every metric when a host times out, so skipping those metrics would have deleted CPU, memory, disk, temperature and fan on each hiccup, recorded history included, for exactly the disk sensor the feature exists for. Once a sensor has been published it stays; the state carries the gap as null, and Home Assistant renders that as unknown rather than as a plausible zero.

Turning the health watch off removes the host entities instead of freezing them. An entity that is no longer being evaluated but keeps reporting its last value is worse than one that is gone, because nothing about it looks wrong.

DeviceShelf 1.6.21 — the server talks to Home Assistant The server edition can now publish its inventory to Home Assistant over MQTT. Presence was never the interesting part; knowing that an unrecognized device just joined is.

The server edition can now publish what it finds to Home Assistant. It uses HA's own MQTT discovery protocol, so there is nothing to install on the Home Assistant side beyond the MQTT integration and a broker. No add-on, no custom component, no HACS release.

Writing a real Home Assistant integration was the obvious alternative, and the worse one. It would have meant a Python component and a review cycle, and underneath all that it would still have needed a transport. Discovery is the supported way for an outside daemon to contribute entities. The daemon writes retained JSON; Home Assistant builds the entities from it.

Each exported device arrives as one HA device. It carries a connectivity sensor plus IP and latency as diagnostics, with vendor, model, device type, operating system, first and last seen, and the open ports as attributes. Home Assistant already knows how to tell whether a phone is home, so presence alone would have been a thin reason to build any of this. The attributes are what it cannot derive on its own. So is the event entity: the server fires new_device, device_online, device_offline and new_port, each with MAC, IP, name and vendor attached. An automation can finally react to an unrecognized device joining the network. No amount of device tracking will tell you that.

Not every host gets exported. Favorites and infrastructure do, meaning the gateway, routers, switches, access points, firewalls, NAS boxes, servers and printers. Everything else waits for an explicit opt-in, device by device. A /24 with 150 hosts would otherwise produce several hundred entities on first connect, most of them the short-lived privacy MACs that modern phones rotate through. That is noise. An integration which floods someone's entity list gets uninstalled the same evening.

The bookkeeping matters more than it sounds. Discovery payloads and states are retained, so Home Assistant repopulates after a restart instead of showing nothing until the next scan, and they are rewritten only when something actually changed. A device that leaves the export has its retained config cleared, which is how Home Assistant deletes an entity rather than leaving it behind as permanently unavailable. The collector registers a last will, so a crash greys its entities out instead of freezing them on a stale "home". A broker outage is logged and otherwise ignored. Scanning and every other alert channel carry on.

Testing this against a real Home Assistant, rather than only against a broker, was worth the trouble. Two defects showed up nowhere else. A NAS with two network cards produced two devices of the same name, and Home Assistant quietly appended a _2, which tells nobody which card it is; colliding names now carry the last four hex digits of their MAC. The second one was subtler. We were setting object_id on every entity, and it turns out to be ignored the moment the payload contains a named device block, because Home Assistant builds the entity ID from the device and entity names instead. Sending it anyway advertised a control we did not have, so it is gone.

The setup, the topic layout and a couple of automation examples are in the Home Assistant guide.

DeviceShelf 1.6.20 — credentials that stay put Stored API tokens could be deleted by a setting that had nothing to do with them. The earlier fix guarded the settings form, but a dozen code paths never go through the form.

Some users saw their API tokens empty after an update, retyped them, and saw it happen again later. A fix shipped for that once, and it was not enough.

Two things went wrong together. The secure store is read once at startup, and a read can fail: a keychain that has not yet granted the freshly signed binary access, or a file another instance is mid-write on. The read returned "nothing here", which is indistinguishable from "nothing was ever stored", so the settings form showed empty fields and the credentials looked gone.

They were not. But the empty values were now in memory, and that is where the second problem started. The settings form drops secret fields the user did not touch, which is what the earlier fix added. Only a dozen places in the code never touch the form at all. They read the settings, change one flag, and write everything back:

cfg := store.Settings() cfg.SyslogEnabled = true store.SetSettings(cfg)

Every secret in that struct was the empty string, and saving wrote those blanks through to the store. Switching on syslog deleted the API keys. So did accepting the AI consent. The credentials the user retyped were then genuinely gone the next time, which is why the problem kept coming back after being fixed.

The guard now sits where the writing happens rather than in the form. A non-empty value always writes. An empty value only writes when that secret's value could actually be read at startup, which is what a deliberate "clear this field" looks like. When the read failed, clearing is refused, because there is no way to tell an intentional deletion from a value we simply never saw.

Underneath that, the two cases are no longer the same case: a read failure is now reported as a failure instead of being flattened into "not found". The settings screen uses that. A field whose value could not be read is marked read-only and says "could not be read — still stored", with a note asking you not to retype it. Restarting the app is the fix, and now the app is the one saying so.

One regression from the compatibility work in 1.6.19 is also fixed: the remote server list waited for the capability check of every server before drawing anything, so a server that was unreachable, during its own update for instance, left the list blank for several seconds.

DeviceShelf 1.6.19 — the alert dialog, and knowing what a server can do The alert settings of a paired server never opened. Fixing that surfaced a quieter problem: the app assumed every server could do everything, so a missing feature looked like a working one.

Opening the alert settings of a paired server always failed with "Server alerts could not be loaded", no matter how healthy the server was. The cause was in the bridge between the Go backend and the interface: a bound method may return a value and an error, and Wails forwards the second value only when it actually is an error. Ours was a status code, a plain string, so it was dropped. The interface expected a pair, received a single object, and the resulting exception surfaced as that message. The call now returns one value carrying both, and a test refuses any bound method with the old shape.

The fix exposed something quieter. A server that predates a setting does not report it as missing. It simply leaves the field out, and the client reads its default. The master mute switch was the visible case: against a server that cannot mute, the app showed an unchecked box. Nothing failed. The switch just did nothing, and looked exactly like a switch that was off.

So clients and servers now establish what they can do before they try it. A server answers a handshake with its version and the list of features it understands, and the app gates on that list rather than guessing from a version number. A feature the server did not declare is hidden instead of offered, so the mute switch appears only where it applies. A server too old to work with is named as such, together with the version required, rather than failing one call at a time. Servers that predate the handshake are read from their version, using bounds taken from the release tags that actually contain each feature.

The handshake needs no token, like the version endpoint it sits next to, because compatibility has to be checkable before pairing. It reports only what the build understands, never how the installation is configured. That stays behind the token.

Two smaller things. The settings button in a server row used a gear emoji, which rendered as a tiny grey mark on a light panel; it is a proper icon now. And the update check misread a release candidate: a version component with a trailing suffix parsed as zero, so 1.6.19-rc1 compared as 1.6.0 and an older release could have been presented as newer. The same parser bug sat in three places and is now one function.

On phones, pulling the device list down runs a new scan. Pulling while a scan is already running joins it instead of cancelling it, and the indicator keeps turning until the sweep is actually done.

DeviceShelf 1.6.18 — keyboard, screen reader, and one set of colours The device list works without a mouse and reads aloud properly, the server dashboard gained a light theme, and all three apps now share the same tokens.

The device list was mouse-only. On the desktop a row was a plain div with a click handler, the favourite star a span, the sortable headers neither focusable nor announced. With a keyboard you could reach the sidebar, the filter, and nothing else. The server dashboard had the same gap in table form.

Both are fixed. The device name is a real button carrying a spoken summary, the star reports whether it is set, headers sort with Enter or Space, and the status dot has text behind the colour. On the server, a focused row now survives a live update instead of dropping you back to the top of the page.

The Flutter app had no semantic annotations at all. VoiceOver and TalkBack read a row as loose fragments in layout order (title, then each badge, then every port chip) while the status dot, the gateway badge and the star said nothing whatsoever. A row is now one sentence: name, gateway or self, address, vendor, port count, plus the warnings that used to be only an icon. Tag chips stay individually reachable, because tapping one filters the list. The fade-in when a device appears is skipped when the system asks for less motion.

The faintest text colour failed WCAG AA contrast in both themes: 2.24:1 in light and 3.71:1 in dark, against the 4.5:1 required, on text carrying port counts and timestamps. Raising it on its own would have collapsed it onto the next step up, so the whole ramp moved together and kept its hierarchy.

The server dashboard was dark-only. It now follows the operating system, with an Auto/Light/Dark picker beside the language selector. Anyone on a light desktop will notice the change on the next load.

Underneath, the three surfaces had drifted apart. The server kept its own stylesheet with different names for the same colours, and the Flutter app reused the light brand palette in dark mode, where the web apps brighten every hue. They share one documented token set now.

Two bugs turned up while working on this. On the server, the ports column had no minimum width, so a device with five open ports produced a five-line row with its name broken mid-word. Anyone running the port scan could see it. And the mobile list re-sorted itself on every rebuild, because Dart's sort is not stable and rows with equal keys had nothing to break the tie.

The device row on mobile also moved: the address and response time sit on the right edge, the maker on the line under the name.

DeviceShelf 1.6.17 — a mute switch, and errors that say what happened One switch silences every alert a server sends, and connection failures finally name their cause.

The per-event toggles added in 1.6.15 only covered five device events. Certificate expiry, domain expiry, WAN reachability, host resources and your own checks had no switch at all, so "make the server stop notifying me" was not something the apps could do. There is now a master switch that mutes everything the server sends. Detection and logging carry on; delivery stops.

Connection failures used to be unhelpful in a specific way. A rejected token was reported as "server unreachable, offline, on another network, or blocked by a firewall". That sent people looking at their network instead of their token. Each case now names itself: the token was refused, no DeviceShelf answers at that address, the server locked you out after repeated failures, it timed out, or it really is offline.

Behind that sat something worse. When the first request to a server failed, the client retried the same request on neighbouring ports of the same host and, on success, saved that address instead. The retry was meant for a server that had moved to a different port, but it also ran when the token was rejected. On a machine running two servers (the installed one and a hand-started build, which is a normal thing to have) you could end up looking at the other server's inventory while your token was quietly wrong. The retry now runs only when nothing answered.

On macOS the installer had three rough edges. It did not wait for the old service to stop, so the new one could race it for the port. The access sheet on your desktop hard-coded port 8088 rather than reading the port the service actually uses. And it was written in German regardless of your language. All three are fixed, and if something was already listening on the port before the install, the sheet says so.

The server list also shows each server's version now. Two servers on one machine were previously indistinguishable, which is how the ports got confusing in the first place.

Smaller: the Wi-Fi permission button on macOS did nothing, because CoreLocation does not present its prompt to a location manager that has no delegate. On mobile, a 404 from any endpoint claimed the server did not support alert settings, even when the real answer was that no DeviceShelf was at that address.

DeviceShelf 1.6.16 — alert editor repairs and MCP hardening Fixes for the remote alert editor that shipped in 1.6.15, plus a security pass over the MCP endpoint.

The remote alert editor from 1.6.15 had four defects. Saving worked exactly once per app start, because the desktop dialog left its Save button disabled after a successful write. When someone changed the policy elsewhere, the server's revision check answered 409 and the dialog kept resending the same stale revision, so no further save could ever succeed. An emptied number field stored 0 rather than leaving the value alone, which turned "offline after 0 minutes" into an alarm on the first missed scan. And the desktop dialog shipped without translations, so everyone saw German labels.

All four are fixed. Mobile and desktop now name a revision conflict for what it is, and the desktop reloads the policy so the next save goes through.

The MCP endpoint got a security pass. Device-reported text reaches an AI assistant through several tools, and the infrastructure tools were handing it over unfiltered — SNMP sysName in particular, which anything on the network can set. Those strings now go through the same filter as hostnames and banners. Monitor checks created over MCP must point at a private, loopback or link-local address, the restriction the scan tools already carried. An assistant that renames a device or edits a check no longer discards the fields it was never asked to change, and a heartbeat monitor keeps its push token.

Browser-based MCP clients could not connect at all: the CORS preflight carries no authorization header, so it was rejected before it reached the endpoint. A trailing slash in the URL used to return dashboard HTML with status 200, which clients reported as invalid JSON. Both are fixed, and the lockout after repeated bad tokens now sends Retry-After instead of leaving you guessing whether the corrected token is wrong too.

Config exports no longer contain notification targets.

DeviceShelf 1.6.15 — remote alert settings in desktop and mobile Configure the most important alert policies of a connected server from desktop and mobile.

DeviceShelf 1.6.15 makes the most important alert policies of a connected server available from desktop and mobile.

The new alert settings let users enable or disable messages for new devices, online/offline changes, new ports and mobile presence. Offline thresholds, port confirmations, digest alerts, email cooldowns, quiet hours and the timezone can also be adjusted.

Destinations, credentials and existing notification channels remain on the server. The apps receive only channel status, never SMTP, webhook or Apprise secrets. Revision checks prevent parallel changes from overwriting one another.

DeviceShelf 1.6.14 — clearer remote views and server onboarding Choose between a server-only view and a safe combined view, fix remote error handling, and get the macOS server URL and access token after installation.

DeviceShelf 1.6.14 makes remote servers easier to use across desktop, mobile and the self-hosted server edition.

Remote servers now have an explicit view choice. Use Server devices only when you want to inspect the remote inventory without mixing it with the local scan. Use Server + local devices when you need one combined view. The combined view is read-only and does not overwrite local data.

Remote error handling is more useful. A text error response from an older or unavailable endpoint is no longer reported as a confusing JSON parsing error. The client keeps optional remote sections available when one endpoint is missing.

The macOS server package explains first access. After installation, it opens an access sheet with the local dashboard URL, the LAN URL and the persistent token. Updates keep the server state and token intact.

The release also includes the current server monitoring and cross-app views described in the server guide.

DeviceShelf 1.6.11 — reliable server discovery and saved scan scope Server discovery now survives stale or non-default mDNS ports, saved remote URLs are repaired automatically, and startup scans honor the selected interface and range.

DeviceShelf 1.6.11 fixes two practical problems that showed up in real networks.

Remote server discovery no longer trusts stale ports blindly. A DeviceShelf server can run on a port other than the old default. The desktop app now reads the announced mDNS/Bonjour port, verifies it against the server API before showing it, and falls back through known working ports if the announcement is stale.

Saved remote server URLs repair themselves. If a saved server address points to the right host but the wrong port, DeviceShelf now probes the same host on the known DeviceShelf ports. When one answers correctly, the saved URL is updated instead of leaving you with a dead manual configuration.

Startup scans honor the selected interface and range. If you saved a specific scan interface or range, the first scan after app start now uses that scope immediately. It no longer falls back to scanning every interface first.

Update: use the desktop updater, download the new installer, or update the server if you rely on LAN discovery.

DeviceShelf 1.6.10 — server availability history in every client Desktop and mobile clients now show the availability history collected by a DeviceShelf server, device names are cleaner, and servers announce themselves on the LAN.

DeviceShelf 1.6.10 is about making a connected DeviceShelf server visible and useful from the clients.

Server availability history in the desktop and mobile apps. When you connect to a DeviceShelf server, each device can now show the monitoring history the server has collected: an uptime bar, response-time chart and outage list. Gaps where the monitor itself was off are marked separately, so downtime is not counted as uptime.

Cleaner names and fewer duplicates. Device names are less cluttered now. Real hostnames are preferred over IP and MAC fallbacks, router suffixes like .local and .localdomain are stripped where they do not add meaning, and the merge logic avoids duplicate rows for the same device more reliably.

LAN server discovery. DeviceShelf servers announce themselves on the local network, so the desktop and mobile apps can find them without typing the address by hand.

Update: use the desktop updater, the new mobile build, or update the server with docker compose pull && docker compose up -d, the .deb, the Windows zip, or the GHCR image.

DeviceShelf 1.6.9 — IP assignment type, steadier AI identification Every device shows how it got its IP (static, DHCP or reserved) straight from the router, AI identification holds up across providers and follows your app language, and a settings save no longer risks your stored secrets.

A smaller release: one new column, a batch of reliability work on the AI side, and a data-safety fix.

How each device got its IP. Devices now carry an assignment type, read from the router's lease table: static, DHCP, or a DHCP reservation. It answers a question the scan alone can't: is that address pinned, or could it move on the next lease? Desktop, server and mobile all show it.

Steadier AI identification. The identification panel used to occasionally leak raw JSON when a model wrapped its reply in prose, added a code fence, or ran out of room mid-answer. It doesn't anymore: the reply is parsed the same way for every provider, and a truncated one still yields a clean result. The text now follows your app language instead of the model's default. On a confident match, a device's still-empty name, tags, type and comment are filled in for you, only the empty ones, never over an edit you made. Results cached by older versions that showed a broken "(— · low)" line now render properly. And name resolution for AI calls falls back across several public resolvers, so a single blocked or hijacked one no longer breaks the request.

A settings save could drop stored secrets. After a failed secure-store read, saving settings could overwrite a stored secret (an API key, for instance) with an empty value. Fixed: a failed read no longer lets an empty value replace what was there.

Update: docker compose pull && docker compose up -d, the new .deb/.rpm, the Windows zip, or the desktop update. Details in the server guide.

DeviceShelf 1.6.7 — Fix for the app freezing over time Two resource leaks that made the app go unresponsive and load the machine after running a while, especially with live monitoring, are fixed.

If you left DeviceShelf running for a few hours, it could go sluggish and start loading the machine, most noticeably with live monitoring on. 1.6.7 fixes the two causes.

The active ARP scan leaked a packet-capture handle and a goroutine on every sweep. In live mode that's a sweep every 5 seconds, so they piled up until macOS ran out of capture descriptors. The scan now uses a bounded read that always releases the handle, so nothing accumulates.

Presence recording also rewrote the whole store to disk (two fsyncs) once per device on every scan tick. That is an I/O storm that grows with your device count and with the store file itself, and it saturated the disk over time. Those writes are batched now, so a busy network no longer thrashes it.

Update: docker compose pull && docker compose up -d, new .deb, Windows zip, or the desktop update.

DeviceShelf 1.6.6 — Router brands, sharper identification, OpenAI-compatible AI Router integration now covers 17 brands, device identification leans on self-declared signals, the AI assistant works with any OpenAI-compatible endpoint, and settings survive a reinstall.

Four larger changes this time, plus the usual fixes.

Router host tables from any brand. When we added router integration it only spoke UniFi. As of 1.6.6, desktop and server read the client list from 17 router and controller families: FRITZ!Box and generic TR-064, UniFi, Netgear, Linksys, ASUS, TP-Link, D-Link, MikroTik, FortiGate, Omada, OPNsense, pfSense, Peplink, OpenWrt, Synology SRM, plus a generic SNMP fallback. The router already knows every device from its own DHCP/ARP table, so this surfaces hosts a local scan can't reach (other VLANs, sleeping devices) and fills in the names the router assigned. Settings has a brand picker (automatic detection, or pin a brand), and a Test button reports which one answered and how many devices it found. The mobile app gets the same picker next. UniFi is the brand we've verified against real hardware; the rest are built against each vendor's documented API and fail closed, so a mismatch returns nothing and never disturbs a scan.

Sharper device identification. Classification now prefers what a device says about itself (its mDNS, UPnP and SNMP signals) over guessing from a name. A local fingerprint database (Recog) matches offline, and a batch of misreads is gone: a Mac advertising AirPlay is a computer, not a speaker; docks, NAS boxes and mesh nodes get the right label; an unknown device at least shows its manufacturer. Vendor names now also resolve for IEEE's smaller MA-M and MA-S address blocks.

An OpenAI-compatible AI provider. The AI assistant can point at any OpenAI-style endpoint: a local vLLM or LM Studio, or a private gateway. Local endpoints skip the cloud-consent prompt, since nothing leaves your network.

Settings survive a reinstall. Your configuration is mirrored out-of-band, so wiping the config folder or reinstalling no longer starts you from scratch.

Also in this release: the server's email-alert section got a Test button with an inline SMTP result, the settings save bar confirms "Saved ✓" reliably again, and a note spells out that one scan run's alerts arrive as a single bundled email. The scan dialog remembers your interface selection without a scan first, and the language catalogs are complete again.

Fingerbank note for existing users: automatic lookups are now opt-in. If you already use Fingerbank with the passive DHCP listener, tick the new automatic-lookup box once. Until you do, DeviceShelf no longer sends fingerprints on its own.

Update: docker compose pull && docker compose up -d, new .deb, Windows zip, or the desktop update. Details in the server guide.

DeviceShelf 1.6.5 — Diagnostics you can actually read The diagnostics tools and device probes stopped dumping raw JSON and now show plain readable results.

A small papercut, gone. The Network diagnostics (ping, traceroute, DNS, speed test) and the device-probe drawer used to print the raw API response as JSON. Now they read like results: the speed test shows ↓ 18.1 Mbit/s · ↑ 71.6 Mbit/s · latency 179 ms · jitter 88 ms, ping shows replies, loss and min/avg/max, traceroute lists numbered hops, and the SSH-key, SMB, traffic, HTTP and credential probes each get a readable line. Anything the UI doesn't recognize still falls back to formatted JSON, so nothing ever renders blank.

Update: docker compose pull && docker compose up -d, new .deb, Windows zip, or the desktop update.

DeviceShelf 1.6.4 — One device, one SLA row A device could appear several times in the SLA report with misleading uptime numbers. Fixed, and existing data merges itself on the first start after the update.

If your SLA report showed the same device three or four times — one row near 100%, the others near 0% and dragging the overall number into the basement — this one is for you.

The cause was subtle: our discovery paths wrote the same MAC address in different formats. ARP and SNMP produced lower-case, DHCP fingerprinting upper-case, and each spelling became its own device identity with its own availability ledger. The phantom identities then collected an offline sample on every scan they weren't "seen" by, which is where the near-0% rows came from.

As of 1.6.4 every path writes one canonical format, the store normalizes anything that reaches it, and a one-time migration merges the fragmented history you already have: availability ledgers are summed day by day, names and metadata are kept. No manual cleanup needed; the report is correct again after the first start.

Update: docker compose pull && docker compose up -d, new .deb, Windows zip, or the desktop update. Details in the server guide.

DeviceShelf 1.6.3 — Dashboard rework, metric history and a disk-full forecast The server dashboard drops from ten tabs to six, settings get grouped sections with direct links, infrastructure metrics keep a week of history, and disks warn before they run full.

We put the server dashboard through a proper usability review and rebuilt what it flagged.

Navigation. Six tabs instead of ten: Topology, Diagnostics and Bandwidth now live inside one Network view, Syslog joined Infrastructure, and the AI assistant became a drawer you can open from any view instead of a mostly empty page of its own.

Settings. One long scroll became five grouped sections (General, Scanning & discovery, Alerts & notifications, Checks & monitors, Status page) with a sticky section nav. Every section is directly linkable, and the empty states across the app now use those links: instead of "Docker monitoring is off, the socket is not mounted, or there are no containers" the page tells you the one cause that actually applies and where to fix it.

Fewer traps. Threshold fields show a live interpretation ("→ alert off", "→ default: 90%", "→ inherits the host threshold") while you type. A clean security report shows a check mark instead of a green "0 /100". The bandwidth view no longer offers a Start button in builds without packet capture. And the phone layout works now; before, tapping a right-hand tab could pan the whole page into the void.

New since yesterday's 1.6.0. Infrastructure metrics (host CPU/RAM/disk/temperature, Proxmox nodes) keep about seven days of history, drawn as sparklines with a 24h/7d switch. On top of the disk history sits a disk-full forecast: a trend line that warns when a volume is projected to be full within your horizon (default 14 days, individually switchable like every other alert). It stays quiet on flat or jittery data.

Also in this release: a hard timeout on macOS Keychain calls so a blocked authorization prompt can never stall the server.

Update: docker compose pull && docker compose up -d, new .deb, or re-run the Windows installer. Details in the server guide.

DeviceShelf 1.6.2 — Windows installer picks the Npcap build by itself Run install.exe with Npcap present and the server comes up with live bandwidth and active ARP, no manual service surgery.

Third and last cut of the day. 1.6.1 added the optional deviceshelf-server-pcap.exe to the Windows zip, but you still had to swap the service binary yourself — nobody should have to. Now install.exe checks whether Npcap is installed and, if so, installs the packet-capture build automatically. Re-running the installer over an existing setup is enough; it doubles as the updater.

No Npcap? The installer keeps the standard build, which runs everywhere. The bandwidth card in the dashboard now spells out the Windows path too.

Update: re-download the Windows zip and run install.exe again. Docker and .deb are unaffected.

DeviceShelf 1.6.1 — Server version fix for Windows and .deb The Windows and .deb server builds reported no version and never offered updates. Fixed, and the Windows zip gains an optional Npcap build with live bandwidth.

Quick patch on top of today's 1.6.0. The Windows and .deb server builds shipped without a stamped version number, so the dashboard header showed none and the update check never considered anything newer. Docker was not affected. Both builds now carry their version again.

The Windows zip also gains a second binary: deviceshelf-server-pcap.exe. If you have Npcap installed, run that one instead of the default and you get live per-device bandwidth and active ARP scanning on Windows too. Without Npcap it won't start; the default deviceshelf-server.exe keeps working everywhere.

Update: re-download the Windows zip or install the new .deb; Docker users can skip this one.

DeviceShelf 1.6.0 — Containers, hypervisors, databases and NAS health The server now watches Docker containers, Proxmox nodes and guests, database services, NAS disk/RAID health and hardware temperatures. Every alert is switchable on its own.

The server edition gets a new Infrastructure tab and watches a lot more than devices now.

  • Docker containers. Mount the socket read-only (or set DEVICESHELF_DOCKER) and you get alerts when a running container goes down, gets stuck restarting, or its healthcheck fails. A container that was already stopped when the server started never alerts.
  • Proxmox VE. Nodes and guests, via a read-only API token (PVEAuditor role). Alerts when a node goes offline, a running VM or LXC stops, or node CPU/RAM load climbs too high.
  • NAS health. On Synology, QNAP and TrueNAS the SNMP sweep now reads the vendor's disk, RAID and pool status. A volume can be half empty and still dying; this catches the dying part.
  • Temperature and fans. Hardware sensors over SNMP (ENTITY-SENSOR and LM-SENSORS), with a temperature alert at a configurable limit.

Checks got four new types: MySQL/MariaDB, PostgreSQL, SQL Server and Redis. Each one speaks the database's own wire protocol, so an open port is not enough to pass; only a real database answer counts. And since the probe needs no credentials, none are stored. The desktop can author these checks too.

Thresholds are now switchable one by one: type 0 to turn a single alert off, leave the field empty for the default. Proxmox node limits can inherit the host limits or differ from them.

For Grafana users, /metrics gained gauges for all of it, including deviceshelf_docker_api_up and deviceshelf_pve_api_up so you can tell a live reading from a stale one during an API outage.

Update: docker compose pull && docker compose up -d, or apt install ./deviceshelf-server_1.6.0_*.deb. Details in the server guide.

DeviceShelf 1.5.15 — New icon, and a real Windows server installer A refreshed icon across platforms, a status-flicker fix, persisted scan settings, and a proper install.exe for the server.

New app icon across macOS, Windows and Linux. We also fixed a status flicker where a device would bounce between "Active" and idle in the middle of a scan, and your scan depth and the UDP-service toggle now survive a restart.

On the server side, the Windows edition ships a real install.exe and uninstall.exe instead of the batch files Windows liked to block — double-click, confirm the prompt, done. And you can read the dashboard token on any platform, Windows, Docker or the .deb, with deviceshelf-server -token.

DeviceShelf 1.5.14 — English UI and router-based topology English is now the primary interface language, and topology can come straight from your UniFi or router controller.

Two bigger changes. The interface is now English-first across desktop, server and mobile — the other languages are still there, English is just the source they're built from now.

And topology no longer leans on SNMP or LLDP guesswork. Point DeviceShelf at your UniFi or router controller and it reads which device sits on which access point or switch port directly from the controller. A Test button confirms the connection before you rely on it.

DeviceShelf 1.5.13 — Fewer, better alerts on the server Alert-flood control for the server edition, more parity with the desktop, and finer scan preferences.

The 24/7 server edition can flood you when a lot changes at once. This release adds the brakes: low-priority events batch into a digest instead of arriving one by one, alerts route by severity so the urgent ones stand out, and a brief flap no longer pages you offline-then-online a second later.

Alongside that, more of the desktop's features made it into the server, and the desktop's scan settings gained finer control over how deep a scan goes.

DeviceShelf 1.5.12 — Pair your phone by QR Connect the mobile app to a desktop or server by scanning a code — no URL, no token to type.

Adding a computer or server to the mobile app used to mean copying its address and access token onto your phone by hand. Now the desktop and server show a QR code: scan it in the app and you're connected.

Same security: the token still authenticates the connection, you just don't type it in.

DeviceShelf 1.5.11 — Settings that stick Settings now save as you type, and the Fingerbank key gets a Test button.

A round of persistence fixes. If you typed an API key or changed a field and then switched tabs before it lost focus, the change could be dropped. Settings now save as you type, so nothing gets lost on the way out.

While we were in there, the Fingerbank key got a Test button — check it works right where you paste it, instead of finding out later that device fingerprinting was quietly off.

DeviceShelf 1.5.10 — Azure OpenAI as an AI provider Azure OpenAI (Microsoft) is now a selectable AI provider, in desktop, server and mobile.

The AI advisor has always been bring-your-own-key: local, optional, off by default, with Anthropic, OpenAI, OpenRouter, Mistral, Groq, Gemini, or local Ollama. New on the list: Azure OpenAI (Microsoft). Enter your Azure resource endpoint, the deployment name, and your Azure key. It's in desktop, server, and mobile.

One caveat: this is Azure OpenAI, not the M365 Copilot license. The Copilot surface in Word or Teams isn't a callable API; what you can wire in is your company's Azure OpenAI access.

DeviceShelf 1.5.9 — real device names in alerts, a clearer uptime report, and unified server management A batch of fixes from user reports across all three apps: notifications and Live Bandwidth show real device names instead of bare IPs/MACs, traceroute is fixed on networks that blocked it, the server's uptime report gets headers and a downtime duration, and the mobile app's server list is unified into one screen.

A larger batch this time, all from user reports. Desktop, server and mobile all get real changes.

Desktop

  • Online/offline alerts show the real device name. A device coming back online or dropping offline could show its bare IP ("Back online: 192.168.1.197") or even its raw MAC address in the notification, which isn't something you can act on. The name lookup now falls back through the same chain the device list already uses: known name, then the manufacturer looked up from the MAC (available as soon as the device is seen at all), and only as a last resort the address itself.
  • Live Bandwidth stopped flickering. Near-idle devices' throughput is mostly measurement noise, so the list re-sorted itself on almost every update. A bar would flash on for one tick and vanish the next. Rows now sort by cumulative traffic instead of the instantaneous rate, and each device's numbers are smoothed before they're drawn. Devices with no resolved hostname also show their manufacturer now, not a bare IP repeated twice.
  • Traceroute works on networks that silently blocked it. The classic UDP-based probing mode is dropped outright by many firewalls, VPNs and NATs — every hop timed out with 100% loss even on a perfectly fine connection. Traceroute now uses the same ICMP echo probes as a plain ping, which get through where the old mode didn't.
  • Ping, DNS lookup and traceroute now come pre-filled with a working target (1.1.1.1 / google.com), so you can check basic connectivity with one click.
  • The DHCP-fingerprinting hint no longer contradicts itself. The "needs admin rights" warning used to stay visible even after the feature was successfully turned on and running.
  • Two settings-only bugs are fixed too: a pre-existing install's port scanning could silently stay off after an upgrade, with no visible cause. And identity resolution (hostname/vendor) could wipe a device's already-known open ports, and with them the quick-connect SSH/VNC/RDP buttons, whenever that pass didn't also happen to run a fresh port scan.

Server

  • The SLA/uptime report finally explains itself. The devices/checks tables had no column headers at all: three columns of bare numbers, nothing labeling what they meant. There's now a proper header row with hover explanations for each column.
  • The percentage now comes with a concrete duration. "94.6%" forces you to do the math yourself; each row now also shows an approximate downtime ("~1h 20m down") right next to the color-coded percentage.
  • Device rows show the manufacturer, not just the IP, when no hostname is known. It's the same fallback the desktop app already uses.
  • The daily-availability heatmap no longer collapses into one meaningless solid-color block when the report's time window is set to 24h; it always shows enough days to be readable.

Mobile

"Remote servers" (a saved list, read-only view) and "Sync" (LAN discovery, a single shared access code) used to overlap without talking to each other. Both are now one screen. Every DeviceShelf server or desktop you connect to is a saved entry with its own name, address and access code; LAN discovery adds new ones to that same list instead of running a separate flow. For each saved server you can open its own read-only view, or pull its full device list (MAC, manufacturer) into your local scan. This also fixes a real limitation: with two servers on the network, there used to be no way to give each one its own access code.

DeviceShelf 1.5.8 — stable device names and a Wake-on-LAN fix Two desktop fixes: device names no longer flicker back to bare IP addresses while background monitoring runs, and Wake-on-LAN works on machines with more than one active network interface. Desktop only.

Two fixes in the desktop app, both from user reports.

  • Device names stay put during auto-scan. With background monitoring on, the device list kept snapping back to bare IP addresses, especially in live mode with its sweeps every 5 seconds or so. Hostname, MAC, vendor, OS: all gone until enrichment caught up again. The cause: each sweep first checks only who is reachable and reports devices with no identity at all. That bare result overwrote the known one. As of 1.5.8, a resolved identity survives scan ticks and is only ever replaced by a newer resolved value, never by a blank. This holds in the device list as well as in exports and the remote API. Measurements like online state and ping still update on every tick. And if an IP gets reassigned to a different device, DeviceShelf drops the old identity instead of carrying it over.
  • Wake-on-LAN works with more than one network interface. The magic packet used to leave through a socket without broadcast permission. The system may reject that outright, and on machines with several active interfaces (Wi-Fi plus VM bridges, say) the packet could also go out the wrong one. It is now sent as a directed broadcast on every active interface, so the target device gets it no matter which network it sits on. When the send fails, the app shows a proper translated message instead of a raw network error.

The server edition gets 1.5.8 only to keep the version numbers in step; nothing changes on the server. Settings, data and monitors are preserved.

DeviceShelf 1.5.7 — device list and scan range cleanup Three fixes from user feedback: a hint when a filter hides every device; the top-bar scope now follows the range dialog; and an implausible From/To range asks before scanning. Desktop only.

A small follow-up to 1.5.6. All three came from user feedback.

  • A filter that hides everything now says so. The device list used to show a blank table when a search term or the favorites star filtered out every found device — which read as "the scan found nothing", even though the counters up top still reported all devices. Now there's a hint ("‘ei’ hides all 32 devices") with a button that clears the search and the favorites filter.
  • The scope display follows the dialog. The range shown top-left only reflected the last scan and changed only on the next one. Pick a different interface in the range dialog, or type a different From/To, and the header now moves with it immediately. Cancel, and it reverts to the real network.
  • A warning before an implausible range. A From/To spanning two networks (say 192.168.1.1 to 10.37.129.254), a reversed one, or a very large one used to silently trigger a slow scan across the wrong subnet — up to 65534 addresses. Now a dialog asks first; a normal in-subnet range still scans without a prompt.

The server edition gets 1.5.7 only to keep the version numbers in step; nothing changes on the server. Settings, data and monitors are preserved.

DeviceShelf 1.5.6 — clearer notifications Collapsed alert rows now list the individual events with timestamps instead of a confusing “flapped N×” counter; the device-list SSID column is gone. Driven by user feedback.

A small update on top of 1.5.5, driven by user feedback.

  • Clearer collapsed alerts. Multiple alerts for the same device within 10 minutes are still merged into one row — but the badge now reads “N alerts” instead of “flapped N×” (which read like the IP had changed), and the row lists every merged event with its time and detail: offline/back online, port opened/closed, IP changed. You can see exactly what happened without opening the details.
  • SSID column removed. The optional device-list column could only ever show the scanning host’s own Wi-Fi (identical in every row) — a LAN scan cannot determine which SSID a remote device is connected to. That came across as “the column shows nothing”. Your own Wi-Fi name remains in the top bar and the Network view.

The server edition gets 1.5.6 as a pure version-lockstep rebuild with no functional changes. Settings, data and monitors are preserved.

DeviceShelf 1.5.5 — richer MCP tool schemas, more hardening MCP tools now declare output schemas, titles and per-field descriptions for clearer AI-client results, plus follow-up security hardening from the 1.5.4 review. Additive, no behaviour changes.

A small additive follow-up to 1.5.4.

  • Richer MCP tool schemas. Every MCP tool now declares an output schema, a human-readable title and per-field descriptions. AI clients (Claude, Cursor, VS Code and the rest) can show clearer, structured results and pick the right tool without guessing. Purely additive — existing connections keep working unchanged.
  • More hardening. A follow-up pass closing review gaps from the 1.5.4 security sweep — API auth, notifications and the mobile export path.

No behaviour changes. Settings, data and monitors carry over.

DeviceShelf 1.5.4 — security & robustness hardening A 27-fix security and robustness pass — stricter API auth (deny-by-default, brute-force backoff, role-spoof guard, request timeouts), redacted notifications, and hardening across scanning, storage, syslog and mobile. No behaviour changes.

1.5.4 is a focused security and robustness pass — 27 fixes, no new features and no behaviour changes.

API & auth (server edition)

  • Deny-by-default. The token guard now uses an allowlist: only the static dashboard shell is public; /api/*, /metrics and /mcp all require a token. (The previous blocklist had to enumerate private paths and once nearly served /mcp unauthenticated.)
  • Brute-force backoff. After repeated failed auth attempts from one IP, further requests get a 429 for a short cool-off, so a bearer token can't be guessed online.
  • Role-spoof guard. The X-DS-Role header is stripped from incoming requests and only ever set by the guard, so a client can't claim admin to downstream handlers.
  • Request timeouts. Read/header/idle timeouts protect against slow-loris-style connection holding (the SSE dashboard feed is exempt).
  • Weak-token warning at startup if the API token is shorter than 24 characters.

Everything else

  • Notifications redact secrets from payloads, and the send path is more defensive.
  • Hardening across the scanner (ping, UPnP), store, syslog receiver and SNMP discovery, plus a safe-goroutine wrapper so a background panic can't take the server down.
  • Mobile: safer desktop-sync parsing and export.

Recommended update. Settings, data and monitors carry over untouched.

DeviceShelf 1.5.3 — the MCP server ships The server edition now ships the MCP server: read-only, strictly local access to your live inventory and monitoring for AI assistants over the Model Context Protocol. Copy-paste setup for Claude, Cursor, VS Code, Windsurf, Cline and Gemini CLI.

The Model Context Protocol server we previewed is now shipping in the server edition.

  • What it is. Set DEVICESHELF_MCP_ENABLE=true and an AI assistant can query your live network in plain language: "what's online?", "which certificates expire soon?", "what's vulnerable?", all answered from your own data. 14 read-only tools (inventory, monitoring, a one-call security overview) plus guided prompts.
  • Strictly local, read-only. The endpoint runs inside your server, behind the same bearer token, reachable only on your LAN. No cloud connector. Write actions (rename, acknowledge, scan) stay off unless you set DEVICESHELF_MCP_ALLOW_ACTIONS, and device-reported strings are treated as untrusted.
  • Connect any agent. Copy-paste setup for Claude Code, Claude Desktop, Cursor, VS Code (Copilot), Windsurf, Cline and Gemini CLI is on the server page. Most connect over Streamable HTTP natively; ChatGPT needs a public HTTPS tunnel because it runs in the cloud.
  • Licensing. Bundled with any paid license and usable during the 7-day trial.

The desktop app is unchanged from 1.5.2.

DeviceShelf 1.5.2 — server version badge, clearer e-mail setup A small server-focused update. The dashboard shows the running version, e-mail/SMTP setup is clearer, and desktop backend messages are now localized in all seven languages.

A small, backward-compatible follow-up to 1.5.1:

  • Server: the dashboard header now shows the running version (plus a public /api/version endpoint).
  • Server: e-mail/SMTP alert setup is clearer. Common providers (Gmail, Yahoo, Outlook, iCloud …) are auto-detected, and a new help section explains the required app password.
  • Desktop: all backend status and error messages are now localized across the seven supported languages.

Monitors, settings and data carry over unchanged.

DeviceShelf 1.5.1 — push/heartbeat monitors, HTTP keyword checks, read-only access, uptime bars 1.5.1 extends the server edition with push/heartbeat monitors, HTTP keyword and status assertions, per-check retries, a read-only access token, daily uptime bars and JSON config backup. Desktop adds custom device types with auto-detection and a new icon set. Backward-compatible.

1.5.1 builds on the 1.5 monitoring foundation, mostly on the headless server edition, and brings the desktop's device handling up a level.

Server edition

  • Push / heartbeat monitors. A new check type for jobs that should "call home": a cron job, backup or script POSTs to a private /api/push/<token> URL on its schedule. If a heartbeat doesn't arrive within the window you set, the monitor goes down and alerts. Good for things a port check can't see.
  • HTTP keyword and status assertions. HTTP checks can now require an exact status code (e.g. 200, or 401 for an auth-gated endpoint) and assert that the response body contains — or does not contain — a given string. A page that loads fast but shows an error no longer counts as up.
  • Retries before alarm. Each check can require N consecutive failures before it raises an alarm, so a single blip doesn't page you.
  • Read-only access token. Set a second, read-only token (DEVICESHELF_API_VIEWER_TOKEN) to hand out view-only access, either over the API (GET only) or as a read-only dashboard. Every change still needs the full token.
  • Daily availability bars. The reports view now shows a per-day uptime bar for every device and check, alongside the SLA percentages.
  • Configuration backup. Export your settings and monitors as a JSON file (no passwords or tokens) and restore them on the same or another server.
  • REST API documentation. The full server API is now documented, including the new endpoints.

Desktop

  • Custom device types with auto-detection for switches, access points and firewalls, a searchable type picker, a refreshed colorful icon set, and a per-row note tooltip on hover.

Fixes

  • Removed the colored left-edge accent on dashboard cards (cosmetic).
  • The server timeline now labels monitor-check events properly instead of showing a raw key.

Compatibility

Backward-compatible; no breaking changes. Existing configuration and data are kept. The new check options and the read-only token are opt-in, so updating raises no new notifications by itself.

DeviceShelf 1.5 — threshold monitoring, dependencies, alarm lifecycle, server connection 1.5 adds threshold-based checks (ping, TCP, HTTP/JSON, SNMP), dependency-based alarm suppression, an alarm lifecycle (acknowledge, maintenance windows, quiet hours), and desktop-to-server connections. Backward-compatible.

DeviceShelf 1.5 adds monitoring beyond plain reachability. Changes:

New in 1.5

  • Threshold-based checks. Define checks per device with warning and error thresholds. Supported types: ping latency, TCP port reachability, HTTP/HTTPS status (optionally comparing a value extracted from a JSON response via a dot path), and SNMP values.
  • Dependencies. A check can depend on a parent device (e.g. a switch or gateway). When the parent is down, alarms for the dependent checks are suppressed, so an upstream outage produces one alarm instead of one per downstream device.
  • Alarm lifecycle.
  • Acknowledge: mark a known alarm as seen so it stops re-notifying.
  • Maintenance windows: scheduled time ranges in which checks do not raise alarms.
  • Quiet hours: recurring time ranges in which no push notifications are sent.
  • Desktop-to-server connection. The desktop app can connect to one or more headless DeviceShelf servers and read their inventory, subnet, online status, timeline and alarms, and acknowledge alarms remotely.

Compatibility

Backward-compatible; no breaking changes. Existing configuration and data are kept. Checks are opt-in (you create them), so updating raises no new notifications by itself.

DeviceShelf 1.4.2 — Fingerbank device ID, richer alerts & an update notice 1.4.2 adds Fingerbank-powered device identification, far more detailed alerts (IP, vendor, type, OS, ports), and an opt-in update notice, plus everything from 1.4.1 (offline devices, dashboard polish, desktop SSH).

A focused follow-up to the big 1.4.0 release: sharper identification, clearer alerts, and a friendlier update path.

✨ New in 1.4.2

  • Fingerbank device identification. Devices are now identified by their DHCP fingerprint via Fingerbank. That means better guesses for vendor, type and OS, especially on IoT gear.
  • Richer alerts. Alert messages now spell it all out: IP, vendor, device type, OS, open ports and the Fingerbank match. A notification tells you what happened without opening the app.
  • Opt-in update notice. A "check now" button (and an optional on-launch check) lets you know when a new version is out. It never auto-installs — you stay in control.

Also since 1.4.0 (shipped in 1.4.1)

  • Offline devices stay visible in the list instead of disappearing.
  • Dashboard polish: auto-save, a favicon, a clearer WAN explanation, and a live-status fix.
  • Desktop: SSH login and scan-range fixes.

🖥 Server edition (beta)

The 1.4.2 server adds the same passive DHCP fingerprinting (it listens on UDP port 67). Because the container deliberately runs as a non-root user, binding that low port needs one tightly-scoped capability: NET_BIND_SERVICE (no root, no NET_ADMIN, just opening low ports). Without it the server still runs fine, only DHCP/Fingerbank identification is skipped.

Update your compose with --cap-add NET_BIND_SERVICE and pull :1.4.2. Full steps in the server install guide. It's a beta — bug reports & feature requests very welcome.

Get it

Update from inside the app (or hit check for updates), or grab 1.4.2 from deviceshelf.app. As always: local-first, no telemetry, no account.

DeviceShelf 1.4.0 — live monitoring, notifications & a server beta A big release: a notification center, auto/live scan, a smarter device table, in-app rollback, a Windows Npcap prompt, and a pile of fixes. Plus the first beta of the headless 24/7 server edition.

1.4.0 is a big one. Here's everything that's new and fixed, plus the first public beta of the server edition.

✨ New

  • Notification center. A bell in the toolbar collects what changed while you were looking elsewhere: devices arriving and leaving, ports opening, alerts firing.
  • Auto / live scan. Turn it on and DeviceShelf watches your network continuously, refreshing every few seconds instead of waiting for a manual scan.
  • Smarter device table. Sort by any column, connect over RDP in one click, and use per-port gating so a scan does exactly what you want.
  • In-app rollback. Need the previous build? Roll back to an earlier version right from inside the app.
  • Windows Npcap prompt. The Windows installer now tells you when Npcap is needed (for live per-device bandwidth) and links you straight to it.
  • Local-API token UX. Setting up and using the local-API token is simpler and clearer.

🐛 Fixed

  • Fingerbank rate-limit handling. Device-identification lookups no longer trip the provider's rate limit.
  • Scan range / CIDR / save fixes. Custom ranges and CIDR inputs parse correctly, and saving is reliable.

🖥 The server edition enters beta

The headless 24/7 server edition ships for the first time in 1.4.0: continuous, GUI-less monitoring you run in Docker (or install the .deb, now built in CI) on a Linux box or VM. It's covered by your existing license. Same key, no extra cost.

This is a beta. Expect a few rough edges. If you hit a bug or have a feature request, please tell us — your feedback decides where it goes next. Subscribe below to follow along.

Get it

Update from inside the app, or grab 1.4.0 from deviceshelf.app. As always: local-first, no telemetry, no account.

Network tips and product updates

The occasional guide, new features and release news, straight to your inbox. No spam, unsubscribe anytime.