tomoya@mato ~/lab $ cat STATUS.md

Homelab

Four Mac minis from 2011 and 2012, a switch, two cameras, and a laptop to drive them. Two hosts are provisioned so far; the rest are hardware waiting for an OS. Written by hand from a machine inside the lab, so it moves in days and not seconds. The one exception is the status band below, which is the lab as it is right now — and it gets here by being pushed out: nothing on this page is scraped, nothing in the lab accepts a connection from outside it, and no port is forwarded to reach anything.

last updated 2026-09-12

tomoya@mato ~/lab $ watch -n15 labstat

The status band is live and needs JavaScript. The rest of this page is written by hand and is complete without it.

A host that stops pushing goes unknown, not down — and so does anything it was the only one watching. A dead observer is not evidence about the thing it was observing, and a page that prints up because nobody is left to contradict it would be worse than no page at all.

tomoya@mato ~/lab $ open topology/index.html

topology/index.html open full →

gw-1 is the uplink and the only Wi-Fi. sw-1 hangs off it, and the minis and the monitor Pi go to sw-1 over 1000BASE-T; sessions leave air-1 and reach the minis by SSH. The cameras take no switch port — they have no Ethernet, so they join gw-1 over Wi-Fi like air-1 does.
gw-1 holds a reservation for every host that has a job, keyed to its MAC, so a reinstall or a power cut brings it back on the address it left on — one source of truth, and nothing to configure on the host. Addresses are grouped by kind: one digit says what a host is, the next says which one it is. The recorder resolves the cameras by name anyway, so the numbers appear in exactly one place.
Addresses and key material are not published. Neither is the router's model: an unmanaged switch has no address and no firmware to attack, but a router has both. Hostnames used to be on that list, and are not any more, because the machines were renamed to the names this page already used — mini-1 is now called mini-1. That looks like giving something up and is close to giving up nothing: a hostname is only worth having once you are on the LAN, and anyone on the LAN can enumerate hosts without it. What it buys is one name instead of four. These machines answered to a hostname, a Prometheus instance label and a name on this page, all different, and the cost was paid in wrong assumptions rather than in anything visible.

tomoya@mato ~/lab $ hosts

NAMEHARDWARERAMOSROLESTATUS
air-1 MacBook Air (M1) 8 GB macOS workstation — drives every session up
gw-1 Wi-Fi 6 router - - LAN uplink, Wi-Fi, DHCP reservations up
sw-1 Buffalo LSW4-GT-8NS/WH - - 8-port unmanaged Gigabit switch up
mini-1 Mac mini (Mid 2011) 8 GB Ubuntu 24.04 LTS (desktop edition, boots to multi-user) main machine — agent session, camera recorder (both cameras), Prometheus, Grafana, labstat up
mini-2 Mac mini (Late 2012) 8 GB Ubuntu 24.04 LTS (running a desktop session) SSH node, one agent session, metrics exporter, status reporter up
mini-3 Mac mini (Late 2012) 8 GB - SSH node, one agent session planned
mini-4 Mac mini (Late 2012) 8 GB - SSH node, one agent session planned
pi-1 Raspberry Pi 3 Model B 1 GB - room monitor — USB webcam, streams rather than records planned
cam-1 Plug-in Wi-Fi camera - - RTSP source over Wi-Fi, recorded on mini-1 up
cam-2 Plug-in Wi-Fi camera - - RTSP source over Wi-Fi, recorded on mini-1 up

