Mar 192018

So, on Friday, I had a job to update some documentation.  Specifically, I had to update the code examples on a Confluence document.

No problem… or so I thought.  The issue I faced was that it seems the Confluence application is getting too clever for its own good.  Honestly, I’d be happier with a plain textarea which took some Wiki syntax such as Markdown… or heck… plain HTML!  I use WordPress on this blog here, and while the editor here isn’t bad, I’m thankful that going to the source editor is just a click away, as there’s some things the WYSIWYG editor can’t do well (inline code), or even at all (tables).

The editor in Confluence is much less polished.  Navigating with the arrow keys is an unpredictable experience, sometimes it moves by single lines, sometimes it jumps a page.  Sometimes, starting several lines deep in a code block, a single up-arrow will move you to the line above, sometimes it moves you to some line in a paragraph above the code block.  It’s an exercise in frustration.

Fine, I thought, I’ll just copy and paste the code into qvim.  Highlight… copy… paste… ohh brilliant, it’s now all stuffed onto one line!  Thankfully what I was editing, was JSON, so it’s real easy to re-format that, vim makes it real easy to pipe the buffer contents through an arbitrary external program such as python -m json.tool.  This lacked the flexibility to auto-format the JSON the way the code examples were formatted though, so I made a work-alike that made use of Python’s OrderedDict to sort the keys a bit more logically, and told json.dump to indent the code with 2-space indentation (this is how the existing examples were formatted).

Having done this, I thought I’d make mention to Atlassian about the issues with their editor.  I hit the Feedback link up the top of the page.  I pointed out the issues I was having.  In closing I also pointed out how sluggish their system was.  The desktop PC at work is a 8-core AMD Ryzen 7 1700 with 16GB of DDR4.  Not a slow machine.  Maybe it’s rose-coloured glasses, but I recall having a smoother editing experience with Microsoft Word for Windows 6.0 on my 33MHz 486/DX, which sported a whopping 8MB RAM.  Hot stuff back in 1994.  My present desktop does fine with LibreOffice, and this WordPress blog works fine in it, so I know it’s not my browser or hardware.  Yet Confluence struggles, on a PC that has 8 times the CPU cores, each running at nearly 10 times the clock speed, and with 2048 times the amount of RAM to boot.

I composed my feedback and sent it Friday afternoon.  I left the browser window open while I submitted the feedback, and went home.  This morning, I get in, enter my password to unlock the workstation, and see this:

Atlassian feedback … *still* sending after a whole week-end!

Yep, about 2kB of plain text has taken more than 50 hours to make its way from my desktop to their back-end servers.  Did a feral cat interrupt their RFC-1149 based Internet link?

Feb 132018

So, over the last few years we’ve seen a big shift in the way websites operate.

Once upon a time, JavaScript was a nice-to-have, and you as a web developer better be prepared for it to not be functional; the DOM was non-existent, and we were ooohing and ahhing over the de facto standard in Internet multimedia; MacroMedia Flash.  The engine we now call WebKit was still a primitive and quite basic renderer called KHTML in a little-known browser called Konqueror.  Mozilla didn’t exist as an open-source project yet; it was Netscape and Microsoft duelling it out together.

Back then, XMLHTTPRequest was so new, it wasn’t a standard yet; Microsoft had implemented the idea as an ActiveX control in IE5, no one else had it yet.  So if you wanted to update a page, you had to re-load the whole lot and render it server-side.  We had just shaken off our FONT tags for CSS (thank god!), but if you wanted to make an image change as the mouse cursor hovered over it, you still needed those onmouseover/onmouseout event handlers to swap the image.  Ohh, and scalable graphics?  Forget it.  Render as a GIF or JPEG and hope you picked the resolution right.

And bear in mind, the expectation was that, a user running an 800×600 pixel screen resolution, and connected via a 28.8kbps dial-up modem, should be able to load your page up within about 30 seconds, and navigate without needing to resort to horizontal scroll bars.  That meant images had to be compressed to be no bigger than 30kB.

That was 17 years ago.  Man I feel old!

This gets me thinking… today, the expectation is that your Internet connection is at least 256kbps.  Why then do websites take so long to load?

