Releases: gitkodak/unifi-map
Release list
v0.11.0
Changed
- Future releases are AGPL-3.0-only. This protects against closed distributed forks and modified versions operated solely as network services: operators must offer corresponding source to users who interact with a modified version over a network.
- Earlier MIT and GPL-3.0-only releases remain under the licenses with which they were published. The bundled Panzoom library remains under its own MIT license.
In memoriam: Ian Murdock. You will always be remembered as long as I'm alive, my friend.
v0.10.2
Changed
- The GPL-3.0-only commitment is now explicit in the README, contribution terms, project metadata, artwork documentation, and project site.
- New distributed modified versions must remain GPLv3 source available. Panzoom's third-party MIT attribution and the historical MIT status of older releases remain accurately identified.
In memoriam: Ian Murdock. You will always be remembered as long as I'm alive, my friend.
v0.10.0
Added
-
--report, saying how much of the map to trust. A map drawn from a
complete fetch and one drawn from a thin one look equally authoritative. This
prints where every part of it came from: how many nodes and links came from
stat/device, fromstat/sta, from the controller's own topology graph, from
an overrides file, or from nowhere at all. Then, where something needs
attention, it names the devices rather than only counting them: clients that
could not be placed, clients with no address from any source, networks a
client claims to be on that the controller does not list, and artwork matches
refused as ambiguous.It also says what the snapshot carried and what each missing piece costs. An
optional endpoint that fails is logged once at fetch time and never mentioned
again, so a snapshot cached before an app was installed renders thinner every
time with nothing saying why.Underneath it, every node and link now records which endpoint or fallback
produced it, at the point that decision is made rather than reconstructed
afterwards. The tool has always refused to guess; until now that refusal was
invisible, because a client the controller reported directly and one recovered
from a second endpoint were drawn identically and with equal apparent
authority. The diagram itself gains the same distinction below.It is not safe to share, and says so at the top. It names your devices,
addresses and networks by design.unifi-map shaperemains the one built from
an allowlist for pasting into a bug report. A device is named only where
something is wrong with it, so a healthy map produces a report with no names
in it and a short report is a good sign.It describes the map as drawn, running after overrides and after
--obfuscate, so pairing it with--obfuscategives a report carrying the
same placeholders as the shared diagram. -
A client placed via the controller's own topology graph, rather than its
own reported uplink, is now marked on the diagram itself. That link gets a
small hollow-circle arrowhead at the child end, in every backend that draws
the network as a picture rather than as text or data (svg,pdf,png,
drawio, andhtml, which embeds the same marked SVG) — the one
distinction--reportsurfaced in text but the map still drew like any
other link. Every other provenance value turned out to already have a
channel: node role throughKind, an override through the existing dotted
style, offline through dashed. Composes with the wireless dashed style
rather than competing with it, and a legend row explains the marker when the
map uses one. -
An interactive HTML viewer,
-f html. A single self-contained file:
scroll or drag to pan, pinch or Ctrl+scroll to zoom, search to dim
everything that doesn't match, click a client to trace its path back to the
gateway, and click a switch or AP to collapse the clients hanging off it.
That last one is the actual point — a switch with thirty clients is
unreadable in every static format, which is the problem this exists to
solve.Pan and zoom is a vendored copy of Panzoom
(MIT, zero dependencies of its own), not hand-rolled and not pulled from a
CDN: a small permissively-licensed file checked into the repo is a
different kind of "vendoring" than the rule against committing Ubiquiti's
artwork, which is about somebody else's copyright rather than about
third-party code existing at all. Node and edge correlation between the SVG
and the topology is computed in Python and stamped onto the SVG asdata-*
attributes, so the JavaScript never has to reverse-engineer anything. The
topology payload is embedded base64-encoded rather than as a JSON literal,
because a label can come from a controller or a support file, both hostile
input by this project's own rule, and a literal</script>inside one
would end the block early no matter how the JSON around it was escaped. -
Controller responses are size-capped, like the CDN artwork always was.
fetchused to read each response whole with no ceiling, while every
download from Ubiquiti's CDN stops at a limit. That was backwards from how it
looked: the defended path is the untrusted CDN and the undefended one is the
controller, which is the endpoint people are told it is ordinary to reach
withUNIFI_VERIFY_TLS=false. Both now stream through the same capped-read
guard, which moved out ofassets.pyinto a shared module so the two cannot
drift apart again. A response past 64 MiB is refused with a message; nothing
a real network returns comes near that. -
A cached snapshot is now an atomic generation. Each payload used to be
written as its own file with stale ones deleted afterwards, so a fetch cut
off mid-write, or two fetches running at once, could leave a mixture of old
and new that a laterrenderread without complaint: a map built from two
moments, indistinguishable from one built from one. Each fetch now writes a
complete set into its own directory and switches a pointer to it as the last
step, so a reader only ever sees one complete generation. An interrupted
fetch leaves the previous snapshot intact and readable, and an old flat cache
keeps working and is migrated by the next fetch. -
pip installworks straight from GitHub, two ways, with no PyPI account.
pip install git+https://github.com/gitkodak/unifi-map.git@v0.10.0builds
from a tag at install time and needed no changes at all. From this version
on, a release's wheel and sdist are also attached to its GitHub Release, so
pip install <wheel-URL>installs with no build step and nogiteither.
Neither is a PyPI decision: no account, no owned name, nothing that cannot
be withdrawn. Seedocs/install-from-github.md.The man page now reaches a wheel install too. Shipped as installed data
atshare/man/man1/unifi-map.1, soman unifi-mapresolves with no
MANPATHconfiguration once the virtual environment is onPATH— both
macOS's and GNU man-db'smansearch a venv'sshare/manautomatically.
Previously the committedunifi-map.1only worked asman ./unifi-map.1
from a checkout.
Changed
-
A controller is only ever contacted over HTTPS.
UNIFI_HOSTcould
previously carry an explicithttp://prefix, which was honoured, so a
mistyped credential file sent the API key over the wire in clear with nothing
said about it. Anhttp://prefix is now upgraded to HTTPS rather than
obeyed, and a host with no scheme at all still gets HTTPS as before.No opt-out is offered, because the case people reach for plaintext to solve
is a bare IP address with a self-signed certificate, andUNIFI_VERIFY_TLS
already answers that without giving up encryption.
Fixed
-
The permission repair for pre-0.9.0 SVG caches no longer follows symlinks.
It restores private modes to rasters an earlier build left world-readable, and
did so throughPath.chmod(), which follows links: a symlink planted at
user-svg/or at one of its cached PNGs redirected the change onto whatever
it pointed at. Nothing was disclosed, since access was removed rather than
granted, but an unrelated path could lose group or world access, and in a
cache directory writable by somebody else that is a local denial-of-service
primitive.Now done through a descriptor opened
O_NOFOLLOW, so a link fails the open
outright and the mode is applied to the thing that was inspected rather than
to whatever the name resolves to a moment later.
v0.9.0
Added
-
An
svgextra, for your own SVG override artwork.pip install 'unifi-map[svg]'rasterises a supplied SVG to a cached PNG as it is read, so
it reaches every output format.Without it, Graphviz loads SVG artwork only for its own
svgdriver:png
andpdfgo through cairo, which has no SVG loader, so the icon is dropped
from both. It also insists on an XML declaration and reports a file that
plainly exists as missing when there is none. Rasterising sidesteps both, so
a file exported by a drawing tool works untouched.Optional on purpose, and converting the file to PNG yourself does the
same job with no dependency at all. Both routes are documented; the tool
warns and names the file when an SVG is about to go missing from a format.
Fetched artwork is unaffected: it is already PNG. -
A warning when an SVG override will not reach
pngorpdf, naming the
icons and the formats that will lack them. Graphviz's own message isNo loadimage plugin for "svg:cairo", which names neither the file nor a way
forward. -
A warning when
--theme darkis combined withdrawio. draw.io
re-themes on load and will render a dark-authored file light. See Known
limitations below. -
make build, producing a wheel and an sdist indist/. Installing the
wheel into a clean environment gives you a workingunifi-mapwithout a
checkout, which is useful for putting it on a machine that should not carry
the source.It needed almost no new machinery: the entry point and the build backend were
already inpyproject.toml. What was missing was a documented way to invoke
them, anddist/andbuild/in.gitignoreso the artifacts cannot be
swept into a commit.This is not a published package and does not promise one. There is no
pip install unifi-mapfrom PyPI, and whether there ever should be is
deliberately still an open question, recorded inTODO.md. Building an
artifact and publishing one are separate decisions, and only the second is a
commitment that cannot be withdrawn. -
UNIFI_CACHE_DIR,UNIFI_ASSET_CACHEandUNIFI_OUT_DIR, so the
directories can be set once instead of passed on every command. A flag still
wins over the variable, and the variable over the default. They can go in the
credential file as well as the environment, which is the natural place, since
that file is already the thing kept outside the project.The cache one is the point. A snapshot is a complete inventory of a network,
the default puts it in the working directory, and for anyone working on this
tool that directory is a git checkout. Acache.bakcopy made before a risky
fetch is not covered by a.gitignoreentry forcache/; one sat untracked
in this repository, onegit add -Afrom being published. The ignore rule was
widened in 0.8.0, and this removes the question rather than guarding it.
Removed
-
The
UDM_*environment variable names.UDM_HOST,UDM_API_KEY,
UDM_SITEandUDM_VERIFY_TLSare no longer read. They existed only because
that is what the author had called things before this tool did, and they have
warned since 0.7.0.If you still use them, rename them to the
UNIFI_*spellings. Nothing
subtle happens if you do not: the tool reports the missing variable by name
and exits, exactly as on a fresh install.UDM_USERandUDM_PASSwere
already dead, unread since password authentication was removed, and are worth
deleting from any credential file that still carries them.One deliberate asymmetry:
layout.pystill stripsUDM_API_KEYfrom the
environment Graphviz runs with. We stopped reading it, which does nothing
about somebody who still exports one, and an unread variable holding a real
key is exactly as worth withholding from a child process as a read one.
Changed
-
Graphviz's warnings are no longer discarded. Graphviz warns on stderr and
still exits 0, and that output was thrown away on every successful run, so
every warning it has ever emitted was invisible. That is how an icon could
vanish from a PNG in silence. Surfaced whole rather than filtered: deciding
which of its messages matter is how the last one stayed hidden. -
Artwork resolution and output writing moved out of
cli.py, into
artwork.pyandoutput.py. No behaviour changes. The reason is layering
rather than length: neither is a command-line concern, and the tell was a
rendering test having to import a private function fromunifi_map.clito
exercise the renderer.cli.pykeeps argument parsing, credential resolution,
logging setup and thecmd_*functions that sequence a run, and drops from
1367 lines to 1027.Only relevant to you if you import from
unifi_map.clidirectly, which is not
a supported interface and which nothing is known to do.
Fixed
-
A repeated
-fis refused instead of silently honouring the last one.
-ftakes several values, so-f svg -f pngoverwrote rather than appended
and wrote png only, with nothing said. That reads as a format that failed to
render. Pass them together:-f svg pdf png.Refused rather than made to append, because an error states what happened
whereas appending would quietly change what an existing invocation produces.
If you have a script using the repeated form, it will now stop with an
error naming the fix. -
draw.io connection lines no longer run through unrelated devices. The
edges carried only their two endpoints, so draw.io routed them with its own
router and drew a long run straight through whatever the layout had placed in
between. Graphviz had already computed a route and it was being discarded;
those waypoints are now written into the file. Safe to pass through unchanged
because both layouts useorthoorpolylinesplines, never a bezier, so the
reported points are corners rather than control points. -
draw.io node captions no longer land on the node below. The label was
positioned outside the cell, while Graphviz had sized that cell to hold the
artwork and the text. The box carried dead space and the caption fell onto
whatever was underneath, so on a dense column every icon wore its neighbour's
caption. The label now renders inside the box the layout was computed for. -
An SVG with only a
viewBoxis accepted. Explicitwidthandheight
were required, and most drawing tools export a viewBox instead. Graphviz
renders those perfectly well and preserves the ratio, checked by rendering
both throughdotrather than by reading a spec. Only the ratio is used
downstream. Explicit dimensions still win where a file has both. -
A refused icon says which rule it broke. "Could not read artwork at
<path>" on a file the reader can plainly open is a shrug rather than an
error, and SVG has requirements that are not guessable from the outside. -
The README implied you could run
unifi-mapbefore installing it. The
quick-look section ranmake demoand thenunifi-map all, as though the
first had prepared an environment for the second. It had not:make demo
calls the venv's copy by full path and puts nothing on yourPATH. -
Nothing anywhere told you to activate the virtual environment. Install
ended at.venv/bin/pip install -e ., which puts the command at
.venv/bin/unifi-mapand nowhere else, while the README and every page under
docs/invoked a bareunifi-map. Anyone following the instructions
literally gotcommand not foundon their first real command. Install now
ends withsource .venv/bin/activateand says why, which makes those examples
correct rather than editing each of them. -
allnow says what it means. It isfetchthenrender, both stages,
and reads to a newcomer as "all output formats". It writes the same default
two files anyrenderwould. The help text says so anddocs/usage.mdhas
the incantation for the other five. -
Documentation said Lucid's
.drawioimport had not been tried. It has, and
it does not work. Lucid reads one cell of the file and stops, a different
cell each time. Neither stripping the embedded artwork nor writing the payload
in draw.io's own compressed form changes it, so it is not a size or an
encoding problem.docs/output.mdnow says so and points at the workarounds,
which are to export from draw.io or to import thesvgorpdfoutput.
draw.io itself remains confirmed working. Nothing about the generated file is
being reshaped to suit a second tool's parser.
Known limitations
-
--theme darkdoes not survive into a.drawiofile. draw.io re-themes
a diagram on load, inverting it to contrast with its own appearance setting,
because its dark mode assumes diagrams are authored light. A file authored
dark is inverted a second time and displays light; a file authored light is
correct in both of draw.io's modes.Nothing is corrupted when this happens. The inversion is holistic, so cells,
text and artwork flip together and the file stays coherent; it simply reads as
the theme you did not ask for.For now the tool warns and renders what you asked for. Use
--theme light
for the.drawio, or set the appearance in draw.io explicitly rather than
leaving it on Automatic.docs/output.mdcovers both.When this is fixed, the behaviour will be:
- It will always warn when
drawiois among the requested formats and the
theme is dark, because the interaction is worth knowing about either way. - With other formats alongside, the
.drawiowill be authored light,
so it displays dark like everything else in the run. - With only
drawiorequested (--theme dark -f drawio), it will be
authored dark as asked, with a warning that draw.io may not display it
the way you expect and a pointer to the documentation. ...
- It will always warn when
v0.8.0
If you keep an overrides file, read this before upgrading. A file
containing wireless = "false", hide = "false", a fractional port, or a
misspelled key used to render; it now stops the run with an error naming the
problem. Those files were never doing what they said, which is why this changed,
but the failure is new and it is at the point of use. The fix in every case is
in the error message.
Changed
-
The documentation is split into
docs/, and links into the README have
moved. It had reached 1233 lines, which is past the point anybody reads,
and is now 220: what the tool is, what it produces, how to install it, and
how to see a map without touching your network. Everything else is a page of
its own, indexed from the README.If you linked to a README anchor, it has moved. Every
#sectionthat is
now a page is a different address:Was Now README.md#usage,#reading-the-diagram,#flag-referencedocs/usage.mdREADME.md#credentials,#unifi_api_keydocs/credentials.mdREADME.md#mapping-from-a-support-filedocs/support-files.mdREADME.md#json-for-programs,#mermaid-for-documentationdocs/output.mdREADME.md#sharing-a-map---obfuscatedocs/sharing.mdREADME.md#artwork-licensing-and-attributiondocs/artwork.mdREADME.md#how-it-works,#caveatsdocs/verification.mdREADME.md#manual-overridesdocs/overrides.md, which already existedPages are organised by why somebody opens them rather than by what they are
about, and each was then checked against what its own opening sentence
promises. That found the artwork page describing where pictures come from with
the answer two files away, the output page covering two formats of seven, and
three pages repeating their own title as their first section. All were the
same artefact: splitting on top-level headings moves text correctly and lands
it by accident.The README's
## Manual overridesand## Also plannedsections are gone
rather than moved, becausedocs/overrides.mdandTODO.mdalready held the
same material and the README was carrying second copies to drift against.The guards were widened before the split rather than after. Every link used to
be a same-file#anchor; most are nowdocs/artwork.md#something, which can
fail two ways a browser renders happily: the file may be missing, or present
without the heading. The link check now resolves cross-file targets, the flag
and command checks read every document rather than the README alone, and the
generated flag reference lives indocs/usage.md. It caught fourteen links
the split broke silently. -
The documentation says which UniFi applications this has been run against,
in the introduction rather than buried: Network for everything, and one
Protect endpoint read purely to tell a camera from an Access reader. Devices
from Access, Talk or a UNAS already draw, since they are clients or UniFi
hardware like anything else, so the gap is narrower than "unsupported": what
is missing is the second source that would let an ambiguous match resolve.
CONTRIBUTING.mdasks for those environments alongside the other things
nobody here has. -
Two released sections described one change twice. 0.7.0 had two entries for
unifi-map shape, written days apart; 0.6.0 had two forRELEASING.mdthat
contradicted each other, one saying a fix had been claimed and never made and
the other making that same claim. Both are merged. A test now fails when a
release describes the same subject twice, with genuinely separate changes to
one thing listed as exceptions rather than the rule loosened. -
The artwork page lists user-supplied artwork among its sources. An
iconin
an overrides file is where a picture comes from when none of Ubiquiti's
catalogues has one, and it is the only source that works under--offline. -
Both generated references were wrong in the same two ways, and neither
staleness check could have noticed.docs/usage.mdand the man page each
printed a synopsis reading{fetch,render,all}, two commands behind, and the
man page listed only those three underCOMMANDS. Neither said that
unifi-map overridesrequires acheckargument, because the introspection
walkedoption_stringsand a positional has none. All three lists are now
derived from the parser.The two existing checks regenerate the file and fail on a diff, which catches
an author who forgot to runmake docsand cannot catch a generator holding a
hardcoded list: it produces the same wrong file every time and compares equal
to itself forever. A separate test now asserts that both documents name every
subcommand and every positional. A document claiming it cannot drift from
--helpis worse than a hand-written one when it does, so the claim is tested
rather than trusted. -
overrides checknow validates against the same topologyrenderwill
build. It passedinclude_offline=Trueunconditionally, which is more
permissive than the default render, so a selector naming a device the
controller merely remembers passed the check and then failed the render it had
just been checked for. That is the one outcome the command exists to prevent.
--show-offlineis now shared between the two subcommands rather than
belonging torenderalone. -
An override that displaces a link the controller reported now says so.
[[link]]and[[hosted]]both detach a node from its current parent before
attaching the stated one, which is necessary and, for[[hosted]], the entire
point: reparenting a VM under its hypervisor displaces a real observation by
design. But the code assumed the displaced edge was always the "uplink not
reported" placeholder, and nothing enforced that, so contradicting the
controller was silent. It warns now, naming both ends. Tidying the placeholder
stays quiet, since warning on the documented case is how a warning stops being
read. -
The runtime hint about unplaceable clients pointed at "Manual overrides in the
README", a section the documentation split moved todocs/overrides.md. -
The man page omitted exit code 3 (Graphviz not installed), and its
description listed neither Mermaid nor JSON among the output formats. -
Several documented behaviours did not match the code, all found by an external
review reading the split documentation against the source. Each is a fix to
the document except where noted:docs/output.mdsaid nothing is ever written to stdout;unifi-map shape
writes its report there, which is what makes it pipeable.- It listed
dotamong the formats needing Graphviz.dot,mermaidand
jsonare all written directly and work without it; onlysvg,pdfand
pngneed it. Verified by rendering with an emptyPATH. - Its JSON example named version 0.6.0 and omitted
titleandnetworks,
the latter promised two paragraphs above it. - Its Mermaid example claimed to be the shipped demo while showing a direction
and header no documented command produced. The example is now byte-identical
to--layout treeoutput, with the one edit stated. docs/overrides.mdcalled[[device]].kindrequired; it defaults to
unknown. It also never documented whatnotedoes, which differs per
block: an edge label for[[link]]and[[hosted]], nothing at all for
[[device]]and[[node]]. And it did not mentionoverrides check.docs/usage.mdsaidfetchdownloads the icon font only when missing; it
replaces any cached copy every time.docs/support-files.mdsaid omitting--fetch-fingerprintsleaves clients
without product artwork. The flag governs the download, not the lookup: a
database already cached is read either way.docs/verification.mdsaid "all five output formats" when there are seven,
and ended with aBEGIN GENERATED FLAGSmarker that had noENDand no
content, left behind when the split moved the flag reference to
docs/usage.md..env.examplepointed at a Credentials section of the README that is now
docs/credentials.md.- The README said Access readers, Talk phones and a UNAS "all still appear".
docs/verification.mdcorrectly calls the UNAS case inference, none of the
three having been seen here, and the README now matches it.
-
Support-file operational guidance was on the artwork page. Site selection,
the four archive limits, the compression-bomb defence and the slow-walk
warning all sat inside a section about client icons, while
docs/support-files.mdcovered none of them. Moved, and site selection and
limits now come before the artwork asides, since a multi-site archive stops
the run before artwork is reached. -
CLAUDE.mdproposed the JSON export,overrides checkand the Mermaid export
as future work, all three having shipped, and still described removing the
sanelayout alias in 0.6.0, which 0.6.0 did. The entries are rewritten to
keep the constraints that still bind rather than deleted. The issue template
for feature requests pointed contributors at that file's planned-work section,
naming two shipped features; it points atTODO.md, which exists precisely
becauseCLAUDE.mdis written for agents. -
Two defects in the override-displacement warning added earlier in this same
unreleased cycle, both found by an external review before either reached a
release:- It leaked under
--obfuscate. Overrides are applied before obfuscation, so
an ordinary obfuscated render logged the node's real label, its old parent's
real label and the selector, contradicting the promise that log output is
scrubbed too. The scrubbed diagram was the whole point, and a terminal
beside it naming the nodes defeats it. Displacements are now carried out on
...
- It leaked under
v0.7.2
Fixed
unifi-map shapesays when its artwork counts are measuring an empty cache
rather than the network. Resolved against a cold cache it reported0 of 19
for any network at all, and that section exists precisely so somebody else's
numbers can tell us how well the fingerprint joins work. The same snapshot
gives0 of 19or19 of 19depending only on whether artwork has ever been
fetched, so reading it as a property of their network was a wrong conclusion
waiting to be drawn.SECURITY.mdcoversunifi-map shape. A command whose entire purpose is
producing something to hand to a stranger was absent from the document about
what this tool discloses, which is the one place somebody assessing it would
look.
v0.7.1
Fixed
- The README's
## Outputtable listsmermaidandjson. It had not, since
both were added, so the first place a reader looks to find out what the tool
can produce named five formats out of seven. A test now checks the table
againstALL_FORMATS: the existing guard covers flag names, not the values a
flag accepts, which is how the table fell behind twice without failing.
v0.7.0
Fixed
-
SOURCE_DATE_EPOCHfixes the time stamped into the title block, following the
reproducible-builds convention. Without it the committed demo screenshots
changed on every regeneration, by exactly the thirty pixels of their
timestamp, which made a real rendering change indistinguishable from the clock
ticking.make demo-imagessets it; a real map still stamps the time it was
drawn. -
The first screenshot's caption said most clients "fall back to plain shapes".
They have not for some time: eleven of them draw the console's own generic
glyphs, which is what the image shows. The caption now describes the image,
and says that those glyphs need an icon font only a controller serves, so a
fresh clone genuinely does see shapes there. The two statements were both
true of different machines, which is the confusing kind of wrong.
Added
-
-f jsonwrites the normalised topology: nodes, edges, networks and counts,
rather than the controller's payloads. The model is the stable thing here and
UniFi's schemas are not, so this is what to build an inventory check or an
integration against, and it is a far smaller disclosure than a snapshot. It
honours--obfuscate, overrides and--per-networklike the diagram does.
Carries aschemanumber: fields may be added and will not be removed. -
-f mermaidwrites a.mmdthat GitHub, GitLab and most wikis draw in
place. It reaches the one destination the other formats cannot: a page that
renders the diagram itself, with no file to open and no colour scheme to
guess. Artwork is lost, necessarily, since Mermaid draws boxes and text; node
kind is carried by shape and link meaning by line style, so nothing depends on
colour. The README embeds a live one of the demo. -
unifi-map overrides checkapplies an overrides file against the cached
snapshot and reports, without rendering anything. Overrides fail loudly by
design, so the only way to find a stale selector used to be producing a whole
map. Exits non-zero on the first selector matching nothing or several things,
which makes it usable in a hook or in CI. A missing file is an error rather
than a pass. -
unifi-map shape, which prints a short plain-text description of the shape
of a network: counts, fan-out, which field names the controller returns, and
versions. Meant for a bug report or for the features that are stuck waiting on
a network nobody here has.Built from an allowlist rather than by redacting: every line is a counted
integer, a boolean, or a field name from a list written in advance. A filter
that strips identifying values can be incomplete, and UniFi's own does exactly
that and was observed leaving unredacted tokens in a real support file. A list
that only ever adds cannot leak by omission.It asks before producing anything, printing what it does and does not collect,
and--yesskips that once read. A non-interactive run without--yes
refuses rather than assuming: a cron job has nobody to consent on behalf of.It reports counts and fan-out, topology depth, artwork resolution rates, the
Graphviz version and the offline device count. The artwork numbers were
already computed and thrown away as log lines, and they measure the most
fragile join in the tool; depth because fan-out alone does not distinguish a
flat network from a daisy chain; the Graphviz version because layout differs
between them.Pointed at an archive with
--support-fileit reads that directly rather
than the cache, and adds how much there was to walk, how many entries, and
how many sites it holds. Those are the numbers behind the support-file limits
(set from a single archive) and the untested multi-site handling. Sites are
counted, never named: the keys carrying those names are user-chosen.CONTRIBUTING.mdasked people to gather this sort of thing by hand and now
points at the command instead.Named
shaperather thanreportbecause a diagnostic--reportis planned
and does the opposite job: it describes your map for your benefit and may
freely name your devices, since it never leaves your terminal. This one
describes a network for somebody else and is constrained so it can be shared.
One name for both would have guaranteed the constraint eventually leaked.Two tests hold the promise up. One renders a snapshot built entirely of
identifying values and searches the output for every one. The other is
stronger and matches the design: it asserts the report's whole vocabulary is
closed, so a value arriving by a route nobody predicted fails even though no
test knew to look for it. -
A small rendering flourish, off unless an environment variable asks for it.
Not in--help, not in the README, and not described further here. It is
emitted straight into the DOT rather than added to the topology, so it cannot
reach counts, filtering, obfuscation or the report; a test holds that. Harmless
and cosmetic, and findable by anyone who already knows to look.
v0.6.0
Removed
--layout sane, and themake sanetarget. Renamed totreein 0.5.0, where
it was deprecated with this version named in the warning, the code, the tests
and the changelog.--layout saneis now an argparse error listing the valid
choices, andStyle(layout="sane")raises. One release of overlap was the
whole point of promising a version rather than leaving it open-ended, which is
still the right call for theUDM_*environment names.
Added
TODO.md, the planned work in one contributor-facing place, since the
alternatives were a context file written for AI agents and a Jira instance
needing an account. Two tests keep it honest: it may not plan work for a
released version, and its commitment section must name the same version the
deprecation warning tells users.--transparentdraws no canvas, so a map can be dropped onto a page that
already has a background. Covers SVG, PDF, PNG and draw.io. The theme still
applies and still matters: with the default icon set, labels have no card
behind them, so on a transparent canvas they land straight on the destination
page and a light map is near-invisible on a dark one.- Every demo screenshot is committed in both themes rather than only dark, so
the documentation shows what the default actually produces instead of
describing it.
Fixed
RELEASING.mddescribes the process as it now is. Four things in it were
wrong. It said to rename## Unreleasedaway at release, which would delete
the sectionCONTRIBUTING.mdtells contributors to use. It never mentioned
make docs, which is mandatory now that the man page carries the version. It
said CI could not be checked from here, which stopped being true onceghwas
installed; the instruction now runsgh run watchand says to read the
per-job output, becauseDependency advisoriesiscontinue-on-errorand
reports success having failed inside. And it counted how many times things had
happened, where two of the counts were wrong and none told a reader anything
the sentence did not.CLAUDE.mdsaid thesanelayout alias goes in 0.5.0. It goes in 0.6.0,
which the code, the tests and the changelog all said; that one heading was
missed when the promise moved.
Changed
--site's help text no longer reads as though multi-site support files are
refused outright. They are refused only when you do not say which site you
want, which the sentence did say, twenty words earlier, in a table cell. It
now states the requirement rather than the refusal. The README prose leads
with the same.- Dependabot auto-merge holds anything whose update type it cannot identify,
rather than merging it. The condition asked "is this not a major bump", which
treats an empty or unrecognised value as safe, so anything leaving
update-typeunpopulated made the step an unconditional merge. Not
hypothetical:fetch-metadataresolvedupdate-typeto null for Python pull
requests until v3.1.0, and Python dependencies are what this repository
tracks. It now asks "is this a minor or patch bump", so an unknown value
merges nothing. - A test asserts the man page header carries a real date. The date comes from
the changelog entry for the current version, so bumping__version__before
dating that section produced an empty one, and the regenerate-and-compare
check could not see it because both sides were generated the same wrong way.
v0.5.0
Deprecated
-
--layout saneis renamed to--layout tree, andsanewill be removed in
0.6.0. It still works and still selects the same layout, but it is hidden
from--helpand warns once, naming the replacement and the version.The old name implied the other layout was not sane, and borrowed a clinical
word as a judgement.treedescribes what the layout is: top down, leaf
staggered, with port numbers on the links.A version is promised here, unlike the open-ended
UDM_*deprecation, because
this is one flag value that anyone using it can change in seconds, and an
indefinite alias would keep the word in--helpindefinitely. The target is
0.6.0 rather than 0.5.0 because 0.5.0 is the release that introducestree;
removing the old name in the same version it is deprecated would leave nobody
a release to migrate in.
Fixed
- A support file holding more than one site is refused until
--sitesays
which, instead of mapping whichever site had the most devices and warning.
The old behaviour was the only place this tool guessed: an ambiguous override
selector is a loud error, an ambiguous product name resolves to nothing, and
an unreported uplink gets a placeholder rather than a plausible parent. It was
also the worst place to guess, because the result is a complete and entirely
ordinary looking map, and nothing about the diagram says it is the wrong
network. A single-site archive still needs no flag. - Every directory this tool creates is restricted, not only the last one.
--out-dir out/private/mapscreated three directories and locked down one,
leaving the other two at the umask. Output filenames come from network names,
so a listable parent disclosed the network layout even though the files
themselves are0600. An existing directory is still left exactly as it is. - Overrides that make a loop are refused, naming the loop. Nothing crashed:
Graphviz draws a cycle without complaint, since DOT is a digraph. It is
refused because a switch cannot be its own uplink, so such a map asserts
hardware that cannot exist while looking as authoritative as any other.
Changed
- A man page,
unifi-map.1, generated from the argument parser and committed
so it works from a clone withman ./unifi-map.1.make docsregenerates it
andmake checkfails when it is stale, the same guard the README flag
reference has. It carries the sections a parser cannot supply: ENVIRONMENT,
FILES, EXAMPLES, exit status, and the warning about support files. - Atomic writes live in one module,
fsio.py. Three copies had grown apart:
two calledfsyncbefore the rename and one did not, and only two set the
file mode before putting it in place. None of the differences were intended. _fetchreturns a smallFetchedobject rather than arequests.Response
with two private attributes assigned by hand. The body is streamed through a
size cap rather than read byrequests, which is why the response had to be
doctored; callers only ever used three fields.- The example unmanaged switch is described as "unmanaged" rather than "dumb",
inexamples/overrides.tomland the README. "Unmanaged" is also the accurate
term: it names the absent management plane, which is why such a device has to
be declared by hand in the first place. make saneis nowmake tree, anddocs/images/example-sane-dark.pngis now
example-tree-dark.png. The old make target still works and says it is going.