tomoya@mato ~/lab $ cat stack.txt

  • Ubuntu 24.04 LTS Server install on each mini, replacing the 2011 macOS they shipped with. in use
  • mac-autorestart A five-line systemd oneshot that clears AFTERG3_EN, bit 0 of the chipset's GEN_PMCON_3 register, so that mains returning is enough to start the machine and nobody has to walk over and press anything. Apple's own setting for this lives in the SMC, which only macOS can write and which both minis lost along with macOS; the chipset register is the same decision one layer down, and Linux can write it with a masked setpci. The bit sits in the RTC well and so survives the outage it is there for, which is also why re-asserting it at boot is insurance rather than the mechanism. Proved rather than argued: mini-2 was set, mini-1 was deliberately left alone, both were unplugged together, and only mini-2 came back. mini-1 has the register write; the unit that re-applies it is not on that host yet. in use
  • OpenSSH server The only way onto a mini. Key auth from air-1. in use
  • Node.js (via nvm) Per-user install, so a mini can be re-pinned without touching the system packages. in use
  • Claude Code CLI One session per host. That ratio is the whole reason there are four of them. in use
  • herdr 0.9.0 on all three, and the claim this line used to make is finally true: air-1 saves each mini as an SSH machine, and their workspaces sit in the laptop's sidebar beside the local ones. Client on the laptop, servers on the machines that stay on — the shape the lab already had, except that leaving is no longer the same as stopping, because the remote server holds the panes and a closed lid now costs a connection rather than the work. Installed per user on every host: ~/.local/bin on the minis, and on air-1 too once Homebrew turned out to be two releases behind, so three hosts share one distribution path on the same standard as node. It costs about 24 MB resident on a mini, measured rather than assumed — a twentieth of what ollama used to hold on mini-1 before that was removed, and the reason the worry about adding it to these machines was misplaced. What it consolidates is the watching and not the work: the agent CLI still runs within one server and cannot see the agents on the other machines, so two sessions sharing a clone can still land on each other. That stayed a repository problem and was solved as one, with a worktree, so that two writers never share a HEAD. in use
  • Prometheus + node_exporter Running on mini-1, scraping every 15s and keeping 90 days. It is the collector, not the page: it lives inside the LAN, so it cannot be what reports that the LAN is unreachable. Its own first finding was that mini-2 answered ping and SSH but had no exporter on it — a target that had never existed, showing as an outage. It has one now, so both provisioned minis are measured. Its targets are addresses with the instance label written beside them, which is why renaming the hosts cost nothing here: the label is in the scrape config, not read off the machine, so ninety days of history stayed one series. in use
  • Grafana Unpacked under ~/monitoring beside Prometheus and run as a user unit, so nothing is installed system-wide and nothing needs root. Its data source and its one dashboard are provisioned from files rather than clicked in, so the install is reproducible — eleven panels chosen for these machines rather than the two-hundred-panel community dashboard, on the grounds that a screen showing every metric shows none of them. It answers on mini-1 only, which is narrower than this line said for a while and narrower than it was meant to be: ufw here is default-deny inbound with a single rule for SSH, so reaching the dashboard from the laptop means a tunnel over that one open port. Nobody had checked, because the person checking was sitting at the machine it answers on. It is deliberately not what reports on the lab from outside: a dashboard served from mini-1 cannot tell anyone that mini-1 is down. Its version and usage reporting are off — the same standard the public site holds itself to about calling third parties. in use
  • labstat The reporter behind the status band, running on both provisioned minis. Each measures itself from /proc and asks systemd about its own services, because a machine that can only describe itself by asking another machine goes quiet about itself the moment that machine dies. Beyond itself, mini-1 reports what its Prometheus knows, and probes a host on another port before agreeing it is gone — the difference between a machine that is missing and one whose exporter was never installed is worth a trip across the room. in use
  • camrec Event recording on mini-1, one watcher per camera. The cameras serve RTSP only while they have an event, so the stream's existence is the trigger and there is no detector to tune: ffmpeg opens its input before its output, so the 404 between events writes nothing and the disk stays asleep. Events are filed under the camera, mirrored off-site, and only then deleted locally. The watcher is a template unit instanced on the camera id, which is what the second camera turned from a claim into a fact — the archive was already split per camera, and the health file, staging directory and address cache beside it were not, so two watchers would have written the same three paths and each would have described the other's camera. in use
  • NFS share for ~/repos Repositories are cloned per host today. A shared mount would save four clones of the same tree. considering
  • Headless Chromium A Playwright build, running without root and without a display, so an agent on a mini can open what it just built, measure the DOM and look at a screenshot. Browser work does not have to be handed back to the laptop. in use

