Wednesday, 22 January 2025

Upgrading to the Mikrotik RouterBoard RB2011, Part 1

_**Aside**: Here's a little tip if you find something a bit niche, but a bit overpriced, on eBay. **Stick a watch on it**. Eventually, the seller will get notified by eBay and prompted to offer *you*, the watcher, a 10% discount. This has been my strategy for low-urgency nerd items for a while and it works a treat._ Anyway, my use case here was a firewall that offers an API that I can call from elsewhere (on the trusted side of the network). Think "default block ALL", then an API call comes in to temporarily open one port to one IP address for an hour, before reverting back to "block all". There are precious few network devices that actually offer this capability; at first this seemed surprising but then I guess there's less overlap between _"I configure secure small networks"_ and _"I write backend code"_ than I thought. Anyway, The [RB2011UiAS-RM](https://mikrotik.com/product/RB2011UiAS-RM) (RB2011 for short) is an absolute network Swiss Army Knife, capable of a heap of stuff that I've had a long-suffering Raspberry Pi 3B doing - things like DNSMasq (and domain blocking à la Pi-Hole) and WireGuard VPN termination. I'd like to take those tasks off the trusty Pi, leaving it to be a true application server rather than a bit of network infrastructure. Of course there are many devices that can perform these tasks, but the Mikrotik stands above the rest with its [control-plane API](https://help.mikrotik.com/docs/spaces/ROS/pages/47579162/REST+API), which lets me do the dynamic-firewall thing I need, rackmountability (I 3d-printed the ears) and the fact that it is **fanless** and thus completely silent in operation.
So here's how I've got it going. # Finding it Firstly (and this may just be some leftover configuration on my secondhand device that a factory reset didn't clear out for some reason), I couldn't get the device to respond in *any* way over the network until the following conditions were *all* true: - Port `ETH1` plugged in to my existing network - IP address allocated to `ETH1` via DHCP - Port `ETH2` plugged into my laptop (Wifi OFF) - IP address allocated to laptop via DHCP over that link This is counter to every bit of documentation (Mikrotik-official or otherwise) I've found online that says the router will factory-reset to `192.168.88.1` and will run a DHCP server to hand out `192.168.88.0/24` IPs to connected clients. Once I'd discovered (via looking at DNSMasq logs on the Pi) that the router was coming online in this way, it was actually a pleasure to use, as it's always annoying having to flip back-and-forth between multiple networks while setting these things up. Effectively the router's web UI is accessible at whatever IP you want to give it's `ETH1` MAC via DHCP, and the rest of the `ETH` ports just come up as a "bridge", making the device feel like a simple 10/100/1000 switch, that happens to have a nice UI. On the subject of UI, Mikrotik does offer a native control application [(WinBox)](https://mikrotik.com/download) but it's not really needed unless you're having major issues finding your device on the network (but see above for some hints!) as it can do some "neighbour discovery" stuff. For me, my 2012 MacBook Pro is running too ancient a MacOS to even consider it. The Web UI ("`webfig`") plus SSH are easily enough for me. After decades of absolutely awful consumer-grade router web UIs, `webfig` is snappy, modern enough, well-considered and bug-free. # Upgrade to v7 and Initial setup ### Upgrade The RB2011 has 128Mb of RAM, allowing it to be [upgraded to Version 7 of RouterOS](https://help.mikrotik.com/docs/spaces/ROS/pages/115736772/Upgrading+to+v7) (important, because that's the version in which the REST API becomes available). Going from v6.49.17 to v7 is as easy as going to _System -> Packages -> Check for Updates_ and switching the _Channel_ to `upgrade`. `v7.12.1` shows up (in January 2025 at least) and is just a _Download and Install_ away. From there, we're on the v7 train and can go as bleeding-edge as desired. For me the `stable` channel seemed like a safe bet, so I further updated to `7.17` (January 2025). ### Backup Once we've done that, we should also start [backing up](https://help.mikrotik.com/docs/spaces/ROS/pages/40992852/Backup) the router config after every successful stage of setup. Log in as `admin` via SSH and just do `[admin@MikroTik] > /system/backup/save`. You can see the file from the *Files* top-level menu in the UI or in the console with `/file print`: ``` [admin@MikroTik] /file> print # NAME TYPE SIZE LAST-MODIFIED 0 skins directory 1970-01-01 11:00:05 1 pub directory 2019-10-23 12:13:14 2 auto-before-reset.backup backup 44.1KiB 1970-01-01 11:00:06 3 MikroTik-20250122-2026.backup backup 28.2KiB 2025-01-22 20:26:13 [admin@MikroTik] /file> ``` ### Turn off unwanted services RouterOS runs a [number of IP services](https://help.mikrotik.com/docs/spaces/ROS/pages/328229/IP+Services) that we neither want nor need; turning them off can only be of benefit; `telnet` and `ftp` are ancient and insecure; we've already established that `winbox` is surplus to requirements. Eventually, it would be good to only allow the `-ssl` versions of the `www` and `api` services, but we'll leave them for now. `ssh` is always wanted. So to begin, let's see what we have: `/ip/service/print`: ``` Flags: X - DISABLED, I - INVALID Columns: NAME, PORT, CERTIFICATE, VRF, MAX-SESSIONS # NAME PORT CERTIFICATE VRF MAX-SESSIONS 0 telnet 23 main 20 1 ftp 21 main 20 2 www 80 main 20 3 ssh 22 main 20 4 X www-ssl 443 none main 20 5 api 8728 main 20 6 winbox 8291 main 20 7 api-ssl 8729 none main 20 ``` Now we can turn them off: `/ip/service/disable telnet` `/ip/service/disable ftp` `/ip/service/disable winbox` We can also reduce the maximum number of concurrent sessions to something more realistic. It's probably unimportant, but it shows off a cool feature of the command-line: ``` /ip/service/set max-sessions=3 numbers: 2,3,4,5,7 ``` ...we can apply the same setting change to all the different services in one go. That's really cool. Here's what we have now: ``` Flags: X - DISABLED, I - INVALID Columns: NAME, PORT, CERTIFICATE, VRF, MAX-SESSIONS # NAME PORT CERTIFICATE VRF MAX-SESSIONS 0 X telnet 23 main 20 1 X ftp 21 main 20 2 www 80 main 3 3 ssh 22 main 3 4 X www-ssl 443 none main 3 5 api 8728 main 3 6 X winbox 8291 main 20 7 api-ssl 8729 none main 3 ``` **Reminder**: Time to backup again!

Saturday, 28 December 2024

End of year 2024 wrapup

Just a quick one; not a great deal of personal development or great strides forward this year, but we can always live in hope for next year. This marks *25 years* of being a professional software developer. I've gone from writing C in `vim` on a [100MHz 128Mb HP-UX workstation](https://blog.themillhousegroup.com/2021/12/computers-i-have-known-part-4.html) to letting ChatGPT help me write TypeScript in VSCode on an 11-core M3 16Gb MacBook Pro at umpteen GHz. I've Created, Read, Updated and Deleted (see what I did there?) a *lot* of code, and I still love it. I have rejected any offers to move into the "people management" side of things, and hope to continue to be "on the tools" for my entire career. How much longer will that last? Dunno - but certainly not another 25 years!

Saturday, 30 November 2024

Mac - when your disk is really, really full

My wife's 2012 (OSX Catalina, 10.15) MacBook Air has been struggling recently with a hard disk that seems to have no hesitation in filling itself to the absolute brim (like `78kb space remaining`). The problem is that APFS, being a journaling file system, wants to write a record of your attempt to call `rm ~/Downloads/stupid-big-file.mp4`, but doesn't have the space to do so - preventing the `rm` from running, and so escaping this situation is far harder than it should be. This has happened a number of times now, and after various attempts to use ["Target Disk Mode"](https://support.apple.com/en-au/guide/mac-help/mchlp1443/mac) via a daisy-chain of Thunderbolt-to-FireWire adapters, Apple Disk Utility in Recovery Mode, and a [GParted LiveCD](https://gparted.org/livecd.php) to mount the drive, this typically-unassuming but excellent [Stack Exchange answer](https://apple.stackexchange.com/a/371323/117415) has ended up being my saviour. It always takes me ages to re-stumble upon it because my Googling is typically for phrases like "resize APFS partition" which leads you to [articles like this one](https://www.macobserver.com/tips/resize-your-apfs-container/) which is totally overkill for the situation. The **TL;DR** is - you can safely use the `diskutil` utility to `remove` the volume that is named `VM`, and APFS will then automatically resize the overfull "Macintosh HD" volume to get that space back. Just be aware that the Mac will always prefer to have *some* VM, so the long-term solution is probably to keep several tens of Gb free so that the two volumes can coexist in harmony. While I'm linking to useful Apple-disaster-recovery-related Stack answers, while you're in Recovery Mode and trying to figure out what files to nuke, Apple helpfully removes the link to `du`. [This answer gives the full path](https://superuser.com/a/1279144/386135) (which on the aforementioned 2012 Mac is actually `/Volumes/Macintosh\ HD/usr/bin/du` as per one of the comments).

Sunday, 20 October 2024

Google Home Minis (1st Gen) bricked

As of this month, I have two out of my fleet of three *Google Home Mini (Gen 1)*s out of commission. Neither will boot; one slows a lonely green LED when the reset button is hit, the other nothing at all (and pulls massive power from USB while it boots).
There is a [truly MASSIVE thread](https://www.googlenestcommunity.com/t5/Speakers-and-Displays/HOME-mini-UNRESPONSIVE-thread-quot-Google-home-mini-4-dots-stuck-problem/m-p/558790/page/209876543210) about this over on the Google Nest forum. The *TL;DR* is this: if you contact Google about this issue, they will stall and give you the runaround. Eventually if you persist, they will get you to confirm the purchase date of the device(s), at which point they will either send you a new device or close the conversation with __"[it is|they are] out of warranty"__. The speculation on the forums is that Google are remote-bricking these devices as they reach their warranty period. This truly saddens me but given the well-documented Google anti-enthusiasm for long-term product support, it makes perfect sense. These devices acheived their aim of massive market penetration and have become almost-indispensable around my house, with the excellent Spotify and Chromecast integrations being used multiple times per day. The very low purchase price (indeed *$free* in one case) made it a no-brainer to dot (hah!) them around the place. The only working *Home Mini* left is a little newer. How much longer will it survive? I can't imagine there is any way to block it from receiving that remote kill-code from the mothership without completely nerfing its internet access, so it's just a ticking time bomb now. There's also a highly visible lack of supply for the obvious replacement, the *Nest Mini (Gen 2)*. I suspect Google is letting these evaporate without replacement, so they can introduce a *Gen 3* model, considerably more expensive, for all the people who they've locked into the Google Nest/Home ecosystem and who now, funnily enough, need to replace a fleet of devices. Damn. Draw your own conclusions about how Google's tracking with _"Don't Be Evil"_ at this point, as tonnes of still-useful electronics make their way into landfill.

Sunday, 8 September 2024

UnSAFe at any speed

I first encountered the [Scaled Agile Framework for enterprise](https://scaledagileframework.com/), (from here-on referred to as SAFe) in 2014, when the large enterprise I was working for decided it was what was needed in order to ship solutions faster. (*Spoiler:* it wasn't, it didn't help in the slightest, it made us considerably slower, at considerable expense). I'll let you peruse their website at your leisure, but before you go, remember the tenets of the [Agile Manifesto](https://agilemanifesto.org/) (emphasis mine): - Individuals and interactions over *processes* and tools - Working software over comprehensive *documentation* - Customer collaboration over contract negotiation - Responding to change over *following a plan* Now look at this, the ***SAFe 6.0*** I-don't-even-know-what-to-call-it diagram:
All I see is prescriptive *processes*, *documentation* and *plans*. You don't "do" agile development by signing up for expensive certifications that basically allow you to continue to ship 4-times-a-year but call yourself an agile workplace when you're recruiting. You also won't fool *anyone* which half a clue during the recruitment process. Just one more example. This is grabbed from a PDF from [Accenture](https://sai2.wpengine.com/wp-content/uploads/delightful-downloads/2020/01/Key_Accenture_Learning_on_Scaled_and_Distributed_Agile_August-18-for-SAFe.pdf), who are [fully on-board with SAFe](https://scaledagile.com/case_study/accenture/), I suspect because: - Acronym - Includes the word Agile - You can get certified for it (at great expense) - It seems to make sense if you don't look at it too closely - It actually makes you go slower than before (more billable hour$) Ready? Here we go. This is how easy it is to "integrate" agile and waterfall:
So simple! It's super-cool that they like, don't have any interdependencies whatsoever! So clean!

Saturday, 31 August 2024

Tesla Model 3 problems that the BYD Seal fixes

As stated at the time, after [having a Tesla Model 3 for 4 months](https://blog.themillhousegroup.com/2024/03/tesla-model-3-2022-standard-range-rwd.html), I won't be buying one. But what I may well get (or at least, lease), is the [*BYD Seal*](https://www.byd.com/eu/car/seal) instead - here's why: | Criteria | Tesla Model 3 2024 RWD Standard Range | BYD Seal Premium 2024 | | --------- | ----------- | -------- | | Door handles | Aero, but annoying and fiddly | Aero, but pop out when needed | | Instrument binnacle | None | Yes, 10.25" panel | | HUD | No | Yes | | Indicator stalks | None | Yes, normal | | Rear entertainment screen | Yes | No | | Roof | Glass | Glass, silver plated, shade insert | | Supercharger access | Yes | Yes | | Manufactured in | China | China | | Apple CarPlay | No | Yes, wireless | | Qi charging pads | 1 | 2 | | 0-100km/h | 6.1 sec | 5.9 sec | | V2L | No | Yes | Yep, it fixes just about everything I disliked about the (pre-Highland) Model 3, plus what they "improved" in the Highland refresh, and it costs less to boot. Nice.

Sunday, 28 July 2024

Next.js Server Actions - how do I send complex data

[Next.js](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#forms) has some extraordinary integration between front- and back-ends. It takes some thinking about, but the thing that stumped me the longest was getting relatively complex objects (i.e. not just simple strings) across the "Server Actions" boundary. ## The Rules There are lots of [rules](https://react.dev/reference/rsc/use-server#serializable-parameters-and-return-values) about __what__ you can send. Basically, it has to be serializable, which makes sense. But *how* do you send these things? Unfortunately, most of the documentation limits itself to [trivially-simple](https://react.dev/reference/rsc/use-server#server-actions-in-forms) examples; like [a single simple string](https://react.dev/reference/react-dom/components/form#handle-form-submission-with-a-server-action) My specific use case was trying to get an object that contained an array of very simple objects across the boundary: ``` type DTO = { id: number; foo: string; people: Person[]; } type Person = { name: string; age: number; } const dataToSend: DTO = { id: 123, foo: "bar", people: [ { name: "Tom", age: 12, }, { name: "Dick", age: 45, }, { name: "Harry", age: 78, }, ] ]; ``` While the order of the objects within the array was unimportant, it was crucial that the elements of the object could be rehydrated (for want of a better expression) correctly on the server side - i.e. with the `Harry`-is-`78` connection intact, guaranteed. ## On the client (version 1) This should not be too hard, I thought to myself. I mean, sending form contents was pretty-much the original "interactive internet" mechanism; `/cgi-bin` and all that old stuff. I found a useful page that explained a [decent mechanism](https://mattstauffer.com/blog/a-little-trick-for-grouping-fields-in-an-html-form/) to serialize arrays, which I implemented like this in my React form: ``` {dto.people.map((p, i) => ( <> ))} ``` I went down that rabbit hole for far too long, before realising ... I *can* just send a string - a **JSON** string! ## Client, version 2 This new version feels much more React-idiomatic, and does a lot less grubbing around in `form` minutae. It uses the `useFormState` hook as well as a `useState` to allow the client to snappily update while the user is doing data-entry. Then when it's submission-time, everything gets shipped off in the everything-old-is-new-again Server Actions kinda way (various validation and button-enablement niceties removed for simplicity): ``` "use client" const initialState = { message: '' } export const SelectionForm = ({ dto: DTO}) => { const [state, formAction] = useFormState(createOrUpdateServerAction, initialState); const [inFlightPeople, setInFlightPeople] = useState(dto.people); const onPeopleChanged = (newPeople: Person[]) => { setInFlightPeople(newPeople); } return (
// render the people, wire in the onPeopleChanged handler