It seems our modern web designers have forgotten the art of how to pack down a website to minimise the amount of data needed to be transmitted so that the page is functional.  In this modern age of “pretty” web design, we’ve forgotten how to make a page practical.

Today, if you want to show an icon on a page, and have it fill the entire browser window, you can fire up Inkscape or Adobe Illustrator, let the creative juices flow and voilá, out pops a scalable vector graphic, which can be dropped straight into your HTML.  Turn on gzip compression on the web server, and that graphic will be on that 28.8kbps user’s screen in under 3 seconds, and can still be as big as they want.

If you want to make a page interactive, there’s no need to reload the entire page; XMLHTTPRequest is now a W3C standard, and implemented in all the major browsers.  Websockets means an end to any kind of polling; you can get updates as they happen.

It seems silly, but in spite of all the advancements, website page loads are not getting faster, they’re getting slower.  The “everybody has broadband” and “everybody has full-HD screens” argument is being used as an excuse for bloat and sloppy design practices.

More than once I’ve had to point someone to the horizontal scroll bar because the web designer failed to test their website at the rather common 1366×768 screen resolution of a typical laptop.  If I had a dollar for every time that’s happened in the last 12 months, I’d be able to buy the offending companies out and sack the web designers responsible!

One of the most annoying, from a security perspective, is the proliferation of “content distribution networks”.  It seems they’ve realised these big bulky blobs of JavaScript take a long time to load even on fast links.  So, what do the bright sparks do?  “I know… instead of loading it from one server, I’ll put it on 10 and increase my upload capacity 10-fold!”  Yes, they might have 1Gbps on each host.  1Gbps × 10 = 10Gbps, so the page will load at 10Gbps, right?

Cue sad tuba sound effect.

At my workplace, we have a 20Mbps Ethernet (not ADSL[2], fibre or cable; Ethernet) link to the Internet.  On that link, I’ve been watching the web get slower and slower… and I do not think our ISP is completely to blame, as I see the same issue at home too.  One where we feel the pain a lot, is Atlassian’s system, particularly Jira and Confluence.  To give you how bad they drink the CDN cool-aid, check out the number of sites I have to whitelist in order to get the page functional:

Atlassian’s JIRA… failing in spite of a crapton of scripts being loaded.

That’s 17 different hosts my web browser must make contact with, and download content from, before the page will function.  17 separate HTTP connections, which must fight with all other IP traffic on that 20Mbps Ethernet link for bandwidth.  20Mbps is the maximum that any one connection will do, and I can guarantee it will not reach even half that!

Interestingly, despite allowing all those scripts to load, they still failed to come up with the goods after a pregnant pause.  So the extra trashing of the link was for naught.  Then there’s the security implications.

At least 3 of those, are pages that Atlassian do not control.  If someone compromised for example; they could inject any JavaScript they want on the JIRA site, and take control of a user’s account.  Atlassian are relying on these third partys’ promises and security practices, to ensure their site stays secure, and stays in their (third party’s) control.  Suppose someone forgets to renew the domain subscription, the result could be highly embarrassing!

So, I’m left wondering what they teach these days.  For a multitude of reasons, sites should be blazingly quick to load, partly because modern techniques ought to permit vastly improved efficiency of content representation and delivery; and that network link speeds are steadily improving.  However it seems the reverse is true… why are we failing so badly?

Jan 302018

So, today I had a problem… I needed to solve a race condition in a test case for my workplace’s WideSky system.  The test case was meant to ensure that, if the AMQP broker crashed or was restarted, it would re-connect and resume operations as quickly as possible.

On my desktop (an 8-core AMD Rysen 7), the test case always passed.  On the CI server (a VM running on a dual-core Core i3), it failed.  I figured the desktop here was running too quickly for me to see the problem.  I needed a machine that ran more like the CI server to see the problem.

Looking around, I couldn’t see any way to reliably slow down QEMU, KVM or VirtualBox… but I do remember one old project from the mid-late 90s that could: Bochs.

Bochs in action… emulating a P4 Prescott on a Rysen 7