tomoya@mato ~/lab $ ls services/

  • Camera recording End to end: a camera detects its own event and opens its stream, mini-1 records whatever answers, and the clip is mirrored off-site and pruned locally only once it is confirmed there. The first real event was 66 seconds and 12.1 MB, uploaded in 12. Events are filed by camera and date rather than by recorder — a recorder can be replaced, and the second camera had to land somewhere that did not collide. It has: both cameras are recorded now, by two instances of one unit. Getting clips off the machine is measured in seconds rather than minutes: the clip uploads the moment it exists, and a single still goes two seconds into the event, over a second RTSP session the camera was measured to allow — a path that was failing on every short event until the second camera produced enough of them to notice. A recording of somebody walking in is worth nothing while it is still on a disk they can carry out. in use
  • Live view wall Every camera on one screen, deliberately not running unless someone is looking. A camera serves at least two simultaneous RTSP clients — measured, not assumed — so watching and recording do not have to compete. There are two cameras to put on it now. planned
  • Lab status The one live thing on the public page. mini-1 pushes a heartbeat outward every minute — what Prometheus already knows about each host, plus the state of the services on it — and the page reads that and never the LAN. Absence is the signal, so nothing has to report that a host died: its beat simply stops, and what it was the sole observer of goes unknown rather than down. It is live: both minis push once a minute and the page reads it from anywhere. Both cameras are on it too, each reported by the watcher that records it rather than by a second prober, and only called missing after several consecutive checks miss it — a plug-in Wi-Fi camera drops off the air when it idles, and a page that cries wolf twice an hour is a page nobody reads. Two reporters may describe the same host, which is wanted rather than tolerated — the endpoint keeps the worse of two live observations, so an exporter that has died on a machine that has not shows as degraded from outside and up from inside, and the page prints the true one. in use

tomoya@mato ~/lab $ cat RULES

mini-1 is the main machine: the agent session, the recorder and the monitoring all live there. That is a decision about today rather than a design — the monitoring hub sharing a host with an agent session is exactly how a monitor ends up reporting latency it caused itself, on a spinning disk especially. When mini-3 has an OS, the hub belongs there, on a host carrying no session. Every mini here has the same 5400rpm spinning disk. It was once written down as a fact about mini-2 alone, which read as though there were a faster one to move work to. There is not. One agent session per host. It is not a style preference: two sessions and a browser on one mini put the load average at twice the thread count and two gigabytes into swap, on a spinning disk. These minis were built for open air, so they sit side by side and are never stacked. A 288 Wh LiFePO4 station backs gw-1, sw-1 and pi-1, so the network and the monitor outlive a power cut. It does not back the minis: four of them under load would empty it inside an hour. They are meant to die in a power cut and come back afterwards, which until this month they did not do without a finger on each power button. No port is forwarded into this house, and nothing here listens to the open internet. That part has not moved. What changed is that there is now one authenticated way in: a mesh VPN, joined by the owner's own devices, where every node dials outward and the coordination happens elsewhere. It is worth being plain that this is a real inbound path and not a clever way of having none — it is narrower than a forwarded port, being identity-bound, key-based and revocable from a phone, and it is still a door where there was a wall. It exists for two things: reaching this lab from away, and being a residential Japanese address to sit behind while abroad, which a commercial VPN does badly because its endpoints are datacentre ranges that the services worth watching block on sight. The status design is untouched by all of it: the heartbeat still leaves outward on its own timer and the public page still reads only what was pushed, so the page that reports a failure is still not inside the thing that failed. Addresses live in three places and no more: DHCP reservations on the router, one mode-600 config per camera on the recorder, and a few static lines in each machine's /etc/hosts. The third was added when naming was looked at properly — the router serves no names at all, and mDNS answered .local with addresses nothing could reach, which is worse than not answering because it looks like the service being down. Nothing pushed to the public endpoint may carry an address: the endpoint publishes a whitelist of fields, so a host that starts sending its own just gets the field dropped. What this page may not say, it says somewhere else rather than nowhere. An index of where each service answers — and the floor plan with what the cameras cover — lives in a private repository and is served on the LAN only, alongside Grafana. Nothing forwards a port to it. The split is the point: this page carries what each thing is and why, that one carries only where, and it reads these descriptions rather than keeping a second copy of them.

