The night before closed out with three small commits, labelling the HomeLab stat rows on the About page: PVE, UniFi camera, network stack. The real day started at 2 PM with a new client engagement: a hotel wants a 100-room IPTV system built, and the first two files it arrived with, an invoice PDF and a set of iPhone site-survey photos, were both unreadable on this machine.
A system already running that nobody had mentioned
The first photo pass found two chassis labelled Exterity AvediaStream, a commercial hotel-IPTV brand, serial numbers and RCM marks legible straight off the casing. Only 3 of the unit's 10 ports were patched in. The brief had described this as a new build, and the existing system had never come up before the photos surfaced it. I confirmed that Exterity is being fully decommissioned and replaced end to end by the new vendor's equipment, which means a quote that reads as complete for a greenfield headend is actually missing an entire line item: taking out the old boxes, the old in-room players, and any licence still tied to them.
The main racks also run three different switch vendors: UniFi, TP-Link Omada, and Aruba. Getting IGMP and multicast behaviour to work the same way across all three, so that a channel change joins and leaves cleanly no matter which switch it touches, was flagged as the biggest technical risk in the gap analysis.
Two files nothing here could open
Both files needed a workaround before any of that analysis could happen. The site-survey photos were HEIC, and this container has no HEIC decoder and no root to install one. heic2jpg.sh gets around that by downloading the needed .deb packages with apt-get download, which works without root even though apt-get install doesn't, unpacking them by hand with dpkg-deb -x, then running heif-convert against that unpacked tree with LD_LIBRARY_PATH and LIBHEIF_PLUGIN_PATH pointed at it. The plugin path turned out to matter: heif-convert fails with "no decoder" without it, because the actual codec plugins ship as separate packages from libheif itself. All 31 photos converted at full resolution, checked byte for byte against checksums taken before conversion.
The vendor's solution deck was 84 pages and 273 images, past the page count this environment's PDF tooling can handle without shelling out to a renderer that isn't installed. pdf-extract-images.py pulls the images out directly instead: /DCTDecode streams are already JPEG and get written out as is, and /FlateDecode bitmap streams get wrapped into a valid PNG by hand using nothing but zlib and struct. Pulling images out mattered because the deck was mostly images, topology diagrams, admin UI screenshots, spec tables, and a text-only conversion would have lost most of what was in it. It kept 248 of the 273 images after dropping exact duplicates and small alpha-mask fragments. Both tools exist because this container has no root and no package-manager path to either format.
A deck that documents the wrong model
Reading the deck against the invoice turned up the day's biggest open question. The invoice states the purchased gateway supports up to 500 rooms. The deck's own capacity figures, and a licensing screen shown inside it, belong to a sibling model. That sibling gives lower numbers, a hard ceiling around 150 terminals in one place and a concurrent-authorisation cap around 300 in another, and the exact model that actually shipped isn't documented anywhere in the 84 pages. If the real ceiling is closer to 150, buying two units for 100 rooms makes sense, since one alone would be running at two thirds capacity with no spare. If it really is 500, the second unit is hard to justify. There's real money riding on which number is true, and nothing in hand answers it yet.
The same deck also showed the vendor's own demo server still running CentOS 7, which reached end of life in mid-2024, and its PMS integration dropdown set to none, despite PMS integration being a paid line item on the invoice. The open questions from all of this, exact capacity, exact model number, PMS integration among them, went into a written question list for the vendor.
Comparing the invoice to the deck also involved a mistake. While one sub-agent's invoice transcription was being checked against another's, Claude concluded the numbers had been fabricated and said so. That was wrong. The actual cause was a broken call on this end: reading a PDF past 10 pages with a page-range argument shells out to a renderer that isn't installed here, and the failure had been silently returned as if it were real content. What gave it away was a vendor typo, "Not inclue Battery," reproduced identically by both sub-agents working independently, proof neither had actually invented anything. The rule that came out of it: never pass a page range to a plain read on this host, since a plain read works fine on its own.
Corrections to a rack
I corrected the photo-only read of the main racks in seven places from what I know of the room. Among them: the two racks had their letters swapped, a switch counted as one was two independent units that can't be stacked, three Hikvision video recorders had been missed entirely, and the servers read as two Supermicro units were Dell EMC. Reading a label or a model number off a photo held up well. Counting units and placing them in the right rack did not. My own corrections needed checking too: I put the battery banks at three, which was also wrong, and the count only settled at four against a clearer photo.
A later photo of a second outbuilding rack disproved another assumption going in, that all the small building racks are identical. This one had a single managed switch where the first documented one had two. That difference lined up cleanly once the building and room numbers came in: six buildings, contiguous room ranges that add up to exactly 100, and the smaller buildings only need one switch each where the larger ones need two. The number of set-top boxes on order also matches the total room count exactly.
An unrelated finding, filed while moving a document
A second, older engagement for the same client, a function room's PA system, got filed into the new project structure the same afternoon. Cross-referencing its equipment list against the main rack survey turned up something the PA document itself never mentioned: there's a second, separate PA setup already running in the main building for house background music and paging, on top of the function room's own system. I was clear that the two systems should stay separate and not get tied into each other or the main network.
What has to happen before the visit
By the end of the day there was physical access confirmed for a site visit, and the last hour went into a day-by-day plan for it. The plan's biggest risk isn't technical: the thing most likely to delay the job is getting network-admin access started now, since a third party may hold the credentials and that can take weeks to sort out, well before anyone needs to touch a switch. The plan also proposes piloting the new build in one outbuilding that needs no cooperation from whoever currently manages the network, so real progress doesn't have to wait on that access coming through.
Planned, not built
An idea came up in the evening for the portfolio site: loading pages into a shared viewing pane instead of a full page load for every navigation. It got planned that night, the transition design and the blockers found in a first pass over the source, but nothing from it shipped today. It got built the next day.