Turns out, far from what it could do back in 1998 when it was strictly a 386 emulator (and a slow one at that!) it now has AMD64 emulation capabilities.  Thus, I can run the software stack inside this VM, and have it throttle the CPU speed down so that hopefully, the problem arises.  The first problem I needed to solve was trying to get the network running.  We have a PXE boot server which can serve up Ubuntu, no problem.  I just needed to bridge the Bochs VM onto the network somehow.

I already have bridge interfaces configured on my two physical network interfaces, and these work great with KVM.  Sadly, Bochs is rather primitive in what it supports… tap-mode networking just did not work, it complained that tap0 was not “running” even if created beforehand by iproute2, but I did find I could bind it directly to one of the enslaved network interfaces (enp36s0.200; yes, a VLAN interface).

e1000 worked for network booting, but then Ubuntu couldn’t retrieve an IP address for whatever reason. ne2k is working fine, and presently, I have the VM installing.  To make it network bootable, you need a boot ROM image, which you can download from the iPXE rom-o-matic service.  The magic PCI IDs you need are 10ec 8029 for ne2k, or (if it gets fixed) 8086 10de for e1000.

The following is my Bochs config file:

# configuration file generated by Bochs
plugin_ctrl: unmapped=1, biosdev=1, speaker=1, extfpuirq=1, parallel=1, serial=1, gameport=1, ne2k=1
config_interface: textconfig
display_library: x
debug: action=report
memory: host=2048, guest=2048
romimage: file="/usr/share/bochs/BIOS-bochs-latest", address=0x0, options=none
vgaromimage: file="/usr/share/bochs/VGABIOS-lgpl-latest"
boot: disk, network
floppy_bootsig_check: disabled=0
# no floppya
# no floppyb
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=disk, path="/tmp/wstest.raw", mode=flat, cylinders=0, heads=0, spt=0, model="Generic 1234", biosdetect=auto, translation=auto
ata0-slave: type=none
ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15
ata1-master: type=none
ata1-slave: type=none
ata2: enabled=0
ata3: enabled=0
optromimage1: file=none
optromimage2: file=none
optromimage3: file=none
optromimage4: file=none
optramimage1: file=none
optramimage2: file=none
optramimage3: file=none
optramimage4: file=none
pci: enabled=1, chipset=i440fx, slot1=ne2k, slot2=cirrus
vga: extension=cirrus, update_freq=5, realtime=1
cpu: count=1:1:1, ips=40000000, quantum=16, model=p4_prescott_celeron_336, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0
print_timestamps: enabled=0
port_e9_hack: enabled=0
private_colormap: enabled=0
clock: sync=none, time0=local, rtc_sync=0
# no cmosimage
# no loader
log: -
logprefix: %t%e%d
debug: action=ignore
info: action=report
error: action=report
panic: action=ask
keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none
mouse: type=ps2, enabled=0, toggle=ctrl+mbutton
speaker: enabled=1, mode=system
parport1: enabled=1, file=none
parport2: enabled=0
com1: enabled=1, mode=null
com2: enabled=0
com3: enabled=0
com4: enabled=0
ne2k: enabled=1, mac=fe:fd:de:ad:be:ef, ethmod=linux, ethdev=enp36s0.200, script=/bin/true, bootrom="/tmp/10ec8029.rom"

Create your hard drive image using qemu-img, then run bochs -f yourfile.cfg and it should, hopefully, work.

Jan 132018

Part of my day job involves being the technical contact for their website, which means we get lots of offers from people offering to put us on the “first page of Google”.

Hmm, last time I checked, the first page of Google was, strangely, Google.  Somehow, I don’t think they outsource their SEO strategy to get there… they wrote the bloody code!

These emails go straight to Spamcop generally… and they send nastygrams to the people hosting the email servers they used.  In some cases, I’ve taken the extraordinary step of blocking frequently abused hosts.

# Block Centrilogic and SmartMailer because they don't act on spam reports.
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
# Block OVH because they don't act on spam reports.
# List taken from
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited
-A INPUT -s -p tcp --dport 25 -j REJECT --reject-with icmp-host-prohibited