tomoya@mato ~/lab $ git log --date=short STATUS.md

  1. 2026-09-11Two folders called cam-2, and the snapshot that was deleted mid-upload

    cam-2 recorded within the hour — seven seconds, then eight — and both clips reached the remote. The archive then showed two folders named cam-2, created 190 ms apart, one of them empty. Google Drive allows two folders with the same name in the same parent. Two rclone processes creating the same path therefore do not collide: they both succeed, and the archive grows a second one. What overlapped was the snapshot still uploading from during the event and the clip upload starting as the event ended. The fix is one rclone mkdir before either writes, so there is no path left to race for. cam-1 had been quietly doing the same thing since the seventh, which nobody had looked for. Underneath that was the worse one. Every snapshot on cam-2 failed — "Failed to calculate src hash: no such file or directory", then three attempts at an object already gone. push_snapshot runs in the background and the loop deletes its staging frame on the way into the next iteration, so a short event meant the frame was removed while the upload was still reading it. cam-1 never showed it because its first events ran a minute and the upload had long finished; cam-2's run seven seconds. The frame now carries the event id and is deleted by the function that made it. That bug is worth more than a line in a changelog. The still exists so that one frame leaves the house while the event is still happening, ahead of the clip that cannot leave until it ends — so a failure there extends, by the whole length of the event, the window in which the only copy of who walked in is on a disk in the room they walked into. It was failing every time on one of the two cameras, and the page said up throughout, because up is a true statement about a camera that is answering. Both of these were found by a second camera doing the same job as the first, faster. Neither needed new code to surface — only a second instance of the old code, which is an argument for the redundancy discussed above and not only for the coverage.

  2. 2026-09-11The second camera, and the three files it would have fought over

    cam-2 arrived, joined the Wi-Fi on a dynamic lease, and was moved onto its reserved address — the one the addressing scheme already predicted for it, which is the first time that scheme has been asked a question it could answer on its own. RTSP went on in the app with digest auth, and the probe came back 404 Stream Not Found rather than 401: the credentials are right and there is simply no event in progress, which is the healthy state for a camera in Events mode and the whole signal camrec is built on. Then the part worth writing down. camrec's own example config said a second camera needed its own file with its own CAM_ID and that nothing else had to change, and that was wrong. The archive was namespaced — var/events/// — and was the only thing that was. The health file on tmpfs, the staging directory and the resolved-address cache were all fixed paths, so two watchers would have written the same three files. None of that fails loudly. The health file is what labstat reads and what the band above this page prints, so the visible result would have been a status row confidently describing the wrong camera, which is the exact failure the drawing on this page has a script to prevent and the endpoint has a whitelist to prevent. All three paths carry the camera id now, the watcher is a template unit instanced on it, and adding a third camera is a config file and an enable. labstat was singular in the same way — one CAMERA_ID, one health path — and now takes a list. What it gained beyond that is a guard. The endpoint constrains a subject id to sixteen characters and drops whatever does not match, silently, which is the right behaviour at a public edge and an expensive one to learn about from the far end: mini-1-camrec-cam-1 was nineteen, and the push answered 202 with a subject count that looked correct while the row never appeared. The rows are mini-1-camrec-1 and -2, and the reporter now checks every id against that pattern before sending, so the next one over the limit is a crash naming the id rather than a row that is quietly not there. Left open. Neither camera has produced an event since the switchover, so the two watchers are proved to resolve, authenticate and stay up, and are not yet proved to have recorded anything through the new paths. The live view wall is still planned and now has two cameras to put on it.</p> </li>

  3. 2026-09-08A breaker tripped, both minis stayed dark, and the fix was one bit below macOS

    A breaker went in the evening and neither provisioned mini came back; both needed a finger on the power button. The first answer here was wrong in a way worth recording, because the cause had been written down an hour earlier. mini-1 keeps local time and mini-2 keeps UTC, so `last` reported 19:07 and 10:07 for what was a single instant, and that read as mini-2 having restarted itself. What settled it was /proc/stat and its btime field, which counts seconds and not cities: 1788862069 against 1788862066, three seconds apart, the interval between two button presses. Neither machine restarted itself. Noticing a trap turns out not to be the same as not walking into it. Apple's setting for this is pmset autorestart, and it lives in the SMC, which takes instructions only from macOS. Both minis lost macOS when Ubuntu went on, down to the recovery partition, and there is no way in from Linux: applesmc publishes fans and temperatures and no power policy, and the EFI variable store holds boot devices and nothing else. Internet Recovery is the documented way back and it did not survive contact with the room — held keys changed the machine's behaviour, so the firmware was seeing the keyboard, but it never put a picture on a monitor that Linux drives without complaint. Wake-on-LAN was proposed here and withdrawn: a mains loss leaves nothing armed to listen. What worked was the chipset. AFTERG3_EN is bit 0 of GEN_PMCON_3 at offset 0xA4 on the LPC bridge, the same choice a PC BIOS offers as Restore on AC Power Loss, and clearing it says boot rather than wait. setpci writes it through a mask so the rest of the byte, which includes a write-1-to-clear power-failure flag, is left alone: 09 became 08 on both minis, HM77 and the older HM65 alike. It was measured rather than trusted. mini-2 was configured, mini-1 was deliberately not, both were unplugged together and restored together with nobody touching a button, and mini-2 booted itself at 12:10:45Z while mini-1 stayed dark until it was pressed. One variable, one answer. The warning that came with the method, that the SMC would overwrite the bit, did not happen on either generation. The outage also resurrected something nobody remembered. ollama had been installed on mini-1 on 2024-07-20, served seventeen requests that afternoon — the last a chat completion that took twenty minutes and thirteen seconds — and was never spoken to again, while starting on every boot since and holding 494 MB against 8 GB with a 4.4 GB model behind it. This page had listed it the whole time, which is the argument for keeping the page. It is gone now, and so is the line. Left open. mini-1 carries the register write but not the unit that re-applies it. mini-1 runs hot, its fan pinned at the 1,800 rpm floor of a 5,500 rpm range, because Linux inherits none of the thermal management macOS did here. mini-2 takes its NTP server from DHCP and is handed one 165 ms away that answered nothing at all for a long stretch, so the host that keeps UTC is the one least sure what time it is. And the two clocks still disagree about which zone they are in, which is the thing that produced the wrong answer above.

  4. 2026-09-08One screen for three hosts, and the lid stops mattering

    The correction below is already out of date, which is the good outcome. herdr 0.9 was on the stable channel all along; what was two releases behind was Homebrew, and that was the whole obstacle. Taking brew out and installing per user put air-1 on the same footing as the minis and left one distribution path for three hosts, which is the standard node already sets here. `machine add` prepares the far end itself, so the two minis went to 0.9.0 without anyone sshing in to update them, and both now sit in the laptop's sidebar under the names the public page uses. The one screen is the smaller half of it. The larger half is that the server is on the mini: the panes stay open when the connection goes, so shutting a laptop in another room now costs a connection rather than the work — which is worth exactly as much at two hosts as at four, and was the reason not to wait for mini-3. The cost was measured rather than feared: about 24 MB resident per mini, a twentieth of what ollama holds, against a worry that these 8 GB machines could not take another resident process. One host resisted for a reason worth keeping. mini-1 answered `command not found` while mini-2 ran the identical binary — same size, same hash, both present. `~/.local/bin` was on the PATH only through `~/.profile`, which a login shell reads and the shell inside a pane does not; mini-2 had a second line in `~/.bashrc` and mini-1 never had. A difference between two machines that were set up the same way, invisible until something asked one of them a question over SSH. One side effect is worth recording rather than enjoying: bringing the remote servers up restarted them, and the sessions they held went with them — including the six on mini-2, five days old, against a house rule that says one. That rule is no less broken for the counter having been reset by an upgrade nobody ran for that purpose.

  5. 2026-09-08herdr runs on every host here and joins none of them

    The stack said herdr did terminal and session management across the hosts, and it does not. Checked rather than assumed: air-1, mini-1 and mini-2 each run 0.8.0, each runs its own server, and none of them can see another's sessions. Across the hosts is done by the laptop, one SSH connection per mini — which also means a session dies with the connection, because there is no tmux here and never was, so a lid closing in another room ends work on a machine that is still running. The line was not wrong about the software so much as it recorded the intent in the place the capability goes, which is the failure this inventory already has a rule about. 0.9 adds `machine add`: an SSH-reachable host whose workspaces sit beside the local ones, and whose agents keep running after a disconnect. Worth having at two hosts and not only at four — one screen for all of them is a convenience that scales with the machine count, but surviving the lid does not scale at all, and is worth the same at one host as at four. It is not on the stable channel here yet, so this records the correction and not an upgrade.

  6. 2026-09-08The diagram is drawn now, and a script keeps it honest

    The generated topology went out and a hand-drawn one replaced it. The drawing is worth the trade: copper is a run that ends in a square jack, the air is a stippled field with no jack anywhere in it, and so the fact that the cameras never touch the switch is visible before the caption says it — which a generator was never going to arrive at, because it is an argument rather than a layout. sw-1 appears as its own front panel, six ports lit and two dark, which is the number this lab once got wrong. But a generated diagram had one property this one gives up: it could not disagree with its source. There is now a live status band a few centimetres above the drawing, and a picture quietly saying planned about a host the band calls up would be worse than no picture, because both look authoritative and only one is right. So the drawing is checked rather than generated: `check-topology.mjs` reads the statuses back out of the SVG and holds them against the inventory, and while it is in there it fails on an address, on an external request, and on the loss of any of the theme hooks the parent page depends on — the ones that stay invisible until somebody visits in the theme the author was not using. The spec, the delta pages and the web-font stripper are retired in place rather than deleted, because dated entries below link to those URLs.

  7. 2026-09-07The band shows what is wrong and counts what is not

    Two machines made eleven rows and four will make twenty, nearly all of them permanently green. The objection that started this was whether mini-1's sshd deserved a line — it does not, but deleting it would have been the wrong fix, because the problem is not that row, it is that a list which is always green teaches the eye to skip the band, and a band nobody reads is the same as no band. So anything not up is a row, worst first, and everything that is up collapses into one line that opens. Nothing is hidden; the healthy majority just stops competing with the exception for the attention the exception needs. `unknown` sorts above `up` on purpose: not knowing is a thing to look at, and it is exactly what a subject becomes when the only host that could see it goes quiet. The ordering lives in the page rather than the endpoint, because how much attention a row deserves is a question about reading, and the endpoint should stay a plain record of what was said.

  8. 2026-09-07The camera going dark is now a thing the page can say

    The status band watched the machines and not the thing they exist for. It watches cam-1 now — and the reason is not tidiness. These cameras are on Wi-Fi, and taking a wireless camera off the air is quieter and easier than anything involving a fuse box, so a camera that stops answering is worth publishing even when the cause cannot be known from here. Nothing probes it a second time: camrec's loop already asks every few seconds and holds the only copy of the camera's MAC, so it writes what it found to a file on tmpfs — tmpfs because that loop touches no disk between events and a write every three seconds would undo it. Two timestamps, not one, so that a missing camera and a missing recorder cannot collapse into the same silence. And it takes three minutes of consecutive misses to call the camera missing, because a plug-in Wi-Fi camera drops out of the neighbour table when it idles: this one went missing 28 times in six hours while recording perfectly throughout, and published straight that is an alert nobody reads by Wednesday. Building it turned up a smaller lie already on the page. The last-upload age was computed against a systemd timestamp that systemd clears while a unit runs, so for the few seconds the upload was actually working the page reported it as hours stale — a monitor that looks most broken exactly when the thing works. It says nothing now instead.

  9. 2026-09-07Eleven minutes in the room, down to fourteen seconds

    The recorder mirrored its clips off-site on a ten-minute timer, which is fine as a backup and useless as a defence. Measured on a real event: recorded at 16:39:55, in the archive at 16:50:48 — ten minutes and fifty-three seconds in which the only copy of who walked in was on a disk in the room they walked into. Two changes. The clip now uploads the moment it exists rather than waiting for the next sweep, and the sweep stays behind it as the thing that guarantees delivery when the immediate attempt loses a link. And one frame goes out while the event is still running, over a second RTSP session — possible only because of a measurement taken on the fourth for an entirely different reason, that this camera serves two simultaneous clients. First evidence out of the house at about fourteen seconds now, whatever the length of the event. The floor is not bandwidth: nine of those seconds are a single rclone spending them on an OAuth refresh and its first API call, against a still image that is half a megabyte. None of it survives losing the line before the upload, which is exactly what the camera keeping its own copy is for.

  10. 2026-09-07The minis are not the same age, and neither line here said so

    The host table called all four minis Late 2012 and the blurb above it called all four of them 2011, which is two wrong answers pointing in opposite directions. Prometheus settled it without anyone getting up: node_exporter publishes each thread's maximum frequency, and mini-1 tops out at 2.9 GHz against mini-2's 3.1 GHz. That is an i5-2415M and an i5-3210M — a Mid 2011 machine and a Late 2012 one, bought as though they were a batch and recorded as though they were identical. Only those two are corrected. mini-3 and mini-4 still have no OS, so nothing has measured them, and their year is still a thing written down rather than a thing checked. The question that surfaced it was whether the minis could take a heavy test suite off the laptop. They cannot, and not by a margin worth arguing: one M1 is roughly eight of these on multi-core, and a test run does not divide across machines the way it divides across cores. The minis are the right shape for what they already do — always on, cheap to leave on, and watching from outside whatever they are watching. Speed was never the job.

  11. 2026-09-07mini-2 is measured at last, and mini-1 is named the main machine

    node_exporter is on mini-2, so the target that had been reporting an outage since it was written down is finally answering, and both provisioned minis are on the graphs. Grafana got a dashboard to go with them — eleven panels chosen for these machines rather than the two-hundred-panel community one, because a screen showing every metric shows none of them. The panel that earns its place is CPU waiting on disk. Naming mini-1 the main machine meant measuring rather than assuming, and two things written here turned out to be wrong. Both minis have the same 5400rpm spinning disk; that had been recorded as a fact about mini-2 alone, which read as though there were a faster machine to move work to. And the desktop session, noted on the third as mini-1's problem, is running on mini-2 — mini-1 boots to multi-user and sits at 1.5 GB, while mini-2 idles at 4.3 GB with a load of 2. The decision survived the measurements, but not for the reasons that were on this page. mini-2 reports for itself now too, and its first minute of doing so was worth the install: a load of ten against four cores, five processes blocked on disk and 2.7 GB in swap — the house rule about one agent session per host, being broken, in the exact shape the rule predicted.

  12. 2026-09-07The pulse is real, and the lab has a dashboard again

    The status endpoint is provisioned and mini-1 has been pushing to it once a minute since. Two things were wrong the moment it was live and only visible because it was. The function had been placed in Virginia while its database sits in Tokyo, so every read crossed the Pacific twice — a view is KEYS then MGET — and it is pinned to the same region as its data now. The cache was worse: stale-while-revalidate let a sixty-second-old copy be served, and because a payload's ages are computed when it is generated, a stale copy under-reports silence by exactly its own age. A dead reporter could have read as healthy for four minutes against a three-minute window, so the window that the whole design rests on was quietly the least trustworthy number on the page. Grafana, downloaded a while ago and never unpacked, now runs beside Prometheus with its data source provisioned from a file. It stays on the LAN on purpose. Reporting on the lab from outside is a different job, and it belongs to the thing that is not inside the lab.

  13. 2026-09-07The recording loop closes, and the page gets a pulse

    The other half is done: the camera opened its stream, mini-1 recorded 66 seconds of it, and the clip is off-site with a matching MD5 — nothing in that chain is a plan any more. With a second camera coming, the archive was re-keyed at the same time. It was filed under the recorder's hostname, which is the one identity in the picture that can be replaced; it is now filed by camera and date, so cam-2 lands beside cam-1 instead of on top of it, and the recorder's name moved into the metadata where it belongs. What starts now is the status band at the top of this page. It is pushed, not scraped, and that is not a convenience: a status view served from inside the lab would render "the lab is unreachable" as a spinner. Prometheus has been scraping here for a while and its first real finding is the shape of the problem — mini-2 answers ping and SSH, but its exporter target has been down since it was written down, because nothing was ever installed to answer it. The collector inside was reporting an outage that was really a typo, and nobody was looking. The reporter that pushes all this now runs on mini-1 once a minute, and it probes a host on another port before agreeing that it is gone — so mini-2 reads as degraded, with the reason, rather than as down. What is left is provisioning the endpoint it posts to.

  14. 2026-09-07A file put on a mini now arrives in cloud storage

    The off-site half of the recording plan works end to end. It took longer than the engineering: the client secret is shown once and never again, and a wrong one fails as "invalid_client" long after the consent screen has already said yes, which sends you looking in the wrong place. Publishing the OAuth client also required a privacy policy, so the site has one now — overdue anyway, given the analytics and the contact form. What remains is the other half: getting events off the camera and onto a mini.

  15. 2026-09-04The camera answers, and the storage problem turns out not to exist

    Measured rather than estimated: the stream is 1080p H.264 at about 1.3 Mbit/s with AAC audio, not the 2K the plan assumed, and the SDP's own bandwidth figure understated it tenfold. More usefully, the camera keeps its own events and a month of them is a couple of gigabytes — so the off-site copy is a rounding error rather than the terabytes this page warned about. The camera also serves two simultaneous clients, so a live view and a recorder need not compete for it.

  16. 2026-09-04The camera speaks RTSP on its own

    The open question was whether these cameras serve RTSP standalone or only through a base station that nobody here owns. They serve it: an unauthenticated OPTIONS comes back 200 OK advertising DESCRIBE, SETUP and PLAY. The recording plan survives its first real test. It has not produced a stream yet — every ordinary path answers 404 — but that is a question of finding the right URL rather than of buying hardware.

  17. 2026-09-04mini-1 turns out to have a desktop on it

    mini-1 is not bare after all — it is running Ubuntu, but the desktop edition rather than server. That is two problems on a machine meant to carry an agent session: a graphical session holds on to memory that is already scarce here, and the desktop edition ships without an SSH server, so there is nothing to connect to yet. It is marked joining rather than up until there is.

    what changed in the topology →

  18. 2026-09-04All four minis are 2012 machines, and recording waits for something to happen

    The owner confirms the minis are one batch, so all four rows say Late 2012 rather than one measured host and three inherited guesses. Recording drops the continuous local copy: nothing is written unless something happens. That retires the claim that the recorder needs a solid-state disk — it was 24/7 writes that made the 5400rpm drive untenable, and there are none. The cost moves to the CPU instead, because deciding that something happened means looking at the stream.

    what changed in the topology →

  19. 2026-09-04mini-2 is a 2012 machine with a spinning disk, and it is carrying two sessions

    Running on the host rather than describing it from outside turns up things the owner did not have to hand. mini-2 identifies as Macmini6,1 — a Late 2012 machine, not the 2011 the rest of the page assumes, so its row now says so and the others stay unverified until someone reads their model off the same file. Its disk is a 5400rpm APPLE HDD, and two gigabytes were paged onto it. The one-session-per-host rule was being broken at that moment by this very session, and it now says what it costs.

  20. 2026-09-04A browser on the server, and the diagram stops overflowing

    A mini can now open a page it just built, measure it and look at it — headless Chromium, no root, no display. The first thing it found was a defect: the topology page was 1221 pixels tall against a 900 pixel laptop viewport, which every composition check had passed. The drawing is flatter now and the cards say only what the diagram cannot, and it fits.

    what changed in the topology →

  21. 2026-09-04The cameras have no Ethernet, so the switch is not full after all

    Yesterday's entry said the eight ports were spoken for. They are not: the cameras turn out to have no Ethernet port — in this product line "wired" describes the power, not the network — so they join gw-1 over Wi-Fi and take no port at all. Six of eight are used, two are spare, and the wired segment now means only what actually reaches sw-1 over copper. Recording is stated the way it will really run: continuous to local disk, and off-site only when something happens.

    what changed in the topology →

  22. 2026-09-03Cameras, a monitor Pi, and a battery that knows its place

    Two wired cameras and a Raspberry Pi 3B are drawn in as planned, which is also the moment sw-1 runs out: the uplink, four minis, two cameras and the Pi are eight ports of eight. Recording will be continuous on a mini and only eventful off-site — two 2K streams uploaded around the clock would be roughly two terabytes a month. A 288 Wh station backs the router, the switch and the Pi, and deliberately not the minis, which would drain it inside an hour.

    what changed in the topology →

  23. 2026-09-03The router joins the drawing, and addressing is settled

    gw-1 was always in the path and the diagram quietly pretended otherwise — air-1 appeared to reach the switch directly. It now shows the real route: Wi-Fi to the router, a wired uplink to the switch, the minis behind that. Addressing is settled at the same time: gw-1 now holds a reservation per mini, and the first one is live. Correcting a claim from yesterday, too — the provisioned host is mini-2, not mini-1; mini-1 is still bare.

    what changed in the topology →

  24. 2026-09-02The lab gets a status page

    First published inventory: mini-1 provisioned and reachable, mini-2 through mini-4 racked but not yet installed, sw-1 wiring them together. The topology diagram and this log start here, so the first entry has nothing to diff against.

  25. </ol>

tomoya@mato ~/lab $