That is not an exhaustive list.  Sorry to people who use OVH for hosting and were trying to contact VRT/CETA legitimately, but OVH have shown themselves to be grossly incompetent with regard to management of network abuse.  Centrilogic/SmartMailer are more recent additions.

Of course, they keep trying, and thankfully, it takes longer for them to write the email than it does for me to deal with it. This doesn’t stop them claiming little gems like this:

Note: We are not spammers and are against spamming of any kind. If you are not interested then you can reply with a simple “NO”.

Errm, hate to disagree (actually no, in this case, I love disagreement)… but a few points:

  1. Your sending me an unsolicited content…
  2. … without my consent… (no listing in domain registration or scraping from a website is not consent)
  3. … that is advertising a paid-for service or otherwise something you’re hoping to make money from…
  4. … by electronic messaging.

That by definition is an Unsolicited Commercial Email… aka SPAM.  If you claim to be an Australian business, you better have a look at this.  If your ISP is complaining that you are abusing their services by sending spam, then perhaps you need to realise the people you are contacting are not interested!  You have your NO.

Nov 062017

So, I’m doing some development on a Cortex M3-based device with access to only one serial port, and that serial port is doing double-duty as serial console and polling a Modbus energy meter.  How do I get log messages out?

My code actually implements the stubs to direct stdout and stderr transparently to the serial port, however this has to go to /dev/null when the Modbus port is in use.  That said, _write_r still gets called, in my code, it is possible to set a breakpoint inside the _write_r function when traffic is identified for the console.

As it happens, gdb can be told to not only break there, but to perform a series of actions.  In my case serial.c:659 is the file and line number inside an if branch that handles the console code.  Setting up gdb to print this data out requires the following statements:

(gdb) break serial.c:659
(gdb) commands
Type commands for breakpoint(s) 3, one per line.
End with a line saying just "end".
>set ((char*)buf)[cnt] = 0
>print (char*)buf
(gdb) c

The result:

Breakpoint 3, _write_r (ptr=, fd=0, buf=0x200068c0, cnt=78) at /home/stuartl/vrt/projects/widesky/hub/hal/src/serial.c:659
659			if (serial_console_target.port) {
$51 = 0x200068c0 "/home/stuartl/vrt/projects/widesky/hub/hal/demo/main.c:226 Registration sent\r\n"

Breakpoint 3, _write_r (ptr=, fd=0, buf=0x200068c0, cnt=46) at /home/stuartl/vrt/projects/widesky/hub/hal/src/serial.c:659
659			if (serial_console_target.port) {
$52 = 0x200068c0 "Received NTP time is Mon Nov  6 04:13:41 2017\n"

Breakpoint 3, _write_r (ptr=, fd=0, buf=0x200068c0, cnt=2) at /home/stuartl/vrt/projects/widesky/hub/hal/src/serial.c:659
659			if (serial_console_target.port) {
$53 = 0x200068c0 "\r\n"

Breakpoint 3, _write_r (ptr=, fd=0, buf=0x200068c0, cnt=89) at /home/stuartl/vrt/projects/widesky/hub/hal/src/serial.c:659
659			if (serial_console_target.port) {
$54 = 0x200068c0 "/home/stuartl/vrt/projects/widesky/hub/hal/demo/main.c:115 Registration timeout: 30 sec\r\n"

Breakpoint 3, _write_r (ptr=, fd=0, buf=0x200068c0, cnt=83) at /home/stuartl/vrt/projects/widesky/hub/hal/src/serial.c:659
659			if (serial_console_target.port) {
$55 = 0x200068c0 "/home/stuartl/vrt/projects/widesky/hub/hal/demo/main.c:130 Select source address:\r\n"

Breakpoint 3, _write_r (ptr=, fd=0, buf=0x200068c0, cnt=53) at /home/stuartl/vrt/projects/widesky/hub/hal/src/serial.c:659
659			if (serial_console_target.port) {
$56 = 0x200068c0 " ? fdde:ad00:beef:0:0:ff:fe00:a400 Pref=Y Valid=Y\r\n"

Breakpoint 3, _write_r (ptr=, fd=0, buf=0x200068c0, cnt=15) at /home/stuartl/vrt/projects/widesky/hub/hal/src/serial.c:659
659			if (serial_console_target.port) {
$57 = 0x200068c0 " ? Selected\r\n"

---Type  to continue, or q  to quit---
Breakpoint 3, _write_r (ptr=, fd=0, buf=0x200068c0, cnt=78) at /home/stuartl/vrt/projects/widesky/hub/hal/src/serial.c:659
659			if (serial_console_target.port) {
$58 = 0x200068c0 "/home/stuartl/vrt/projects/widesky/hub/hal/demo/main.c:226 Registration sent\r\n"

Not as nice as having a dedicated port, but better than nothing.

Oct 102017

So, over the last few years, computing power has gotten us to the point where remotely operated aerial vehicles are not only a thing, but are cheap and widely available.

There are of course, lots of good points about these toys, lots of tasks in which they can be useful.  No, I don’t think Amazon Prime is one of them.

They come with their risks though, and there’s a big list of do’s and don’ts regarding their use.  For recreational use, CASA for example, have this list of rules.  This includes amongst other things, staying below 120m altitude, and 30m away from any person.

For a building, that might as well be 30m from the top of the roof, as you cannot tell if there are people within that building, or where in that building those people reside, or from what entrance they may exit.

I in principle have no problem with people playing around with them.  I draw the line where such vehicles enter a person’s property.

The laws are rather lax about what is considered trespass with regards to such vehicles.  The no-brainer is if the vehicle enters any building or lands (controlled or otherwise) on any surface within the property.  A big reason for this is that the legal system often trails technological advancement.

This does not mean it is valid to fly over someone’s property.  For one thing, you had better ensure there is absolutely no chance that your device might malfunction and cause damage or injury to any person or possession on that property.

Moreover, without speaking to the owner of said property, you make it impossible for that person to take any kind of preventative action that might reduce the risk of malfunction, or alert you to any risks posed on the property.

In my case, I operate an amateur radio station.  My transmitting equipment is capable of 100W transmit power between 1.8MHz and 54MHz, 50W transmit power between 144MHz and 148MHz, and 20W transmit power between 420MHz and 450MHz, using FM, SSB, AM and CW, and digital modes built on these analogue modulation schemes.

Most of my antennas are dipoles, so 2.2dBi, I do have some higher-gain whips, and of course, may choose to use yagis or even dish antennas.  The stations that I might choose to work are mostly terrestrial in nature, however, airborne stations such as satellites, or indeed bouncing off objects such as the Moon, are also possibilities.

Beyond the paperwork that was submitted when applying for my radio license (which for this callsign, was filed about 9 years ago now, or for my original callsign was filed back in December 2007), there is no paperwork required to be submitted or filled out prior to me commencing transmissions.  Not to the ACMA, not to CASA, not to registered drone operators in the local area, not anybody.

While I’ve successfully operated this station with no complaints from my neighbours for nearly 10 years… it is worth pointing out that the said neighbours are a good distance away from my transmitting equipment.  Far enough away that the electromagnetic fields generated are sufficiently diminished to pose no danger to themselves or their property.

Any drone that enters the property, is at risk of malfunction if it strays too close to transmitting antennas.  If you think I will cease activity because you are in the area, think again.  There is no expectation on my part that I should alter my activities due to the presence of a drone.  It is highly probable that, whilst being inside, I am completely unaware of your device’s presence.  I cannot, and will not, take responsibility for your device’s electromagnetic immunity, or lack thereof.

In the event that it does malfunction though… it will be deemed to have trespassed if it falls within the property, and may be confiscated.  If it causes damage to any person or possession within the property, it will be confiscated, and the owner will be expected to pay damages prior to the device’s return.

In short, until such time as the laws are clarified on the matter, I implore all operators of these devices, to not fly over any property without the express permission of the owner of that property.  At least then, we can all be on the same page, we can avoid problems, and make the operation safer for all.

Sep 132017

So it seems that the Same Sex Marriage postal votes are finally being sent around.  This is good news in a way: we get to have a say in the matter and hopefully put the matter to bed one way or the other.

No more umming and arring, which I’m frankly sick and tired of, as I feel there are more pressing needs.  Yes, it’s important, but we have two nuclear armed crazy-haired nutters at opposite sides of the Pacific ready to light the planet up like a neon light!

I’m in support of the legislation changing by the way.  I think same-sex couples are entitled to the same rights, and it wasn’t that long ago that marriage was restricted to those not just of the opposite sex, but also had to be of the same “race” and religion.

To quote a song by John Williamson: “They’d chain you up to a boab tree, for kissing an Aborigine!”

So to my way of thinking, society changes.  What was taboo yesterday, we don’t think twice about today.  An Anglican family sending their children to a Catholic school would be heresy years ago… but for my sister and I, that is exactly what happened.  The world doesn’t seem to have imploded as a result.

The status quo regarding marriage is a hang-over from when the Church was the only place where you could get married, and ruled with far greater weight than today.  This is no longer the case, thus it no longer makes sense to hang onto this concept.

Anyway… my opinions on this are beside the point.  In spite of the good intentions, it looks as if the postal vote envelopes overlook one serious flaw: with sufficient light they are see through!

So my proposal: Put a thin piece of card in with the postal vote to block the light.  Not thick enough that it might cause the envelope to jam or interfere with sorting equipment, just opaque enough to prevent the contents being visible.  A small piece of black paper would likely do the job nicely.

Sure the ABS will have a little bit more paper to dispose of, but then at least, our votes are secure and people can’t “manipulate” the vote by snooping on sealed envelopes and discard the ones that disagree with their opinions.  At least then we won’t be wasting $122M.

Sep 132017

I have a virtual machine that I set up as a secondary DNS server which runs OpenBSD 6.1.  Today logging into it, I noticed system messages were piling up in /var/mail because I hadn’t configured the mail server to deliver those messages.  Setting up OpenSMTPD was no trouble, but then I had the old mail (thankfully not much) that was still to be delivered.

There are a couple of solutions out there, written in Perl, Python and PHP (urgh!).  I don’t have Python on this box, and the Perl one didn’t seem to work with the mailbox.  So I cooked up my own:


for file in "$@"; do
        grep -n '^From ' ${file} | {
                while read line; do
                        cur=$( echo "${line}" | cut -f 1 -d: )
                        if [ "${prev}" != "${cur}" ]; then
                                sed -ne "${prev},$(( ${cur} - 1 )) p" ${file} > ${prev}.eml

If there’s a line in your email body starting with “From “, it may get confused, but it was good enough for the messages that OpenBSD’s daemons send me. I was then able to pipe these individually into sendmail -t to send them on their way.

Sep 102017

… Come now, Microsoft… are you telling me your operating system just makes up its own error codes?  How can the error code be “unknown”?  The computer is doing what you told it to do!

Moreover, why can’t you fix your broken links?  Clearly the error I’m getting is not any of the ones you’ve listed, so why even offer them as suggestions?

Sep 072017

This is a quick brain-dump, as doing a quick Google search did not help, taking me to a mailing list thread I had posted about 2.5 years ago.  I swear there’s a song in that… something about the dreaded Google Echo.

Anyway, unlike that last occasion where the modem wasn’t even seen at all (and no, I didn’t solve it, we stuffed a 3G dongle in the case in the end), this time around, ModemManager sees it.  It just so happens that nmtui doesn’t do wireless broadband. These were the magic commands.

root@wsg-74fe481fe117:~# nmcli connection edit type gsm con-name telstra-nextg

===| nmcli interactive connection editor |===

Adding a new 'gsm' connection

Type 'help' or '?' for available commands.
Type 'describe [.]' for detailed property description.

You may edit the following settings: connection, gsm, serial, ipv4

From here, we need to set the APN, telstra.internet.

nmcli> set gsm.apn telstra.internet

Having done that, we give the configuration a last check before saving it:

nmcli> print all
… lots of settings …
nmcli> save persistent
Saving the connection with 'autoconnect=yes'. That might result in an immediate activation of the connection.
Do you still want to save? (yes/no) [yes] (enter)
Connection 'telstra-nextg' (57c78d91-4a66-475b-8843-2cba590fbcfd) successfully saved.
nmcli> quit