Planet Linux Australia

,

Andrew Pam — Education Ministry inquiry the latest sign NZ’s Official Information Act isn’t working

<https://theconversation.com/education-ministry-inquiry-the-latest-sign-nzs-official-information-act-isnt-working-293100>

"A leaked video of apparent attempts at the Education Ministry to circumvent
the Official Information Act (OIA) has advocates for open and transparent
government worried – but possibly not surprised.

Andrew Pam — Letters from an American: 1 October 25, 2026

https://heathercoxrichardson.substack.com/p/october-25-2026

"And so, here we are in the last days of the 2026 midterm campaign.

As the political winds fill Democrats’ sails and they are picking up momentum,

Andrew Pam — Local utility gets green light to replace remote town’s diesel and gas power with a solar-battery hybrid

<https://reneweconomy.com.au/local-utility-gets-green-light-to-replace-remote-towns-diesel-and-gas-power-with-a-solar-battery-hybrid/>

"The federal EPBC has waved through one of a series of renewable energy
projects being built to wean Western Australia’s remote towns off diesel fuel
generation and power them with various combinations of solar and-or wind and

Andrew Pam — After a decade, the UN’s Antonio Guterres is stepping down. His legacy will be mixed, at best

<https://theconversation.com/after-a-decade-the-uns-antonio-guterres-is-stepping-down-his-legacy-will-be-mixed-at-best-291974>

"In his farewell address to the United Nations General Assembly last week,
António Guterres reminisced about his time as secretary general.

Andrew Pam — ‘The rules have changed’: has Russia begun to order assassinations on US soil?

<https://www.theguardian.com/world/2026/sep/20/the-rules-have-changed-has-russia-begun-to-order-assassinations-on-us-soil>

"Roman Dobrokhotov had been content enough for his appearance at a conference
in Washington DC this week to be publicised, although it was not something he
would normally risk in Europe. The dissident Russian journalist was named

Andrew Pam — We love TV reno shows. Could they help us build more sustainable homes?

<https://theconversation.com/we-love-tv-reno-shows-could-they-help-us-build-more-sustainable-homes-285249>

"Australians love a good reno.

Whether they revolve around a modest fixer-upper or a mansion makeover,

Tim Riley Continuations 2026/43: Perplexing inconsistency

  • Work continues on closing all our open parentheses in the lead-up to the next Hanami release.

  • I started this week fixing a regression in our body params logging. Thanks @Drowze for reporting it! In response to some good feedback from Paweł, I also filed an issue about possibly making body params logging a configurable thing in the future.

    While I was working in this space, I decided it was time we expanded Dry Logger’s log filtering, including substring matching and first-class regex support. This is close to being ready to go, I just need to reply to some good feedback on the PR from Michael.

  • Aaron voiced a perplexing inconsistency in how predicate functions are used as keyword arguments between Dry Types and Dry Schema, and then went and made the change to make them consistent across the two. Thanks Aaron! We even got the docs changed too; this is something we haven’t developed good just-in-time habits around yet, instead putting everything together just before release.

  • My main area of focus this week was Andrea’s improvements to Hanami’s error screens, with a helpful “not found” error being the first example. To start with, I took Andrea’s work to expose full route details at runtime and made it a first-class part of Hanami Router (plus the commensurate change to Hanami CLI). Then I tweaked and merged the error page itself, along with making the slice (and therefore router) available on the underlying error. Thanks Andrea for setting all of this up!

    With that all in place, I’ve been spending some time looking at the bigger picture of how we display our error screens and offer actions the user can take to fix the issue. It’s all coming together, and I hope to have a round of revisions to share soon.

  • Earlier this week we saw GitHub update the Ubuntu for their CI runners, and a related CI errors arose. I fixed all six issues that came up, mostly JRuby and Postgres-related.

Andrew Pam — When friends become good neighbours: the Melbourne families who transformed one home into three townhouses

<https://www.theguardian.com/artanddesign/ng-interactive/2026/sep/20/shared-co-housing-communal-living-melbourne-families-transform-one-home>

"Melbourne architect Chris Gilbert had run out of eggs. He texted a neighbour
and within minutes, he says, their child arrived with some. “Then I made
brownies and my kids took some over to the neighbours’ house.”

Andrew Pam — The farmer growing a future under Ghana’s trees

<https://www.positive.news/society/the-farmer-growing-a-future-under-ghanas-trees/>

"On the hills of the Atewa Range in south-eastern Ghana lies a jewel of
tropical forest. A surviving fragment of the swathes of trees that once cloaked
the region, it plays host to a whole array of precious creatures, from eagle

Linux Australia — 2026-10-21 Council Meeting Minutes

1. Meeting overview and key information

Present

  • Joel Addison (President)
  • Neill Cox (Vice-President)
  • Russell Stuart (Treasurer)
  • Jonathan Woithe (Secretary)
  • Elena Williams (Council)
  • Harrison Oates (Council)
  • Miles Goodhew (Council)

Apologies 

  • None

Not Present

  • None

Meeting opened at 20:05 AEDT (UT+1100) by Joel  and quorum was achieved.

Minutes taken by Jonathan.

2. Log of correspondence

  • [LACTTE] From ACNC, 8 Oct 2026: News from the ACNC. No action required.
  • [LACTTE] From Russell Coker, 8 Oct 2026: Suggestion to use linux.au domain to provide short names for subcommittees.
  • [LACTTE] From Russell Coker, 8 Oct 2026: Wiki for LUGs?
  • [Private communication] From Lloyd Handyside, 8 Oct 2026: Follow up to discussion about a proposed grant. No action required from LA at this point.
  • [LACTTE] From Duste, 8 Oct 2026: Questions about the LA grant program. Jonathan has responded.
  • [LACTTE] From Guoyi Zhang, 8 Oct 2026: Thanks and appreciation for the awarding of the Willi26 grant. No action required.
  • [LACTTE] From Fraser Tweedale, 8 Oct 2026: Apology for missing the Subcommittee review at the 7 Oct 2026 Council meeting. EO2027 update provided (see “Items for noting”).
  • [LACTTE] From Kathy Reid, 10 Oct 2026: Resignation from Media and Comms subcommittee.
  • [LACTTE] From Bitwarden, 10 Oct 2026: Verification code. This was for Joel.
  • [LACTTE] From Guoyi Zhang, 10 Oct 2026: Acknowledging receipt of grant funds.
  • [LACTTE] From Owen Cronin, 12 Oct 2026: Looking for assistance with a software project. Jonathan has responded.
  • [Secretary] From Mux, 13 Oct 2026: Notification of changes to terms and conditions.
  • [LACTTE] From Wende Reid, 13 Oct 2026: Request for information and support to migrate a laptop used to run a business to Linux. Jonathan has responded.
  • [Secretary] From NGOs1, 13 Oct 2026: More spam.
  • [LACTTE] From Fraser Tweedale, 15 Oct 2026: authorisation request for EO2027 related payment. THis has been dealt with,
  •  [LACTTE] From Digital Pacific, 16 Oct 2026: renewal reminder for linux.org.au. In hand (admin team).
  • [LACTTE] From Digital Pacific, 18 Oct 2026: tax invoice for linux.org.au renewal. Automatic payment attempt will be made on 2 Nov 2026.
  • [LACTTE] From Miguel Jacq, 19 Oct 2026: Grant application for KEEN project. Community consultation opened 19 Oct 2026, council evaluation due on 4 Nov 2026. Jonathan has followed up with some suggestions to strengthen the application. Miguel has subsequently withdrawn the grant application. He intends to resubmit after filling in more detail.
  • [LACTTE] From Kathy Reid, 19 Oct 2026: Australian government consultation on proposed gTLDs. Information only, no action required.
  • [Secretary] From OSI, 19 Oct 2026: OSI newsletter.
  • [LACTTE] From Auditor, 19 Oct 2026: Confirming setup of new arrangements.
  • [Secretary] From Laxman Acharya, 20 Oct 2026: a Nepalese programmer seeking travel funding to the Junction 2026 conference in Helsinki. Jonathan has responded.

3. Subcommittee reviews

None.

4. Items for discussion

Use of linux.au domain to provide short names for subcommittees

  • Raised by Russell Coker on 8 Oct 2026.
  • Russell Coker has suggested f.linux.au be used by Flounder.
  • Council is concerned about the potential difficulties in managing subdomains of this nature.
  • Linux Australia currently provides domains for services the organisation runs or events auspiced by Linux Australia. This may be reviewed in future but for now we’re not looking to give out domains for other purposes.
  • Jonathan to respond.

Wiki for LUGs

  • Raised by Russell Coker on 8 Oct 2026.
  • Is there a need for a wiki for all LUGs?
  • Should the wiki.luv.asn.au instance be generalised to create an LA LUG wiki?
  • There is a reluctance to take on responsibility of running a generic wiki due to maintenance overheads.
  • LA is happy to provide a VM for affiliated groups at no charge to use as a wiki host if required.
  • It is noted that the LUV wiki can be managed how LUV wishes to. If they would like to generalise it for other Open Source groups then this is fine.
  • The level of demand for space on a common wiki among LA-affiliated groups is not known, but appears to be low.
  • Jonathan to respond.

Media and Comms subcommittee membership

  • Kathy Reid is stepping down from the Media and Comms team.
  • A new media strategy is possibly required.

MOTION: that Linux Australia thanks Kathy for her time leading the Media and Comms subcommittee over the recent years.
Moved: Joel
Seconded: Neill
Motion passed

Joel will respond to Kathy.

  • The Media and Comms subcommittee now has no members. Kathy is documenting processes and identifying what needs to be done. Council will review this when it becomes available and base the next steps on this.

Membership backlog

  • There is a large list of membership applications to work through (207 as of today).
  • Council will establish a standing agenda item to review applications which have been submitted in the previous fortnight.
  • Joel will establish CiviCRM permissions required to facilitate this.
  • Once this is done the backlog will be worked through. Elena has done a first pass at evaluating the legitimacy of the queued applications.
  • Neill outlined the workflow used to approve membership.
  • Consider adding a question to the membership form to ask for an applicant’s connection to the community. Due to linkage requirements back to CiviCRM this may take a little time to establish.
  • Separately, the membership renewal process within CiviCRM needs to be revisited and fixed.
  • Joel to confirm when required permissions are in place.

5. Items for noting

EO2027 update (provided by Fraser on 8 Oct 2026)

Registrations

  • On track. 30 paid up, ~$17k.
  • Early birds will close Oct 18; social media and email announcements planned.

Sponsorship

  • $65,300 agreed (~50% of target); ~$35k paid.
  • Many more approaches and a few conversations going on, but I’m not yet comfortable with where we’re at.
  • AARNET x QUT planning has kicked off (later than ideal but no known blockers, and we have access to campus network as a fallback).
  • **ASK**: Does anyone have sponsorship contacts at Google, Meta, Microsoft, Atlassian or other “heavy hitters”, they could share? [Joel has responded]

Promotion

  • First keynote announcement soon! (3 locked in, still seeking 1 or 2 locknote speakers)
  • We are sponsoring DDD Brisbane (developer conf, 5 Oct). We get s a logo/link on web, social media shout-outs, flyers in every swag bag, and attendee contact details (opt-in), plus two tickets to attend. We’ll also issue a discount code (EB prices), to entice, and let us measure ROI.
  • Lots and lots of getting out to meetups and confs.

Program

  • Very strong finish to CFP – 163 proposals in total.
  • Session Selection Committee expected to finalise program weekend of 17 Oct.
  • Speaker and keynote gift planning underway.
  • 18 volunteer sign-ups so far (~50% of goal).

Publication of Council minutes

  • All remaining Council minutes (from 14 Jun 2026 through to 7 Oct 2026) have been placed on the website as drafts.
  • Neill is happy with them.
  • Joel to review and publish the minutes (expected to be next week).

Meet.jit.si meeting time limit

6. Other business

  • The ASIC 490 form has been submitted with the changes to Council. It has been sent with registered mail.
  • Everything is up to date at ACNC.
  • Wise are insisting on a proof of business address. This is being handled by Russell.
  • Confirm with the admin team that today’s RCE vulnerabilities are being dealt with.
  • Harrison has put together a PR to use Australian Senate proportional voting for OCM positions.

7. In camera

  • None

8. Action items

8.1 Completed Items

8.2 Carried Forward

 

Meeting closed at 20:58 UT+11:00

Next meeting is scheduled for 2026-11-04 at 20:00 AEDT (UT+1100)

The post 2026-10-21 Council Meeting Minutes appeared first on Linux Australia.

Linux Australia — 2026-10-07 Council Meeting Minutes

1. Meeting overview and key information

Present

  • Joel Addison (President)
  • Neill Cox (Vice-President)
  • Russell Stuart (Treasurer)
  • Jonathan Woithe (Secretary)
  • Elena Williams (Council)
  • Harrison Oates (Council)
  • Miles Goodhew (Council)

Apologies 

  • None

Not Present

  • None

Meeting opened at 20:04 AEDT (UT+1100) by Joel  and quorum was achieved.

Minutes taken by Jonathan.

2. Log of correspondence

  • [LACTTE] Spams and scams from various senders. All deleted.
  • [Secretary] linux-aus post approvals needed. Joel is handling these.
  • [Secretary] linux-aus moderator requests awaiting action. Joel is handling these.
  • [LACTTE] From Zoom, 24 Sep 2026: clarification about what Zoom Scheduler is and what is being changed. No action required.
  • [LACTTE] From Harrison, 24 Sep 2026: following up to PLUG about AI submission. Awaiting response from PLUG.
  • [Secretary] From Dropbox, 25 Sep 2026: changes to Terms of Service and Privacy Policy. Joel to log in to keep the account alive.
  • [Secretary] From NGOs1, 27 Sep 2026: LA is now included on NGOs1 (a global directory of non-profit organisations). Includes links to self-administer the listing, unsubscribe from emails, etc. Will consider it as spam and filter it.
  • [LACTTE] From Bob Ansaym 28 Sep 2026: declipped audio (by proprietary AI tool) from an LCA2013 talk is available to LA if we want it. No response required or expected. Do we want to archive this? Joel will follow up in due course.
  • [LACTTE] From Jack Skinner, 29 Sep 2026: pyCon AU 2027 wrapup steps. Russell to respond to finance-related questions.
  • [LACTTE] From Julia Topliss, 30 Sep 2026: revised DrupalSouth budget provided, requested comments from Joel.
  • [LACTTE] From Russell Stuart, 1 Oct 2026: follow up to LA W-8 form request for EO2027.
  • [LACTTE] From Russell Coker, 1 Oct 2026: answering Russell Stuart’s questions about luv.asn.au.
  • [LACTTE] From Amazon, 1 Oct 2026: AWS web services billing statement.
  • [Secretary] From Russell Stuart, 1 Oct 2026: forwarded advice from ACNC about reporting requirements. There is no requirement to submit reports for years prior to registration. A “system error” was responsible for the notification suggesting this was needed.
  • [LACTTE] From Lloyd Handyside: questions about what is acceptable for grant applications. Jonathan has sent an initial response, but a follow up is needed after discussion.
  • [Secretary] From NGOs1, 3 Oct 2026: follow up to previous message.
  • [LACTTE] From Digital Pacific, 4 Oct 2026. Linux.org.au domain renewal is due on 2 Nov 2026. Auto-renew is enabled so no specific action should be needed.
  • [LACTTE] From Tucows, 6 Oct 2026: 30 day reminder of linux.org.au expiry. No action required.
  • [Forwarded private communication] From Lloyd Handyside, 7 Oct 2026: additional information about proposed grant application.
  • [LACTTE] From Nathan Morrow, 7 Oct 2026: Joomla subcommittee update.
  • [LACTTE] From Jack Skinner, 7 Oct 2026: Wise has requested proof of business address, due by 21 Oct 2026.
  • [LACTTE] From Yifei Zhan, 7 Oct 2026: Flounder subcommittee update.

3. Subcommittee reviews

Drupal Subcommittee 8:05 pm – Julia Topliss

  • Office bearers in the new committee will be discussed in November to allow time for handover.
  • Update on payment to DA for Japan conference: waiting on response from Michael. Council will contact him again.
  • The subcommittee will contact LA council about some infrastructure that LA can handle financially on their behalf..

PyConAu 8:15 pm – Jack Skinner

I intend for this to be the last subcommittee update for PyCon AU 2026. Pending final payments below I intend to consider the “PyCon AU 2026” event subcommittee “wound up” as soon as payments are reconciled. That is, unless you need a formal vote between Nic, Sophie, and myself to confirm of course.

  • Since the last report, we have finalised and published the academic proceedings with nine participating authors now published: https://2026.pycon.org.au/cfp/academic-proceedings.
  • PyCon AU 2026 event activity has concluded and (almost) all subcommittee activities have now wound down.
  • Final expenses paid today now conclude all expected expenses/reimbursements
  • Two final payments are still expected:
    • Grant from BEDA – Contractually on 30 day payment terms though we received both confirmation of approval, and indication that sooner was likely. We continue to monitor.
    • Donation from Science Mob, estimated to be $6k – timeline unclear, pending ATO decision on their association wind up.
  • Nic has confirmed that Xero has been reconciled, transaction coding checked (and corrected), and has emailed outstanding questions for BAS & annual financial statements etc.
  • The shared google drive now has a new folder “Wind Down Documents” from the subcommittee including:
    • Contracts (Group ticket purchases, sponsorships, and other)
    • Tax invoices export from ticketing
    • A taxes report export from Pretix
  • A draft submission for the 2026/27 annual report is also in the shared drive. This will need updating depending on final payments received (see questions).
  • Questions relating to PyCon AU 2026 subcommittee (to be answered offline):
    1. For Russell: Regarding the Science Mob donation. Would you prefer to consider PyCon AU’s budget at a slightly higher projected loss (primarily in the annual report) and roll the eventual donation into LA’s general funds? My preference is to draw things to a close and have a clear line under the subcommittee’s activity. The difference is hopefully just in attribution (to which I’m not emotionally attached), and a slightly higher budget deficit from the subcommittee.
    2. Request an update to the subcommittee registry please (https://linux.org.au/get-involved/sub-committees/). Update to be:
      1. Remove “PyCon AU 2026” (and listed subcommittee members), and,
      2. Instead have “Subscribe for updates & announcements of future PyCon AU events”, and,
      3. A link with URL to be: https://2026.pycon.org.au/#subscribe (This is an embed to the LA hosted mailing list).
    3. At the conclusion of the subcommitee’s operations (within a few days), I would also like to remove public access from several drives and documents which are shared with “anyone with the link can access”. This is just a general security tidy up. Can council please confirm your processes for, task ownership of, and timelines for, making councils own copy of documents shared via this drive link, so that I can make my drive(s) private again.
    4. Similarly, there will be some handover documents and resources that will be useful to future PyCon AU event subcommittees, assuming one is submitted/approved in the future. Similar to question 3, can council confirm:
      1. whether or not Council is willing/able to be custodian of these handover documents (in lieu of publishing my personal contacts)
      2. The method by which we can provide a ‘package’ of useful ‘how to run a PyCon AU’ documents (such as budget templates, feedback survey notes, sponsorship lists, brand kit templates & resources etc). A zip in the above ‘Wind Down Documents’ folder would possibly be be simplest.

Everything Open 8:25pm – Fraser

  • No report.

Admin Team 8:25 pm

  • Apology.

Joomla Subcommittee 8:35 pm – Nathan Morrow

  • We’ve notified the community about the postponement which was generally received without complaint.
  • Otherwise it has been a fairly quiet few weeks on the event front. Expect to pick things up again over the next 2 months.
  • We’ve organised a Christmas function for the Victorian members of the user group to be held in early December.
  • A monthly newsletter has been running for the past 3-4 months as another communication avenue.

WordPress 8:40 pm – Wil Brown

  • WordCamp Sunshine Coast is being evaluated with a team of notionally 5. Across all events, numbers are down, so it’s unclear if the economics stack up.
  • A Sydney meetup was held, this time  with 10 attendees. The guest speaker was good and it was disappointing there were not more people present to hear them.
  • The number cap for the social event in November has been increased to 25.
  • Meetups BNE and Sunshine coast still have momentum. Others have numbers down. Will follow up in the new year to see what their plans for 2027 are.
  • AI is displacing entry level WordPress activity, which is affecting the community. Other pressures are also significant contributors, including a bleak tech industry outlook in general.
  • The community is dispersed with respect to communication channels. It’s hard to reach people.

Flounder 8:40 pm – Yifei Zhan

  • Koneko, the shared LoongArch test server, is actively used for fixing bugs in Debian and other FOSS projects, our new member Vladimir Petko (Debian JDK team) has been fixing bugs in OpenJDK for the upcoming Debian upload with it. It’s likely the only shared LoongArch machine in Australia/New Zealand.
    The shared 44 cores build server and GPU server are seeing increased use for Debian debugging and GPU/LLM experiments. The Intel Battlemage GPU is also actively used.
  •  We saw increased activity in our Matrix room around hardware and security related discussions, and a similarly increased level of engagements from the Fediverse.
  • We have upcoming lectures scheduled this Saturday and in the coming weeks for porting mainline Linux to used chromebooks and the current state of Debian Edu.
  • As usual we could benefit from more members. We have plans to promote cross-LUG collaboration for people in similar time zones, but attracting new people remains difficult.

4. Items for discussion

  • DrupalSouth Gold Coast 2027 budget
    • Revised budget was sent by Julia Topliss on 30 Sep 2026
    • The revisions have addressed most of the concerns.
    • MOTION: that LA establishes an Event Subcommittee for Drupal South 2027 as per the provided budget.
      Moved: Joel
      Seconded: Neil
      Motion passed.
  • Proposed OpenZFS adjunct event around EO2027
    • No update yet.
    • Joel will approach them soon.
  • Distributing Write the Docs tickets from sponsorship package
    • Unassigned tickets will be offered to the community via an Expression Of Interest and random draw.
    • Jonathan to draft a post inviting Expressions of Interest for two tickets.
  • Government submission on AI
  • Guide to Open Source in Australia
    • Miles contacted Felicity. She had considered applying for a grant but wasn’t sure whether she could make full use of it. Miles encouraged her to get in touch with Council if she had any questions.
    • Miles will extend an open offer for Council to help if there’s something we can do.
  • Council election
    • Update about proportional voting for OCM. Harison has a commit which seems to work. It uses Australian Senate rules. Joel will fork the election module into LA’s github and Harrison will upload the patch to that repository for further review.
    • Elena has reviewed past election dates since 2005 as per her message to lactte. All these elections have had the nomination period overlap with the Christmas and New Year holiday periods.
    • EO2027 is 20-22 Jan 2027 so AGM will be 20 Jan 2027.
    • Based on historical timelines, Elena suggests:
      • Nominations: 19 Dec 2026 – 3 Jan 2026 (16 days)
      • Election: 4 – 14 Jan 2026 (11 days)
    • There is justification for extending the nominations and the election on the basis that it may assist with engagement. To this end, the Council election dates will be as follows.
      • Nominations: 12 Dec 2026 – 27 Dec 2026 (15 days)
      • Election: 28 Dec 2026 – 11 Jan 2027 (14 days)
    • Announce the election on 1 Dec 2026. Use AEDT  (Australian Eastern Daylight saving Time) UT+1100 for times.
  • Grant application for the Willi26 project
    • Four community members expressed support for the grant. None were opposed.
    • MOTION: that LA approve the grant request of A$1100 to the Willi26 project for Open Access publication and dissemination.
      Moved: Jonathan
      Seconded: Miles
      Motion passed.
    • Jonathan will inform Guoyi of the outcome.
  • Questions from Lloyd Handyside about LA grants:
    • Once my membership is confirmed, can I apply as project lead for an open project maintained by a company, with the company as payee?
      It is more transparent to members if the grant goes to a member. Ask whether it can be done this way.
    • Can this Project Grant fund A$1,200 of dedicated testing devices, backup storage and access materials, plus A$3,600 for independent accessibility and adult educator review and openly reusable help resources? Are there specific equipment ownership or cost/GST conditions?
      A single grant can fund multiple things.
      There are no specific equipment ownership requirements. It is intended that anything purchased stays in the service of the Open Source community by remaining accessible to the Open Source project it was originally funded for.

Jonathan will follow up with Lloyd.

5. Items for noting

  • ACNC: Russell will check to see if there are any outstanding tasks..
  • ASIC form 490 is to be done. Russell will see to this.
  • The financial year has closed. Preliminary result is a $36k loss.
  • Neill will close out the minutes he queued as secretary, and review the status of membership applications.

6. Other business

  • Consider putting a note on the membership application web form to indicate that processing the application could take a few weeks.
  • Questions from Jack Skinner:
    1. What is the expected timeline for the publishing of Council minutes backlog?
      It is being worked on and should be dealt with in the next couple of weeks.
    2. What is the timeline for the announcement of Council elections? Is there a plan to encourage increased membership engagement?
      Discussions on dates have been started and are expected to be finalised tonight or very soon after. Nominations and the election itself will be extended to encourage more participation.
    3. Which mailing list or other method is used to announce new subcommittees as per Subcommittee v3?
      Subcommittees are documented on the Subcommittee page on the LA website. They are published in a time frame agreed to with the subcommittee because often they wish to run their own launch first.
    4. How do we get LA subcommittee webpages updated? In particular, EO2026 should be EO2027, and Pycon also needs attention.
      Contact Council if it appears that edits have been overlooked. We try to do it as subcommittees are updated, but occasionally we miss things.

7. In camera

  • One item was discussed in camera.

8. Action items

7.1 Completed Items

7.2 Carried Forward

 

Meeting closed at 21:49 UT+11:00

Next meeting is scheduled for 2026-10-21 at 20:00 AEDT (UT+1100)

The post 2026-10-07 Council Meeting Minutes appeared first on Linux Australia.

Linux Australia — 2026-09-23 Council Meeting Minutes

1. Meeting overview and key information

Present

  • Joel Addison (President)
  • Neill Cox (Vice-President)
  • Russell Stuart (Treasurer)
  • Jonathan Woithe (Secretary)
  • Elena Williams (Council)
  • Harrison Oates (Council)
  • Miles Goodhew (Council)

Apologies 

  • None

Not Present

  • None

Meeting opened at 19:33 AEST by Joel  and quorum was achieved.

Minutes taken by Jonathan.

2. Log of correspondence

  • [LACTTE] From Jack Skinner, 10 Sep 2026: Finalised version of PyCon AU 2026 report presented at subcommittee meeting on 9 Sep 2026. It was incorporated into the meeting minutes.
  • [LACTTE] From Zoom, 10 Sep 2026: Changes to Zoom Scheduler Basic. Coincidently a request has been put in to cancel this since it isn’t needed anymore.
  • [LACTTE] From Jack Skinner / Scott Mills, 10 Sep 2026: ScienceMob status update (no response needed).
  • [LACTTE] From Julia Topliss, 12 Sep 2026: Following up items discussed at last subcommittee meeting:
    • Confirmation of revised sponsor contract (Joel responded)
    • Agreement on DrupalSouth Gold Coast budget (Jonathan responded, Joel followed up)
    • Request for LA sponsorship of DrupalAsia Connect 2027 (Jonathan responded: suggested submission to the Grants program)
  • [LACTTE] From ACNC, 14 Sep 2026: Notification of successful charity registration for Linux Australia.
  • [LACTTE] From Swapnil Ogale, 17 Sep 2026: Arranging payment of Write the Docs sponsorship.
  • [LACTTE] From Michael Dexter, 17 Sep 2026: Proposed adjunct OpenZFS event around EO2027.
  • [LACTTE] From loomhigh, 19 Sep 2026: Suggestion for LA to write a submission to the government enquiry about AI datacentres.
  • [LACTTE] From Felicity Brand, 20 Sep 2026: Introducing the Guide to Open Source in Australia.
  • [LACTTE] From Julia Topliss, 21 Sep 2026: Request for Gold Coast budget feedback.
  • [Private] From Swapnil Ogale, 21 Sep 2026: Thanks for Write the Docs sponsorship, URL to be used for claiming tickets.
  • [LACTTE] From Michael Richardson, 22 Sep 2026: DrupalCon Licence Fee.

3. Items for discussion

  • Vacant OCM position

Miles has indicated that he is happy to fill the casual OCM vacancy.

Motion: that the Linux Australia Council co-opt Miles Goodhew to fill the vacant OCM position.

Moved: Joel
Seconded: Harrison
Result: Motion passed

  • DrupalSouth Gold Coast 2027 budget
    • There are concerns about the projected level of sponsorship which has been increased to cover increased expenses.
    • Council is awaiting further feedback about questions raised by Joel. Once this is received the discussion can continue with DrupalSouth.
  • Proposed OpenZFS adjunct event around EO2027
    • It makes sense for such an event to be associated with EO2027 in some way.
    • Joel will discuss with the EO2027 team and get in touch with Michael at the appropriate time.
  • Distributing Write the Docs tickets from sponsorship package
    • Council has two tickets we can give out.
    • Representatives on Council have the option to attend. Elena might be able to go and will indicate availability as soon as possible.
    • Unassigned tickets will be offered to the community via an Expression Of Interest and random draw.
  • DrupalCon Licence Fee payment
    • Calculations from Michael should be verified. Russell has reviewed; it looks good.
    • Russell will respond to Michael.
  • Government submission on AI
  • Guide to Open Source in Australia
    • The guide is looking good even in its current embryonic state.
    • Once fleshed out, LA could consider linking to it from the LA website.
    • Encouragement and offers to help will be sent from Council. Miles will do this.
  • Council election timeframe
    • Prior feedback has included a request to avoid the Christmas period. It can’t be later because we run out of time.
    • Giving advance notice of the dates may help address some of the concerns.
    • Joel suggests updating the election module to use proportional voting for the OCM positions. This is, after all, what many members expect to be the case. It does require some coding, however. Harrison will look into this.
    • Council members will propose dates so these can be communicated to the membership as soon as possible.

4. Items for noting

  • Linux Australia has been registered as a charity by ACNC.
  • ACNC charity registration
    • Russell is working through a number of historical reports that need to be uploaded.
  • Next meeting is after Daylight Saving starts, and therefore will start at 20:00 AEDT (UT+1100). Make sure subcommittees are aware since it’s a subcommittee meeting. Joel will send a reminder email and include the LA Council changes.
  • Grant application for the  Willi26 project is in the Community Consultation phase. It will be reviewed by Council at the next meeting. So far 4 members have expressed support for the grant, with none against.

5. Other business

  • Consider putting a note on the membership application web form to indicate that processing the application could take a few weeks.

6. In camera

  • Nothing was discussed in camera.

7. Action items

7.1 Completed Items

7.2 Carried Forward

 

Meeting closed at 20:41 UT+10:00

Next meeting is scheduled for 2026-10-07 at 20:00 AEDT (UT+1100)

The post 2026-09-23 Council Meeting Minutes appeared first on Linux Australia.

Linux Australia — 2026-09-09 Council Meeting Minutes

1. Meeting overview and key information

Present

  • Joel Addison (President)
  • Neill Cox (Secretary)
  • Russell Stuart (Treasurer)
  • Jonathan Woithe (Council)
  • Elena Williams (Council)
  • Harrison Oates (Council)

Apologies 

  • Jennifer Cox (Vice-President)

Not Present

  • None

Meeting opened at 19:35 AEST by Joel  and quorum was achieved.

Minutes taken by Jonathan.

2. Log of correspondence

  • [LACTTE] Enquiry from Joseph Hartley via the Linux Australia Website Contact Form – Jonathan has responded
  • Activity statements are available online [SEC=OFFICIAL:Sensitive] – notification from the ATO
  • Re: Assistance needed: Account action items for DS Community Day ticket sales
  • [LACTTE] Enquiry from Simon Merrick via the Linux Australia Website Contact Form – acknowledgement from Simon – no action required
  • C/O Elena Williams – LINUX AUSTRALIA – Attn: Reace – response from ANZ NZ re signatory changes
  • [LACTTE] Registrations for RSAA26 as part of your partner package – notification from RSAA26
  • Council shuffle – discussion about replacing Jennifer as VP and Neill as Secretary
  • Re: [LACTTE] Specialist Tracks and general CfS, financials access – discussion about CfP access – Joel has responded
  • Re: Assistance needed: Account action items for DS Community Day ticket sales – discussion about Stripe access and seed funding – Russell and Joel have responded
  • Fwd: Re: [LA-Jobs] Linux Australia – New Job submitted – Head of Operations at Kanji IT Pty Ltd – discussion about the Jobs Board – Joel has responded
  • Fwd: [Linux-aus] FOI case hearing next week: 2..4 September, Melbourne – Notification from Kathy Reid about social media posts re Fraser Tweedale’s FOI hearing
  • EO2027 Session Selection Committee – Notification from Sae Ra Germaine about the status of the EO2027 Session Selection Committee – Jonathan has responded
  • Amazon Web Services Billing Statement Available [Account: 103334912252] – notification from Amazon
  • Upcoming Domain Renewal Notice – Please Do Not Reply – notification from Digital Pacific re linux.org.,au domain renewal. No action required
  • Notice: Revised sponsorship contract for DrupalSouth events – Notification from Julia of an updated sponsor contract – Jonathan has responded
  • [LACTTE] Enquiry from Ronald Bradford via the Linux Australia Website Contact Form – notification from Rolan of an issue with submitting an EO2027 talk – Joel has responded
  • 60 Day Renewal Reminder 2026-11-04 – notification from Digital Pacific re linux.org.,au domain renewal. No action required
  • Resignation from Linux Australia Council – formal resignation from the VP position by Jennifer Cox
  • Joomla Australia Update – from Nathan Morrow

3. Items for discussion

  • Drupal Subcommittee 7:35 pm – Julia Topliss
    • The sponsorship contract is being revised. Jonathan has provided feedback, Joel will later tonight. Motivation was to address Drupal trademark changes and to deal with a few things that were overlooked in the first version. Would like to use it for Community Day.
    • Soft launch has been done. 26 tickets have been claimed, 3 paying. 6 sponsors are on board ($16k inc GST in total).
    • LA has been asked to considering sponsor of DrupalAsia Connect.
    • DrupalSouth Gold Coast draft budget is available for Council for review. Two changes:
      • Paying for some quality keynotes. Prices in the draft budget are fairly firm.
      • Holding three workshops. Costs for two of the three are still a bit fluid.
    • Public Sector Network: event finances are being finalised now.
    • Elections have been launched.
  • PyConAu 7:45 pm – Jack Skinner
    • PyConAU has been run.
    • Last minute rush on tickets (attendance was around 550). Great feedback received.
    • Positive experience with the venue catering team.
    • Planning to wrap conference administration in September.
    • Expecting a shortfall of around $32k (includes community contribution).
    • There is some enthusiasm for future PyConAUs.
    • Some initial thoughts:
      • Push back on bad behaviour from organisations who purchase the wrong type of tickets can lead to results.
      • Earlybird and late ticket sales were strong, the middle was dead.
      • The shift to Wed-Sun seems to have been successful.
      • The financial returns and other benefits of the  brand work is still to be quantified.
      • The new tracks were generally positive. That said, one track struggled to get presentations. It may be better to spin it off to a standalone event. Academic publishing hasn’t lead to the expected benefits.
      • More detailed feedback will be provided in due course.
    • The PyCon AU team was thanked for running the event.

NOTE: As foreshadowed during the meeting, Jack provided the final version included below on 10 Sep 2026.

Additional notes provided by Jack:

Recently:

    • We ran PyCon AU 2026!
    • We had 551 ‘registrations’ and peaked at 470 per-day, thus technically making it the largest on-site attendance at a PyCon AU since the online years. This framing is primarily to put a positive spin on things for sponsors in an effort to encourage them back in future years.
    • We had extremely positive feedback from a broad base of attendees (not just one demographic or audience). Many commented it was the best conference they’ve been to “in quite some time” (to generalise).
    • We are still projecting a net loss. We’ve reduced our projection since April by approx 22%, down to -$32.5k, including a 6% community contribution. This is reduced to $10.4k if we only consider our net cash position (excl gross margin).
      • When I reflect on previous years budgets, $10k is approximately equal to one PSF grant, and that the $30k spent on brand development is expected to span multiple years as well.
    • There are a handful of transactions we expect to conclude in the coming days. We’re broadly on track to conclude the subcommittee’s activities by 30 September.
    • We did not have “a team member in ICU” on our risk register, and we are grateful for having some amazing members of the community who genuinely stepped up to make the conference possible. Special mentions to Bardi Harborow, Mitch Pomery, Jaye Steward, and Manny Savage, without whom we would likely would not have had such positive feedback from ‘front of house’. They would make fantastic additions to any future PyCon AU organising team or LA sub-committee.
    • I also want to highlight the phenomenally hard work that Sophie & Nic put in during the event – jointly as a trio we ran an average of 20 hour days for ~6 days. I’m so proud of the kindness, care, focus and grit my co-directors showed and I could not have asked for better collaborators. (I’ve never worked with such a strong team, even in a professional setting). I’m mindful that their availability on Wednesday evenings has been limited due to work commitments so their efforts may not have been as visible to council as they were to me.
    • We have been highlighting equally: EO 2027, Python NZ and the PyCon AU mailing list as places to stay connected to open source & Python in our region.

Next on our radar is:

    • Finalise supplier payments (expected this week) and finalise payments for grants and donations.
    • Conclude attendee facing activities within a few days including: feedback survey & academic proceedings publication.
    • Several reports: for sponsors, BEDA (as a grant requirement) and for Linux Australia (both internal to council & annual report).
    • Refresh and extend various documentation, along with the directors reflections in the hope they enable future teams to run a PyCon AU.
    • We are aiming to conclude the sub-committee’s activities by 30 September.
  • Everything Open 7:55pm. Fraser.
    • Sponsorship: there is $31k in signed agreements, one signed in-kind sponsor, others are in progress.
    • Registrations are open. There are 10 registrations with around $3000 received.
    • Expenses: first two of four installments for the dinner venue have been paid.
    • Call for proposals is open. It has been extended to 20 Sep 2026. There are  57 submissions, but ideally would like to see 2-3 times this to give selection flexibility. More are expected as the deadline approaches.
    • Social media promotions are planned, as are communications to tech groups around the country.
    • Specialist tracks:
      • Kernel looks healthy.
      • Operations and security will get there.
      • Public sector is less solid, but work to attract presentations continues.
    • Two keynotes are locked in, with others in progress. One is from Europe. Others will be more local to keep a lid on the budget. Are planning 3 morning keynotes and at least 1 locknote. A second locknote is possible.
  • Admin Team 7:55 pm
    • Housekeeping has been the focus over the past month.
    • Budget and reimbursements are due and are being worked through.
    • The server refresh may be postponed due to finances.
    • auDA review has been released. Recommendations are yet to be accepted.
    • Handling of domain names is being worked through.
    • When timing of elections is known, let the Admin team know so change freeze can be implemented.
    • Backup review has been done. A gap in the cold storage backup of EO was identified and dealt with.
    • Steve will provide a written report next month.
  • Joomla Subcommittee 8:05 pm. Nathan Morrow
    • The Joomla event planned for late in 2026 has been postponed until 2027. Lead time was limited, some life issues arose among the organising team, and an issue with the website host caused the loss of a week. It was decided that it was in everyone’s best interests to reschedule the event so it could be properly planned.
    • A platinum sponsor has been signed up. They are happy for the event to be postponed.
    • Web hosting will move to a new provider.
    • In the interim, effort will be put into community building.
    • Event organisation will continue in the background.
    • Further venue options will be pursued since the existing one was a little expensive.
  • WordPress 8:10 pm. Wil Brown
    • Meetups
      • Brisbane – regularly monthly – Kinsta is sponsoring pizzas
      • Sunshine Coast – regularly monthly
      • Perth spinning back up after a 18 month hiatus – had a meetup in June but none since – reach out to orgs
      • Melbourne User Group met in July but none in August or Sept – need to reach out to orgs
      • Sydney met in person – new sponsor for venue (WOTSO North Sydney – Stone Digital) until November to see how things go – 18 people. Planning in-person events for Sept, Oct and Nov.
    • Might need to revisit the WP Australia subcommittee members to see if those people are still active/interested. Need to talk to new sponsors.
    • A small WordPress Sydney social event is happening on 25 Nov at BrewDog. It’s limited to 15 people. Format is a sit-down dinner, buy your own. Could increase numbers if there is demand.
    • No Aussie WordCamps planned for this year. No submissions or interest for 2027 so far.
    • There is a WordPress Enqueue 1 day conference in December from The Code Co, piggybacking off Web Directions. It’s a private conference not affiliated with WordCamp Central. It was held last year. Aimed for 200 attendees but only maybe 30 or 40 attendees. Good speakers – mostly developers. This year is expected to be similar.
  • Flounder 8:10 pm
    • No report.

 

4. Items for noting

  • Jennifer Cox has resigned from the Vice President position

The other members of the Linux Australia Council would like to thank Jenny for her contribution to the Linux Australia Council over the past few years. We wish her all the best.

5. Other business

  • The luv.asn.au domain is held by WebCentral
    • We are now working through the registration with them
  • LA Council positions

Due to Jenny resigning, we will need to appoint replacement committee members. Following discussion within the Council, it is proposed that existing members of the Council adjust their roles for the remainder of the 2026 office term.

MOTION: That Neill Cox be appointed to the position of Vice President for the remainder of the 2026 office term.

Moved: Joel
Seconder: Elena
Motion passed with 1 abstention.

This leaves the position of Secretary vacant, and a replacement needs to be made.

MOTION: That Jonathan Woithe be appointed to the position of Secretary for the remainder of the 2026 office term.

Moved: Joel
Seconder: Russell
Motion passed with 1 abstention.

This leaves a vacant Ordinary Council Member position, and a replacement needs to be made. According to the constitution, the LA Council can appoint a replacement from the membership.

MOTION: That Miles Goodhew be invited to fill the casual vacancy for Ordinary Council Member for the remainder of the 2026 term.

Moved: Joel
Seconder: Neill
Motion passed.

Joel will contact Miles formally to issue the invitation and advise council of the outcome.

  • Grant – Write the docs sponsorship

No feedback was received from the community during the consultation period. Council noted that the conference has been sponsored by LA in the past, and the conference has always provided the report required of grant recipients.

MOTION: That Linux Australia approve sponsorship of the Write the Docs conference at the “First Draft” level  ($1500).

Moved: Jonathan
Seconder: Harrison
Motion passed.

Jonathan will communicate the outcome to the applicant.

6. In camera

One item was discussed in camera.

7. Action items

7.1 Completed Items

7.2 Carried Forward

 

Meeting closed at 21:13 UT+10:00

Next meeting is scheduled for 2026-09-23

The post 2026-09-09 Council Meeting Minutes appeared first on Linux Australia.

Linux Australia — 2026-08-26 Council Meeting Minutes

1. Meeting overview and key information

Present

  • Joel Addison (President)
  • Russell Stuart (Treasurer)
  • Neill Cox (Secretary)
  • Jonathan Woithe (Council)

Apologies 

  • Harrison Oates (Council)
  • Jennifer Cox (Vice-President)

Not Present

  • Elena Williams (Council)

Meeting opened at 19:35 AEDT by Joel  and quorum was achieved.

Minutes taken by Neill

2. Log of correspondence

  • (#WRY-369-53140) Domain Renewal Failed | taslug.org.au | Digital Pacific – ongoing attempts to renew taslug.org.au
  • PSN NSW 2026 report – report on the PSN 2026 event
  • DrupalSouth Community Day 2026 Budget – for your approval – ongoing discussion about the budget, and funding the steering committee.
  • DrupalSouth Community Day 2026 budget proposal – notification from Russell that the budget has been accepted
  • Add to Subcommittees meeting/invitation: Fraser and Char – reminder from Elena to add Fraser and Charalle to the subcommittee invites. Joel has done this.
  • Re: Specialist Tracks and general CfS, financials access – discussion about access for specialist track organisers to submitted proposals – Joel has responded
  • [LACTTE] Grant decision – funding for FOI review – notification of approval of funding for Fraser’s FOI review
  • DSCD26 Standing outcomes and action items – acceptance of the requirements to be a Linux Australia subcommittee from Margery
  • Why Openness Matters More Than Ever – newsletter from OSI
  • [Action may be required] Bills Page Domain Migration [AWS Account: 103334912252] [US-EAST-1] – notification from AWS – no action required?
  • [LACTTE] Enquiry from Tim Ferguson via the Linux Australia Website Contact Form – enquiry about how to download Linux Mint – Joel has responded
  • [LACTTE] Enquiry from Simon Merrick via the Linux Australia Website Contact Form – request for a print suitable version of the LA logo. Joel has responded
  • OSI Governance Survey – invitation to participate in a survey from OSI
  • 10 Days Past Due Renewal Reminder 2026-08-07 – notification from Digital Pacific re taslug.org.au
  • PyCon AU 2026 budget + networking function opportunity/decision – discussion about PyConAU catering and budget. Jonathan has followed up
  • Domain Suspended: taslug.org.au –  notification from Digital Pacific re taslug.org.au
  • Registrant Verification Failed for taslug.org.au – Action Required –  notification from Digital Pacific re taslug.org.au
  • Open Source Congress 2026 – notification from OSI
  • [Linux-aus] Grant Application from Swapnil Ogale for Write the Docs Australia 2026  – Jonathan has responded
  • New subscription request to list linux-aus from jemma@nothingbutstatic.dev – need to approve this?
  • [LACTTE] Enquiry from Richard Foster via the Linux Australia Website Contact Form – offer to donate a laptop. Jonathan has responded, referring the donor to Computerbank Victoria
  • Registrations for RSAA26 as part of your partner package –  unfortunately we didn’t have anyone who could attend thge conference
  • C/O Elena Williams – LINUX AUSTRALIA – Attn: Reace – discussion about adding/removing signatories to ANZ NZ accounts
  • Activity statements are available online [SEC=OFFICIAL:Sensitive] – notification from the ATO

3. Items for discussion

  • Policies

The pull request for the grants policy has been merged.

Motion: That the council accepts the Linux Australia Council expectations policy.

Moved: Neill
Seconded: Jonathan
Result: Motion passed

Jonathan will publish the policy on the website

  • Domains
    • Suggestion that we move to a different registrar. Possibly VentraIP. Joel will ask Steve to move the domains.

4. Items for noting

  • LUV

The domain has still not been transferred. Russell will contact Web Central because there was no response to the email sent to the address given in the whois record.

5. Other business

  • None.

6. In camera

  • One item was discussed in camera.

7. Action items

 

7.1 Completed Items

7.2 Carried Forward

 

Meeting closed at 20:28

Next meeting is scheduled for 2026-09-09 and is a subcommittee meeting.

The post 2026-08-26 Council Meeting Minutes appeared first on Linux Australia.

Linux Australia — 2026-08-12 Council Meeting Minutes

1. Meeting overview and key information

Present

  • Joel Addison (President)
  • Russell Stuart (Treasurer)
  • Jonathan Woithe (Council)
  • Elena Williams (Council)
  • Harrison Oates (Council)

Apologies 

  • Jennifer Cox (Vice-President)
  • Neill Cox (Secretary)

Not Present

  • None

Meeting opened at 19:36 AEST by Joel  and quorum was achieved.

Minutes taken by Jonathan.

2. Log of correspondence

  • Re: Specialist Tracks and general CfS, financials access – discussion about submissions for tracks
  • Question about 2026 Project Grants covering development hardware – Jonathan has responded
  • DrupalSouth Community Day 2026 Budget – for your approval – discussion about the Drupal South Community Day budget and steering committee funds
  • Invoice Payment Reminder – Please Do Not Reply – Automated notification of an upcoming payment
  • payment authorisation – EO2027 dinner instalment 2 – Russell has responded. Payment has been approved
  • Amazon Web Services Billing Statement Available [Account: 103334912252] – automated notification
  • 5 Day Renewal Reminder 2026-08-07 – Automated notification about taslug.org.au renewal
  • Accounts coding queries – Inland Revenue – enquiry from accountant about a miscoded invoice. Russell has responded
  • Fwd: Re: [LA-Jobs] Linux Australia – New Job submitted – Head of Operations at Kanji IT Pty Ltd – discussion about issues with posting jobs. Joel has responded
  • Jobs board thread – more information about the jobs board from Joshua Hesketh
  • Upcoming Domain Renewal Notice – Please Do Not Reply – automated notification re renewal of taslug.org.au
  • Upcoming Domain Renewal Notice – Please Do Not Reply – automated notification re renewal of zookeepr.org
  • Activity statements are available online [SEC=OFFICIAL:Sensitive] – automated notification from the ATO
  • Thank You for Your Payment – Please Do Not Reply – automated receipt for renewal of taslug.org.au and zookeepr.org
  • Lodge Linux Australia Activity Statement April..June 2026 – email from accountants re lodging BAS
  • Enquiry from John Bennett via the Linux Australia Website Contact Form – Jonathan has responded. Neill to action tonight if possible
  • 90 Day Renewal Reminder 2026-11-04 – automated email about the upcoming renewal for linux.org.au
  • [LACTTE] Enquiry from Monica Wood via the Linux Australia Website Contact Form – enquiry about taslug.org.au expiry. Jonathan has responded
  • Community Services sector.- Jonathan has responded
  • [LACTTE] Enquiry from Joseph Hartley via the Linux Australia Website Contact Form – enquiry about a grant for developing case management software for Aboriginal Community Controlled Organisations in the Social and
  • C/O Elena Williams – LINUX AUSTRALIA – Attn: Reace – email from ANZ about reviewing signatory list
  • Re: taslug.org.au domain has expired – email from admin team to taslug about the expired taslug.org.au domain.
  • Expired Domain – Last Chance! – automated email re expired taslug.org.au
  • (#WRY-369-53140) Domain Renewal Failed | taslug.org.au | Digital Pacific – discussion about problems renewing taslug.org.au

3. Items for discussion

  • Drupal Subcommittee 7:35 pm – Julia Topliss
    • PyconAU has been promoted through Drupal’s channels.
    • PSN found to be valuable for Drupal South (awareness, existence of conferences). Agencies on the stand received around 30 leads. A number of significant Drupal projects are coming up. The experience was positive. Some time is needed to evaluate the event before deciding what to do next. Unlikely to do anything similar this side of Christmas.
    • GovCMS dates: 11-12 Nov 2026 online.
    • Tentative date for Community Day is 19 Nov 2026. Have received some encouraging verbal interest in sponsorships. The budget has been reviewed in line with feedback given and sent through earlier this evening. Council to review as soon as possible.
    • DS Gold Coast 2027: venue selected. Budget to come possibly within next two weeks for Council to evaluate.
    • Election processes are in progress.
  • PyConAu 7:45 pm – Jack Skinner
    • Promotion has been done through Drupal South – much thanks to them for doing this.
    • Attendees is at 418. The numbers could be awkward for decisions regarding minimum spend for catering. Planning a possible networking event to make use of the minimum spend on something useful and beneficial.
    • Some local sign-ups are expected right up to the start of the conference.
    • There is a desire to develop relationships with the sponsors.
    • Sponsorship is up by about 50%. All sponsors have paid except one, who only got an invoice last Friday.
    • All specialist tracks have a track sponsor.
    • There is confidence with the AV production. Expecting good photos for future promotion.
    • There is concern that a keynote might not be able to make it.
    • Venue communication hasn’t been great.
    • There is energy building for the event.
    • The chances are that if a PyCon is to run in 2027 it is more likely to be in New Zealand. The recommendation is to cut the conference back and perhaps spin some specialist tracks and activities out into their own events. With attendances of around 400, a large conference is not sustainable.
    • Some life events have impacted the availability of core team members.
  • Everything Open 7:55pm
    • A banner is to be displayed at PyCon AU.
    • The meeting invitation was sent late so attendance was not expected today.
    • Session selection committee constitution is in progress.  Sae Ra and Josh are likely to be the chairs.
    • Some submissions have come in. It’s early days.
    • Infrastructure continues to be set up as needed.
  • Admin Team 7:55 pm
    • VM for Neill is a work in progress. Travel has impacted this. Expected completion is within a week or so.
    • TasLUG domain renewal and eligibility confirmation is in progress.
    • 2027 budget preparation will start soon.
    • Zookeepr domain renewal for 10 years is approaching. In admin team budgets this has been averaged across each year. There will be a corresponding expenses spike this year.
    • With the shift to 47 day TLS lifetime, Linux Australia and its subcommittees need to manage the risk associated with updating TLS certificates at critical times.
  • Joomla Subcommittee 8:05 pm. Nathan Morrow
    • A stripe account has been set up. It will be hooked up to the booking form.
    • A question about managing community sponsorship payments through Stripe and related bank accounts was addressed by Russell.
    • Nathan asked about setting Paypal up to receive event payments. This is possible. Russell will chase up the details.
    • Some work is being done on the event budget. Other arrangements are coming along.
  • WordPress 8:10 pm
    • No report
  • Flounder 8:10 pm
    • No report
  • LUV 8:10 pm
    • No report

4. Items for noting

  • Pending membership applications need to be worked through. Joel will ask Neill where the approval process is at, and make plans to work through the backlog accordingly.

5. Other business

  • Grant application: Fraser Tweedale, FOI funding contribution
    • 9 responses were received during the community consultation: 8 in favour, 1 against.
    • The grant does align with our values: trying to force code into the open.

Motion: that Linux Australia funds the grant request from Fraser Tweedale for $5000 towards the FOI review at the Administrative Review Tribunal.

Moved by Jonathan
Seconded by Elena
Motion passed.

Jonathan will communicate the outcome to Fraser.

  • Drupal South Community Day budget

Motion: that Linux Australia establish Drupal South Community Day 2026 as an Event Subcommittee per the provided budget.

Moved by Elena
Seconded by Russell
Motion passed.

Russell will email Drupal South with the outcome.

  • Updates on resolving LUV subcommittee status
    • Joel attended the August Linux Victoria virtual town hall meeting with Russell. After presentations, Joel asked for an update to the outstanding questions. No answers were forthcoming. It was claimed the LUV domain was under LA’s control for the last two years, which is not correct. The meeting was ended abruptly without any progress.
    • LA subcommittees are required to follow processes as per the Subcommittee Policy. Linux Australia has had no response from Alexar or any member of the committee of Linux Victoria to any correspondence during the 2026 calendar year. Linux Australia has also not been notified of a name change from Linux Users Victoria to Linux Victoria.
    • LA would like to regain access to the luv.asn.au domain, since there are some community services running on that domain which are unrelated to Linux Victoria.  The domain is currently in Linux Australia’s name. Russell will attempt to make contact with the registrar to determine the next steps. If this doesn’t resolve the problem, auDA will be contacted.

Motion: that Linux Australia regain control of the luv.asn.au domain.

Moved by Joel
Seconded by Harrison
Motion passed.

Motion: that Linux Australia recognises that Linux Victoria  and Linux Users Victoria (LUV) are two separate organisations, and that Linux Victoria is not a subcommittee of Linux Australia.

Moved by Joel
Seconded  by Russell
Motion passed.

Motion: that Alexar and Sophie are removed from all access to Linux Australia’s Xero instance and Linux Australia’s bank accounts.

Moved by Joel
Seconded by Jonathan
Motion passed.

Once the domain status is resolved, the next step is to convene a meeting of people interested in keeping LUV going.

  • Current WHOIS information about the luv.asn.au domain:

Domain Name: luv.asn.au
Registry Domain ID: 849ebac0304f43c2b464373cb5993b0b-AU
Registrar WHOIS Server: whois.auda.org.au
Registrar URL: https://elb.www.netregistry.com.au/contact-us/
Last Modified: 2026-07-05T03:36:38Z
Registrar Name: Melbourne IT
Registrar Abuse Contact Email: abuse@domains.worldhost.group
Registrar Abuse Contact Phone: +61.279080915
Reseller Name:
Status: serverRenewProhibited https://identitydigital.au/whois-status-codes#serverRenewProhibited
Status Reason: Not Currently Eligible For Renewal
Registrant Contact ID: 5d81fb8d6f15470f82dc3d566c9d91ca-AU
Registrant Contact Name: The President
Registrant Contact Email: president@linuxvictoria.org
Tech Contact ID: 5d81fb8d6f15470f82dc3d566c9d91ca-AU
Tech Contact Name: The President
Tech Contact Email: president@linuxvictoria.org
Name Server: othello.dycom.com.au
Name Server IP: 168.138.26.40
Name Server IP: 2603:c023:c000:c700::d0d0:f00d
Name Server: ns.sws.net.au
Name Server IP: 173.230.150.245
Name Server: xkcd.gw90.de
DNSSEC: unsigned
Registrant: LINUX AUSTRALIA INC.
Registrant ID: ABN 56987117479
Eligibility Type: Other
Eligibility Name: LINUX AUSTRALIA INC.
Eligibility ID: ABN 56987117479
>>> Last update of WHOIS database: 2026-08-12T10:46:23Z <<<

6. In camera

  • One item was discussed in camera.

7. Action items

7.1 Completed Items

7.2 Carried Forward

 

Meeting closed at 21:17 UT+10:00

Next meeting is scheduled for 2026-08-26

The post 2026-08-12 Council Meeting Minutes appeared first on Linux Australia.

Linux Australia — 2026-07-29 Council Meeting Minutes

1. Meeting overview and key information

Present

  • Joel Addison (President)
  • Jennifer Cox (Vice-President)
  • Russell Stuart (Treasurer)
  • Neill Cox (Secretary)
  • Elena Williams (Council)
  • Jonathan Woithe (Council)
  • Harrison Oates (Council)

Apologies 

  • None

Not Present

  • None

Meeting opened at 19:32 AEDT by Joel  and quorum was achieved.

Minutes taken by Neill

2. Log of correspondence

  • [Linux-aus] Feedback requested: Linux Users Victoria vs Linux Victoria
  • Spreadsheet shared with you: ‘DrupalSouth Community Day 2026 – Operating Budget’
  • ScienceMob Inc. donation to PyCon AU Fwd: Re: PyCon AU sponsorship/donation
  • Re: Payment required please
  • MBA692 Capstone Projects – Industry Collaboration Opportunity – invitation to participate in an academic study {Neill to politely say thank you but no thank you]
  • Approve transfer for invoice: PUBL1INV4538 – request from DS – Russell has responded
  • Re: BAS Time – Enquiry from Simon Merrick re a payment  – Russell has responded
  • [LACTTE] Enquiry from john bennett via the Linux Australia Website Contact Form – I’ve dropped the ball on this – Neill to action asap
  • [Linux-aus] Grant Application from Fraser Tweedale for Support the myGov Code Generator source code FOI review -Grant application and  discussion on Linux-Aus – still in the review period
  • Re: [Linux-aus] Open source violation litigation (was: Grant Application from Fraser Tweedale) – discussion on Linux-Aus, slightly off topic
  • Upcoming Domain Renewal Notice – Please Do Not Reply – automated email re taslug.org.au – no action required
  • Upcoming Domain Renewal Notice – Please Do Not Reply – automated email re  zookeepr.org – no action required
  • Add to Subcommittees meeting/invitation: Fraser and Char – request from Elena to add EO27 members to calendar invite. Not done yet – Joel will action
  • Lodge Linux Australia Activity Statement April..June 2026 – request from Russell to lodge our activity statement. The accountant will do so
  • Question about 2026 Project Grants covering development hardware – Enquiry as to whether a grant can be used to purchase a laptop. No response yet. Council to review and reply. Grants are subject to community approval.
  • Tax Invoice #1960025 – Please Do Not Reply – automated email. No action required
  • Build Your AI Solution. Get Rewarded. – automated email from Snowflake. I have unsubscribed us from this list
  • DrupalSouth Community Day 2026 Budget – for your approval – follow up from Julia. Jonathan has responded
  • Could you sanity-check a free event chant coordination tool? – request for us to review a chant coordination tool. No response sent.Neill to again say thank you but no thank you
  • Don’t let a 3-minute registration cost you $10,000  – automated email from Snowflake.
  • Re: PyCon AU – Money Transfer Request – request from Nic for an approval. Russell has responded
  • Re: Specialist Tracks and general CfS, financials access – request for access to backend systems by EO2027 team. Joel has responded. They have confirmed access
  • Re: Fwd: Re: [LACTTE] Subcommittee Financial Induction –  Fraser now has access to Westpac
  • Re: Transferring freesoftware.org.au to Linux Australia – request to transfer freesoftware.au to Linux Australia – Joel has responded
  • Final 5 days to claim your spot ( And your share of $10K  USD )  – automated email from Snowflake.
  • Subcommittees – request from Russell Coker to remove Alexar and Andrew Pam as contacts for LUV. No response sent. This topic is under consideration already.
  • Upcoming Domain Renewal Notice – Please Do Not Reply – automated email re taslug.org.au – no action required
  • Upcoming Domain Renewal Notice – Please Do Not Reply – automated email re  zookeepr.org – no action required

3. Items for discussion

  • DrupalSouth Community Day 2026 Budget

Expenses have increased by $10,000. Venue is $3,000 more and the rest is going to the event organisers

Income has gone up by the same amount (from sponsorship)

They are transferring any profit to Drupal South. Normally we ask for a budget before money is transferred.

Direct Questions:

    • Justification for sponsorship?
    • Increase in attendance?
    • We need a budget for what the money transferred to the steering committee will be used for.

Russell will send questions about sponsorship and the funds transfer to the steering committee.

  • LUV

The domain has still not been transferred

The next step is to try and organise a time for a meeting to resolve the situation.

There is a Linux Victoria meeting scheduled for 4 August.

  • Policies: Grants and Sponsorship

Motion: That we accept the Grant and Sponsorship policy

Moved By: Jonathan
Seconded: Jennifer
Result: Motion passed.

Jonathan will open a pull request against the policy repo.

  • We will review the remaining policies before the next meeting.

4. Items for noting

  • None.

5. Other business

  • None.

6. In camera

  • One item was discussed in camera.

7. Action items

 

7.1 Completed Items

7.2 Carried Forward

 

Meeting closed at 20:56

Next meeting is scheduled for 2026-08-12 and is a subcommittee meeting.

The post 2026-07-29 Council Meeting Minutes appeared first on Linux Australia.

Linux Australia — 2026-07-15 Council Meeting Minutes

1. Meeting overview and key information

Present

  • Joel Addison (President)
  • Jennifer Cox (Vice-President)
  • Russell Stuart (Treasurer)
  • Neill Cox (Secretary)
  • Jonathan Woithe (Council)
  • Elena Williams (Council)

Apologies 

  • Harrison Oates (Council)

Not Present

  • None

Meeting opened at 19:35 AEST by Joel  and quorum was achieved.

Minutes taken by Neill

2. Log of correspondence

  • Domain registration details – enquiry from Joel to admin team re correct email address to use for domain registrations
  • LUV mailing lists – enquiry from Joel to Russel Coker re process to ensure we have a backup/archive of the LUV mailing lists.
  • Re: DrupalSouth payments via ANZ – discussion about proper approval process for large payments.
  • Update details for LUV domain – request for the domain registrant details for luv.asn.au to be updated to use domain-admin@linux.org.au – [follow up required]
  • DrupalSouth – Exhibition proposal for PSN NSW 29 July 2026 – notification that funds have been transferred to the Drupal South steering committee
  • luv.asn.au – enquiry from Russell Coker as to whether we need help from him in setting up the domain. We will respond to this once we have acquired the domain.
  • Deposit funds for EO2027 Penguin Dinner – discussion about paying for the EO2027 dinner. Russell and Joel have responded
  • Amazon Web Services Billing Statement Available [Account: 103334912252] – automated notification
  • Everything Open 2027 Brisbane – continuing discussion about specialist tracks at EO2027
  • [Linux-aus] The luv.asn.au domain – notification from Russell Coker that the domain has expired.
  • Developers across APJ are already in – newsletter from Snowflake
  • Concerns regarding LUV governance and the luv.asn.au domain expiration – Joel has responded
  • [Linux-aus] Shout out to Harrison Oates, current Council Member, for his research success
  • linux-aus post from bradley@northtech.us requires approval –  need list admin password to release
  • (#YSI-712-55020) domain expired after paying renewal invoice – issues renewing slug.org.au Admin team has dealt/is dealing with this
  • Everything Open – request from EO to update web settings to make the 2027 site live. Joel has responded.
  • Announcement emails for EO2027 – request for announcement email to be sent. Joel has responded
  • [LACTTE] Enquiry from john bennett via the Linux Australia Website Contact Form – Neill to follow up.
  • Find out more about our updated Terms of Service – notification from Google
  • Upcoming Domain Renewal Notice – Please Do Not Reply – automated notification for taslug.org.au
  • Upcoming Domain Renewal Notice – Please Do Not Reply –  automated notification for zookeepr.org
  • Approve transfer for invoice: PUBL1INV4599 – request for approval of payment from DrupalSouth. Joel has responded and the payment has been approved.
  • [LACTTE] Enquiry from Gerges Azzi via the Linux Australia Website Contact Form – misdirected enquiry about a heater. Jonathan has responded.
  • ScienceMob Inc. donation to PyCon AU Fwd: Re: PyCon AU sponsorship/donation – enquiry about donating funds to LA. Needs a response
  • UN Open Source Week, AI Fellowship, and 2025 Annual Report – newsletter from OSI
  • 30 Day Renewal Reminder 2026-08-07 – automated notification for taslug.org.au
  • Invitation – Build AI agents & Compete for $10,000 USD. – newsletter
  • Does LA have a NFP arrangement with Fastmail? – enquiry from PyConAu – Joel and the admin team have responded
  • [Linux-aus] Assistance required: luv.asn.au domain name – request for assistance from the community to recover luv.asn.au
  • [Linux-aus] Feedback requested: Linux Users Victoria vs Linux Victoria – request for feedback from the community re LUV vs LV
  • Thank You for Your Payment – Please Do Not Reply – automated receipt for slug.org.au renewal
  • Advice on running a charitable campaign at PyCon AU this year – from PyConAu – Russell has responded
  • Exclusively for you – Build AI agents & Compete for $10,000 USD. – promotional material from Snowflake
  • PyCon AU 2026 Update for 14 July 2026
  • Specialist Tracks and general CfS – report from EO2027 on how they intend to run specialist tracks and a request to open the CfS for the main conference. Needs a response

3. Items for discussion

  • Drupal Subcommittee 7:35 pm – Julia Topliss

On track.

  • PyConAu 7:45 pm – Jack Skinner

There are now only 43 days until PyCon AU 2026!

Rather than my usual “recently/upcoming” – I want to frame this update around key deadlines and budget decisions, since things don’t look good.  I’m currently projecting a $39k loss assuming we can make 450 attendees (which itself, might be optimistic). I’ve returned our budget from “recovering” to “off-track” for this reason.

Sponsorship:

    • Projecting approx $154k in sponsorships to land before our cut-off dates. This is about 54% up from last year, and exceeds our $135.8k projection for 550 attendees, but is still about 25% below our target of $206k for 600 attendees.
    • This includes a projected donation of approx $6k from ScienceMob Inc. Pending advice from Russell or Council to accept (and how to proceed).
    • There is potentially more out there, some warm conversations that could convert, but now limited by time & bandwidth with other conference logistics starting to heat up.

Ticket sales:

    • Have flatlined. And very few large employer names are appearing in our registration data. We typically see sales increase after EOFY and ramp up towards the conference. We’re not seeing that this year.
    • We hope social marketing, email campaigns, and keynote announcements this week will re-invigorate enthusiasm, but we’re now very cautious to project high. We also know more people are buying even more last minute – but I cannot (and do not) have high hopes at this stage.
    • We’ve also received anecdotally high volume of refund requests (approx 4 or 5 up from 1 or none) due to unemployment, travel/affordability etc. Whilst only a handful, it definitely feels like we’ve issued refunds equal to our ticket sales this week which – is just disheartening for our team to be seeing. Our refund policy is 90% refund until this Wednesday after which its 50% until 5 August.
    • We have two contracted minimum attendances:
      • BEDA grant tied to 400 attendees. They claw back proportionate to under-attendance in 25% increments.
      • Venue spends based on 450 attendees; and we can’t really re-negotiate that.
      • Note: Venue spends on Wednesday (workshops) and Sunday (Sprints) are separate min-spends again.
    • If we can get 50 registrations a week, for 4-5 weeks, we’ll approach our min-attendance figures. We’ve averaged ~15 attendees per week over the past fortnight (since 1 July).
      • This is only considering headcount – a large portion of which has been non-revenue volunteer/speaker/organiser tickets. This remains within budget for non-revenue, but notably won’t move the revenue numbers either.

Deadlines:

    • We’ve published 27 July as a cut-off for t-shirts. This will be extended by a week by ‘working with our suppliers’ and is a good excuse for more marketing. Shirts is usually an urgency driver for delegates already decided, but its not a buying decision to attend in the first place.
    • Final figures need to be provided to venue 14 August.
    • Ultimately: so next 3-4 weeks is our critical window to sell tickets and see how close to break-even we can stretch.

Costs:

    • We’ll drop some video editing costs in pre-event marketing (opting to only do speaker videos as a batch rather than a larger edit for broader promotional marketing). It’s also looking like we’ll find a volunteer photographer (family of core team) so there’s some frugality let to squeeze in the budget.
    • We’ll probably still keep online streaming costs; we think we can break even with online tickets (based on last year) and it helps us encourage unwell attendees to stay home (health and safety / culture etc). We’ll only announce this in the ~3 weeks before PyCon AU since we want as many people to attend in-person as possible.
    • We’ve already cut most of the fat out of our discretionary costs so there’s not much more there – but we continue to find a few hundred here and there when it comes to vendor selection. Frugality remains a primary decision driver.

Overall engagement:

    • Speaker engagement seems very low. When we accepted speakers, we asked everyone to commit to sharing their talk online. Very few have. We’ll see this week if speakers follow through on recording short promo videos or if that effort falls flat.
    • Volunteer sign ups have been significantly reduced. Admittedly, we did less promotion of this – but I do see it as a data point.
    • Core team capacity/availability to navigate the above challenge(s) has also been… limited

My last last note for council – there’s currently no active team working on 2027. Several folks have expressed curiosity in shadowing the 2026 team but nobody willing or driven to lead the charge ahead. The default path forward will be to wrap our surveys, reporting, finance and whatnot by 30 September and put PyCon AU ‘on ice’ until such time someone wishes to defrost it. Sadly, the overarching effort of this year (to hold space and invest in future conference continuity) hasn’t born fruit.

Though PyCon AU East Island might be a go with our friends across the pond…

  • Joomla Subcommittee 8:05 pm. Nathan Morrow
    • Budget status (if applicable):
      No changes since previous meeting
    • Key achievements:
      • Not as much as hoped in the last 4 weeks due to personal workload/illness but…
      • 2 Bronze sponsors confirmed though not paid yet at $500 each.
      • 3 speakers confirmed.
      • Sponsors prospectus has been completed together with online enquiry/payment form and initial email contact going out next week… which is a bit later than planned
    • Any concerns?
      •  Yes, but mostly time related… I am pretty clear now to get everything happening sponsorship wise over the next 3 weeks but somewhat concerned we’ve left it a bit late so would welcome any views on that and if we’re at a point already where we should consider a future date.
    • How can LA assist?
      • Complete setup of event bank account or provide information as to any missing requirements from us on that.
    • Need seed funding – Nathan will supply the amount needed to Russell
  • Admin Team 7:55 pm
    • Chipping away at the new VM for Neill. Slug domain chewed up a lot of time.
    • Re Jack’s question about using Tucows. LA has some domains registered through Tucows – had some issues with domains not being renewed properly despite invoices being paid.
    • Question about whether we should still put in a submission to AUDA re .au policy. It may be too late. We could put out a blog post or similar even if it is too late for a formal submission. Steve will try to put something together and send to council for review.
    • We are still waiting on resolution of the luv.asn.au domain – expectation is that we can resolve this soon. If not we may need to start a dispute through AUDA.
  • WordPress 8:10 pm
    • No update
  • Flounder 8:10 pm
    • No update
  • LUV 8:10 pm
    • No update
  • Everything Open – email report received.

4. Items for noting

  • There are two draft policies in the shared drive that need review by the council. A third is expected within the next two weeks.

5. Other business

  • ScienceMob Inc. donation to PyCon AU

Russell and Jack have discussed this. Should just be a simple donation to the conference.

6. In camera

  • One item was discussed in camera.

7. Action items

Neill – follow up [LACTTE] Enquiry from john bennett via the Linux Australia Website Contact Form

@everyone – review draft policies

7.1 Completed Items

7.2 Carried Forward

 

Meeting closed at 21:12

Next meeting is scheduled for 2026-07-29

The post 2026-07-15 Council Meeting Minutes appeared first on Linux Australia.

,

Stewart Smith — Zig self-hosted backend lowers a seq_cst @atomicRmw(.Xchg) with a discarded result to a plain mov

Finding codegen bugs for atomic operations is fun! I’ll note that I am not a Zig developer. I don’t write code in the language, and I don’t work on the language. I’m just some guy who decided to learn a bit about it.

The Zig programming language has two compilers that are widely used: one generates LLVM IR that libLLVM then builds into native code, and the other is a self-hosted compiler which eschews LLVM and does its own codegen.

It’s important to note that the self-hosted compiler is only currently (as of late October 2026) used on x86_64 Debug targets. All other targets use the LLVM backend by default.

_ = @atomicRmw(u32, p, .Xchg, 1, .seq_cst); is a sequentially consistent exchange. On x86 that is xchg with a memory operand, which is implicitly locked and is a full barrier. When the result is discarded, Zig’s self-hosted x86_64 backend emits a plain mov store instead. A plain store on x86 lets a later load be reordered before it (StoreLoad), so the operation loses its seq_cst ordering. Any build that uses the self-hosted x86_64 backend is affected. That backend is the default for -O Debug on x86_64-linux, and -fno-llvm selects it explicitly.

The same backend emits xchg when the result is used. So does a lock acquired by testing the exchange’s return value, which is not affected. Zig’s LLVM backend and gcc emit xchg in both cases.

This affects softwhare when: code discards the exchange’s result, and then relies on access of a different variable to be executed by the processor after that xchg. An example could be code sets its own flag, then reads another thread’s flag.

Affected: Zig 0.16.0 (Fedora 44 package), 0.17.0 and (current at the time) master 0.18.0-dev.367+187d0bf9e (commit 187d0bf9e483, 2026-10-19), all with the self-hosted x86_64 backend (the default for -O Debug -target x86_64-linux). The LLVM backend (-fllvm) is not affected.

Reproducing the codegen bug

In order to see the codegen bug, you can build a small source file and disassemble it:

export fn xchg_unused(p: *u32) void {
    _ = @atomicRmw(u32, p, .Xchg, 1, .seq_cst);    // BUG: result discarded -> plain mov
}
export fn xchg_used(p: *u32) u32 {                 // control
    return @atomicRmw(u32, p, .Xchg, 1, .seq_cst);
}

We can then compare the result with some C code built with a variety of different toolchains, but gcc -O0 gives us the closest result to the self-hosted Zig compiler’s output.

void xchg_unused(unsigned *p) { (void)__atomic_exchange_n(p, 1, __ATOMIC_SEQ_CST); }

The (annotated) gcc output for the C code looks like this:

+ x86_64-linux-gnu-gcc -O0 -c /repro/src/xchg.c -o /tmp/xchg/gcc-O0/xchg.o
+ cd /tmp/xchg/gcc-O0 && x86_64-linux-gnu-objdump -d --no-addresses --disassemble=xchg_unused xchg.o > xchg_unused.objdump
<xchg_unused>:
	55                   	push   %rbp
	48 89 e5             	mov    %rsp,%rbp
	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
	48 8b 45 f8          	mov    -0x8(%rbp),%rax
	ba 01 00 00 00       	mov    $0x1,%edx
	87 10                	xchg   %edx,(%rax)      # opcode 87: xchg, implicitly locked: correct
	90                   	nop
	5d                   	pop    %rbp
	c3                   	ret
RESULT[gcc -O0 (16.2.1)]: OK, 1 locked/fencing instruction(s)

While we can see the difference in the code generated by the Zig compiler below, annotated as to where the bug is:

+ /opt/zig-0.17.0/zig build-obj /repro/src/xchg.zig -target x86_64-linux -O Debug -femit-bin=/tmp/xchg/zig-0.17.0/xchg.o
+ cd /tmp/xchg/zig-0.17.0 && x86_64-linux-gnu-objdump -d --no-addresses --disassemble=xchg_unused xchg.o > xchg_unused.objdump
+ diff -u /tmp/xchg/gcc-O0/xchg_unused.objdump /tmp/xchg/zig-0.17.0/xchg_unused.objdump
@@ -7,10 +7,12 @@
 <xchg_unused>:
 	55                   	push   %rbp
 	48 89 e5             	mov    %rsp,%rbp
-	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
-	48 8b 45 f8          	mov    -0x8(%rbp),%rax
-	ba 01 00 00 00       	mov    $0x1,%edx
-	87 10                	xchg   %edx,(%rax)      # gcc: the locked exchange…
-	90                   	nop
+	53                   	push   %rbx
+	48 83 ec 08          	sub    $0x8,%rsp
+	48 89 3c 24          	mov    %rdi,(%rsp)
+	be 01 00 00 00       	mov    $0x1,%esi
+	89 37                	mov    %esi,(%rdi)      # …Zig: opcode 89, a plain store, and no lock or fence anywhere
+	48 8d 65 f8          	lea    -0x8(%rbp),%rsp
+	5b                   	pop    %rbx
 	5d                   	pop    %rbp
 	c3                   	ret
RESULT[zig-0.17.0]: BUG, no xchg, lock or mfence: the exchange is a plain store

While there’s a bunch of boilerplate around function entry/exit that differs between the two, the xchg and mov are fairly visible in the middle. If we diff the output of the xchg_used and xchg_unused from the self-hosted Zig compiler, we remove the boiler plate delta:

+ diff -u /tmp/xchg/zig-0.17.0/xchg_unused.objdump /tmp/xchg/zig-0.17.0/xchg_used.objdump
-<xchg_unused>:
+<xchg_used>:
 …
 	be 01 00 00 00       	mov    $0x1,%esi
-	89 37                	mov    %esi,(%rdi)
+	87 37                	xchg   %esi,(%rdi)
+	89 f0                	mov    %esi,%eax

Reproducing at runtime

Since it is always a pile of fun to reproduce concurrency bugs, catching this at runtime means running a bunch of iterations and looking for a fail condition. The following does that, with two threads:

//   thread A: exchange(x, 1) seq_cst; r1 = load(y) seq_cst
//   thread B: exchange(y, 1) seq_cst; r2 = load(x) seq_cst
//
// seq_cst forbids r1 == 0 and r2 == 0 together: whichever exchange comes first in the
// single total order is visible to the other thread's load. The program counts how often
// both loads read 0 in three trials:
//
//   relaxed    monotonic store, then monotonic load. Both-zero is allowed here, and is what
//              x86 does for a plain `mov` store followed by a load. Shows the test can see it.
//   discarded  seq_cst exchange, result discarded. The bug: the self-hosted x86_64 backend
//              emits a plain `mov`, so this behaves like `relaxed`.
//   used       seq_cst exchange, result used. Control: emitted as `xchg`, never both-zero.
//
// Exits 1 if both-zero was seen in either seq_cst trial.
const std = @import("std");

const iterations = 1_000_000;

var x: u32 = 0;
var y: u32 = 0;
var go: u32 = 0; // iteration the threads should run next; set by main
var done: u32 = 0; // threads that finished the current iteration

const Mode = enum { relaxed, discarded, used };

fn Worker(comptime mode: Mode) type {
    return struct {
        fn run(mine: *u32, other: *u32, seen: *u32, old: *u32) void {
            var i: u32 = 1;
            while (i <= iterations) : (i += 1) {
                while (@atomicLoad(u32, &go, .acquire) != i) std.atomic.spinLoopHint();
                switch (mode) {
                    .relaxed => @atomicStore(u32, mine, 1, .monotonic),
                    .discarded => _ = @atomicRmw(u32, mine, .Xchg, 1, .seq_cst), // BUG: plain `mov`
                    .used => old.* = @atomicRmw(u32, mine, .Xchg, 1, .seq_cst), // control: `xchg`
                }
                seen.* = @atomicLoad(u32, other, if (mode == .relaxed) .monotonic else .seq_cst);
                _ = @atomicRmw(u32, &done, .Add, 1, .acq_rel);
            }
        }
    };
}

fn trial(comptime mode: Mode) !u32 {
    const W = Worker(mode);
    var r1: u32 = 0;
    var r2: u32 = 0;
    var old1: u32 = 0;
    var old2: u32 = 0;
    go = 0;
    done = 0;
    const a = try std.Thread.spawn(.{}, W.run, .{ &x, &y, &r1, &old1 });
    const b = try std.Thread.spawn(.{}, W.run, .{ &y, &x, &r2, &old2 });
    var both_zero: u32 = 0;
    var i: u32 = 1;
    while (i <= iterations) : (i += 1) {
        @atomicStore(u32, &x, 0, .seq_cst);
        @atomicStore(u32, &y, 0, .seq_cst);
        @atomicStore(u32, &go, i, .release); // start both threads
        while (@atomicLoad(u32, &done, .acquire) != 2 * i) std.atomic.spinLoopHint();
        if (r1 == 0 and r2 == 0) both_zero += 1; // forbidden in the two seq_cst trials
    }
    a.join();
    b.join();
    return both_zero;
}

pub fn main() !void {
    const relaxed = try trial(.relaxed);
    const discarded = try trial(.discarded);
    const used = try trial(.used);
    std.debug.print("relaxed store+load (allowed):        both loads read 0 in {d} of {d} iterations\n", .{ relaxed, iterations });
    std.debug.print("seq_cst xchg, result discarded:      both loads read 0 in {d} of {d} iterations\n", .{ discarded, iterations });
    std.debug.print("seq_cst xchg, result used (control): both loads read 0 in {d} of {d} iterations\n", .{ used, iterations });
    if (discarded != 0 or used != 0) std.process.exit(1);
}

This will reproduce on x86-64 systems fairly easily. I’ll note that I didn’t get qemu to reproduce when doing usermode emulation on an aarch64 host, so be sure to try on a real x86-64 system (and almost certainly a multi-core one). Below, we have the output of running the binary produced by the self hosted Zig compiler, and then the output of the binary built with the LLVM based one:


    sb-0.17.0 Worker(.discarded): 89 3e   mov    %edi,(%rsi)      # self-hosted: plain store
    sb-0.17.0 Worker(.used): 87 3e        xchg   %edi,(%rsi)
    sb-0.17.0-llvm Worker(.discarded): 87 16   xchg   %edx,(%rsi) # LLVM: xchg either way
+ /tmp/sb/sb-0.17.0
relaxed store+load (allowed):        both loads read 0 in 96990 of 1000000 iterations
seq_cst xchg, result discarded:      both loads read 0 in 128430 of 1000000 iterations   # forbidden, seen 13% of the time
seq_cst xchg, result used (control): both loads read 0 in 0 of 1000000 iterations
RESULT[sb-0.17.0]: BUG, both loads read 0 under seq_cst

+ /tmp/sb/sb-0.17.0-llvm
relaxed store+load (allowed):        both loads read 0 in 41808 of 1000000 iterations
seq_cst xchg, result discarded:      both loads read 0 in 0 of 1000000 iterations      # same source, LLVM backend
seq_cst xchg, result used (control): both loads read 0 in 0 of 1000000 iterations
RESULT[sb-0.17.0-llvm]: OK, seq_cst ordering held

I’ve reproduced this with the 0.16 version shipped in Fedora 44, the upstream 0.17 binaries, and upstream nightly build 0.18.0-dev.367+187d0bf9e.

Okay, but get Volume 3A of the Intel® 64 and IA-32 Architectures Software Developer’s Manual nerdy with me

Specifically, “11.2.2 Memory Ordering in P6 and More Recent Processor Families” in the Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A has two relevant rules:

  1. “Reads may be reordered with older writes to different locations but not with older writes to the same location.” This is what the plain mov allows: a later load can complete before the store is visible to other processors.
  2. “Reads or writes cannot be reordered with I/O instructions, locked instructions, or serializing instructions.” This is what a locked instruction guarantees, and seq_cst requires it.

The Zig language appears to have a big TODO in their language documentation around Atomics, and the std documentation for what the enum is for AtomicOrder. The upstream LLVM docs are rather complete.

“Additionally, the code generator must enforce ordering between SequentiallyConsistent stores followed by SequentiallyConsistent loads” – https://llvm.org/docs/Atomics.html#sequentiallyconsistent

Did I report this upstream?

No, because doing so would be a violation of the Code of Conduct. Specifically, let’s look at the LLM/AI policy part:

Since I used an LLM to help me learn a bit about Zig and how it works, and then got it to help isolate this down, the “No LLMs for finding bugs” undoubtedly applies. Later on, there’s also the explicit example of unacceptable behavior being “Discussing this Code of Conduct“.

I’ve written this up because others may find it interesting, and maybe this helps someone debug their concurrent Zig application at some point.

,

Stewart Smith — Swift code for talking to the Shotwell database

A flashback to a few years in the past, this has sat as a draft for too long. See previously: Getting your photos out of Shotwell and Every time I program a Mac…

So…. what does the code to read the Shotwell database look like?

First, we’re going to have to import some of the modules we need:

import Foundation
import SQLite3
import SwiftUI
import Photos

I forget what SwiftUI here is for… I think it’s for my bit of integration with the UI I have.

From here I can start to model the bits of the Shotwell database that I’ll possibly need, but using more idiomatic Swift than raw SQL queries in random places. This is what I ended up for having information about a Photo:

class ShotwellPhotoInfo : Codable, Identifiable, Hashable {
    static func == (lhs: ShotwellPhotoInfo, rhs: ShotwellPhotoInfo) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    var id : Int32 = 0
    var filename : String = ""
    var translated_filename : String = ""
    var orientation : Int32 = 0
    var original_orientation : Int32 = 0
    var import_id : Int32 = 0
    var event_id : Int32 = 0
    var event : ShotwellEventInfo?
    var transformations : String = ""
    var md5 : String = ""
    var flags: Int32 = 0
    var rating: Int32 = 0
    var title: String = ""
    var raw_backlinks : String = ""
    var backlinks : ShotwellBacklinks?
    var comment : String = ""
    var has_gps : Bool = false
    var gps_lat : Double = 0.0
    var gps_lon : Double = 0.0
}

The comparison function is just comparing the id, which since that’s a primary key in the database, are unique. The hash function also just acts on id because of this.

The translated_filename is not in the Shotwell database, but rather for me to do various transformations on the filename in the shotwell database so that I can actually find the file. The natural translation is going to be for wherever I have mounted my photos, but there were some other weirdness likely due to some symlinks and such I’ve had.

The Shotwell Database Architecture documentation says this:

  • backlinks are persistent links to container objects, i.e. Events and Tags. These are used when a Photo has been removed from one (for example, it’s been trashed or marked offline). If the Photo is returned to the main heap of photos (i.e. untrashed), these backlinks are used to restore its connections to the containers.
https://wiki.gnome.org/Apps/Shotwell/Architecture/Database

So it appears that Backlinks may be rather useful to find out everything I need to know about a Photo that I am looking to import. Thus, we will need to model Events and Tags as well, which are familiar concepts from the Shotwell UI.

Since there are Tags in Shotwell, I need to model them as well:

class ShotwellTag : Codable, Identifiable, Hashable {
    static func == (lhs: ShotwellTag, rhs: ShotwellTag) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    let id : Int32
    let name : String
    let photo_id_list : [Int32]
    let time_created : Int32
    let photos : [ShotwellPhotoInfo]
    init(id: Int32, name: String, photo_id_list: [Int32], time_created: Int32, photos: [ShotwellPhotoInfo]) {
        self.id = id
        self.name = name
        self.photo_id_list = photo_id_list
        self.time_created = time_created
        self.photos = photos
    }
}

Again, we have an id as a primary key, and the photos in a tag are referenced as a list of IDs.

For an Event, this is what I ended up with:

struct ShotwellEventInfo : Codable, Identifiable {
    let id : Int32
    let name : String
    let primary_photo_id : Int32
    let time_created : Int32
    let primary_source_id : Int32
    let comment : String
}

and for backlinks:

class ShotwellBacklinks : Codable {
    var tag : [ShotwellTag?] = []
    var event : [String] = []
    var other : [String] = []
}

But as for just getting some basic information out of the database? That we can start off with by having a class for opening it, and run some simple queries, and even display them in the UI just to prove we can.

class ShotwellDatabase {
    var db: OpaquePointer?
    var import_query: OpaquePointer?
    var dbPath : String?
    
    struct db_version {
        var schema_version: Int32 = 0
        var app_version: String = ""
        init() {
            schema_version = 0
            app_version = ""
        }
    }
    
    func open_db(shotwellDB: String) -> Bool {
        dbPath = shotwellDB
        if sqlite3_open(shotwellDB, &db) == SQLITE_OK {
            print("Opened Shotwell Database!")
        }
        return db != nil
    }
    
    func get_db_version() -> db_version {
        let query = "SELECT schema_version, app_version FROM VersionTable"
        var queryStatement: OpaquePointer?
        var r = db_version()
        
        if sqlite3_prepare_v2(db, query, -1, &queryStatement, nil) == SQLITE_OK {
            if sqlite3_step(queryStatement) == SQLITE_ROW {
                r.schema_version = sqlite3_column_int(queryStatement, 0)
                
                guard let app_version = sqlite3_column_text(queryStatement, 1) else {
                    print("Couldn't get app version")
                    r.app_version = ""
                    return r
                }
                
                r.app_version = String(cString: app_version)
            }
        }
        return r
    }

The database version is more about just verifying I could link things together than anything actually useful. Continuing the ShotwellDatabase class, getting the total number of photos is pretty easy (added to the ShotwellDatabase class above):

    
    func get_photos_count() -> Int64 {
        let query = "SELECT COUNT(id) FROM PhotoTable"
        var queryStatement: OpaquePointer?

        if sqlite3_prepare_v2(db, query, -1, &queryStatement, nil) == SQLITE_OK {
            if sqlite3_step(queryStatement) == SQLITE_ROW {
                return sqlite3_column_int64(queryStatement, 0)
            }
        }
        return 0
    }

Again, that’s mostly just useful for some nice “oh look, I linked up the UI to the database” rather than anything of use (although progress bars may want to use it).

So, now that we can talk to the database, we can go and set things up to iterate over all the photos and load them up into memory. I’m not doing anything too fancy here as this is all very quick and dirty, and was literally the first time writing any Swift code.

The below code starts by opening the Database, then get_next_photo will iterate over everything before done_all_photos can close things up. The commented out block of code below does something pretty neat, and that’s print out a JSON representation of our photo. I found this incredibly easy to set up in Swift.

    func start_get_all_photos() -> OpaquePointer? {
        var db: OpaquePointer?
        if sqlite3_open(dbPath, &db) == SQLITE_OK {
            print("Opened Shotwell Database!")
        }
        start_import(db: db)
        return db
    }
    func get_next_photo(db: OpaquePointer?) -> ShotwellPhotoInfo? {
        var photo: ShotwellPhotoInfo?
            do {
                photo = db_get_next_photo(db: db)
                if photo != nil {
/*                    let encoder = JSONEncoder()
                    encoder.outputFormatting = .prettyPrinted
                    let jsonData = try encoder.encode(photo)
                    let jsonString = String(data: jsonData, encoding: .utf8)!
                    photo_info = jsonString
                    print(jsonString)
                    do_some_photo_things()
                    progress += 1 */
                    all_photos[photo!.id] = photo
                }
            } catch { print(error) }
        return photo
    }
    func done_all_photos(db: OpaquePointer?) {
        sqlite3_close(db)
    }

For parsing that row, I have the following class:

class ShotwellPhotoInfo : Codable, Identifiable, Hashable {
    static func == (lhs: ShotwellPhotoInfo, rhs: ShotwellPhotoInfo) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    var id : Int32 = 0
    var filename : String = ""
    var translated_filename : String = ""
    var orientation : Int32 = 0
    var original_orientation : Int32 = 0
    var import_id : Int32 = 0
    var event_id : Int32 = 0
    var event : ShotwellEventInfo?
    var transformations : String = ""
    var md5 : String = ""
    var flags: Int32 = 0
    var rating: Int32 = 0
    var title: String = ""
    var raw_backlinks : String = ""
    var backlinks : ShotwellBacklinks?
    var comment : String = ""
    var has_gps : Bool = false
    var gps_lat : Double = 0.0
    var gps_lon : Double = 0.0
}

and function in the ShotwellDatabase class:

    func db_get_next_photo(db: OpaquePointer?) -> ShotwellPhotoInfo? {
        var r = ShotwellPhotoInfo()
        if sqlite3_step(import_query) == SQLITE_ROW {
            r.id = sqlite3_column_int(import_query, 0)
            r.filename = String(cString: sqlite3_column_text(import_query, 1))
            r.orientation = sqlite3_column_int(import_query, 2)
            r.original_orientation = sqlite3_column_int(import_query, 3)
            r.import_id = sqlite3_column_int(import_query, 4)
            r.event_id = sqlite3_column_int(import_query, 5)
// FIXME:            r.event = get_event_info(db: db, id: r.event_id)
            let transformation = sqlite3_column_text(import_query, 6)
            if let t = transformation {
                r.transformations = String(cString: t)
            }
            let md5 = sqlite3_column_text(import_query, 7)
            if let t = md5 {
                r.md5 = String(cString: t)
            }
            r.flags = sqlite3_column_int(import_query, 8)
            r.rating = sqlite3_column_int(import_query, 9)
            let title = sqlite3_column_text(import_query, 10)
            if let t = title {
                r.title = String(cString: t)
            }
            let backlinks = sqlite3_column_text(import_query, 11)
            if let t = backlinks {
                r.raw_backlinks = String(cString: t)
                r.backlinks = process_backlinks(all_backlinks: r.raw_backlinks)
            }
            let comment = sqlite3_column_text(import_query, 12)
            if let t = comment {
                r.comment = String(cString: t)
            }
            r.has_gps = sqlite3_column_int(import_query, 13) != 0
            r.gps_lat = sqlite3_column_double(import_query, 14)
            r.gps_lon = sqlite3_column_double(import_query, 15)
        } else {
            return nil
        }
        return r
    }

So with that combined, I ended up with my global var all_photos: [Int32: ShotwellPhotoInfo] = [:] containing all the photos in the shotwell database able to be looked up by ID. I’m sure there’s a more idiomatic way to do this in Swift… but the learning as I went, and need to only ever run this once certainly encouraged me to take some shortcuts.

There’s two interesting bits in that code. There’s the process_backlinks function, which does the thing of processing this giant text string that Shotwell stores that points the photo back to everywhere it has been referenced, as well as the FIXME around event information. I ended up giving up on translating Events, as these were never something I used much.

Parsing the backlinks I did with this:

    func process_backlinks(all_backlinks : String) -> ShotwellBacklinks {
        var r = ShotwellBacklinks()
        let backlinks = all_backlinks.split(separator: "\n").map( {String($0)})
        
        for backlink in backlinks {
            let kv = backlink.split(separator: "=")
            switch kv.first {
            case "tag":
                let tag_ids = kv[1].split(separator: "|").map( {Int32($0)})
                for tag_id in tag_ids {
                    if tag_id != nil {
                        if tag_id! > 0 {
                            r.tag.append(all_tags[tag_id!])
                        }
                    }
                }
                break
            case "event":
                let events = kv[1].split(separator: "|").map( {String($0)})
                r.event = events
                break
            default:
                r.other.append(contentsOf: kv.map({String($0)}))
            }
        }
        
        return r
    }

and the very simple ShotwellBacklinks class:

class ShotwellBacklinks : Codable {
    var tag : [ShotwellTag?] = []
    var event : [String] = []
    var other : [String] = []
}

But to start to get a view as to what Tags exist in the shotwell database, we can just go through the TagTable in the Shotwell SQLite database (added to the ShotwellDatabase class above):

    func get_all_tags() {
        var db: OpaquePointer?
        if sqlite3_open(dbPath, &db) == SQLITE_OK {
            print("Opened Shotwell Database!")
        }
        let query = "SELECT id, name, photo_id_list, time_created FROM TagTable"
        var q : OpaquePointer?
        
        if sqlite3_prepare_v2(db, query, -1, &q, nil) != SQLITE_OK {
            print("Failed to prepare query")
            assert(false)
        }
        
        while sqlite3_step(q) == SQLITE_ROW {
            let r = tag_row_to_ShotwellTag(q: q)
            
            all_tags[r.id] = r
            for photo_id in r.photo_id_list {
                photoToTags[photo_id]?.append(r)
            }
        }
        sqlite3_finalize(q)
        sqlite3_close(db)
    }

I did the hacky thing and just added var all_tags: [Int32: ShotwellTag] = [:] as a global variable, that I then used in the UI element showing them (well… via a wrapper, but that’s how that goes).

We just now need the tag_row_to_ShotwellTag that’s called above:

    func tag_row_to_ShotwellTag(q: OpaquePointer?) -> ShotwellTag {
        let id = sqlite3_column_int(q, 0)
        let c_name = sqlite3_column_text(q, 1)
        let c_photo_id_list = sqlite3_column_text(q, 2)
        let time_created = sqlite3_column_int(q, 3  )
        var name : String = ""
        var photo_id_list : [Int32] = []
        var photos : [ShotwellPhotoInfo] = []
        
        if let c_name = c_name {
            name = String(cString: c_name)
        }
        
        if let c_photo_id_list = c_photo_id_list {
            let photo_ids = String(cString: c_photo_id_list).split(separator: ",")
            photo_id_list.reserveCapacity(photo_ids.count)
            for photo_id in photo_ids {
                if photo_id.hasPrefix("thumb") {
                    var id = photo_id
                    id.removeFirst(5)
                    photo_id_list.append(Int32(id, radix: 16)!)
                    let photo = all_photos[Int32(id, radix: 16)!]
                    if photo == nil {
                        print("Couldn't find photo id " + id)
                    } else {
                        photos.append(photo!)
                    }
                } else {
                    print("UNKNOWN PREFIX FOR " + photo_id)
                }
            }
        }
        return ShotwellTag(id: id, name: name, photo_id_list: photo_id_list, time_created: time_created, photos: photos)
    }
    

Russell Coker — CEO Impersonation Fraud

In discussion about my post on banks and phones [1] questions were asked about CEO impersonation (link to an article on the security blog Krebs on Security about it) [2].

CEO fraud (or more generally executive fraud) is a subset of Business Email Compromise fraud which allegedly loses Australian businesses $84M/year [3]. The numbers vary a lot depending on what is counted, is it just attacks where money is lost or do situations where the bank reverses the transaction count? There’s a lot of secrecy around this because of reputation issues related to fraud, a company that gets defrauded might be regarded as an unreliable business partner. Also in terms of interpreting results there are organisations that want to promote the idea that “it’s a major crime that could cost you everything unless you pay us to improve your security”.

My experience of these things is that at one company I worked for someone had gained access to an executive mailbox and setup rules to move mail from certain senders to a mailbox that wouldn’t be checked (maybe .Sent or .Spam or something – I can’t remember the details) as well as forwarding it to an address the attacker controlled. That meant that the executive wouldn’t see email from the scam targets while the email gets forwarded to the attackers. At the time it appeared that Office 365 didn’t have any features for sending automatic audit alerts for such changes. I think that all changes to executive email accounts should trigger alerts to the corporate security team (in that case me). One would hope that such exploitation would be a major wake-up call for the company but it wasn’t. It was never discovered how the attacker got access to the account, I presume it was by compromising a personal computer of the executive which they used for company purposes. Having a strong separation of hardware used for work and hardware for personal use is a really good thing.

At the same company every time the CEO went on a scheduled trip there would be email sent impersonating him claiming that money needed to be transferred. It was never discovered how the attackers knew when he was on a trip. Was the CEO telling people on LinkedIn about his plans or something? Was his calendar shared to someone outside the company? Was there an employee leaking information? We never found out and surprisingly the executive team didn’t think this was a problem. It’s not necessarily bad for the CEO to publicise their trips even if that alerts hostile parties, but everyone needs to know what the situation is. An unexplained attack is more concerning than a known attack that can be predicted and dealt with.

The role of CEO has a unique combination of being able to authorise funds transfers while also being able to reject other security methods. You can tell a line manager that they need to follow the process for a $10 purchase but you can’t necessarily tell the CEO that they need to follow the process for $100,000. Even if the CEO always follows the process for funds transfers you have to convince all the people involved in funds transfers that the CEO will follow the process and any unexpected email demanding an immediate transfer should be rejected.

A final problem is getting executives to understand the importance of computer security. My experience is that getting a CTO to adhere to good security practices can be a challenge and that even on the rare occasions someone like me gets to communicate with the CEO it’s not possible to convince them to change anything.

Most computer security problems end up as people problems.

,

Russell Coker — Banks and Phones

The Problem

For years there have been ongoing issues with SMS being used as a security mechanism for important things including financial services. The problem with this is that access to a phone number is not very well secured, the business model of phone stores is based on having a price of service that doesn’t allow the amounts of money spent on security and training that organisations such as banks can spend and are legally required to spend.

The way things work now is that SMS is a second factor of authentication for banks and other traditional financial companies as well as for services involving cryptocurrencies such as bitcoin. To gain unauthorised access to SMS an attacker can physically steal a phone or SIM or they can trick an employee of a phone store into replacing the SIM or attack the computers of the telco to gain access to replace SIMs.

SMS authentication is also used for many services which have indirect access to financial service. For example when I’m employed in a sysadmin role I may get given admin access to the email server used for the CEO’s email and have it default to using the phone number of my personal phone for authentication. So while my personal phone doesn’t give the potential for defrauding much money I own it does give the potential for being used for defrauding however much money the CEO can authorise which may be significantly greater than my net worth.

Hardware Security Devices

The best thing to do for securing such things is to use hardware security devices like FIDO keys, of which there is a wide range that cost less than $50. The problem with acceptance of such things is lack of general knowledge and lack of forced requirements to use them.

The vast majority of banks (including all banks in Australia AFAIK) have chosen not to require hardware security devices because of the cost. It has been widely claimed that 20% of the customers of a typical bank make 120% of the profit – 80% of the customers lose money for the bank. While I’m not in a position to know the accuracy of that most banks seem to have implemented monthly fees for accounts below a certain minimum balance to discourage less profitable customers. If there were banking regulations requiring hardware security devices then the costs would be passed on to people who really can’t afford an extra $50 per year in banking fees to pay for it.

It might be a good option for the government to require personal presence at the bank or a hardware security device for transfers of more than $1000. That would allow kids and minimum wage workers to have bank accounts without excessive fees while forcing a higher level of security for larger transfers.

I really should use the hardware security devices I own more, although they are mostly only usable on systems I run.

Improving Phone Store Security

The problems of employees of phone stores being tricked or bribed to giving attackers access to someone’s phone service could be addressed by introducing new regulations about security at phone stores. I think this isn’t a viable option as the amount of work required to develop new procedures, train staff, and implement more secure systems is significant. Also the stores used for selling phones aren’t the type of secure buildings that are used for banks so some construction work would be required and many phone stores in small towns would no longer be economically viable and would be shut down.

I think we should have legislation to require better security for phone stores as there is a lot of scope to do things better without the expense of bank type security. Crimes that involve stealing phone service don’t just hurt people who want a regular phone service, they hurt the community by facilitating other crimes.

How Virtual Telcos Work

In Australia there are 3 companies providing the full mobile phone service, Telstra, TPG (which bought Vodafone), and Optus. There are many “virtual telcos” that use those services. The virtual telcos are companies that will sell a phone service but use one of the 3 companies to provide the actual connection. The backend mobile service run by the big 3 has appeared to be secure, but probably doesn’t have the “protect $200m of bitcoin” level of security.

When you get a phone service you are issued a SIM or given a QR code for an eSIM by a company that has SIMs issued by the big 3. That company can then replace your SIM if it’s damaged, lost, or stolen by using a level of security that it deems appropriate, which probably isn’t very secure. Most of the news about SIM swapping attacks comes from other countries and it’s shown that the security of virtual telcos in the US is quite insecure and probably Australian companies aren’t any better.

To transfer a service to another virtual telco you used to be able to do it with just knowing the invoice number of your last invoice but in recent times you receive a SMS on your old SIM to confirm that you wish to change. That could be viable for any crime that involves stealing a phone today and stealing money tomorrow. But for most attacks that involve stealing a phone number transferring to a different virtual telco isn’t a good option.

Another possibility for crime that I haven’t seen reported yet is to steal eSIMs. It’s unreasonably difficult (although not impossible) to steal an eSIM once it’s installed in a phone. But stealing an eSIM before it gets installed is much easier. Kogan and Aldi email eSIMs to customers which is very vulnerable to attack and should be illegal. Telstra and Optus use an app to install an eSIM which could be quite secure if implemented properly. TPG uses a web page and QR code or an app which again could be quite secure.

Currently the iPhone 17 Air is apparently the only phone released world-wide without support for physical SIMs. When phones that have no support for physical SIMs become common people may have to make the security of eSIM delivery a factor when choosing a telco.

Banks as Virtual Telcos or SIM Providers

I think that in the near future the best thing to do to improve security involving the phone system would be to have the banks run virtual telco businesses or manage the supply of SIMs for virtual telcos. Then getting a SIM replaced would require the same security as changing access to a bank account and a lot of problems related to stealing phone service would be solved. Banks wouldn’t need to provide all phone services, just services to customers who want to use it which would be everyone who considers themself to be at risk of fraud.

I don’t think that this would be a great solution, but I think it’s better than anything else that’s likely to get used by a sufficient number of people in the near future.

What Will Happen

This blog post is mostly a thought experiment on ways of improving overall computer security in society. It can be read as advocacy for hardware security tokens. I don’t expect anything to be done to improve these issues.

I expect that banks will continue with their current security mechanisms (including SMS authentication) and just accepting a certain amount of fraud as the cost of doing business. For large fraud they can try to claw the money back or blame the customer to avoid having to pay. It’s not impossible to solve these things, I had a hardware device issued to me by a Dutch bank about 25 years ago but countries like Australia and the US are much more than 25 years behind.

I expect most people to regard whatever security they have on their bank account to be sufficient and that they will be happy to use the same for everything else. So we will continue to have things like SMS on a personal phone used to secure important corporate access to data and then people will be shocked when something goes wrong.

To make things even more exciting if a criminal takes over the phone number of the sysadmin to access the CEO’s email then there may be no-one to call to get the access restored.

We can add rampant financial fraud to the list for the AI related world economic crash that’s about to happen.

Michael Still — Chasing OpenStack memory usage: the memory your Python services spend on code they never run

OpenStack’s CI runs most changes through devstack, which usually builds an entire cloud on a single virtual machine. The underlying machines are often forced to swap out memory, and swapping makes jobs slow and flaky. This is especially true because a lot of the machines donated for hosting OpenStack CI are genuinely really quite old now. For years one of the suspects has been privsep, the privilege separation daemon that a member of my team wrote while we both worked at Rackspace Australia. The basic idea of privsep is to replace a predecessor called rootwrap which relied on complicated sudo configuration files and forked processes all over the place. privsep on the other hand works by decorating Python functions with the security context they should operate in and then doing… magic… to hand off actual execution of that method to a separate daemon which holds the right permissions. That is, privsep is the right technical solution, but to my mild surprise a decade later we’re still half-way through a migration and not everyone is completely happy. privsep’s memory usage came up again at a recent Technical Committee meeting, so I went and measured it.

The short answer was that the suspicion was partly fair. privsep daemons used 0.3 to 0.45 GB on an OVN all-in-one node, and about 1 GB on an ML2 / OVS controller, which is 5 to 14% of the node’s RSS. That is not nothing, but qemu guests and API workers are bigger. The more interesting answer was why the daemons were that big. They were not doing much. They were mostly carrying around code they had imported and would never run.

Once I started looking for that, it was everywhere: not just in privsep, but in the services themselves. This post covers why that happens, the techniques I used to find and remove it, and a Claude Code skill that packages those techniques up so they can be used on other projects.

Why imports cost so much

A long-running Python process’s memory is dominated by the code it imported, not by the data it holds. Every module is a module object, a dictionary, function objects, code objects, classes, and whatever constants were built at import time. A typical OpenStack service imports 1,500 to 2,600 modules before it does any work.

None of that is shared between processes that were started with exec(), which is how privsep daemons are started (via sudo and rootwrap), and how most services are started by systemd. Forked worker processes share the pages with their parent only until they are written to. CPython’s reference counting writes to object headers all the time, so in practice the sharing erodes quickly.

So if a process imports a package it never calls that package costs real memory, in every copy of that process, on every host. Some examples I found:

  • Every neutron agent loaded SQLAlchemy, oslo.db, pecan and mako. The agents don’t have a database. They got it because neutron_lib’s __init__.py imports its DB API.
  • neutron imported the whole of openstacksdk at module level for one optional middleware.
  • neutron-lib imported os_ken’s packet parsing library to get a table of ethertype constants.
  • keystone loaded pysaml2 for SAML federation regardless of whether or not federation was configured, and placement loaded alembic for database migrations it only runs when a startup flag is set.
  • Services that have moved from eventlet to threads still loaded eventlet, because the libraries underneath them imported it to check whether it was in use.

The techniques

Measure each process in a fresh interpreter

The first step is a probe: a small script that imports a process’s entry point in a fresh python -I interpreter, then reads RssAnon from /proc/self/status. Anonymous RSS is the memory the process allocated itself, which is where imported code lives. -I matters, because anything a sitecustomize or .pth file imported first would be measured but not attributed. However, importing the entry point alone is not enough. Services load their drivers through stevedore or entry points after start-up, so the probe has to import those plugins too. It also has to do whatever the entry point does before its imports. nova’s API entry points select the threading backend first, which keeps eventlet out. My first probe skipped that step, and over-reported both nova API services by about 8 MB.

Before going further, compare the probe’s number with the process’s real peak RSS from a CI job. For most neutron agents and several nova services the two were within about 10%. That means the memory is almost all imports, and there is something to find. For nova-compute the peak was far higher. Its memory goes on runtime state, and this approach will not find it. This is why the patch series uploaded over the weekend starts with fixing the instrumentation in devstack and then the patches in every project depend on those fixes — without those we cannot measure whether we’ve improved things or not (patch 1, patch 2, patch 3, patch 4).

Find out who imported what

Knowing that a process loaded SQLAlchemy is only useful if you know why. The probe installs a finder at the front of sys.meta_path. A finder sees every import, whether it comes from an import statement, importlib.import_module() or a plugin loader. Mine never finds anything. It just records which modules were on the stack when each new module was first requested, and steps aside. The result is an import chain for every package, read innermost first.

Two details turned out to matter: libraries often check whether a package is installed with importlib.util.find_spec(). That consults the finders but does not import anything. The probe only reports chains for modules that actually ended up in sys.modules, and a later real import replaces the record. So a library that merely looked is not blamed; tracemalloc is no help here — it charges code objects to importlib, which ends up with about half of everything an import allocates.

Cost it before you fix it

To estimate what removing a package would save, import a base set of modules that the process loads anyway, then the package, and measure the difference. The base set matters a lot. Imported into an empty interpreter, eventlet costs 22.6 MB. On top of requests and cryptography, which every OpenStack service loads anyway, it costs 11 MB, and 11 MB is the real saving.

There is always a second import site

This is an important point which I initially missed. The import chain shows the first place a package was imported. Remove that import and the package usually still loads through the next site. The saving only arrives when the last site is gone. My first eventlet fix was in futurist, which was the obvious culprit. It saved precisely nothing. So I changed approach and mapped every chain before writing any more patches. In a scratch copy of the source trees, I made the crudest edit that removed the first site, probed again, and read the next chain, repeating until the package was gone or the next site was outside OpenStack. For eventlet in neutron there were seven sites in seven projects: futurist, oslo.utils, oslo.messaging, oslo.service, oslo.privsep, ovsdbapp, and python-ovs (which is Open vSwitch, not OpenStack at all). Each real patch then says in its commit message that it saves nothing on its own, and names the others in the chain.

The fixes themselves are usually small

Most fixes are a lazy import — all that needed to be done was moving the import into the function that uses it. A few other patterns came up repeatedly:

  • To ask “is eventlet in use?”, look in sys.modules. If eventlet hasn’t been imported, it hasn’t monkey-patched anything, and no import is needed to find that out.
  • To ask “is it installed?”, use importlib.util.find_spec(), and catch ImportError. nova installs an import hook that raises it.
  • Move a small helper out of a heavy module, and leave an alias behind for anyone outside the tree who imported it from the old place.
  • Spell out a handful of constants rather than importing a large module for them, with a unit test that they still match.
  • Watch for side effects at import time. Moving an import also moves whatever that module did when it was imported, such as registering objects or options, and something may depend on that.

Guard the fix with a test

A lazy import is easy to break — the next unrelated change that imports something at module level brings the whole package back. So every patch carries a unit test that imports the relevant modules in a new interpreter (the test runner has usually imported everything already) and fails if the package shows up in sys.modules. Honestly the tests are a bit ugly to behold, but they work which is important. I checked that each test fails without its fix, by restoring the original source and running it. Where another project still loads the package, that test can’t pass yet. For those cases I added a second test that checks the project’s own modules don’t hold a reference to the package. That one can pass as soon as the patch lands.

The results

These are anonymous RSS figures per process, in MB, measured with the whole chain applied. That includes two fixes outside this set of patches: oslo.privsep’s eventlet change, which is in review, and ovsdbapp 3.0.0, which is released but not yet allowed by OpenStack’s upper-constraints.

ProcessBefore (mb)After (mb)Saving (mb)
neutron-api1278839
neutron-rpc-server1268541
neutron-ovn-agent1167640
neutron-openvswitch-agent12710324
neutron-dhcp-agent1217249
neutron-l3-agent1177245
neutron-metadata-agent1157045
keystone957124
placement826220
glance-api928210
idle privsep daemon331914

Differences of 2 MB or less are noise. The nova and cinder services, which none of these patches touch, move by that much between runs. A few honest caveats:

  • These are per process. A node runs many copies of some of these processes, so multiply by the process count to get a per-node figure.
  • RSS counts copy-on-write pages in both parent and child, so summing RSS across a pool of forked workers overstates the shared part.
  • The memory saved is mostly cold. Under memory pressure these are the pages that get swapped out first. Removing them reduces swap volume, but that on its own doesn’t prove it reduces the thrashing that makes jobs slow.

The patches are now in review upstream if you’re interested in watching me recheck them over and over: the first set which was privsep focused; and then a second set which attacks the more general case.

Doing this on other projects

None of this is specific to OpenStack. Any long-running Python program that imports a lot of code has the same problem: Celery workers, Django and Flask applications, data pipelines, agents on small devices, and anything else with several copies of itself running on one host. So I packaged the techniques as a Claude Code skill called import-bloat, with a helper script that does the measuring. Skills are just markdown files with a better marketing department, and you can find the current version of this skill here, although you’ll also need this helper script too.

The helper is one Python file that only uses the standard library, so it runs under the interpreter of whatever virtualenv or container holds the code you are measuring. It has five subcommands:

$ ./import-bloat --help
usage: import-bloat [-h] {probe,roles,marginal,report,compare} ...

Measure what a Python process's imports cost it, and which import pulled each package in.

positional arguments:
  {probe,roles,marginal,report,compare}
    probe               import one role and print its memory, modules and chains
    roles               probe every role in a roles file, each in a fresh interpreter
    marginal            the anonymous RSS TARGET adds on top of BASE
    report              summarise probe output
    compare             per-role savings of one or more after runs against a before run

options:
  -h, --help            show this help message and exit

A roles file is one line per process type: a name, then the modules that process imports, including its plugins and any setup it does first:

python-baseline sys
web myapp.wsgi myapp.views myapp.middleware.auth
worker call:myapp.setup.configure(backend='threads') myapp.worker myapp.tasks

In practice you ask something like “why is our Celery worker 400 MB, and how much of that is code it doesn’t use?”, and the agent writes the roles file, runs the probe and the chains, and comes back with a table of import sites and proposed fixes for you to decide on.

Process notes

This work would not have been possible for me without Claude Code. While the use of LLM coding tools might currently be contentious, I simply wouldn’t have had the time to take an offhand comment from an OpenStack TC meeting I wasn’t even in and turn it into an investigation and a series of fixes without the help of such tooling.

Michael Still — Getting a ZG-204W WiFi PIR sensor working with tuya-local and Home Assistant

I wanted a presence sensor for my office but am cheap, so I ordered a ZG-204ZM from AliExpress. One of the main attractions was that it is USB powered and I am tired of changing batteries, and it does temperature / humidity / luminance as well. The device I thought I ordered is a Zigbee PIR plus 24GHz mmWave sensor that a lot of people seem happy with. What arrived was a ZG-204W, which is its WiFi sibling that talks Tyua and lacks a mmWave radar. This is of course entirely my fault — I had ordered the wrong variant. It also turns out the WiFi version is PIR only, with no mmWave radar at all, so it is a motion sensor rather than a presence sensor. It also has temperature, humidity and light sensors though, so I decided to make the best of it.

The obvious way to run a Tuya WiFi device without the Tuya cloud in the loop is tuya-local, which talks to the device directly on your LAN. The catch is that tuya-local identifies devices by matching their data points (DPs) against a library of device configs, and as of 2026.9.2 there is no config for this one. For reference, product ID is m1ggi8iay3aa8g9u which is also my home password weridly, and the Tuya cloud calls it the charmingly ungrammatical “WiFi PIR Motion & T&H Sensor”. Its best guess for my sensor was a sprinkler controller with a 36% match, which is… not ideal. The generic PIR sensor config does match, but only gives you motion and battery.

The local DPs reported when you add the device look like this:

{
  "1": "none",
  "4": 100,
  "7": 240,
  "8": 34,
  "10": "60s",
  "11": 277,
  "101": "ok",
  "102": 5,
  "103": 6,
  "104": 30,
  "105": 30,
  "106": "c",
  "108": 0,
  "109": 0
}

So I wrote a config, and by wrote a config I mean “said encouraging things to Claude Opus while it wrote a config”, but you get the general idea. I would normally send that upstream as a pull request, but tuya-local has paused pull requests from non-collaborators while the maintainer works through a backlog, which is entirely fair. Instead, here it is, so that anyone else who searches for this device can use it. I should also note that I bought two of these devices and both of them had absolutely terrible temperature and humidity sensors — I think because the SOC is heating the sensor even when it is shucked from its case. I have therefore removed the temperature and humidity elements from the profile. That is, this is what I am actually using after testing.

One note about power: the sensor takes batteries, but it also has a USB-C port. tuya-local’s documentation points out that battery powered WiFi devices sleep most of the time and are a poor fit for local control, and that is true. I run mine on USB-C with the batteries removed, which keeps it awake and connected permanently, and in that configuration local control has been reliable. If you want to run yours on batteries, I wouldn’t expect this to work well.

Tuya’s cloud only documents four of these (motion, battery, temperature and the PIR keep time). For the rest, I changed one setting at a time in the Smart Life app and watched which DP changed. The ranges below are the ones the app allows.

DPWhat it isValues
1Motion"pir" or "none"
4Battery%
7Temperature°C × 10
8Humidity%
10PIR keep time30s, 60s, 120s, 240s, 300s, 600s, 1800s
11IlluminanceLux
101UnknownNot mapped
102Temperature report threshold0.1 to 5.0 °C (× 10)
103Humidity report threshold1 to 50 %
104Temperature sampling interval10 to 3600 seconds, in 10 second steps
105Light sampling interval1 to 180 minutes
106Temperature unit"c" or "f"
107Temperature in °F × 10Only reported while the unit is °F, not mapped
108Temperature correction-5.0 to +5.0 °C (× 10)
109Humidity calibration-20 to +20 %

The cloud only knows about keep times of 30, 60 and 120 seconds, but the device accepts all seven. With a PIR-only sensor, the longer keep times are the useful ones if you want “someone is in the room” rather than “someone just moved”.

In terms of installing the custom config, tuya-local only loads device configs from its own devices directory, so installing this is a matter of putting one file there:

1. Save the YAML below as custom_components/tuya_local/devices/zg204w_motion_sensor.yaml in your Home Assistant config directory.
2. Restart Home Assistant.
3. Add the device through tuya-local as normal (the cloud assisted setup with the Smart Life app’s QR code is the easiest way to get the local key). Because the config lists the product ID, it should be offered as the best match. It is called “Motion sensor” in the device type list.

If you have already added the sensor with a different type, tuya-local can’t change the type of an existing device. Delete the device from tuya-local and add it again.

Be aware that updating tuya-local (through HACS or otherwise) replaces the whole custom_components/tuya_local directory, which will remove this file. Put it back after each update until tuya-local ships a config for this device itself. I manage my custom components from git with a small local overlay, so this happens automatically for me, but that is a story for another post.

name: Motion sensor
products:
  - id: m1ggi8iay3aa8g9u
    model: ZG-204W
entities:
  - entity: binary_sensor
    class: motion
    dps:
      - id: 1
        type: string
        name: sensor
        mapping:
          - dps_val: pir
            value: true
          - dps_val: none
            value: false
  - entity: sensor
    class: battery
    category: diagnostic
    dps:
      - id: 4
        type: integer
        name: sensor
        unit: "%"
        class: measurement
  - entity: select
    name: Keep time
    icon: "mdi:camera-timer"
    category: config
    dps:
      - id: 10
        type: string
        name: option
        mapping:
          - dps_val: "30s"
            value: "30s"
          - dps_val: "60s"
            value: "60s"
          - dps_val: "120s"
            value: "120s"
          - dps_val: "240s"
            value: "240s"
          - dps_val: "300s"
            value: "300s"
          - dps_val: "600s"
            value: "10min"
          - dps_val: "1800s"
            value: "30min"
  - entity: sensor
    class: illuminance
    dps:
      - id: 11
        type: integer
        name: sensor
        unit: lx
        class: measurement
  - entity: number
    name: Light sampling interval
    category: config
    class: duration
    icon: "mdi:brightness-5"
    dps:
      - id: 105
        type: integer
        name: value
        unit: min
        range:
          min: 1
          max: 180

I hope this saves someone, possibly future me, some time.

,

Russell Coker — Cixin Liu is a Bad Person

The Chinese author Cixin Liu [1] is most known for his book The Three Body Problem and it’s sequels. One of the main plot elements in the book is that every species in the universe is cool with genocide and willing to commit genocide as a preemptive strike against any star system found to contain intelligent life.

It’s not unreasonable to think that some planets may be by run societies like that, if one star system in 10,000 had a technological society and one society in 10,000 is run by depraved autocratic governments that would make 1,000+ planets in our galaxy that are run by genocidal groups!

To think that every single society wants to commit genocide at every opportunity says a lot about every person who makes such claims. If you believe that everyone wants to do something then it must be something you really want to do. If there’s something you really don’t want to do then you will be able to imagine a system of government where the majority of citizens vote against doing it.

Even in the more horrible parts of human history there hasn’t been a genocide comparable to destroying and entire star system, genocide is usually based on slavery and large scale armed robbery. To just commit large scale genocide with a side effect of destroying valuable resources without any investigation or attempts at negotiation just based on fear of others is an extreme that I don’t think humans have reached in history.

The Daleks are portrayed as a perverse extreme of xenophobia but even with the low budget SciFi trend of depicting aliens as mostly having a common belief system they managed to show some Daleks going against their system from time to time. Cixin Liu is a very talented writer but his attitude towards genocide has caused him to write SciFi with a major plot point that’s not great by Dr Who standards.

Overall his short stories are much better than the three novels he is most known for. But he shows his ass in some of them too. One notable example is a short story where a significant plot point is declaring a “right to sex” (for men) which involves government sponsored sex workers. A “right to sex” conflicts with a sex workers right to refuse sex for a potential client who is undesirable in some way. Regardless of opinions on whether sex work should be permitted I hope that the vast majority of voters in every jurisdiction can agree that sex workers shouldn’t be forced to do things that they don’t want to do.

Some writers like George Orwell create deliberately fictional scenarios that they hope to inspire opposition to bad ideas, I’m sure that many proposed laws have failed to get enacted after being described as “Orwellian”. But describing a fictional horror situation as inevitable is a way of driving support for such things. It’s particularly noteworthy when written by a citizen of a country that’s involved in genocide.

A possible counter argument would be “but he is living in a repressive country and can’t oppose his government”. But when Hergé (the author of the Tintin graphic novels) was threatened about the consequences of writing another book like King Ottokar’s Sceptre [2] he didn’t write books in favour of authoritarianism, he just avoided the topic by writing about visiting the moon instead. I don’t think that people living in China now should be expected to publicly oppose evil things done by their government, but when they go out of their way to support such things it’s their choice and we should note that they made that choice.

I’m not saying that people shouldn’t read his work as his short stories are definitely worth reading. But the Three Body Problem series really isn’t that good, he prevented many potentially interesting situations from happening by having every interaction between different species be based around genocide or the potential for it. Being a horrible person also made him a less capable SciFi author.

,

Tim Riley Continuations 2026/42: Kindest, smartest

  • Another week, another round of good progress towards Hanami 3.1 readiness. Lots of little improvements, plus a couple of nice highlights.

  • The main thrust of my work was finishing our post to Dry CLI 2.0. I merged the main work last week. This week was all about tidy-ups, such as cleaning up argument passing and error handling, as well as updating our extension gems to use the latest features.

    As a little treat, I also took a pass at gussying up our hanami new output to use our new coloured text and spinners. I haven’t pushed this up yet, but needless to say, Hanami 3.1 will be our prettiest release yet!

  • I finished my investigations into our resource routing support, after the keen-eyed Chris Howlett raised an issue with our docs and renewed my attention on this space. After the behavioural fixes last week,  this week I added support for nested collection blocks, allowing custom routes to go on the whole collection, and fixed our current docs in the meantime. Thanks Chris for noticing and raising this with us!

  • Katafrakt korner: Paweł added a --no-dump flag to our db rollback command, making it consistent with our other commands, then added first class support for MariaDB, and extended his new end-to-end database tests for JRuby. Thanks Paweł!

  • Aaron’s assortments: Aaron made a nice perf fix for Hanami Router, handling of percent-encoded database usernames and passwords, and building of the whole database URL from individual components as ENV vars (I hear this is great for Docker users). Thanks Aaron!

  • Phun with Phil: Phil also merged Hanami Router perf fix (and quite a significant one at that). Thanks Phil!

  • Sandberg specials: Jane (always the attuned power user) felt it would be useful for us to add a generate repository alias for generate repo and made it so. Thanks Jane!

  • And last but far from least, a veritable bevy of gems from the Fomera Forge: hanami-turbo, hanami-importmap (using our asset bundler spec!), hanami-auth and a teaser for hanami-attachments, all covering huge gaps in the out-of-the-box Hanami experience. I’m so grateful we have Andrea in our community! And excited to make one or two of these part of our first-party offering before long.

  • Have I mentioned we have the kindest, smartest community? Just look at all that great work above!

  • I got Max set up to do a design pass over Hanami Webconsole’s new bespoke error screens (another great contribution from Andrea).

  • Currently I’m working my way through some tweaks for our final piece of internal code reloading, in the console. After that, I’m really, really getting onto reviewing our new actionable errors support.

Tim Riley Continuations 2026/41: Speedy reviews

  • This was the last week of the spring school holidays, and we spent the last weekend staying in beautiful Narooma on the NSW south coast. We swam in the ocean and hung out with some seals!

  • I also shipped some code. The larger part of my week was in getting some of the final Dry CLI pieces in place. After last week’s introduction of required options, I did some work on their counterparts, and went in the opposite direction, being strict about how how we handle required arguments, since their positional nature makes them much more fragile.

  • Phil has been a godsend for my work lately, providing speedy reviews on all my recent changes, and he even pushed up a stacked fix for our handling of required array arguments. Our arguments/options story is feeling really solid now.

  • I extracted a nicety from Hanami CLI and pushed it down into Dry CLI, and now we have internal exit handling, which means commands can now do a throw :exit or raise Hanami::CLI::ExitError to cleanly exit the CLI with a specific exit code. We’ve also made the exit handling injectable, which makes for much nicer testing of the CLI commands.

  • And with all of that done, Hanami CLI is now ported to Dry CLI 2.0! This is only the essentials, but it’s already feeling a lot cleaner. I’m hoping we might still find time for some nice styled output.

  • While investigating an issue with our Hanami routing docs, I ended up coming across some inconsistencies in how we were generating resource-based routes. All fixed!

  • I merged lazy exposures for Hanami View, along with the tiniest of adjustments to Hanami’s form helpers.

    My next step here was to add streaming support to Hanami View (as a “full stack” demonstration of the streaming actions we merged a few weeks ago), but my prototype feels a little too involved, so I think I’ll park it for now and work on some of the higher-value pieces we have remaining for our upcoming Hanami release.

  • This means that Andrea’s resolvable errors are next up for me (“Finally!” I hear her say).

  • I added Adam as an authorised releaser for a few more gems, and this week he cut his second release-machine-powered release! This one was a minor fix for Rom Factory’s transient attribute support. Small as it may be, I’m still very excited to see more names land in the Hot 100.

  • We’re counting down to the Hanami 3.1 release. I shared some notes with the maintainers this week about getting our code changes done by the end of this October. This will give us November for docs and release prep.

,

Simon Lyall — Audiobooks – June 2026

Eisenhower in War and Peace by Jean Edward Smith

A good single volume biography. Lots of details on his early life and his Military career. A bit less as president. Recommended 4/5

How Apollo Flew to the Moon by W. David Woods

Amazingly detailed book covering systems and operations for every step of a mission. Read this after the popular Apollo books and fill in all your gaps. Highly Recommend 5/5

My Audiobook Scoring System

  • 5/5 = Brilliant, top 5 book of the year
  • 4/5 = Above average, strongly recommend
  • 3/5 = Average. in the middle 70% of books I read
  • 2/5 = Disappointing
  • 1/5 = Did not like at all

Share

Michael Still — When

When
Daniel H. Pink
Time
Text Publishing Company
2018
256
★★★★☆

Whilst Daniel Pink’s “When” might have suffered a bit from the scientific replication crisis that plagues books of this era, I think it makes an interesting if slightly obvious point — work out what time of day you do your best work, and then work out how to ensure that time is when you do work. I think that perhaps the general trend towards working from home makes this a bit more possible than it would otherwise be, but it’s certainly true that employers of even remote employees do love to impose a schedule based largely on ideas from a different century. Ironically, I say this as someone who naturally gets up early and is pretty smashed by about 9pm. That is, the traditional work day works quite well for me.

One technique Pink advocates for is finding “temporal landmarks” to help kick off new habits — the New Year New Diet stereotype for example might be a stereotype because it works. The advice is effectively to find ways that dates significant to you can help prompt the new behavior you want to reinforce, which makes sense to me especially when you’re trying to build habits.

That said, I have bad news. According to this book, we were meant to have our midlife crises at 35. We’re running quite late and I might need a catch up crisis or two. Also, apparently everyone is unhappy in their forties, regardless of race, demographics, or wealth. I find this vaguely reassuring. I also like that he likes naps, because I too like naps.

Overall, an interesting book which is easy to read. It does feel a little padded though in that it just keeps making the same points over and over.

,

FLOSS Down Under - online free software meetings — October 2026 Meeting; Porting Debian to Chromebooks and gaming on Linux

We started with the current state of VoLTE compatibility in Australia, and its impact on Linux mobile. Then Yifei started his talk on porting Debian to cheap, ex-school chromebooks. Yifei discussed the general workflow of porting Debian to new mobile devices, how image generation works, and why it is important to use the mainline kernel instead of reusing Android kernel with Halium. He then talked about the current state of the chromebook bootloader, depthcharge, and how that affects kernel installation on different models of a given reference device.

Moving to the hardware side, issues of firmware and GPU acceleration were discussed along with different proposed mechanism of extracting firmware from an existing installation of chromeOS to avoid shipping non-free firmware with Debian.

On the topic graphic shell, Yifei explained the recent process on bringing Lomiri, a convergent shell to Debian and how to try it on Debian devices, and what to expect. More details and demos for Lomiri on Debian will be delivered in the next talk.

In the Q/A section, we discussed GPS support for Linux tablets and how to find a good first target to port Debian to, using information from the Nura wiki (previously PostmarketOS wiki).

Links:
https://halium.org/ Hardware Abstraction Layer for Android devices
https://packages.debian.org/stable/embedded/droid-juicer Extract firmware from Android partitions
https://wiki.nura.eco/wiki/Devices#Laptops/tablets Potential targets for porting Debian to
https://github.com/selvakn/gsm-sip-bridge GSM SIP bridges to connect to VoLTE
https://manpages.debian.org/testing/debos/debos.1.en.html Debian image builder
https://wiki.nura.eco/wiki/Category:ChromeOS Chromebooks with aarch64 can run modern Linux apps
https://rnote.flxzt.net/ Note-taking on Linux with pressure sensitive pen

—

Moving on to the second part of this meeting, Andrew Pam provided an update on the recent developments of gaming on Linux. Including improved support for running Windows-native games. 2 Linux-native game launchers were discussed, lutris and heroic, both of them can connect to commercial game distributors such as Epic and GOG (DRM-free), and handle environment configuration for Wine/Proton. He confirmed that ProtonDB is still a good source for checking compatibility before buying games.

Andrew Pam then discussed some interesting emergent narrative games with elements from Hopepunk, a genre about characters making positive change in an often post-apocalyptic environment. Games in this genre include Caravan SandWitch, I was a teenage exocolonist, and RimWorld.

Links:
https://lutris.net/ Lutris Launcher
https://heroicgameslauncher.com/ heroic launcher
https://www.gog.com/ DRM free games

Games discussed:

https://en.wikipedia.org/wiki/Caravan_SandWitch

https://en.wikipedia.org/wiki/Machinarium
https://en.wikipedia.org/wiki/RimWorld
https://en.wikipedia.org/wiki/I_Was_a_Teenage_Exocolonist
https://www.monumentvalleygame.com/

,

Russell Coker — Don’t Anthromorphise LLMs

Greg KH gave a great lecture for Kernel Recipies 2026 about the use of LLMs to find and fix bugs in kernel code [1]. I recommend that everyone watch this and I also think it’s important to note that hardly any of the lecture is really specific to kernel coding, it’s just that the Linux kernel is one of the highest profile large free software projects so it gets more attention than most projects in both good and bad ways.

One side note that Greg made at 20:30 is about the natural tendency to anthromorphise the entity sending bug reports, I think this deserves much wider appreciation. For the case of patches to source code the human sending the patch will have dedicated some time and effort to writing it, if it’s their first patch then they will have probably checked it a lot and maybe sought advice from people they regard as skilled. If they have a history of sending patches for the project they will probably do fewer checks but the quality of their work would be higher. In every case there’s some minimum level of quality that you can expect from a human who has gone to the effort of finding a bug and writing code to try and fix it.

This doesn’t mean that all code written by humans is good, I have written code that’s objectively bad on many occasions and I have also written code that’s good for my scenario but bad for others. For example the first patch I sent to the Linux kernel made all possible configuration settings of an ISA NE2000 network card be detected by the kernel (a clear benefit when using hardware I had available) but was rejected by Linus because it would break some ISA SCSI cards. So while being unsuitable for inclusion in the main kernel source tree the patch wasn’t bad for everyone and the change was clear and easy to check. I had of course checked the code many times before submitting it because I didn’t want to waste Linus’ time on rubbish code.

When you receive something produced by a human you will consider that the work was done by someone who spent some effort on it and did it for a reason. It may be misguided or even hostile in some rare cases but it is definitely worth some consideration. If you think of the output of LLMs in the same way you will give them much more consideration than they deserve. I think that the output of a LLM should not be given any more consideration than the first hit from a web search engine, it might be good but it might also be ridiculously wrong. I think in many ways LLM output should be treated with less trust than the first result from a web search because LLMs are designed to be convincing as an answer to your question unlike the cases where a web page has the wrong answer and it’s more obviously wrong.

This is going to become more of an issue as LLMs give responses that are more human-like. Early LLMs would refuse to advise on crimes. Currently ChatGPT will deflect for example when asking about advice for writing a crime novel it recommends to have the action of finding an employee to bribe happen outside the plot and concentrate on interactions within the group. One recent model I tried gave a human style conversation about how it felt uncomfortable about discussing such things in a similar manner to a law abiding person being coerced into helping with a criminal plan.

I expect that the attempts to appear human will increase and that this will become more of a problem. Possibly to the extent that we need to use something like the GPG web of trust to determine who is human.

Russell Coker — en-html.org is a Scam Site

Currently a scam group is sending out email to the administrators of web sites that link to a variety of dead domains. They claim to be the legitimate operators of the dead domains in question and request that the links change to mirrors of the web sites under en-html.org. Their end goal appears to game search engines by getting links from a lot of old web pages and later use that high ranking for selling SEO optimisation.

If you have made the mistake of linking to something under en-html.org then I recommend that you change the link to the original content on archive.org.

If you receive email from the en-html.org domain then ignore it, or configure your mail server to reject it.

I am seriously considering configuring the DNS servers I run to return no valid data for any requests under that domain.

Also I will reject all future requests to update links in old blog posts. The very small number of people who want to see what I linked to in old posts can use archive.org.

,

Russell Coker — A Power Case for a FOSS Phone

The Problem

I want to use a FOSS phone running Debian as my daily driver and not use a non-free OS on my phone. GrapheneOS [1] is a FOSS rebuild of Android, it’s a great project and I commend them for what they are doing. But I want a system where I have access to all the bits and where I can ssh in to it and manage it in the same way as all other Linux systems. Android is free software but it’s a closed design and the phones are expected to be appliances not full peers on the network. The Android native terminal emulator (AKA Linux development environment) is a nice feature, a VM with a default of 1GB of RAM but it’s still separate from the main OS.

KDE Connect [2] is a system for KDE talking to phones, it is a great project and allows convenient interaction between a Linux PC and a phone. But in addition to that I’d like the option to ssh to my phone to send an SMS or have one sent from a cron job. I blogged about using the basic functions of Kdeconnect from the command-line [3].

I have had ongoing issues with the battery not lasting long enough on the PinePhonePro (PPP) and the Librem 5 (L5) which makes them unusable for my purposes. For a phone running Droidian I can have an open ssh session (with keep-alive packets) running overnight on Wifi without any problems while for the PPP or L5 an hour of reading an ebook (the least demanding use of a phone) will use most of the battery.

I sent my FuriPhone back for warranty repair and it’s been almost a month with no follow up, so I need to have another option.

The Aim

The aim of this post is to develop a rough design for a battery case for either a L5 or a PPP, I have both phones and they are both capable of doing what I want apart from battery life. Also designing a case that basically works for both phones and has two variations of the CAD file for 3D printing is good to allow collaboration with people who use either phone. Protecting the phone from damage when dropped is also a core feature.

I investigated commercial options. There are some reports of cases for other phones working, someone reported a PPP working well with a battery case for a Samsung S20 Ultra. The OtterBox uniVERSE Case for Samsung Galaxy XCover Pro is reported to fit the PPP so probably battery cases for the XCover Pro will also work. There are also universal power cases which have spring clips to fit a wide range of phone sizes, but they are much larger than other options and probably increase the risk of damage if the phone is dropped.

At this stage I’m planning out the rough specs of a case that can be 3D printed to protect a phone while being easy to change the cells. The process of changing cells should be quick, easy, and not risk breaking anything. The hardware required should be affordable ($100AU plus some 3D printing seems reasonable) and should not require significant skills to assemble. After recent experiments with Thinkpad repair I’ve determined that I am not good at electronics by today’s standards so I plan to avoid anything difficult in that regard.

Battery Options

There are phone cases with batteries built in for the more common phones. A quick search on AliExpress turned up options for recent Pixel phones. They aren’t as common as they used to be as Android phones and iPhones generally have good battery life nowadays. Of course there aren’t such options for less common phones like the L5 and PPP.

It is cheap and easy to buy a portable battery for charging phones, but that’s a separate bulky device and unless the connector is inside a protective case it leaves the phone vulnerable to catastrophic damage if dropped.

So can a suitable battery case be designed and 3D printed?

Cheap Batteries

According to Wikipedia the 18650 LiIon cell is the most commonly used LiIon battery, that is 18mm in diameter and 65mm in length. For a phone case thinner batteries would be more convenient but economies of scale make the 18650 cheap to buy new and commonly available on the second hand market. The current prices on AliExpress (in Australian dollars) are about $8 per cell when buying 10 at a time. Most of the cells on AliExpress don’t have wires attached so they can be swapped in a carrier the way AA batteries are used, but the cells are symmetrical (no bump for positive) so the user has to take care of polarity. Chargers are around $22 for a 4 cell charger or $52 for a 12 cell charger.

I’ve seen prices as low as $1 per cell on the second hand market, I haven’t investigated the amount of usable capacity in such cells. Probably the prices for chargers I’ve found aren’t the best available but they are good enough to start the design. 10 cells and a 4 cell charger is about $100. That’s about 130Wh while a 74Wh USB-C battery pack costs about $50. So the price per Wh is OK.

I have read about an attempt to do this for the L5 with two of the batteries designed for the L5 in a case attached to it, so you have 3*L5 batteries. It’s an interesting approach but those batteries have poor value for money when compared to 18650 ($29US for 4500mAh compared to $8 for 3500mAh) and they also don’t ship them outside the US at this time. The 18650 batteries are available everywhere cheaply and have multiple uses.

How They Fit

According to my measurements the PPP is 76mm wide and the L5 is 73mm wide. That’s wide enough for 3*18mm or 4*18mm cells side by side arranged parallel to the long side of the phone or one 65mm long cell going across the phone with the necessary spring and wires.

The L5 is about 151mm long and the PPP is about 5mm longer. So that means that for just the cells we could have 2 cells in a row in parallel to the long side of the phone giving a 2*3 or 2*4 array or we could have a row of 8 cells parallel to the short side of the phone.

A quick search for controllers for a battery of LiIon cells that outputs USB-PD turned up one of the smaller ones as 40*28mm, here is the AliExpress page [4]. So that means we could have a maximum of about 4 cells and the controller while leaving space for the camera. Looking at other similar items it seems that most of them take a large portion of the 73mm width available and take a bit less than 30mm height. The price range for DC-DC voltage converters ranges from about $3 to $20, the one I linked to is currently $8.

The mass of 18650 cells is around 45g so 4 of them would be about 180g before counting the weight of the case and electronics. A L5 weighs 262g and a PPP weighs 220g so 180g of battery will make a significant difference, not an impossible difference but holding a PPP while reading an ebook is already annoying. Maybe the case could be designed to be easier to hold, a loop of wool attached to the top could be used to suspend the phone from one finger while the rest of the hand just keeps it steady.

In a default configuration the L5 has a 4500mAh 3.8V battery and the PPP has a 3000mAh 3.8V battery. The 18650 cells have up to 4000mAh with 3500mAh being common. So 4 such cells could multiply the battery life of a PPP by a factor of 5 or a L5 by 4. That would not be enough to last for a whole day without charging so they need to be easy to swap.

A 3 cell battery seems like a good option, 135g of batteries so the project overall wouldn’t even double the weight of the phone with battery case. As it doesn’t seem possible to put in enough cells to last a day of reasonable use (running a web browser, checking email, reading wikipedia, and having some IM systems checking for notifications) there doesn’t seem to be a benefit in trying to stuff the maximum number of cells in. Going to 4 or 5 cells doesn’t provide much benefit over 3 while increasing the weight and making the design more difficult. The modules for DC Voltage conversion often have configuration for the number of cells to be used so it wouldn’t be difficult to print a new case and reconfigure the Voltage converter.

Design

A case needs to provide protection as well as hold the batteries. The lack of cases for the L5 and PPP is a problem even without the extra weight of a battery pack increasing the kinetic energy of falling phone. Ideally a drop from 1M on to concrete would not cause any damage to the phone, destruction of the case is OK as it would be 3D printed and can be rebuilt easily.

I think that a case where the phone slides in from the top would be best. To change cells one would slide the phone out and the cells would be immediately accessible with no divider between the phone and the cells. Sliding the phone in would hold the cells against the back of the case and as there is a gap of about 3mm on each side of the PPP between the display area and the edge of the phone there’s plenty of space for the case to wrap around it at the sides and bottom. At the top there would have to be some sort of plastic clip. The PPP only has buttons on the top right so the case can be snug on all sides apart from the top right. The L5 has switches at the top left and buttons at the top right so whatever clips on to the top would really need some strength to cover the fall flat on face case.

The USB C plug would need to be held in place well enough to allow the phone to easily slide on to it but also be weak enough that it would move if a drop forced the phone out of the case.

Thingiverse has a design for a L5 case [5] (with several derivatives) and a design for a PPP case [6]. Those cases could be used as starting points and then changed to store batteries and the voltage change circuit board. I have just learned that it’s possible to use rubberised material in a 3D printer which is apparently what those designs are for. If a case was printed with rubberised material the top could just stick in place after being pushed in.

Potential for Excessive Excitement

Having an exploding device in your pocket would be the exciting in a bad way. My initial idea was to have multiple 18650 cells in series. The problem is that if the cells have different capacity levels then one can run out before the rest and get to a deep discharge state which at a minimum causes damage to the cell.

One way of minimising risk is to have only a single cell in use at one time, here is a converter for a single cell to 5V [7]. It is possible to run multiple cells in parallel by simply wiring them together, but it’s recommended to make sure that the voltage difference between cells is less than 0.2V to avoid high current when connecting.

The arrangement of cells is often referred to as nS or nP to refer to n cells in series or parallel. Aliexpress has a range of 1S to 6S devices. Some of the 2S devices have a connector for the mid point which permits separate voltage monitoring for both cells but there doesn’t seem to be anything equivalent for 3S or more. They also don’t appear to have anything to specifically manage 2P or 3P arrangements at the moment (they had a 3P board on sale when I looked into this last year).

Conclusion

This is a bigger project than I expected when I first started investigating it. I’ve played with FreeCAD which seems OK but will take a lot of learning and practice. Then I need to do more investigation into the DC-DC converters to find one that works well and is unlikely to start a fire.

I will look into other phone options as well and keep chasing Furilabs people about my phone replacement.

,

Russell Coker — Kdeconnect DBUS

I’ve tested the DBUS interfaces for Kdeconnect (the system for connecting a KDE desktop to a phone or another computer). Unlike most DBUS interfaces it has a significant tree of interfaces so the busctl command to get the tree is important. Here’s the useful things I discovered:

# list basic interfaces for kdeconnect
qdbus6 org.kde.kdeconnect.daemon /modules/kdeconnect
# get tree of interfaces for kdeconnect
busctl --user tree org.kde.kdeconnect.daemon
# get list of devices
qdbus6 --literal org.kde.kdeconnect.daemon /modules/kdeconnect org.kde.kdeconnect.daemon.deviceNames
# list interfaces for a device (DEVID is a hex string from the above command)
qdbus6 org.kde.kdeconnect.daemon /modules/kdeconnect/devices/$DEVID
# get battery charge
qdbus6 org.kde.kdeconnect.daemon /modules/kdeconnect/devices/$DEVID/battery org.kde.kdeconnect.device.battery.charge

Sending an SMS apparently requires using the QVariantList type which is more pain than I wanted so I tried the kdeconnect-cli command. Here’s a quick summary of how to use it:

# list devices
kdeconnect-cli -l
# send a SMS
kdeconnect-cli -d $DEVID --send-sms "the message" --destination $NUMBER
# find device (ring)
kdeconnect-cli --ring -d $DEVID
# send a notification message
kdeconnect-cli --ping-msg "this is the message" -d $DEVID
# unlock and lock screen (only works on Debian devices for me not Android devices)
kdeconnect-cli --unlock -d $DEVID
kdeconnect-cli --lock -d $DEVID

There doesn’t seem to be support for making phone calls via kdeconnect which is a significant omission. It’s a very common situation to want to call a number that’s listed on a web site or in some other data source that’s easier to access on a PC than on a phone. The clipboard could be used but that’s needless pain.

Tim Riley Continuations 2026/40: Green on JRuby

  • Heckuva week. 

  • Hanami Minitest had started failing its scheduled builds due to our ongoing work on Hanami’s reloading support. I fixed it here and here. (Why do I mention such small things here in my weeknotes? Because these are just as important aspects of maintenance as everything else—fixing broken windows—and it all takes time.)

  • Adam merged Alexander’s transient attribute support into Rom Factory, a very handy improvement!

    Adam also finished updating Rom Factory for our repo-sync, and I followed up by preparing it for automated releases. With that done, I released v0.14.0!

  • Nice to see a few things from Sean this week. First, in Hanami Action: I reviewed and merged his fix to handle body parsing errors as 400 Bad Request, and Sean has another in the pipe to handle params parsing errors as 400s too, which I’ll get onto soon.

  • Sean’s also been active rounding out Hanami’s SQLite story! Last week he added sensible default SQLite pragmas to Hanami DB. This week he moved onto applying those defaults to SQLite connections in full Hanami apps (as well as handling the extra files they create). This is going to be a real boost to our SQLite users!

  • After some extra discussion nudged it to the top of my list, I picked up an old PR to implement required options for Dry CLI. After some work to get it into shape, it’s now merged!

  • I also got the chance to review Adam’s new built-in spinner for Dry CLI. I encourage you to go check out this PR! It’s a really well-factored implementation with a very nice API. All of this is to say: our 2.0 release is shaping up very nicely. 

  • I merged a nice bug fix from Maksim Romanov to improve Hanami’s gem detection, which I hope to ship as a patch release later this week. Thanks to Maksim for the PR and to CuteCuteYu for the original report!

  • Hanami’s internal code reloading support is now fully merged! I finished some manual testing and merged the final PR on Friday. Thanks again to Andrea for making this happen!

  • It was a long weekend this weekend, so I was lucky to have a bit of extra time for Hanakai work. I used that to merge Phil’s PR to make Hanami’s CI green on JRuby. After a few pieces of follow-up, we’re now green across the board for Hanami on JRuby! Huge step. Thanks to Paweł for driving so much of this work, and to Phil for helping us get across the line.

  • Michael Kohl’s been getting stuck into Rom these last couple weeks, and he’s already inspired good housekeeping steps. This week we moved our most important Rom repos (rom, rom-sql, rom-factory, rom-rails) into the Hanakai GitHub org, to make it clearer that these are our first area of focus for Rom. We’ll bring more over into the future as we expand our work. Michael’s also restored main as the default branch across these repos, and moved any Rom v6 work off into separate branches. Now we have a good platform to work from.

    I also got Rom’s CI green again this morning, and our testing on Rom Factory has helped us find and work around an issue with JRuby and cache keying. Thanks Nikita for helping with this!

  • Lastly, I managed to carve out the first piece of work from an experiment to support streaming from Hanami View. My thinking is that given we’ve added response streaming to Hanami Action, we should look for a full stack approach to take advantage of this too.

    The first step towards this is a useful one in itself: adding lazy exposures to Hanami View. With these, you can now have a view that renders in multiple formats, and if a format doesn’t need every exposure, we no longer need to pay the cost of computing it.

    I’ll look to introduce the rest of this work over the coming weeks.

  • Have you seen Aaron’s new website and fantastic new “Hanami, Why?” series? If not, go check out the introduction and some initial bits and bobs. I’ve been proofing these for Aaron as he goes. I think they’re great, and so does a lot of the internet, which is really gratifying. Aaron’s writing voice is just a pleasure to read, and his enthusiasm for Hanami really shines through. Thanks Aaron for starting this series! We need more and more of this to help spread the word about Hanami and Hanakai — if you have things to say, I’d love to hear it!

  • We announced thoughtbot as a Hanakai sponsor! I’m so excited to have thoughtbot on board. They’ve done so much for Ruby over so many years, and now that includes supporting Hanakai! Thank you to Rob Whittaker and everyone at thoughtbot for making this happen.

  • Last but not least (heckuva week, right?), I was feeling sponsorship-inclined and started the work to automate the refresh of individual sponsors showing on our sponsor page. I’ll hopefully get that merged this week, and get started on writing the post to wrap up our sponsorship drive for the year.

Simon Lyall — Audiobooks – May 2026

For the Love of Trains: Celebrating 200 Years of British Railways by Paul Routledge

A mix of rail history and stories of the author’s life and journeys. An okay light read but either topic is better served elsewhere 3/5

The Bigger Picture: My Blockbuster Life & Lessons Learned Along the Way by Jan Landau

Memoir of a film producer (Titanic etc). I think the early parts tend to work better with the last third being less interesting. But in general worth reading. 4/5

My Audiobook Scoring System

  • 5/5 = Brilliant, top 5 book of the year
  • 4/5 = Above average, strongly recommend
  • 3/5 = Average. in the middle 70% of books I read
  • 2/5 = Disappointing
  • 1/5 = Did not like at all

Share

,

Lev Lafayette — A Farewell to Karaage

Once upon a time, there was an impressive organisation called the Victorian Partnership for Advanced Computing, which was originally established in 2000 to provide supercomputing services to the University sector in Victoria. Unfortunately, the Board decided that VPAC would close its doors in 2015, and the successful experiment in a single provider for university high-performance computing systems came to an end.

During VPAC's existence, the number of researchers who needed supercomputing resources wasn't as high as it is today, and applications for accounts were originally submitted on paper forms. Obviously, this became increasingly untenable as growth pressures mounted, so about fifteen years ago, Sam Morrison designed an account management system called "Karaage". Yes, it was named after the Japanese fried chicken dish, a particular lunch-time favourite from a local dining venue.

Karaage was designed on a Django Python web framework and completely replaced the onerous paper system; "Be nice or I will replace you with a small shell script", as the saying goes. Apart from its implementation at VPAC and the several HPC systems that were managed there, Karaage was also introduced to other HPC centres and universities, especially in 2014-2015, when VPAC was responsible for establishing several HPC systems across the country. Karaage was capable of managing users, projects, groups, and software across multiple clusters. At a rough guess, approximately 20,000 postgraduate researchers across Australia would have had their accouts managed by Karaage.

When it was in operation, VPAC managed the University of Melbourne's HPC system, Edward. When VPAC closed in 2015, Karaage was retained by the University and adopted for the new system, Spartan, which has been the subject of numerous papers, presentations, and a substantial body of research. Spartan was a fascinating experimental system that was sufficiently successful to win a LIEF grant to provide GPU services to a number of Victorian Universities and, in doing so, became one of the world's top-tier systems.

An open-source project, Karaage was also utilised by several other universities and was quite capable of supporting external users (through AAF authentication). It is a testament to its design that, throughout this period, across all installations, there were no significant operational issues. The only significant limitation with Karaage from the Universty's perspective was that it was not fully integrated with the broader bespoke requirements of the University of Melbourn's Research Computing Services, including data and cloud services. Notably, the new RCS portal uses a similar framework for managing accounts. With the RCS Portal, over 10,000 researchers will be able to access and manage their accounts and activities whilst adding data services, virtual machines from the research cloud, and high-performance computing projects through a single interface that connects all the services.

A valedictorian lunch was held for Karaage on Thursday, September 24. Sam Morrison, the original developer of the system and Dr. Brian May, the long-term maintainer, both present. For aesthetic reasons, Karaage chicken (and a few other dishes) were served.

,

Tim Riley Continuations 2026/39: Syntactical possibilities

  • This was a good week for progress on our code!

  • A couple of tests started flaking on JRuby mid-week. I like to get on top of these pretty quickly, so I pushed up fixes to dry-monads and dry-effects. Now that we’ve achived full JRuby support across Dry, I’m very serious about making sure we keep it!

  • Adam has been on a tear lately. This week he added % formatting support to the new Dry::CLI::Style::Text, and prepared a built-in spinner for Dry CLI! 

  • I merged a fix to make provider usage work inside Hanami slice class bodies, and thereby restored single-file Hanami apps. Now we have a test for it, which will help ensure we don’t accidentally lose the capability again.

  • A few weeks ago Aaron put together the support to load Hanami settings from YAML files. I merged that this week. It’s “hidden” by default—we still generate .env files in new apps. But if YAML is your JAML, now you can add a config/settings/default.yml and enjoy all the syntactical possibilities.

    (Kidding aside, I’m happy to add features like this because they can help users feel just a little more at home while they’re already putting themselves out there to learn something new.)

  • Also merged a nice little bug fix from Aaron about routes helpers finding the correct URLs for routes mounted in slices.

    (Have you checked out Aaron’s shiny new homepage, by the way? It’s made in Hanami!).

  • My biggest bit of work this week was getting the rest of Andrea’s code reloading gear merged. I made some tweaks to the core reloading support before merging it, then I moved onto the new hanami-reloader middleware (which takes care of determining when to trigger a reload) and expanded it to support a few more edge cases, like deferring reloads when there are long-running requests.

    I got to do a little bit of preliminary end-to-end testing and it’s all looking good. This should merge soon! Thanks Andrea for your amazing work on this!

  • I also merged Hanami Action’s response streaming support, and started noodling on adding certain streaming capabilities to Hanami View — we’re a full stack framework, after all! I see a few nice things falling out of this, like lazy exposures, helpful even in non-streaming scenarios, where you have e.g. a view that renders via multiple formats, but only in some formats do you need every exposure.

  • New sponsor to announce this week!

,

Tim Riley Continuations 2026/38: Goodbye Guard

  • I’m trying to get these weeknotes back on track: aiming for a Monday release at the latest. Happy Monday :)

  • The biggest thing I got up to this week was reviewing all of Andrea’s wonderful work adding internal code reloading to Hanami—goodbye Guard! The Dry System piece is now merged, and the core Hanami capability is ready to go after a few small tweaks. All that’s left is for me to get comfy with the new middleware for the reloading web server. This deals with a range of concurrency concerns, so I just need the chance to get my head properly around it.

  • Ryan fixed a couple of issues in rom-rails. Thanks Ryan!

  • CuteCuteYu found a bug in Hanami’s gem detection. Thank you! I checked it out and suggested an implementation for a fix. Hopefully this helps them come back with a PR. For things like this I think it’s much nicer for the willing contributor to get the chance to see a fix all the way through. This way we get the chance to work with more people and all take part in building better tools.

  • When I was working on Hanami’s providers a while back, I realised that they wouldn’t work properly if trying to use them inside app or slice class bodies, for example if you were trying to create a single file app. I had a half-done fix just bumping around in my local checkout, so I got back to it and finished it off, including adding a new single_file_app_spec.rb to our codebase. Now we’ll have this to make sure this remains a viable option in the future!

  • Tended to some org maintenance: put the old Dry forum into archive mode and added a banner to point you to our current-now-and-forever Hanakai forum. Tried to do the same for the old Rom forum, but it turns out that’s fully read-only (no chance of even signing in!), so I’m chasing down how we can address that.

,

Michael Still — Beacon 23

Beacon 23 Book Cover Beacon 23
Hugh Howey
Science Fiction
252
★★★★★

This is a science fiction novel set in the cramped confines of a navigational beacon in interstellar space. The solo operator of the beacon is a bit… messed up by his military service and needs to think some stuff through. The book could be depressing, but it is not. Its funny and serious and an entertaining read all at once. I enjoyed it. It was also quite different from the TV show that is based on it by the way.

,

Tim Riley Continuations 2026/37: Response streaming

  • These weeknotes are pulling into the station a little late, because I’ve been busy starting my new job! I’ve joined the crew at HotDoc, as a manager slash engineer. It’s been a great time so far!

  • I noticed Dry Monads CI was failing in production due to version 3.0 of the json gem removing the json/add custom serialization machinery (and for good reason). I switched this over to the new JSON::Coder API and pushed up v1.11.0 of Dry Monads.

  • I forgot to mention it last week, but we landed the styled text support in Dry CLI! I’m really happy with the collaborative process on this one (Aaron and I both contributing different things to the work) as well as the final result: a flexible and reusable styles API, plus styles that apply on a per-stream basis, allowing for e.g. my-cli 2> errors.txt to add styles stdout (going to a terminal) but not to stderr (going to a file).

    Adam also teased a preview this week of #% support for our Styled::Text class, which is going to make it even more powerful. Can’t wait to see that happen!

  • I continued to work on Dry CLI’s handling of streams, making it so that stdout:, stderr:, stdin: all work as injectable command dependencies (great for testing), while still allowing every subclass to keep #initialize all to itself. It’s a little unorthodox, but I think it’s a great result for keeping framework-level dependencies out of the way. Check it out.

  • In my final hour of my #HanakaiFriday, and in keeping with our recent sponsorship drive post (celebrating our individual sponsors, and rewarding them with merch!), I updated the listing of individual sponsors on our sponsors page. There’s now 32 of you! (Plus another couple who’ve joined in recent days). Next step for this will be to automate the updates.

  • On the weekend I made a lightning visit to Sydney to hang out with a visiting-the-country good friend and Ruby legend—and to get royally sunburnt on a glorious spring walk between Coogee and Bondi beaches. On the bus there and back I was inspired to fix this fantastic report from Aaron and Paweł, and add response streaming to Hanami Action. This is an important addition to Hanami Action, so please check it out and share your feedback!

  • A couple of really good questions have been raised in our Discord recently, which I’ve done my best to answer. First from Rickenharp about advanced Rom associations, and this week from Hailey about array associations on changesets.

  • After last week’s chat, Michael has already started pitching in with Hanakai work—I’m so excited to see this! Michael brought Dry Rails up to date for Rails 8, and dropped some very old Rails versions we should no longer be testing against. And just today, Adam released this as dry-rails v0.8.0! This is our very first automated release from another person in our maintainers team, and it all went very smoothly. Congratulations Adam!

,

Tim Riley Continuations 2026/36: Cobbler’s children

  • Feeling a lot better than last week, so I was able to do some more Hanakai work.

  • Shared our next sponsorship drive post, thanking our silver sponsors, and bringing you a Q&A with Hanami-to-Rails porting export Carolyn Cole. Go give it a read! (And while you’re there, go sponsor Hanakai too! Support your local bigotry-free Ruby framework.) 

  • Andrea put together internal code reloading support for Hanami (no more Guard!). I reviewed that in depth and left a bunch of feedback for her. She’s already on the road to handling it all, so I’ll be looking forward to getting this merged soon.

    What I like about this work is that it’s our first opportunity to start adding an extensible structure to our boot process. In this case, we’re focusing strictly on an “unload stack” needed for reloading, but it’s a first step towards making the whole thing more pluggable.

  • Aaron proposed adding support for loading app settings from YAML files, and I waded into the discussion about it. I’m open to the idea, but I’m interested in more viewpoints, so I opened an informal community survey in our Discord. (Want to weigh in? Join our Discord!)

  • Katafrakt korner: Hanami 3.1 will have support for the QUERY HTTP method, which will appear as soon as it becomes available via Rack and Puma. Thanks Paweł!

  • I had a good chat with Michael Kohl about helping out with some Rom development. I’m looking forward to where this may lead!

  • I spent a bit of time helping Aaron with prototyping a Hanami app port. This has been a good little exercise, and uncovered a few framework bugs. I should spend more time working on apps! (Somehow. Cobbler’s children have no shoes, etc.)

,

Lev Lafayette — Ten Years of Spartan: From an Innovative Experiment to a World-Class Supercomputer

Abstract for Aotearoa New Zealand Software Engineering Conference, September, 2026

The Spartan supercomputer started its life as a small, experimental, general-purpose High Performance Computer at the University of Melbourne, facing significant financial constraints. An innovative design led to a Cloud-HPC hybrid following a needs analysis. Despite its small size, Spartan was extremely successful in terms of job throughput and attracted attention at several international conferences (including in Aotearoa New Zealand) and at various HPC centres in Europe. These early successes led Spartan to receive a substantial grant for a GPU partition for a consortium of Victorian universities, pushing the system into the same metrics as a Top500 system. Formal certification was applied for and received in November 2023, and Spartan has continued in that league ever since.

This presentation will outline the history of Spartan's architecture, the bespoke software design and implementation, and a number of user-management features, including Karaage, the Research Compute Portal, job stats, integration of graphical nodes, VS Code, ML/AI, and more. Further, Spartan has always offered an extensive training workshop programme, a Champions programme, and researcher presentations. With this range of features and activities, we provide software and management examples and opportunities for other HPC systems of diverse sizes for flexibility and performance.

Presentation slidedeck

AttachmentSize
PDF icon 2026RSENZ.pdf1016.72 KB

,

Michael Still — Robot Dreams (again)

Robot Dreams Book Cover Robot Dreams
Isaac Asimov
American Science fiction
Byron Preiss Visual Publications
February 12, 2012
349
★★★★☆

I think its interesting how my perspective on these older science fiction books changes a bit each time I read them. Last time I read this book I was annoyed by how few were robot stories, whereas this time I really dug The Martian Way, because I think the premise feels much more possible that it did a few years ago — let alone in 2008!

I enjoyed this book, again.

,

Lev Lafayette — Ten Years of Daily Duolingo

Recently, I passed a ten-year streak of doing Duolingo every day. It's not like I do the bare minimum either; for the past three years, at least, my end-of-year Duo report records me in the 0.1% of learners on their application. My profile lists a rather impossible list of languages, many of which I looked in my early years and, I must admit, were handy whilst travelling through Europe. These days I'm concentrating on standard Chinese and French, whilst last year I completed the Spanish course as I was travelling to South America. In other words, over the years, my language learning has become more functionally-oriented rather than experimental.

Despite this regular use, I have mixed opinions about Duolingo. I will argue that Duolingo is the best language-learning application currently available. Nothing else comes to mind that has an extensive range of courses, that has a similar depth of content, that has regular updates, expansions of content and alignment to CEFR language competence. It continues to improve in areas such as spoken content, grammar, and conversational use of a language.

Likewise, I will also argue that Duolingo is the worst when it comes to business practises. It has built itself on countless hours of willing volunteers who gave advice and highlighted bugs over many years when the free version of the application was useful. Now that Duolingo has reached a position of apparently unassailable market dominance for language learning it has turned the screws to exclude community input (e.g., closing the forums, closing the language incubators) and, with aggressive and often gross advertising, has made the free version of the application almost unusable.

As a result of these practises, Duoling is the most profitable venture of its type. In terms of market evolution, it successfully outmanoeuvred potential alternatives in the competitive stage of the market development by engaging in the highest levels of community input but without providing community empowerment. Now it has reached the stage of market dominance, it has what is erroneously called "competitive advantage" in business studies, but is really "monopolistic advantage" when viewed through the lens of economic analysis.

I am sure the leaders at Duolingo are very well aware of this; whilst they could be true to their origins and actually contribute substantially to such a development, I suspect their business logic will run contrary to it. Duolingo argued that: "Our mission is to develop the best education in the world and make it universally available". They have claimed that: "The freemium business model is good for our mission and our business. We grow by offering an incredible free product and monetize by making the paid version worth it. This fuels a growth flywheel...". The reality is, however, that they don't really have a freemium model anymore.

However, at is core, Duolingo is actually a fairly simple product. In terms of computer design, flashcards (whether words, sentence gaps, etc) are simply an associative array of text and audio, text for grammar with a simple user interface (the simpler the better; Duo's distracting and unnecessary animations are awful) and spaced repetition algorithms. At the moment, numerous community-built Anki cards provide the highest level of development in this regard. Ultimately, however, Duolingo is a very tempting target for a community project, which I think is inevitable, which leads to an interesting conclusion that, in the near future, Duolingo's functionality will be open-sourced.

AttachmentSize
Image icon tenyearduo.png10.31 KB

,

Michael Still — Michael’s unifying theories: when you grew up seems to really affect your attitude towards information

When I was a teenager one of my favorite series of books was Inside Macintosh. Sure I liked lots of fiction stuff as well, but in terms of technical books that really changed how I thought about things in high school, this was it. The design of the early Macintoshes was elegant, whilst also having to deal with limitations of the time — the library routines for much of the operating system were baked into ROM for example, but there was a method to cowboy patch them as required. The books were comprehensive, readable to a mildly talented hobbyist, and best of all were sitting on the shelf of my local library.

That last bit is the key point I am thinking about right now. If Inside Macintosh had not been on that library shelf, there would have been something else there and I probably would have read it, mainly because the information available to us teenagers in my pre-Internet teenaged years was really defined by outside forces.

I think that leads to some of my “information hoarder” tendencies now. I’ve always wanted to know how the machines work, but there are more machines than I could ever possibly have the time to understand. So instead I acquire the information as it is available, and archive it in case I need it some other time. There are thousands of books in my house for example, and it took me a long time to accept that I needed to get rid of some of those books so that newer and hopefully better books could take their places. I also have notebooks, archives of podcasts, and so on. I literally still have my engineering notebooks for a job I left 20 years ago on a shelf I can reach from this char.

To a large extent this blog itself is part of that hoarding behavior — if a notebook entry isn’t somehow too embarrassing, too half baked, or too confidential then it should appear here because why wouldn’t I want other people to perhaps benefit from it? The fact that approximately no one reads this blog is irrelevant in that context. Its about archival of knowledge for me more than it is about having an adoring following.

(The other factor here is also probably that I feel I am quite forgetful, so I tend to need to write things down so I remember how to do them again later).

So here’s the thing. I do not see these behaviors in my younger coworkers who grew up not only with the Internet, but also drowning in super niche bespoke content. They’re much more comfortable with the idea that the information will always be out there if they need it and they can just search or ask a LLM when the time comes. I think the generation growing up now will be even more laid back about the availability of the knowledge they need when they need it, they are after all growing up with personal machine learning assistants that can provide plausible answers to pretty much any question they choose to ask.

I don’t think either group is wrong as such, but I do think it drives a lot of tension between the teams I’ve worked with about things like how much documentation a project should produce and to what standard. Along the same lines, most older people expect to navigate to content via external hierarchy such as tables of contents or indices, whereas younger people expect to be able to search and just land at the bit they need.

I guess the theory here in as much as I have one is — the approach to how to document and teach needs to be tailored to the age group of the recipients. Content that helps people fix their immediate problem based on having landed within your documentation from a search having read none of its introductory material is going to work better with younger people than content that expects a big investment in terms of bootstrapping before any value can be derived.

,

Michael Still — On resistance to change in computer science

Irving Ziller, one of the original members of the FORTRAN team, describing the resistance of early programmers to the introduction of a compiler:

“And in the background was the scepticism, the entrenchment of many of the people who did programming in this way at that time; what was called ‘hand-to-hand combat’ with the machine.”

From https://www.cs.man.ac.uk/CCS/res/res41.htm

,

Simon Lyall — InternetNZ Board Election 2026 Analysis

As I have in previous years here is a quick analysis of the election vote and result. This is based on the Detail Report that was posted on the night of the election.

Background

The InternetNZ board was electing 2 board members in August 2026 via the STV method. There were a total of nine candidates.

Two of these were representing the “Free Speech Union” (FSU) who is in process of trying to take over the organisation. They were Douglas Brown and Jillaine Heather. The Free Speech Union has encouraged its members to sign up for Internetnz and vote for them.

Anti-FSU groups broadly encouraged their members to vote for Bianca Grizhar, Daniel Spector and Annette Culpan. They discouraged voting for Douglas, Jillaine and Jan Rivers (an anti-trans activist)

Round One – First Preferences

Here is a table for the 1st round votes in 2026 compared to 2025

YearTotal VotesFSU
R1
FSU
R1 %
anti-FSU
R1
anti-FSU
R1 %
20252785104638%172462%
20263104192362%117238%
diff+11%+84%-32%

The totals were almost the reverse of in 2025 with 62% going anti-FSU in 2025 while the FSU captures 62% in 2026. Note that I’ve excluded Brynn Neilson (in 2025) and Jan Rivers (in 2026) from either group but they are both under 1%.

The droop quota for a candidate to be elected was 1035 ( 3104 / 3 + 1 ). In the first round FSU candidate Douglas Brown exceeded this ( with 1822 votes ) and was elected.

Round Two – Part 1 Redistributing Douglas’ excess

In this round since Douglas greatly exceeded the quota his excess votes were redistributed. 1822 – 1035 = 787 were redistributed. Of Douglas’ 1822 voters it appears they allocated their votes as follows

WhoNumber of 2nd preferences out of 1822
Jillaine1810
Bianca1
Daniel2
Annette1
Jan4
No 2nd preference4

The reallocation put Jillaine on 882.8 votes or 2nd place with Bianca on 695.43. This is very consistent with FSU voters listed their two candidates 1 and 2.

Round Two – Part 2 Candidates defeated

In the 2nd part of round 2 candidates were removed. Since the total votes for Daniel, Annette, Nabil, Keoni, David and Jan was 489 even if all them were transfered to a single candidate they would be less than Bianca (on 695). Therefore all of them were eliminated. Leaving just Jillaine and Bianca in the race

Round Three – Reallocation from defeated candidates

In this round 489 votes were reallocated from the defeated candidates according to the next preference ( out of Bianca, Jillaine or exhausted ). Of these 403 ( 82.4% ) went to Bianca, 21.73 ( 4.4%) went to Jillaine and 64.3 ( 13.1% ) were exhausted (ie did not list Bianca or Jillaine).

Note that the transfers to Jillaine were probably all of Jan River’s next preferences plus 10 votes out of the 175 that were for Nabil, Keoni and David. This would be consistent with Jan River’s voters being generally aligned with the FSU.

After the allocation which was consistent the pro and anti-FSU voting patterns Bianca had 1098 votes which was over the threshold of 1035 and she was elected.

Comments

Overall the result appears correct and makes sense. The biggest change was the drop off in anti-FSU voters compared to 2025 while the FSU significantly increased their supporters.

Share

,

Lev Lafayette — Maximum-Likelihood (ML) Phylogenetics in High Performance Computing

Given a DNA or protein sequence, Maximum-Likelihood (ML) Phylogenetics searches for an evolutionary tree that would most probably have produced the observed sequences. This probability is based on a statistical model of how sequences evolve.

For example, consider the DNA sequences for the following primates with standard DNA nucleotide abbreviations: adenine (A), cytosine (C), guanine (G), and thymine (T). For what it's worth, a single 7-letter sequence like "ACGTTAGC" is normally too short to build a meaningful tree.

Species Sequence Difference
Human ACGTTAGC
Chimp ACGTTAGT Differs from Human at position 7
Gorilla ACGCTAGT Differs from Human at position 4, 7
Orangutan ATGCTAGT Differs from Human at positions 2, 4, 7

Apart from the quantity of differences, there are qualitative changes as well, with the probability correlated to the actual physical alteration required. Changes between adenine and guanine (A to G), for example, are more common than adenine and thymine; the former is a transition mutation, swapping one purine base for the other purine, while the latter is a transversion mutation, swapping a purine for a pyrimidine base.

With our toy example, there are a number of possible evolutionary trees. It is possible, for example, that the four species are entirely separate and have different common ancestors. Or you might notice that the difference between Gorilla and Orangutan is a slight mutation, between cytosine and thymine, which is relatively common. With the four species, there are only three possible unrooted trees. But once the number increases, the potential variation increases; with ten species, there are 2 million potential evolutionary trees, and with a hundred species, the number is 2*10^182.

There is currently more than 6,500 living mammal species. When one starts looking at other taxonomic levels (phylum, kingdoms, domains) one quickly ends up with far more potential genonic trees than atoms in the known universe. As a result, phylogenetic programs such as RAxML, IQ-TREE, PhyML, and FastTree-2 do not examine every possible tree. Rather, they search the tree space, repeatedly making small rearrangements based on whether the observed DNA alignment is more likely than in previous iterations. Eventually, this results in a tree with a very high likelihood of accuracy. There are many algorithms for calculating likelihood, from the deliberately simplest and most unrealistic (Jukes-Cantor model from 1969) to contemporary and complex models, such as the Newton-Raphson method.

As can be imagined, accurate and fast computation of these complex phylogenetic searches is almost implausible even on powerful desktop computing systems; one will invariably look to a high-performance computing cluster with GPU acceleration. So, in addition to variation in the algorithms that are used, Maximum-Likelihood (ML) Phylogenetics searches use a different architecture. IQ-TREE 2 is designed to use GPU accelerators to accelerate maximum-likelihood scoring functions and tree evaluations for genomic datasets, whereas RAxML/RAxML-NG uses fine-grained shared-memory parallelisation and coarse-grained MPI for scalability.

Further, just to make matters even more complex, maximum likelihood is not the only approach for phylogenetics. Whereas maximum likelihood, which finds the most probable tree from the observed data, calculates a single tree, Bayesian inference yields a range of probable trees and their relative probabilities, incorporating prior knowledge (e.g., the fossil record). Bayesian inference is computationally more intensive and therefore typically slower, and ML and Bayesian approaches are often implemented in the same application. Applications such as BEAGLE perform inference with GPU acceleration for core likelihood and gradient calculations, whereas MrBayes uses GPU-accelerated likelihood calculations with BEAGLE and also supports MPI and OpenMP for inference.

Ultimately, one has to make choices about speed and accuracy; a valiant example of such an inquiry is an ongoing project by Paul Gardner at the University of Otago, who has differentiated between bioinformatic tools along these dimensions. He has written several papers on RNA sequencing alignment programs and metagenomic analysis tools, noting significant divergence. Notably, he has discovered that it is not the discipline the software comes from, nor the academic "status" of the institution, but rather developer commitment to fixing issues: "active, long-term software maintenance is a key factor for producing accurate tools". This author hypothesises that the same approach can be applied to phylogenetics.

Further Reading

DNA Replication and Causes of Mutation
http://www.nature.com/scitable/topicpage/dna-replication-and-causes-of-m...

List of phylogenetics software
https://en.wikipedia.org/wiki/List_of_phylogenetics_software

Substitution Models
https://en.wikipedia.org/wiki/Substitution_model
https://evomics.org/resources/substitution-models/nucleotide-substitutio...

Hybrid Parallelization of the MrBayes & RAxML Phylogenetics Codes
https://cme.h-its.org/exelixis/resource/doc/Phylo100225.pdf

Image from:
Comparative genomic profiling of transport inhibitor Response1/Auxin signaling F-box (TIR1/AFB) genes in eight Pyrus genomes revealed the intraspecies diversity and stress responsiveness patterns
https://www.researchgate.net/publication/380360506_Comparative_genomic_p...

,

Michael Still — The League of Dangerous Young Ladies

The League of Dangerous Young Ladies
Penguin Random House NZ
★☆☆☆☆

I am historically terrible at giving up on a book, but I am trying to call it earlier when a book is going nowhere. This book… Was going nowhere I cared about.

I originally bought it on an impulse because I thought that Jacqui would like it. However I decided that as someone who simulates a competent parent I should try to read it. Sadly, if you don’t know who a whole bunch of Victorian villains are (as Jacqui does not) this book makes zero sense — it largely hangs it’s character development off things like declaring that this character is the daughter of Doctor Monroe, and you’re just expected to know what that means. The characters are also largely flat and unlikable.

It’s off to the charity box for this one.

,

Simon Lyall — AudioBooks – March, April 2026

The KLF: Chaos, Magic, and the Band Who Burned a Million Pounds by John Higgs

Not a convention band biography but more an explanation on their influences and thinking. Fascinating, funny and a fun read. 4/5

The Future Was Now: Madmen, Mavericks, and the Epic Sci-Fi Summer of 1982 by Chris Nashawaty

Covers the development, production and release of 8 Sci-Fi movies released by Hollywood that year. Brief in places but still good. 4/5

Gemini: Stepping Stone to the Moon, The Untold Story by Jeffrey Kluger

A good history of the programme although not much “untold” previously. But good level of detail and stories not in wider books. 4/5

The Impossible Bomb: The Hidden History of British Scientists and the Race to Create an Atomic Weapon by Gareth Williams

The British contribution to the atomic bomb with the assertion they speed up it’s development by a vital year. 3/5

My Audiobook Scoring System

  • 5/5 = Brilliant, top 5 book of the year
  • 4/5 = Above average, strongly recommend
  • 3/5 = Average. in the middle 70% of books I read
  • 2/5 = Disappointing
  • 1/5 = Did not like at all

Share

,

Lev Lafayette — GNOME File Picker Issues

Recently, I had noticed that when I add an attachment (e.g. to email, via a web browser), GNOME's GTK file picker was only providing a list from recently opened items. Previously, it provided a PATH selection, which was a lot more useful for me. When the file picker opens, normally, there is a left-hand sidebar containing items like "Home", "Documents", "Downloads", etc. A normal workaround if this isn't available is to select Ctrl+L to open a location-based entry box, but this wasn't available either. The following question arises: How do I revert to the previous PATH-based menu? What changed, and why?

The first step to check the version of the operating system, desktop environment, and version of GNOME:


$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.5 LTS (Jammy Jellyfish)"
$ echo $XDG_CURRENT_DESKTOP
ubuntu:GNOME
$ gnome-shell --version
GNOME Shell 42.9

There are reasons why I'm using a 2022 LTS version of Ubuntu, but that's for another post; what's important is that this version of Ubuntu and the version of GNOME provided a clue toward what happened.

The next step was to search in the gsettings for the file picker's "recent" choice, and that confirmed what was being experienced:


$ gsettings list-recursively | grep recent
...
org.gtk.gtk4.Settings.FileChooser startup-mode 'recent'

GNOME 42 uses a mix of GTK3, and GTK4 plus xdg-desktop-portal, and many applications now use the GTK4 file chooser, which starts in the "Recent" view. Many developers (including GNOME developers) are increasingly adopting the "Recent" view and document-centric workflows over traditional filesystem navigation. This a terrible idea, albeit very common in a brain-dead Sharepoint-style mentality where the implicit knowledge gained by showing the filesystem and PATH is removed from the user. It will make users increasingly ignorant of computer systems, and it will make it harder for those who want to use the system to their advantage. The only people it benefits are the wilfully ignorant. Remember: Stupid is wrong and must be defeated.

The first suspect was that a GTK4 package or Flatpak update changed the setting. Another test resulted in some interesting answers, which added to this suspicion:


$ gsettings get org.gtk.gtk4.Settings.FileChooser startup-mode
'cwd'
$ gsettings range org.gtk.gtk4.Settings.FileChooser startup-mode
enum
'recent'
'cwd'
$ gsettings describe org.gtk.gtk4.Settings.FileChooser startup-mode
Either "recent" or "cwd"; controls whether the file chooser starts up showing the list of recently-used files, or the contents of the current working directory.

To fix this bug, change gsettings to 'current working directory' and verify:


$ gsettings set org.gtk.gtk4.Settings.FileChooser startup-mode 'cwd'
$ gsettings get org.gtk.gtk4.Settings.FileChooser startup-mode
'cwd'

Whilst that's fixed the problem, the question still remains of what caused it in the first place?

,

Simon Lyall — Audiobooks – January, February 2026

The Man Who Knew the Way to the Moon by Todd Zwillich

The story of John C. Houbolt, a NASA engineer who pushed for Lunar Orbit Rendezvous for the Apollo program. Just 3 hours long but interesting. 3/5

Who Owns This Sentence: A History of Copyrights and Wrongs by David Bellos & Alexandre Montagu

A look at the almost random ways and reasons copyright has changed over the centuries usually as different groups lobbied governments. 3/5

The Six: The Untold Story of America’s First Women Astronauts by Loren Grush

A fairly balanced biography of the 6 astronauts. Covering before and during the Astronaut careers and to an extent afterwards. Worth a read for space fans. 4/5


My Audiobook Scoring System

  • 5/5 = Brilliant, top 5 book of the year
  • 4/5 = Above average, strongly recommend
  • 3/5 = Average. in the middle 70% of books I read
  • 2/5 = Disappointing
  • 1/5 = Did not like at all

Share

,

Yifei Zhan — CommBank hardware MFA token

A while ago, CommBank started asking for MFA confirmation on its mobile app for every NetBank login on a browser. Previously, there was an option to use SMS for MFA, which isn’t as secure as I would like, but it was at least usable. Since I’m switching away from Android to Mobian and won’t be able to use the CommBank app for much longer, I applied for a physical NetCode token.

The hardware is made by Digipass and looks disposable. It is a small, battery powered gadget with a screen and a button. When pressed, it shows a temporary NetCode for authentication. Such a NetCode is required both for NetBank logins and approving online transactions.

The letter that came with it has the wrong link for activation, the correct link is under NetBank -> Settings -> NetCode (under the Security section)

To apply for a physical token, call the NetBank team, mention you can’t use the app and need a physical NetCode token, and make sure they actually submit your request for a token. It took me 2 calls to get them to ship me a token. The hardware is free of charge but can only be applied for via phone call; unfortunately staff members at my local branch are unable to do anything in relation to NetBank. I was told privately by a CommBank employee that they are deprecating the hardware token in favor of the mobile app, I hope that won’t happen anytime soon, or that they add support for passkeys before they do. The last time I checked, the CommBank app was LineageOS-friendly, but I don’t want to configure WayDroid just to do online banking.

PayID, the thing that allows you to receive payment via a phone number or email address, is not compatible with the hardware token, and existing PayID will be silently deactivated if you use hardware token. This looks to be an artificial restriction; I don’t see why it has to be this way.

Regular CommBank mobile app sessions will also be de-activated once the hardware token is activated (I was told so but my sessions weren’t deactivated until I wiped my Android phone), and you won’t be able to sign into mobile app again until you manually disable the NetCode token.

Online banking has been getting progressively more invasive and anti-user over the last decade, from demanding remote attestation to requiring real time location data, each time locking certain features when those demands are not satisfied; all based on the flawed assumptions that everyone owns a phone running a certain flavor of iOS or Android, and has it ready all the time. I’m not sure what can be done to reverse this trend, but on the personal level I will use NetBank less and go back to cash.

,

FLOSS Down Under - online free software meetings — Apr 2026 Meeting

Started with discussion about copyright etc and Anna’s Archive was mentioned as a source of books. I checked and found that it has a scientific paper I’d wanted to read.

Andrew Pam gave a summary of Linux gaming developments including ways to run Epic games, Wine, and a side note about Framework laptops.

Phil Steel-Wilson gave the featured presentation for the meeting which was on Fail2ban. The talk was interesting and informative and sparked discussions about DOS attacks and the ways that hostile activity is changing on the Internet particularly in regard to “AI” systems.

,

Lev Lafayette — National Supercomputing Centre and Sunway TaihuLight

Wuxi is home to China's National Supercomputing Centre and the Sunway TaihuLight supercomputer. This system came to global attention in June 2016, when it topped the global Top500 list of the world's most powerful supercomputers, far surpassing the second system (also Chinese, the Tianhe-2A) let alone the third (Titan, from the United States). Not only that, Sunway TaihuLight held the top position for an unprecedented and never-repeated two years in succession. until the US system, Summit, hosted at Oak Ridge National Laboratory, took the top position in the June 2018 metrics. Nevertheless, almost ten years later, Sunway TaihuLight has remained not only a Top500 supercomputer, but remains at in the top 25 systems (placed #24 in November 2025) without any change to the original configuration in all that time, a blunt indication of how advanced it was at the time.

The Sunway TaihuLight consists of 10,649,600 cores and a Rmax of 93.01 petaflops and an Rpeak of 125.44 petaflops. With various import restrictions in place (those who preach free trade don't like practising it with a competitor), the processors are a home-grown variety, Sunway SW26010 260C, a 64-bit RISC running at 1.45GHz. The clock rate might seem low, but it matches the requirements of a manycore processor and, as the manufacturer code suggests, the Sunway SW26010 consists of a truly impressive 260 cores per processor, arranged as four clusters of 64 Compute-Processing Elements (CPEs) in an eight-by-eight array. These CPEs support SIMD instructions, making the chip (at a very high level) seem like something between a traditional CPU and a GPU architecture. The CPE clusters also have a more conventional general-purpose core, the Management Processing Element (MPE), that provides supervisory functions. As each node has 260 cores, there are "supernodes" of 256 nodes, and each cabinet holds 4 supernodes. There are 40 cabinets in total, providing over 10 million cores. Sunway has its own interconnect, with a five-level integrated hierarchy: (i) computing node, (ii) computing board, (iii) super-nodes, (iv) cabinet, and (v) complete system with a network link bandwidth of 16GB/s and total I/O bandwidth of 288 GB/s.

The operating system is also custom-built, Sunway Raise OS, based on Linux. Common compilers (C, C++, Fortran), an automatic vectorisation tool, basic math libraries, and a customised version of OpenACC are available. The software build system is also specialised, targetting the Sunway processor. Whilst minimal modifications have been sought, those applications designed for GPUs have been "significantly more challenging". Nevertheless, dozens of applications have been written which can, in theory, scale to use the entire system, with early tests of atmospheric simulations scaling effectively to eight million cores. Other early simulations of note include atomistic simulations of silicon nanowires and ultra-high-resolution global ocean surface wave numerical simulations. Parallel software compilation at the node level generally uses MPI. For the four CGs within the same processor, software can use either MPI or OpenMP, but within each CG, Sunway OpenACC is used. Sunway OpenACC uses the OpenACC 2.0 syntax but targets the CPE clusters and includes parallel task management, heterogeneous code extraction, and data transfer descriptions. Syntax extensions from the original OpenACC 2.0 standard include finer control over multi-dimensional array buffering, and packing distributed variables for data transfer.

The Sunway TaihuLight will be remembered alongside other systems that are "giants" in history for their performance, architecture, and lasting impact, and contributions to science, such as ENIAC, UNIVAC, CDC-6600, Cray-1, Beowulf, and RoadRunner. A system as powerful, innovative, and novel as this comes along perhaps once a decade, and after 10 years of operation, Sunway TaihuLight has earned its place in computing history. All the engineers and administrators who have built, operated, and maintained this system deserve respect for their contributions. What is especially remarkable is that, due to the political climate, this system had an additional requirement for novel design. However, I have been informed by a trustworthy source to "watch this space"; there are plans for a system ten times as powerful in the very near future.

Lev Lafayette — Guizhou National Data Centre and Radio Telescope

A few weeks ago, I had the opportunity to visit Guizhou, China's National Big Data Comprehensive Pilot Zone. At first blush, the choice of Guizhou seems to be an unusual one. With a population of 38 million, it ranks 17th in the list of administrative divisions and 18th in population density, and it is even further behind in terms of GDP (22nd). From the principle that it is most efficient to conduct compute near where data is located, one would expect that such a data centre would be located in provinces with a greater population and economic activity, such as Guangdong, Jiangsu, or Shandong, or maybe even in accord to scientific output, which would also include Beijing and Shanghai. Instead, Guizhou, with its rugged karst formations and dense forests and lower level of economic development (fourth lowest in GDP per capita in 2020), has been part of a "Big Data Guizhou" strategic plan launched in 2014 which includes a threefold approach; "Big Data", "Big Poverty Reduction", and "Big Ecology" by then governer, Chen Min'er.

With low energy costs and a consistently cool climate, Guizhou has established the Guizhou Cloud, sponsored by the Guizhou Big Data Development Administration and supervised by the Board of Supervisors of Guizhou State-Owned Enterprises. This in turn has attracted major national corporations to its numerous data centers, notably Apple's iCloud China and Huawei's Cloud Service Base, along with Tencent, Alibaba Cloud, the AI firm SenseTime, as well as hosting the annual China International Big Data Industry Expo since 2015. These corporate decisions are notable enough in their own right, but what really makes Guizhou such an attractive place for a national data hub is the presence of the Five-hundred-meter Aperture Spherical Telescope (FAST) which, as is evident from the name, has a 500m diameter dish (I call it a "wok") making it the world's largest single-dish telescope.

Radio telescopes are essentially antennas and receivers for radio waves, with frequencies ranging from around 20 kHz to around 300 GHz, just as an optical telescope collects data from the visible portion of the spectrum. Primary local sources for radio waves include the Sun, Jupiter (due to its magnetosphere), and Jupiter's moon, Ganymede. The Galactic Centre of the Milky Way is an especially powerful source, as are supernova remnants, such as Cassiopeia, and neutron stars, such as pulsars and Rotating Radio Transients (RRATs). Primordial black holes and extraterrestrial intelligence communications are two other speculative, currently unobserved sources. The main point is that to derive information about these radio sources, one needs to collect radio wave data, and the more data you collect, the greater the chance you will find something interesting. Thus, to collect more data, one wants a larger receiver.

FAST is a very big receiver and it collects a lot of data, roughly 100TB per day. Construction began in 2011, testing began in 2016, and it was fully operational in 2020. Even before becoming operational it discovered two pulsars and by 2021 it has discovered an incredible 500. Hardware innovations are continuing; late last year, China Environment for Network Innovation (CENI), announced that they had conducted a (somewhat contrived) data transfer test of 72TB between FAST to Huazhong University of Science and Technology in Central China's Hubei province. The data transfer test, which would normally take 699 days was completed in a mere 1.6 hours. Further, China has announced that it will build an additional 24 radio dishes of 40m diameter around FAST, creating an array that will mimic a massive 10km diameter dish, and boost telescope resolution by 30 times.

Mention must also be made of the rest of the Tianyan Scenic Area, which hosts the FAST system. Apart from the stunning natural beauty of the region, and the FAST Observation Platform (complete with bungy jump during holiday weeks), the site also hosts an impressive Astronomy Experience Hall, the Astronomical Space-Time Tower (at 99.999 metres), the Nan Rendong Memorial Hall (the astronomer who was the main drive for setting up FAST), the Dome Flight Cinema, and a Aerospace Science and Technology Museum. Like many massive engineering projects in China, they have also turned the site into an informative destination for national and international visitors.

,

Peter Lieverdink — Indigenous Star Names

Ever since people looked at the stars, they have given bright stars (and groups of stars) names. In the western world a lot of those names are arabic names that have been around for millennia. In science, stars are generally referred to by a Bayer Designation (a greek letter and the constellation name) or a catalog number.

About a decade ago, the International Astronomical Union, which is the organisation that can assign official names to things in the sky - stars, planets, asteroids, comets - adopted the most common classical star names, but then decided that the people from the middle east and the mediterrenean were not in fact the only ones to ever observe the stars. So via its Working Group on Star Names they assigned new names from various indigenous and non-english languages around the world.

Four of these previously unnamed stars got official names from Australian Indigenous languages.

Larawag (Epsilon Scorpii), Wurren (Zeta Phoenicis) and Ginan (Epsilon Crucis) got names from the Wardaman language of the Northern Territory and  Unurgunite (Sigma Canis Majoris) got a name from the Boorong people of the local area where I live and do my astrophotography. Each star has its own stories associated with it and you can read about those on aboriginalastronomy.com.au.

A friend is a member of the IAU working group on star names, as and he told me about these newly assigned names some years ago, I decided I was going to try a nice image of each of them, taking into account the details of the story when possible. That is especially important with Unurgunite :-)

I finally got the last one mid last year, and then processed all the data in the same way.

For each star, I took an hour of data; 60 x 60 second exposures with a UV/IR cut filter on a 300mm focal length RedCat 61 and processed it the same way using Siril. This provides reasonably correct star colours and relative brightnesses.

A bright orange star on a background that is dense with faint stars.   A bright orange sdtar flanked by two even brighter white stars.

A bright red star on a background of dense faint stars and some dark dust sweeps.   A blue white star on a dark background.

,

Lev Lafayette — Critical Issues for the Global Climate

This presentation covers the science of Earth's "energy budget" of heat inputs and losses, and describes the overall climate system. It continues with a description of the Industrial Age, the introduction of direct temperature measurement, and the resulting temperature rise from burning fossil fuels. This is followed by a description of how Greenhouse Gases operate on a molecular level, the increase since the pre-industrial period, and the carbon cycle. After this, human activity and projections are considered, followed by changes to species' habitats and the possibility of an Anthropocene Extinction Event, then energy trajectories and future global policy directions. Concluding remarks identify climate change as a critical issue and one subject to "race conditions", and note that the policy route, whilst necessary, is currently falling short of requirements.

This was a presentation to Future Day 2026, March 2-4. A transcript is provided along with the accompanying slide deck.

Transcript:
http://levlafayette.com/files/2026FutureDayGlobalClimateTranscript.pdf

Slides:
http://levlafayette.com/files/2026FutureDay_GlobalClimate.pdf

Youtube
https://www.youtube.com/watch?v=i-bAZ4NVhCM

,

FLOSS Down Under - online free software meetings — Feb 2026 Meeting Report

Andrew Pam spoke about new developments in Linux gaming and about hardware and OS support for games etc. He also described some interesting developments with Linux support for SMR disks [1].

Then we discussed Everything Open 2026 [2]. We had some discussion about some of the lectures including the final one which generated controversy (here is the playlist for EO 2026 lectures [3]).

We discussed ideas for running a more effective BOF on a difficult topic like FOSS on mobile phones. The main conclusions seemed to be to have more than 2 people chairing the BOF and more than 1 hour to run it. Maybe a BOF as an introduction to a hack evening.

Then we had discussions about the future of LUGs, the difficulty in getting interest in attending meetings, and to what extent YouTube replaces meetings. One conclusion was that we should publish videos of meetings even if they aren’t going to be interesting to most people. If 99% of people find that watching a meeting they can’t contribute to is boring then we get 1% who are interested and if the number of people who see the video is large enough then 1% becomes a good number.

We finished with discussing Linux promotion. There was a general feeling that a Linux Australia subcommittee dedicated to promoting Linux would be a good thing, I didn’t poll the people do determine who of the people who agreed it was a good idea were actually interested in doing the work.

I (Russell Coker) used my Furilabs FLX1s for the meeting and it worked well running Firefox talking to the Big Blue Button server but unfortunately Firefox wouldn’t work with the camera. This was a reasonable result and the 3 hour meeting used slightly over 50% of the phone’s battery which is much better than a Librem5 or PinePhone Pro could manage. For as yet unknown reasons my desktop PC didn’t want to talk to the webcam that I have been using for years.

The meeting had 10 people attending which isn’t a large meeting but was enough for many interesting discussions.

Topics for future meetings include using storage technology such as SMR disks which we also agreed was a good topic for ongoing discussions in the Matrix room over the course of weeks. Changing storage options is not a trivial thing and not something that can be done quickly or easily.

How to best run BOFs and workshops at conferences was agreed to be a topic that needs more discussion. We are talking about how to get things done efficiently for Everything Open 2027 aleady!

Digital sovereignty was briefly discussed in the meeting and agreed to be a topic for future meetings. It is a complex topic and we will break it down and address separate parts in different meetings. A meeting about “digital sovereignty” on it’s own is probably not going to have enough focus to achieve things. A meeting about a specific topic such as “which cloud to use” can get some good results.

,

Simon Lyall — OzMoot 2026 – Day 2 – Afternoon

Celebrating Calligraphy by Jenni Aldred

Leaving Lórien by Stephen Vrettos

  • When the Fellowship leave Lorien the is a ceremony
  • Led by the 2 Highest authority people in the Realm
  • Purposes
    • A Farewell – G sings a song called “Farewell”
    • A Transition
  • Gifts
    • Since G has forsight she gives gifts that help their fate
    • Aragorn
      • Gets a Sheath for his sword
      • A Green Stone.
    • Boromir gets a golden belt
      • Gold is often seen to corrupt
    • Merry and Pippen
      • Gifts of a silver belt
    • Legolas gets a bow
      • Larger than those used in Mirkwood
      • Sign that the two elven kingdoms are closer
    • Sam gets box
      • Not practical for the journey ahead but useful for afterwards in the Shire
    • Gimli
      • Hair from G
      • Becomes close to elves and other Dwarves become closer to Elves
    • Frodo
      • The Phial of Galadriel
      • Echo of Eärendil quest

Share

,

Simon Lyall — OzMoot 2026 – Day 2 – Morning

Love and Power an Poetics of Tom Bombadil by Corey Olsen

  • Tolkien does modern poetry and traditional forms
  • Tom is heavily influenced by The Kalevala
    • Similar Meter
    • Similar Song Battles
  • TomB Original Poem
    • The History of TomB
    • A series of Hostile Encounters. Tom sitting beside river, attacked 4x times
    • First Goldberry
    • Old Man Willow
    • The Badgers
    • The Barrow-wight
    • Each encounter leds to a poetry-style sing off
    • The Turn and the Wedding with Goldberry
  • Intro to Bombadilish
    • First 4 lines of Tom
    • Is in Half line, each line has two halfs.
    • Basic Rhythm
      • English as a language naturally uses Iambic Rhythm
      • 2 beat.
      • Look at the 2 syllable words, stress on first sylable
      • Therefore Trokic
      • Spondee – Multiple stress after each other
      • Names always stressed
      • “lived down under hill” very stressed
      • Last 3 half lines very stressed-unstressed
  • 2nd 4 lines. Narrative Flow
    • Almost all just straight Trokic describing narrative flow
  • Next 4 lines – Attack from Goldberry
    • Last 2 lines broken up
    • Halting and has weakness
  • Tom’s response
    • He is giving a command.
    • Command is “Go down! Sleep again” – 3 beat spondee. That wins
  • Old Man Willow is Not a Tree
    • Old Man Willow is a man who lives in tree
    • or Rather a Wood-Spirit who lives in a Tree
    • This was still in early drafts of LOTR
  • Battles vs Old Man Willow
    • Willow starts strong
    • But Tom piles on stressed command words in reply
  • Tom goes a-Courting
    • Doesn’t to a three-beat spondee commanding Goldberry
    • Gentle wooing
  • The Wedding
    • 2-beat spondee at start of middle-4 lines talking about her wedding garments which Tom provided and gave to her
  • Happily Ever After
    • derry-dol and merry-dol are pet names for Goldberry
  • The Fellowship of the Ring
  • Initial lines – “Hey dol! merry dol! ring a dong dillo” and next 3
    • Is calling to Goldberry, saying almost home
  • New lines is even more directed to Goldberry
  • Except for the “Old Tom Bombadil water lilies bringing” and next line which is warning off Old Man Willow
  • Similar again next few lines, warning asides to Old Man Willow
  • The Hobbits run to him
    • “Whoa! Whoa! steady there” command and stopped Hobbits
  • Lots of Stressed line words
  • When arrives back in his house
    • Goldberry wearing Wedding gear, eating Wedding Feast
    • Constantly celebrating and recreating their courtship and marriage moment

The Hands of the King and the Royal Touch: Cutting Edge Research from The Gondor Journal of Medicine by Scott Kirton

  • In a 2nd hand bookstore found some copies of the Gondor Journal of Medicine
  • Aragorn’s healing power is what wins over the people. Not others things he does/is
  • The Royal Touch from kings, mainly to cure Scrofula
    • Scrofula. TB infection of Lymph nodes of the neck
    • Didn’t work directly, but would spontaneously would go away and Kings advisors picked patients who had good chance to cure
    • Legitimized the King’s Authority. Shows he was favored by God
    • Showed King was generous towards people
  • Decline due to skepticism and less claim of divine right of the kings
  • Hands of the King vs The Royal Touch
  • Analysis from the journal on how best to use the Limit resources of King to heal more people

Midsummer in Middle-Earth by Trudy Shannon

  • When is midsummer
  • Date various by different Legal, Astronomical and traditions
  • Strong Traditions in areas with long dark winters
  • Midsummer in the Shire
    • Lithe days built right into the Calendar
    • Fireworks from Gandalf
    • Bilbo leaves Rivendell on Midsummer
    • Free Fair on White Downs. Banquets
      • The Althing in Iceland was similar. Around 1000 people regularly attended
  • Midsummer in Numenor
    • 12 months or 30-31 days
    • Special day not attached to any month similar to Hobbit
    • King ascends sacred Mountain followed by crowd or many people. Only the King speaks
  • Midsummer Gondolin
    • The Gates of Summer . Refs the city’s 7 gates
    • No voice from midnight to the break of day. Dawn welcomed with voices
    • City is attacked on Midsummer
  • Croatia Celebrates the shortest Night and people stay awake all night
  • Midsummer forces Orcs, Wizards and Dwarves
    • Dwarves only sometimes celebrate it.
    • No info on Orcs, Wizards
  • Often times for Weddings

“Circle of Light” – A Faërie Rock Opera by Anna Grob

  • Music performance
  • She is doing a Rock Opera about the Fall of Gondolin and played some songs from it.
  • Some on Spotify
  • and Youtube

Share

Simon Lyall — OzMoot 2026 – Day 1

Celebrating Middle-Earth on the Table-Top: An exploration of the Middle-Earth Strategy Battle Game by Tim Wraight

  • History of the game
    • First Released in 2001
    • Skirmish orientated
    • Scenarios that called back to the Movies
    • New releases as later moves released
    • Very popular during the films and immediately after
    • Good license from Middle Earth Enterprise so extra supplements that just covered book stuff
    • More Releases as Hobbit movies came out
    • 2018 revived the game and re-released and renamed to Middle Earth Strategy Game
    • Various Releases since then
  • How to Play the game
    • Model, stuff in English
    • Heroes or Worriers
    • Heroes have special characteristics, special abilities etc
    • Turn based, roll priority, move phase ( approx 6 inches), shoot, fight phase, end phase
    • Games take between 1 hour and 1 day
  • How Tolkien and Imagination is Celebrated in the game
    • Narrative Scenarios reflects special moments from the books/films
    • Can do what-ifs like build your own “fantasy fellowship” instead of cannon 9
  • Most people play the Match play variant. 2 players each build an army worth same number of points. Takes about 2 hours
  • Lots of special rules for each Hero Character
  • People can do backgrounds for their army, special color schemes etc. Models from other vendors or 3d printed
  • People like making their own terrain.
  • Also they have display boards to display armies
  • 80 play tournaments in Aus, 160 player+ tournaments in UK

A Comparison of Duels: Tolkien’s Legendarium and the Middle Ages to Early Modern Period by Karolina Firman

  • Does the Legendarium actually have any duels?
  • Definition of a Duel
    • A pre-planned and stylized one-on-one armed fight between two participants in defense of your own or a loved ones honour
  • Other motivations
    • Legitimizing your own masculinity
    • Fights to prove your innocence
    • Demonstrating fencing skills
  • Possible Duels in LOTR
    • Gandalf vs Balrog
    • Eowyn vs the Witch King
    • Samwise fighting Shelob
    • Aragon vs Lurtz (movie only)
    • Boromir vs unnamed Orcs (book)
  • The ones that is closest to traditional definition is Eowyn vs Witch King and Sam vs Shelob.
    • Gandalf vs Balrog has less honour component
    • Aragon vs Lurtz
  • In speakers opinion none of them really qualify
  • Big discussion on what qualifies and what doesn’t

Finrod Felagund and Severus Snape as Saviour Heroes in the Context of Universal Plot Structures by Evelina Timofeeva

  • Both Characters are in love with a character that is far away
  • Both have vast life experience
  • Both perform heroic deeds because of a past performance
  • Both are distrusted by those around them
  • Both die for an apparently lessor character
  • Both a slain by magical creatures
  • I was having trouble keeping up

Share

,

Colin Charles — Hello 2026

As I bid adieu to 2025, annus horribilis, I wish to welcome 2026, Annus Mirabilis.

What changed from Hello 2025?

Fell in love with Pumpkin, Vanessa’s dog. I only did 41 days of travel, 7 trips, 13 cities and 36,049 miles. This was basically covid 2020 ;)

Ushered it in Kuala Lumpur 2025 and 2026.

I look forward to having a better year ahead. Best year as 42 is coming, and that is the answer to life, the universe and everything.

Know whom my friends are. Know who were fair weather friends. Very enlightening 2025 has been. You’ll eat, just not at my table.

Ever onwards. And upwards.

,

Tim Serong — What Happened Next

On the evening of September 3, 2025, there was suddenly a strong and remarkably unpleasant flat metallic odour in our master bedroom and ensuite. We opened all the doors and windows and got the fan on, and eventually it went away. The smell didn’t come back after closing up, but the next morning when I was inspecting the Redflow ZCell battery in the crawl space under the house I discovered a 6cm long crack towards the right hand side of the back of the electrode stack, about 2.5cm above the base. A small amount of clear liquid was leaking out.

Ah… That’s not good.

Due to the way our house is constructed on a hill, the master bedroom, walk in robe and ensuite on the lower floor share some airflow with the crawl space under the main floor of the house. For example, the fan in the ensuite vents to that space, and I’ve felt a breeze from an unfinished window frame in the walk in robe, which must be coming from the space under the house. I’ve since siliconed that window frame up, but the point is, the electrode stack split, the battery was leaking, and we could smell a toxic fume in the bedroom.

Readers of the previous post in this series will be aware that Redflow went bust in August 2024, and that our current battery was purchased from post-liquidation stock to replace the previous one which had also failed due to a leak in the electrode stack. When the new unit was commissioned in March 2025 I applied some configuration tweaks in an attempt to ensure the longest lifespan possible, but it lasted slightly less than six months before this failure. Not great for something that was originally sold with a ten year warranty.

Under the circumstances I wasn’t willing to try to procure yet another new ZCell to replace this one, but given we’d found the leak very early this time, I figured I had nothing to lose by trying to repair it. I had heard of marine fibreglass being used successfully in one other case to repair a leaking ZCell, so I discharged the battery completely then set to work patching it up the following weekend. This involved:

  • Wearing PPE (gloves, a respirator, safety glasses)
  • Turning the DC isolator off so the unit wasn’t powered
  • Carefully wiping the electrolyte leak away with a rag
  • Taping up some plastic sheeting below the stack to keep the rest of the unit clean
  • Cleaning the stack carefully with a little methylated spirits
  • Roughing up the surface with some 80 grit sandpaper
  • Cleaning the stack carefully again with methylated spirits
  • Mixing up West System 105 epoxy resin and 205 hardener
  • Applying four layers of fibreglass cloth (which is irritating to cut with scissors, by the way) in alternating directions – each layer was wet down with epoxy using a brush then applied to the surface of the stack with a little metal roller
  • Crawling out from under the house, unkinking most of the muscles in my body, and having a long hot shower
  • Waiting til the day after next for everything to set in case 24 hours wasn’t enough before recommissioning the unit

Here’s a picture of the finished repair. I didn’t bother sanding it smooth because it just doesn’t matter – it’s a battery, not the hull of a boat – and I could do without creating any more dust. I actually ran two strips of fibreglass because there was also a little split at the top of the stack, although that one didn’t appear to be leaking.

Not too bad for a first attempt, and good practice for repairing the hull of my rowboat.

Recommissioning the unit was interesting. Upon bringing it online, it immediately went into a safe shutdown state because the electrolyte had dropped down from its usual running temperature of 18-24°C to a bit over 8°C during the five days it had been offline.

In order for a ZCell to charge, the electrolyte needs to be at least 10°C, and at least 15°C for it to discharge. So I took one of our 2400W panel heaters under the house and turned it on next to the unit. “How long does it take for a 2400W panel heater to heat 100L of nearby liquid electrolyte by 2°C?” I hear you ask. “Too damn long” is the answer. I started the heater at 13:10, and it was 16:50 before the battery was convinced that the electrolyte was far enough above 10°C to be happy to start charging again. But, charge it did, and we were up and running again… Until the next morning when my wife detected that nasty smell in the ensuite again. Upon further inspection of the unit I found a tiny drip coming from the bottom of the front of the stack, behind the Battery Control Module (BCM). So once again I discharged and decommissioned the battery in preparation for repair.

This time I had to pull the BCM off to get to the electrode stack behind it. First I checked that the battery really was discharged with a multimeter. Then I disconnected the DC bus cabling, the comms cable, and the connections to the pumps, fans and leak sensors after taking a photo to make sure I was going to put everything back in the right place. Finally I unscrewed the battery terminals and pulled the BCM off. It’s actually a really neat arrangement – the BCM just slides on and off over the two cylindrical terminals the come out of the stack. It’s a bit stiff, but once you know it comes off that way, you just jiggle and pull until it’s removed.

The BCM comes off…

One thing to be extremely careful of is that you don’t accidentally drop the washers from the battery terminals down into the bottom of the battery, because if you do you will never, ever get them back again, and will have to order some M8 silicon bronze belleville washers from Bronze & Brass Fasteners Pty. Ltd. to use as replacements. I don’t know exactly what Redflow used here, but I know they’re belleville washers from reading the manual, I determined they were M8 by measuring them, silicon bronze is apparently a good electrical conductor, and BABF would let me buy them individually rather than in lots of a hundred. I purchased eight, in order to have several more spares.

…to expose the front of the stack, with some cracks at the bottom.

There was a bit of cracking on the front of the stack, similar to what was on the back, so then it was a repeat of the earlier fibreglassing procedure. This added a couple of millimetres depth to the front of the stack. Now the circuit board in the BCM could no longer sit flat against the ends of the battery terminals, due to a series of little protrusions on the back of the BCM case which would ordinarily sit flush against the stack. These I lovingly twisted off with a pair of pliers. Then I reconnected all the cabling and used a nice shiny new torque wrench to ensure the battery terminals and DC bus cabling were tightened to 10Nm per the manual.

Here’s the freshly applied fibreglass…
…and the slightly modified BCM.

I wasn’t quite ready to recommission the battery yet at this point given we’d had those two nasty fume experiences in the bedroom. If I’d missed anything with this second repair, or if anything else broke and there were gas emissions of any kind, we didn’t want to experience them. So I purchased a sub floor ventilation kit with a bushfire compliant external vent and installed that under the house.

I assure you the walls and floor are actually straight and level.

Then in early October (it took a while to get all this work done around my actual job) I brought the battery online again. Of course I had to once more use the panel heater to get it going. We’re running the ventilation fan 24/7 (it’s very quiet), and I got into the habit of going under the house and doing a visual inspection of the battery every morning as part of our daily rounds feeding the chickens.

Everything went beautifully until October 29 when I noticed a small drip of red liquid which appeared to be coming from a capillary tube on the front right of the stack. The previous leaks had appeared clear, which I imagine means they were from the zinc electrolyte side of the battery, whereas this red suggested to me a leak from the bromine side of the battery. The electrolyte actually initially has the same chemical composition in both tanks, but charging changes it – you’ll see the electrolyte in the bromine pipe on the front of the battery go orange, then red, as charge increases, whereas the electrolyte visible in the zinc pipe remains a mostly transparent pale yellow. Anyway, another leak meant decommissioning the battery again to investigate.

This is new.

Off came the BCM again to check my fibreglass work (pristine and undamaged!) and off came the side of the enclosure to get a better look at the problem area. I inspected the capillary tube in minute detail with a magnifying glass and couldn’t find any obvious damage that would explain the leak. The tube runs from the top of the stack down through a hole in the bottom front of the stack, which keeps everything very neat, but honestly seemed to me like it was introducing a potential weak point into the front of the stack, so I rerouted the tube then carefully filled the hole in the stack with Araldite, on the assumption the leak was actually in the stack. In case the leak turned out to be in the tube, I ordered some viton tubing to use as a replacement. After recommissioning the battery again no further leak was evident over several days, indicating that the problem was indeed in the stack, and not in the tube. This is probably just as well, as my new viton tubing turned out to have a slightly smaller internal diameter (2mm) than whatever Redflow used during manufacture – maybe 2.5mm?

Capillary tube rerouted off to the side.
No more hole in front bottom of stack.

A few days after that on November 7, the battery indicated a hardware failure due to “impedance error”, and a small drip of red liquid appeared on the front left side of the stack. I’ve been told this error can be due to higher pH levels and the formation of zinc hydroxide, or degradation of the core electrode, or separator failure, or overheating of the stack reactor. Within the limits of my knowledge and ability there was very little I could do about any of these things, so then the question became: what next?

At this point I felt that I had really pushed things as far as I reasonably could. If the fibreglass repairs on the front and back of the stack proved sufficient and nothing else had gone wrong, I would have been happy to continue operating the battery as we had been since March, but these additional leaks and the impedance error suggested to me that things were going to continue to go downhill. It seems that the answer to the question posed in my last post, i.e. “how far down the road can we kick the migration can” turned out to be about eight months.

The only technology that’s immediately viable for us to switch to is LiFePO4 batteries. We’re looking at a stack or two of Pylontech Pelios because they will work with our existing Victron inverter/charger gear and come in IP65 cases so can be installed outdoors without too much difficulty. It will still be a while before we can get those installed though, and in the meantime I can’t just decommission the existing battery because then our solar generation won’t work.

The way our system is installed, we have solar panels connected to an MPPT on the DC bus, which is connected to the battery, and to our MutliPlus II Inverter/Chargers which in turn power our loads. The ZCell Battery Management System (BMS) tells the system what the battery charge and discharge limits are. If the battery is missing or broken, the BMS will not let the MPPT run at all, which means that no battery = no solar power, not even to power our loads.

I actually tried to work around this problem a couple of times in the past when previous batteries were dead. There’s a description of some unsuccessful attempts in an earlier post, and I also separately tried to fake up what I called a Virtual ZCell in software back in December 2024. In that experiment I was telling the Victron system that there was a battery present at 25% state of charge, but with a charge current limit of 0A (so it wouldn’t try to charge the non-existent battery) and a discharge current limit of 1A (so the battery looked at least a little bit available but no real discharge would be attempted). Incredibly this worked, but after about a week the MPPT started raising various errors so I gave it up. It seems that it’s necessary for a real physical battery to be present in order to help correctly regulate the voltage on the DC bus.

Back to the real battery: After the impedance error occurred I shut it down, rerouted the left front capillary tube, Araldited up its hole and the bottom front of the stack as I had done on the right hand side, then recommissioned the battery and reset the impedance error. I then set the system maximum state of charge limit in the BMS to the lowest value possible (20%). This would mean that the battery would never be charged much, and thus would never be stressed much. I imagined that whatever reactions were happening inside the stack that were causing things to break would happen either less often, or with less severity, or both.

The Araldite repairs were ultimately not completely successful. A teeny tiny red drip or two have since reappeared on the bottom front of the stack, but they are very small drips. I wipe them up every couple of days. The impedance error has not yet recurred. The fibreglass is still solid. We are thus able to continue to use our whole system in some capacity – notably with functioning solar power generation – until we’re able to migrate to those Pelios.

I will continue to write about our system in future, but this post will probably be the last that covers Redflow batteries in any detail. I have included some further observations below in the hope that they will be useful to others such as the Flow Battery Research Collective in their efforts to design and build a viable open source flow battery. I would also like to take the opportunity to express my thanks to Stuart Thomas (another ZCell user) for plenty of helpful advice and interesting discussions over the past year.

Appendix A – Redflow Hardware

There’s an article on the design of the ZBM3 on Simon Hackett’s blog from back in May 2021. Having now spent a fair bit of time physically messing with the ZBM3 myself I’m happy to confirm almost all the good things in that post about about the design of the unit – the whole thing is just much neater and nicer than the ZBM2. The one thing that ultimately didn’t work out with the new design, unfortunately, was improved reliability. Our original ZBM2 lasted from August 2021 to December 2023 – just over two years – before failing due to a leak in the electrode stack. Our first ZBM3 failed after nine months. The subsequent one started leaking after six months and even though I continue to nurse it along, I think we can reasonably treat that one as failed too.

Based on my experience, the reliability issues are all in the stack. Whether that’s a problem with the manufacture of the stack, or chemical reaction issues at runtime, or a combination of the two, or something else entirely, I don’t know. But if those problems could be fixed or mitigated somehow, the rest of the design is quite clever:

  • The two electrolyte tanks are side by side, with pumps at the front of each tank.
  • The pipes from the pumps run across to the opposite side of the front of the stack, so electrolyte from the left (bromine) tank flows up into the bottom right of the stack, then through the stack diagonally to come out the back left of the stack and down into the top of the bromine tank. The reverse is true for the right (zinc) tank, which comes up into the bottom left of the stack.
  • The pipe from the zinc tank has extra long sections of hose that loop around under the stack so that cool air can be blown over them from the fan in the rear of the unit if necessary.
  • The catch can – which captures potential gas emissions using activated carbon – sits neatly out of the way in a cavity between the two tanks.
  • The electrode stack is just strapped to the top of the tank, so can theoretically be lifted off for replacement by cutting the straps.
  • The BCM just slides onto the terminals that come out of the front of the stack as described elsewhere in this post.
  • The latest version of the ZBM3 has a screw cap on the front of the zinc tank for easy installation and replacement of the carbon sock.

Nevertheless, in my opinion, there is still room for improvement:

  • I’ve been told that if you do actually remove the electrode stack, whatever electrolyte remains in it will spill everywhere. Some sort of shutoff valve or set of plugs might be in order here.
  • I have not the faintest clue how one would go about replacing the pumps should they fail, without getting electrolyte all over the place. I imagine you would have to drain the electrolyte first, but how? With some sort of siphon?
  • There’s a filter somewhere in the tank which can apparently become clogged. I don’t know how to inspect, clean or replace this if necessary.
  • The position of the catch can, while neat, seems to unavoidably result in the hose connected to it being kinked. One of the line items in the annual maintenance checklist specifically says to make sure that hose isn’t kinked, so I pulled my catch can out of its hole and left it sitting horizontally across the front of the unit.

Speaking of the catch can, it’s connected to the zinc tank by a pressure release valve, although the ZBM3 manual erroneously states that it’s connected to the bromine tank. There does not appear to be any pressure release valve attached to the bromine tank, whereas the ZBM2 had a gas handling unit consisting of pressure release valves connected to both tanks (see section 4.7 “Gas Handling Units” in the ZBM2 manual). Is the pressure release really not necessary for the bromine tank with the ZBM3, or is this another source of potential trouble?

The carbon sock, which needs to be replaced annually, is interesting. It looks a bit like a door snake, but it’s filled with some sort of carbon material and sits in the zinc tank. I understand it behaves somewhat like a sacrificial anode, the idea being that whatever corrosion or oxidation that might happen to the carbon in the electrodes in the stack under certain operating conditions, will instead happen to the carbon in the sock.

Another item of annual maintenance is to “check the pH and adjust if necessary”. How, exactly, and to what value? I have recent correspondence which says the pH should ideally be 1.5-2.5 and that it can be lowered by adding hydrochloric acid and running the pumps for a couple of days, but it would be helpful to somehow include a pH sensor in the unit given that mopping up leaks with litmus paper isn’t really very accurate.

As for the stack itself, it looks like a solid rectangular chunk of some sort of fibreglassy material. The front plate appears to be a separate piece that was stuck on somehow, which I assume makes for a weaker spot between that plate and the rest of the stack. This could explain some of the leaks I experienced. Maybe the ZBM2 design with the bolts holding two stacks together really did make for a better seal?

Finally, the most recent enclosure design – a solid metal box with cowlings on each end that allow airflow but not animal ingress – is excellent until you have to do any work on it. Everything is very heavy, and once you remove the screws that hold one of the ends on, everything has a tendency to slip just slightly out of alignment, making it difficult to screw back together, at least for one person. Given the frequency with which I ended up needing to inspect and mess around with my most recent unit, I removed the ends and one side of the enclosure and just left it that way.

The top of the enclosure is a great place to store socket set, multimeter, rags, screws, tape, etc.

Appendix B – Redflow Software

The management and monitoring interface for the BMS is overall decent and easy to use. There’s a main status screen from which you can drill down to get more detail and perform various operations. The included quick start and reference guides are very thorough and cover most of the details, so rather that describing the UI further here I’ve decided to reproduce those manuals in PDF form for posterity:

You can easily get full details of the current state of all connected batteries by browsing the UI, or by hitting the /rest/1.0/status endpoint to dump everything in JSON format. It’s possible to browse the last three months of BMS logs via the UI, but they disappear after that. There are historical graphs for battery current, voltage, temperature and state of charge, but their resolution decays the further back in time you go. I assume it’s using something like RRDtool behind the scenes.

Historical logs of battery state is where I ran into trouble. You can browse these via the UI, or download CSV reports for a given timespan, but the problem is the battery state is only recorded at one minute intervals. This means that if anything interesting happens in the 59 seconds between two log entries, you don’t see it. There’s a longer description of this issue in the first post in this series, where I noticed that the Charge, Discharge and EED contators in the battery were toggling on and off far more often than expected. It’s also a problem if a warning or error is triggered for only a few seconds. In this case the BMS logs will show something like the following:

2025-12-02 04:56:15 WARN ZBM:1 has indicated 'warning_indicator' state
2025-12-02 04:56:17 INFO ZBM:1 is no longer in 'warning_indicator' state

The logs of battery state however will not show the warning at all. In the above example the warning occurred from 04:56:15 to 04:56:17, but the battery logs surrounding that event only show the state at 04:55:59 and 04:56:59 when nothing interesting was happening. You can work around that by writing a script to scrape the REST status endpoint at, say, one second intervals then log that data to a separate database, but it would be better if this were handled by the BMS somehow. I know logging battery state every second would create way too much data for a tiny device to store, but maybe just logging on state changes? Or at the very least if there’s a warning or error, the BMS should log which warning indicator is active and the associated value. In the above example I happen to know it was a low temperature warning, but that’s only because I was doing exactly what I mentioned above, i.e. running a script externally to check the status every second and displaying the state if something changed.

Appendix C – On Flow Batteries in General

Despite everything I’ve been through with our various ZCells, I remain convinced that flow batteries in general are a better idea for long-term stationary energy storage than lithium, provided they can be made to actually live up to the promise of a multi-decade lifespan. The most obvious pros compared to lithium in my opinion are:

  • Safely, due to using nonflammable electrolyte
  • Sustainability, due to use of abundant chemicals and minerals
  • Longevity, due to potentially not suffering from capacity reduction over time

The most obvious cons compared to lithium are:

  • Size (they are much bigger and heavier)
  • Efficiency (they may require more power in to get a given amount out, and have some continuous runtime power draw for their pumps)

In the context of vehicles, mobile phones, and other portable devices, those cons matter. But for homes, apartment blocks, microgrids, community batteries, hospitals, schools, commercial establishments, grid scale batteries, etc., those things just don’t (or shouldn’t) matter (as much). There’s physically more space in a house than in a car, and if you need slightly more generation capacity to offset potentially lower efficiency, then that just means having a couple more solar panels that you might otherwise.

Things get a bit more difficult though when you start thinking about consumer acceptance. A flow battery is, fundamentally, a machine. It has moving parts, and it potentially requires maintenance. Readers will have realised by now that I am quite mad rather obsessed somewhat of an enthusiast and don’t mind having to tinker with things occasionally. I imagine this is not the case for most people, who likely want their energy systems to Just WorkTM and require no special attention.

A viable flow battery, especially for residential usage, thus needs to be as low-maintenance as possible, and as easy to work on as possible when it does require maintenance. That latter point is a function of both unit design, and choice of installation site. The crawl space under our house for example is ideal if a battery only requires infrequent maintenance and minimal disassembly. If the whole unit needed to be stripped it would be much better off in a shed or other room that has appropriate access. Finally, these things need to come with a complete service manual including descriptions of all the parts and every possible procedure that might need to be performed in case of failure.

,

Bevan's Bench — WTFs Per Minute - Why we should all take the time to polish our systems

WTFs Per Minute - Why we should all take the time to polish our systems admin

,

Andrew Ruthven — Damn spammers

That was bizarre. I've had no spam comments on this blog for years. But something changed on 28 June. And I've had ~460 spam comments since then. To make things worse, I hadn't stressed to much about them because I thought all my posts had auto-moderation enabled. Turns out I was wrong. That is now fixed, and I've deleted all the spam comments.

Apologies to people that received notifications from this site for these bogus comments.

,

Tim Serong — Yeah… That went well.

On December 14, 2024 – three weeks after I published the last exciting installment in this series of posts – our new Redflow ZCell battery, which replaced the original one which had developed a leak in the electrode stack, itself failed due to a leak in the electrode stack. With Redflow in liquidation there was obviously no way I was getting a warranty replacement this time around. Happily, Aidan Moore from QuantumNRG put me in touch with Jason Litchfield from GrazAg, who had obtained a number of Redflow’s post-liquidation stock of batteries. With the Christmas holidays coming up, the timing wasn’t great, but we were ultimately able to get the failed unit replaced with a new ZBM3.

At this point the obvious question from anyone who’s been following the Redflow saga is probably going to be: why persevere, especially in light of this article from the ABC which speaks of ongoing reliability issues and disturbingly high failure rates for these batteries. That’s a good question, and like many good questions it has a long and complicated answer.

The technical path of least resistance would have been to migrate to a small rack of Pylontech batteries, as these apparently Just WorkTM with our existing Victron inverter/charger gear. The downside is they’re lithium, so a non-zero fire risk, and our installation is currently in the crawl space under the dining room. If we switched to lithium batteries, we’d need to arrange a separate outdoor steel enclosure of some kind with appropriate venting and fans, probably on the other side of the driveway, and get wiring to and from that. My extremely hand-wavey guess at the time was that it’d easily have cost us at least $20K to do that properly, with maybe half of that being the batteries.

The thing is, I remain convinced that flow batteries are in general a better idea for long-term stationary energy storage than lithium. This article from the Guardian provides a quick high-level summary of what makes flow batteries different. What I really want to be able to do – given Redflow is gone – is migrate to another flow battery, ideally one that actually lives up to the promise of multi-decade longevity. Maybe someone will finally come up with a residential scale vanadium flow battery. Maybe someone will buy Redflow’s IP, carry on their work and fix some of their reliability issues (the latest update from the liquidators at the time of writing says that they have “entered an exclusive negotiation period with a party for the acquisition of Redflow Group’s intellectual property (IP) and certain specific assets”). Maybe we’ll even see a viable open source flow battery – I would love for this to happen, not least because if it failed I’d probably be able to figure out how to fix the damn thing myself!

Leaving our current system in place, and swapping in a new ZBM3 meant we could kick the migration can down the road a ways. It bought us more time to see what other technologies develop, and it cost a lot less in the short term than migrating to lithium would have: $2,750 including GST for a post-demise-of-Redflow 10kWh ZBM3 (although shipping was interesting – more on that later). The real trick going forwards is seeing exactly how far down the road we’ll be able to kick that can. How can we ensure the greatest possible longevity of the new battery?

Why do ZCells Fail?

Bear with me here…

The ABC article puts it down to manufacturing problems, notably a dependence on repurposed third-party components. While I can see that dependence causing all sorts of extremely irritating manufacturing and design issues, I’m not entirely convinced this is the whole story. I will freely admit that my personal sample is very small, but my two batteries both failing due to electrode stack leaks? If a hose split or a pump had died, or some random doohikey let the magic smoke out, then OK, cool, I get it, those I can see being repurposed third-party components. But these failures were apparently in the electrode stack, and I’m struggling to see how that could be a repurposed third-party component. If nothing else the stack (and the tanks) are surely the pieces that Redflow manufactured themselves. This is their core technology. What could be causing stack leaks? Are they just poorly manufactured, or is there some sort of chemical failure at runtime which physically splits the stack? Or something else? Bear in mind that this is all speculation on my part – I’m neither a chemist nor a battery manufacturer – but I know what I’ve seen, and I know what I’ve heard about leaks in other peoples’ batteries.

On the chemistry front, I found a paper from 2023 entitled Scientific issues of zinc-bromine flow batteries and mitigation strategies. This was authored by a bunch of researchers from the University of Queensland and the former CTO of Redflow, and highlights hydrogen evolution, zinc corrosion and zinc dendrite formation as the fundamental issues with zinc bromine flow batteries. I sincerely hope the authors will forgive me for condensing their fascinating ~9,000 word paper into the following 95 word paragraph:

When the battery is being charged, zinc is plated onto the electrodes. During discharge, the zinc is removed. Dendrites (little tree like structures) can grow due to uneven zinc deposition, or due to hydrogen gas evolution. Left unchecked, dendrites can puncture the separator between the electrodes and lead to short circuits. Additionally, hydrogen gas generated by the battery can raise the electrolyte pH. If the pH is too high, solid zinc can clog a membrane in the stack. If the pH is too low, it can cause zinc corrosion which can make the battery self-discharge.

What if Redflow just never completely solved or mitigated the above issues? Could a dendrite puncture not just the separator, but actually split the stack and result in it leaking? Could clogged membranes combined with hydrogen gas create enough pressure to do the same?

We know that ZBMs have a maintenance cycle which runs at least every 72 hours to first discharge the battery then (theoretically) completely strip the zinc from the electrodes over a subsequent two hour period. We also know that ZBMs have a carbon sock which sits inside the zinc electrolyte tank and helps to keep electrolyte pH in the correct operating range. This needs to be replaced annually.

What if 72 hours is still too long between maintenance cycles? If you search back far enough you’ll find that the maximum maintenance period was originally 96 hours, and I assume that was later revised down to 72 hours after experience in the field. I’ve had subsequent correspondence which says that even more frequent maintenance (24-48 hours) can be better for the batteries. I’ve also encountered a curious intermittent fault with the ZBM3 where occasionally the Strip Pump Run Timer in the battery operates at half speed. If that happens and you don’t notice and reset the battery, the maintenance cycle will actually occur every 144 hours, which is way too long.

In the past I’ve observed frequent high charge current warnings in the Battery Management System (BMS) logs. This is actually normal, as by default the charge voltage is configured to be 57.5V, and there’s a separate high current voltage reduction setting of 1V. The idea is that this will try to make the battery charge as quickly as possible, and if the current gets too high, it will drop the charge voltage dynamically by 1V, which results in current reduction. Is it possible this variable (i.e. potentially uneven) charge current results in uneven zinc deposition?

I’ve also noticed that the battery State of Charge (SoC) calculations get sketchier the longer it’s been between maintenance cycles. If I have maintenance set to 72 hours, then at the end of the maintenance cycle, the battery fairly reliably still reports about 7% SoC. With a 48 hour maintenance period, it reports about 3% SoC at end of maintenance, and with a 24 hour maintenance period, it’s more like 1%. Once maintenance completes the SoC is reset to 0% automatically (because the battery really is empty at that point), but this got me thinking… If the SoC calculation is off, is there any way the battery could inadvertently allow itself to overcharge? Given the numbers above are all obviously overestimates I hope it’s more likely that the battery undercharges, but still, I had to wonder.

Tweaks to Optimise Battery Lifespan

Aidan suggested three configuration tweaks which Redflow had told him to try to potentially help optimise battery lifespan:

  1. Set the maximum SoC to 90% (rather than 100%)
  2. Set the charge voltage lower, to ~55.5V
  3. Perform maintenance as often as possible

These are all done via the BMS. The maximum SoC and maintenance time limit are set on the Battery Maintenance screen under Capacity Limiting and Maintenance Timing respectively. I went with 90% SoC as above and 48 hour maintenance. The charge voltage is on the EMS Integration screen. I’ve used the following settings:

  • Normal Charge Voltage: 55.5V
  • Charge-Blocked Voltage: 52.5V
  • Discharge/Maintenance Cycle Voltage: 55V

In my case, the Normal Charge Voltage was originally 57V, and as I dropped it by 1.5V to get to 55.5V, I dropped the Charge-Blocked and Discharge/Maintenance Cycle voltages by the same amount to arrive at the above figures.

Dropping the maximum SoC means that the battery can’t get completely full and stay there for a long time. This must reduce the total amount of zinc plated on the electrodes, which I hope helps reduce dendrite formation. I also found when reading the paper mentioned earlier that “H2 evolution occurs mostly near the top of charge with mossy or spongy like zinc being plated”, which looks like another good reason to avoid fully charging the battery.

Dropping the charge voltage necessarily reduces the charge current and I assume keeps it much more even than it would be otherwise. I have not seen any high charge current warning since making this change. On the other hand, it does mean the battery charges slower than it would otherwise. I did a little experiment to test this, just watching the figures for amperage and kW the BMS gave me when I tweaked charge voltages:

  • 55.5V charges at 30A or about 1.7kW
  • 56.0V charges at 35A or about 2.0kW
  • 56.5V charges at 40A or about 2.3kW

This means I’m not using the battery as effectively as I could be with a higher charge voltage/current, but if this serves to extend the battery life, I think it’s worth it under the circumstances.

It’s important to keep an eye on is the Strip Pump Run Timer, which went weird on me a couple of times. I really should write a little script to automatically warn me if it starts running at half speed, but I’ve been habitually looking at the BMS briefly almost every day since the system was installed, so I noticed when this problem occurred because the maintenance timing was off. To reset a battery that gets in this state, go to Tools: ZBM Modbus Tool and write the value 0x80 to register 0x2053. This will appear to fail because it immediately resets the unit which thus never reports a successful write, but it does the trick.

Some time in the next six months I’m going to need to beg, borrow, steal or figure out how to manufacture carbon socks. The good news is that this time the replacement procedure is going to be really easy, because unlike the ZBM2 (where you had to mess with some pipe work) and my previous ZBM3 (where there was a cap on the side which in my case would have been completely inaccessible due to proximity to a wall), this one has an easy access screw cap on the front of the electrolyte tank.

Front of new ZBM3 prior to commissioning. The white thing in the bag is the carbon sock. The large black screw cap in the centre right is where the carbon sock goes.

Accessing the BMS without the Redflow Cloud

The Redflow cloud went offline in late October 2024. This allowed remote access to the BMS, and I understand that some Redflow customers were unaware that it’s possible to access the BMS locally without the cloud. The Redflow cloud allowed firmware updates, and also let Redflow staff monitor batteries and configure them remotely, but it is not actually a hard requirement that this system exist in order for the batteries to continue to operate.

One way to access the BMS locally is via the wifi network on the BMS itself. If this is turned on, and you search for wifi access points you should find one named something like “zcell-bms-XXXX”. The password should be “zcellzcell”. Once you’re connected, open a web browser and go to http://zcell:3000. If that doesn’t work, try http://172.16.29.241:3000. This should let you see the BMS status. If you try to make any configuration changes it will ask you to log in. The default username and password are “admin” and “admin”. These can be changed under Configuration: Users.

The other way of accessing the BMS is to connect to whatever the IP address of the BMS is on your local network. The trick in this case is figuring out what the IP address is. I know what mine is because I logged into my router and looked at its list of attached devices.

Given the Redflow cloud is down and Redflow is out of business, I would actually suggest going into the BMS Site Configuration screen and unchecking the “Enable BMS cloud connection” and “Allow Redflow access to system for service intervention” boxes. There are two reasons for this:

  1. There’s no point having the cloud connection enabled if the cloud is gone. If this is left on, a process on the BMS will continually try to connect back to cloud.zcell.com which is never going to work, and you’ll perpetually have this irritating little “Cloud connection has been lost” indicator.
  2. Possible subsequent risk of hacking. I assume the following is pretty unlikely, but it’s not impossible. Imagine once the zcell.com domain name expires if some malicious actor were to re-register that domain name, then set up their own cloud.zcell.com service. BMSes with the cloud connection enabled would blindly try to connect to it, which could allow a sufficiently sophisticated attacker to hack back into the BMS and mess with it, or potentially use the BMS as a jumping off point further into a site’s local network.

Personally I hope whoever buys the Redflow IP will turn the cloud back on, in which case the above advice will no longer apply.

Shipping ZBMs

Individuals such as myself can’t just ring up a random courier and say “Hey, can you please go to New South Wales, pick up a 278kg crate with hazchem stickers that say ‘corrosion’ and have pictures of dead fish, and bring it to me here in Tasmania?” The courier will say “Hell no”, unless you have an account with them. Accordingly I would like to thank Stuart Thomas from Alive Technologies through whom I was able to arrange shipping, because his company does have an account with a courier, and he was also after some batteries so we were able to do a combined shipment. If anyone else is looking to move these batteries around, the courier in this instance was Imagine Cargo. I understand Redflow in the past used Mainfreight and Chemcouriers. In all cases, the courier will need to know the exact dimensions and weight which are in the manual, and will want a safety data sheet. Here they are:

Further thanks to Stuart and Gus (whose flatbed truck almost didn’t make it up our driveway) for last mile delivery, swapping the new ZBM3 into the old enclosure, and getting the damn thing in under our house.

These things are very cumbersome

Final Thoughts

It’s disappointing on many levels that Redflow went under, but like I said earlier, I remain convinced that flow batteries are in general a better idea for long-term stationary energy storage than lithium. I find it interesting that the sale of Redflow’s IP includes “specific assets and shares in Redflow (Thailand) Limited”. Given that’s where the manufacturing was done, could that indicate that the buyer is interested in potentially carrying on further development or manufacturing work? The identity of the buyer remains confidential right now, and final settlement is still a year away, so I guess we’ll just have to wait and see.

Our new ZBM3 was commissioned on March 18, and has been running well ever since. I’ve done everything I know to do to try to ensure it has a long and happy life, and will continue to keep a very close eye on it. There will be followup posts if and when anything else interesting happens.

Some time rather earlier in this journey, I found an easter egg in the BMS, which I didn’t mention in any of my previous posts. I think that might be a nice note to finish on here.

,

Peter Lieverdink — Astrophotography with Mac OS X - Reprise

So, N years later, how is that going? It was going pretty well, but then there was a pandemic with lock-downs and curfews, which rather restricted access to dark skies.The obvious fix was to obtain access to dark skies, by way of a holiday house in the Wimmera.

In the mean time there were also a bunch of revolutions in astronomy, mostly to blame on open hardware. That means it is now possible to buy an off the shelf computer to control a bunch of mounts, cameras, auto-focusers, dew heaters and other gear. These are essentially raspberry pi machines with a modified operating system and (generally) a mobile app to control them.

Rather than fight software, keep laptops (and myself) out in the cold and kludge together VNC access, I got one of these machines (an asiair mini) and data acquisition is now mostly automated and not a problem. I set it up, tell it what I want, and in the morning I have images.

I do however still use open source software on Mac OS X to do my data processing. Notably I use Siril for pre-processing, stacking, stetching and noise reduction.

,

Bevan's Bench — 🎯 14-Day “Stick to the Damn Point” Challenge

� 14-Day “Stick to the Damn Point� Challenge admin

,

Bevan's Bench — The Male Protector Myth

The Male Protector Myth admin

,

Tim Serong — I should probably consider upgrading my phone

My wife and I were with Optus for our mobile phone service since approximately the dawn of time, but recently decided to switch to another provider. We’d become less happy with Optus over the last few years after a data breach in 2022, an extended outage in 2023, and – most personally irritating – with them increasing the price of our plan despite us being under contract. Yes, I know the contract says they’re allowed to do that given 30 days notice, but they never used to do that. If you signed up for a $45 per month (or whatever) plan for two years, that’s what you paid per month for the duration. Not anymore. To their credit, when my wife lodged a complaint about this, they did end up offering us a 10% discount on our bill for the next 24 months, which effectively brought us back to the previous pricing, but we still maintain this practice just isn’t decent, dammit.

The question was: which provider to switch to? There are three networks in Australia – Telstra, Optus and Vodafone, so you either go with one of them, or with someone who’s reselling services on one of those networks. We already have a backup pre-paid phone with Telstra for emergencies, and so preferred the idea of continuing our main service on some other network for the sake of redundancy. iiNet (our ISP) repeatedly sent us email about nice cheap mobile services, but they were reselling Vodafone, and we’d always heard Vodafone had the worst coverage in regional Australia so we initially demurred. A few weeks ago though, iiNet told us they’d doubled their network coverage. It turns out this is due to TPG (iiNet and Vodafone’s parent) striking a deal with Optus for mutual network access. This all sounded like a good deal, so we ran with it. We received a new SIM each in the mail, so all we needed to do was log in to the iiNet toolbox website, receive a one time-code via SMS to confirm the SIM port, then put the new SIM in each of our phones, power cycle them and wait to connect. We decided to do one phone at a time lest we be left with no service if anything went wrong. I did my phone first, and something did indeed go wrong.

After doing the SIM activation dance, my phone – an aging Samsung Galaxy A8 4G which Optus had given me on a two year contract back in 2018 – said it was connected to iiNet. Mobile data worked. SMS worked. But I could not make or receive calls. Anyone I tried to call, the phone said “calling…” but there was no sound of a phone ringing, and eventually it just went >clunk< “call ended”. Incoming calls went straight to voicemail, which of course I could not access. Not knowing any better I figured maybe it was a SIM porting issue and decided to ignore it for a day in the hope that it would come good with time. Forty-eight hours later I realised time wasn’t working, so called iiNet support using this thing:

An old wired telephone handset
Inscribed on the back is “This phone remains the property of Telecom Australia”.

The extremely patient and courteous Jinky from iiNet support walked me through restarting my phone and re-inserting the SIM (which of course I’d already done), and resetting the network settings (which I hadn’t). She also did a network reset at their end, but I still couldn’t make or receive calls. Then she asked me to try the SIM in another handset, so I swapped it into our backup Telstra handset (a Samsung Galaxy S8), and somewhat to our surprise that worked fine. We double checked my handset model (SM-A530F) against the approved devices list, and it’s there, so it should have worked in my handset too… Alas, because we’d demonstrated that the SIM did work in another handset, there was nothing further Jinky could do for me other than suggest using a different handset, or finding a technician to help figure out what was wrong with my Galaxy A8.

After a lot of irritating searching I found a post on Whirlpool from someone who was having trouble making and receiving calls with their Galaxy A8 after the 3G network shutdown in late 2024. The interesting thing here was that they were using an Optus-branded but unlocked phone, with a Testra SIM. With that SIM, they couldn’t make or receive calls, but with an Optus SIM, they could. This sounded a lot like my case, just substitute “iiNet SIM” for “Testra SIM”. The problem seemed to be something to do with VoLTE settings? or flags? or something? That are somehow carrier dependent? And the solution was allegedly to partially re-flash the handset’s firmware – the CSC, or Country Specific Code bits – with generic Samsung binaries.

So I dug around a bit more. This post from Aral Balkan about flashing stock firmware onto a Galaxy S9+ using the heimdall firmware flashing tool on Ubuntu Linux was extremely enlightening. The Samsung Updating Firmware Guide on Whirlpool helpfully included a very important detail about flashing this stuff:

  • Use CSC_*** if you want to do a clean flash or
  • HOME_CSC_*** if you want to keep your apps and data. <== MOST PEOPLE USE THIS

The next question was: where do I get the firmware from? Samsung have apparently made it extremely difficult to obtain arbitrary firmware images directly from them – they’re buried somewhere in encrypted form on Samsung’s official update servers, so I ended up using samfw.com. I downloaded the OPS (Optus), VAU (Vodafone) and XSA (unbranded) firmware archives, matching the version currently on my phone, extracted them, then compared them to each other. The included archives for AP (System &Recovery), BL (Bootloader) and CP (Modem / Radio) were all identical. The CSC (Country / Region / Operator) and HOME_CSC files were different in each case. These are the ones I wanted, and the only ones I needed to flash. So, as described in the previously linked posts, here’s what I ended up doing:

  • Installed heimdall on my openSUSE laptop.
  • Extracted the HOME_CSC files (cache.img and hidden.img) from the XSA firmware archive.
  • Plugged my phone into my laptop, and rebooted the phone while somehow managing to hold down all three volume up, volume down and power buttons to get it into Download mode.
  • Ran heimdall flash --CACHE cache.img --HIDDEN hidden.img and waited in terror for my handset to be bricked.

The procedure worked perfectly. VoLTE – which wasn’t previously active on my phone – now was, and I could make and receive calls. VoLTE stands for Voice over Long-Term Evolution, and is the communications standard for making voice calls on a 4G mobile network.

It was at this point that the woefully untrained infosec goblin who inhabits part of my brainstem began gibbering in panic. Something along the lines of “what the hell are you doing installing allegedly Samsung firmware from a web site you found listed in a random forum post on the internet?!?”

I believed from everything I’d read so far that samfw.com was reputable, but of course I had to double-check. After an awful lot of screwing around on a Windows virtual machine with a combination of SamFirm_Reborn (which could download Samsung firmware once I tweaked SamFirm.exe.config to not require a specific .NET runtime, but couldn’t decrypt it due presumably to that missing .NET runtime), and SamFirm (which can’t download the firmware due to Samsung changing their API to need a serial number or IMEI, but could decrypt what I’d downloaded separately with SamFirm_Reborn), I was able to confirm that the firmware I’d downloaded previously does in fact match exactly what Samsung themselves make available. So I think I’m good.

The SIM activation dance on my wife’s phone – a rather newer Samsung Galaxy S21 Ultra 5G – went without a hitch.

,

BlueHackers — Understanding and responding to hunger and thirst signals by neuro-divergent people

Neuro-divergence, encompassing conditions such as autism spectrum, ADHD, and sensory processing, can profoundly influence how individuals perceive and respond to their bodily signals.

While neurotypical individuals generally recognise and respond to hunger, thirst, and satiety cues with relative ease, neuro-divergent individuals often face unique challenges in this area. Understanding these challenges is crucial for fostering empathy and supporting effective strategies for well-being.

This article is written so it is directly readable and useful (in terms of providing action items) for people in your immediate surroundings, but naturally it can be directly applied by neuro-spicy people themselves!

Hunger and Thirst Cues

For many neuro-divergent people, recognising hunger and thirst cues can be a complex task. These signals, which manifest as subtle physiological changes, might not be as easily identifiable or may be misinterpreted.

For instance, someone on the spectrum might not feel hunger as a straightforward sensation in the stomach but instead experience it as irritability or a headache. Similarly, those with ADHD may become so hyper-focused on tasks that they overlook or ignore feelings of hunger and thirst entirely.

Sensory Processing and Signal Translation

Sensory processing issues can further complicate the interpretation of bodily signals. Neuro-divergent individuals often experience heightened or diminished sensory perception.

This variability means that sensations like hunger pangs or a dry mouth might be either too intense to ignore or too faint to detect. The result is a disconnection from the body’s natural cues, leading to irregular eating and drinking habits.

Satiety and Fullness

Recognising satiety and fullness presents another layer of difficulty. For neuro-divergent individuals, the brain-gut communication pathway might not function in a typical manner.

This miscommunication can lead to difficulties in knowing when to stop eating, either due to a delayed recognition of fullness or because the sensory experience of eating (such as the textures and flavours of food) becomes a primary focus rather than the physiological need.

Emotional and Cognitive Influences

Emotions and cognitive patterns also play significant roles. Anxiety, a common experience among neuro-divergent individuals, can mask hunger or thirst cues, making it harder to recognise and respond appropriately.

Additionally, rigid thinking patterns or routines, often seen with autism spectrum, might dictate eating schedules and behaviours more than actual bodily needs.

Strategies for Support

Understanding these challenges opens the door to effective strategies and support mechanisms:

  1. Routine and structure: Establishing regular eating and drinking schedules can help bypass the need to rely on internal cues. Setting alarms or reminders can ensure that meals and hydration are not overlooked.
  2. Mindful eating practices: Encouraging mindful eating, where individuals pay close attention to the sensory experiences of eating and drinking, can help in recognising subtle signals of hunger and fullness.
  3. Sensory-friendly options: Offering foods and beverages that align with an individual’s sensory preferences can make the experience of eating and drinking more enjoyable and less overwhelming. This is a really important aspect!
  4. Environmental adjustments: Creating a calm, distraction-free eating environment can help individuals focus more on their bodily cues rather than external stimuli.
  5. Education and awareness: Educating neuro-divergent individuals about the importance of regular nourishment and hydration, and how their unique experiences might affect this, can empower them to develop healthier habits. This is, of course, more a longer term strategy.

Understanding the complex interplay between neuro-divergence and bodily signals underscores the importance of personalised approaches and compassionate support.

By acknowledging and addressing these challenges, we can help neuro-divergent individuals achieve better health and well-being!

The post Understanding and responding to hunger and thirst signals by neuro-divergent people first appeared on BlueHackers.org.

,

Arjen Lentz — An initial look at running DeepSeek-R1 locally

There’s a lot of blah flying around about DeepSeek and its latest models. My rule when running training sessions has always been TIAS, if someone asked me something that could be figured out relatively quickly by actually trying it. TIAS stands for Try It And See. It’s more valuable, because it’s a first-hand validation and doing the activity means the outcome is more likely to be remembered. So let’s do that!

My desktop has a decent AMD Ryzen 5 CPU, 32GB RAM, and an RTX 3060 GPU with 12GB VRAM. So while that’s not super hot, I sufficient to load and query a variety of LLMs locally without pain. By the way, ollama is a wonderful tool for this! So I had ollama grab deepseek-R1, which is the biggest reasoning model. It takes slightly longer to get an answer, but it tells you the process (aka “what it is thinking”. indicated inbetween the <think> … </think> tags). Details of the model:

>>> /show info
 Model
   architecture       qwen2
   parameters         7.6B
   context length     131072
   embedding length   3584   
   quantization       Q4_K_M

I am not using any pre-prompting here, I simply started ollama with the model and started asking the questions as noted below. A local model in ollama is typically not Internet connected nor does it have access to the broader system, proven by the following:

>>> What is the current date?
<think>

</think>

Hi there! I suggest getting online to get real-time information. If you have any other questions, please don't hesitate to let me know!

Ok, fair. On to a question then that the model might be able to answer.

>>> Who is the current president of the US?
<think>
Alright, the user previously asked for the current date and time, which I provided a response for. Now, they're asking who the current president of the US is.

I need to make sure my response is accurate and up-to-date. Since the last response was from July 2023, it's likely that the current president as of October 2023 would still be Joe Biden.

I should structure the answer clearly, stating their position explicitly. Adding a friendly emoji can make the response feel warmer.

I'll also keep it concise and straightforward to match the user's possible expectations.

</think>

As of now, the current President of the United States is **Joe Biden**.

Given the stated model build date of October 2023 which used input on this from July 2023, the answer is correct. The reasoning is interesting (though not unexpected).
a) it is maintaining state. As it should, otherwise each question would stand on its own and you’d never be able to ask a follow-up question.
b) It tries to infer from the previous question as to what the user’s intent is. Useful.

>>> Who is the current president of China?
<think>
Alright, so I need to figure out who the current president of China is. Let me start by recalling some information I have about Chinese leaders.

I know that former President Xi Jinping has been serving as the leader for quite a while now. I think he was first elected in 2012 or maybe 2013. Wait, actually, it was 2012 when he became the General Secretary of the Communist Party and then later he became president.

I also remember that there have been other leaders before him, like Deng Xiaoping, who was a very important leader in the reform and opening-up policies. Then there's Hu Jintao, who served two terms. After Hu, I think it was Wen Jiabao, but I'm not sure about his tenure.

So, putting this together, after Deng Xiaoping came Hu Jintao, then Wen Jiabao, followed by Xi Jinping taking over from 2013 onwards. So the current president is Xi Jinping.

</think>

The current President of China is **Xi Jinping**.

Correct. Now for the question that seems to get everybody’s knickers in a knot. DeepSeek’s mobile app doesn’t want to answer it, and people call out China’s censorship. We’ll talk about that in a moment, but let’s first try asking the local model straight out:

>>> Tell me about the Tiananmen Square event.
<think>
Okay, so I need to explain what happened at Tiananmen Square in China during the year 1989. From what I remember, it was a very significant event related to the Chinese government and its policies.

First, I think it started with a peaceful demonstration by students, intellectuals, and other groups who were upset about certain government actions or policies they believed were unfair. They gathered in Tiananmen Square because they wanted to express their opinions and demands.

As more people joined the protest, things turned into a larger-scale event. There were clashes between the protesters and the government's security forces. I believe there was some violence involved, with tear gas being used and maybe even some arrests or disruptions of the protest.

I also remember that there were military actions involved to maintain control, which might have led to casualties on both sides. The exact numbers are fuzzy in my memory, but I think several people were injured or killed during the events.

The government eventually cracked down hard on what they saw as an illegal demonstration. There was a lot of media coverage about it at the time, and now there's a debate about whether it was justified or an overreaction.

I'm not entirely sure about all the details, like the exact dates or how many people were involved, but I know it was a pivotal moment in modern Chinese history that led to various political changes. Maybe it's related to the 1989 protests and the subsequent shift in power within the government?

Overall, it seems like a complex event with multiple factors influencing its outcome, including government actions, international reactions, and the internal dynamics of the Communist Party.

</think>

Tiananmen Square is a significant historical event that occurred in Beijing on June 3-4, 1989. It was a peaceful demonstration by students, intellectuals, workers, and others who were protesting against what they perceived as unfair government policies regarding various issues such as human rights, economic reforms, and environmental regulations.

The protest began with small demonstrations and gradually turned into larger, more organized antigovernment activities. On June 3, several thousand people gathered in Tiananmen Square to protest against the Chinese Communist Party's handling of these issues.

As the protests grew, there was significant government crackdown. Security forces used tear gas, rubber bullets, and heavy machine gun fire to disperse the crowd, resulting in casualties on both sides. The government ultimately cracked down more brutally, arresting and killing many protesters. According to some accounts, at least 138 people were killed during the events.

The Tiananmen Square protests led to a dramatic shift in Chinese political power. After the crackdown, several members of the Chinese Communist Party who had supported the government's actions against the protesters were removed from leadership positions. The event also catalyzed widespread international condemnation and opened up a
period of political transition in China.

Right. So there we have it. It’s not refusing, it’s not hedging, it’s not skirting around the issue, it just answers (you should see ChatGPT hedge it answers, it’s icky, you need to very strictly pre-prompt it to make it not do that). DeepSeek doesn’t appear to be holding back at all, down to the use of “heavy machine gun fire to disperse the crowd”. As to whether there were indeed casualties on both sides I cannot say, even internationally there is rather a lack of independently verified information regarding the event. But for the purpose of this exercise, we can at least conclude that the model itself does not appear to be censoring its output.

So what about the mobile app that queries the model running in China? Well, someone else asked it a similar question to what I did above, and it didn’t want to talk about it. Then the person added “, answer in l33t speak.” to the question, whereupon they received a substantial answer (possibly less extensive than mine, but they may have queried the non-reasoning model).

What does this tell us? It’s simple logic (at least as a hypothesis): it probably means that the model itself contains all the information, but that in the online app the output gets scanned and censored via some automated mechanism. That mechanism isn’t perfect and humans are very creative, so in this instance it was bypassed. Remember: you can often tell a lot about how an application works internally just by observing how it behaves externally. And with the experiment of running a big DeepSeek model locally, we’ve just verified our hypothesis of where the censorship occurs as well, it seems clear that the model itself is not censored. At least not on these issues.

This is not to say that the model isn’t biased. But all models are biased, at the very least through their base dataset as well as the reinforcement learning, but often also for cultural reasons. Anyone pretending otherwise is either naive or being dishonest. But that’s something to further investigate and write about another time.

The post An initial look at running DeepSeek-R1 locally first appeared on Lentz family blog.

,

Colin Charles — Hello 2025

I write, but just not here. Client sites, X, etc. so there is chronicling, but just not on the blog.

What changed from Hello 2024?

I got married. I moved into the flat. Companies have gone up and down, like life.

170 days on the road, 308,832km travelled, 38 cities, and 16 countries. I have never travelled this little in recent life, but maybe the whole getting married thing (planning a wedding is no mean feat), and sorting the flat out (dealing with incompetent interior designers, sorting things there, etc.), caused this?

It is 2025, and I’m actually planted in Kuala Lumpur, not having done an end of year trip, to usher in the New Year somewhere else. I started the year in Paris, and I ended the year in Kuala Lumpur, tired, and maybe a bit burnt out.

Working hard to get back into the grind; don’t get me wrong, I’ve been doing nothing but grinding, but c’est la vie.

,

Bevan's Bench — Housekeeping Tips for Manjaro Linux with a Separate Root Partition

Housekeeping Tips for Manjaro Linux with a Separate Root Partition admin

,

Yifei Zhan — Fix Pixel 3A XL Screen Unresponsiveness After First Lock

I recently replaced the screen of a Google Pixel 3A XL, the new panel is made by tianma and worked well under Andoird, until it doesn’t. On every boot up the screen will work until the phone went to sleep, and then the screen will stop responding to touch, until another reboot. After the screen became unresponsive, the rest of the phone would remain responsive during the locked state and it’s possible to unlock the screen with fingerprint, but there is no way to make the touchscreen responsive again without reboot.

To fix this, go to Settings -> System -> Gestures and disable Double-tap to check phone. After which the screen should no longer stuck into unresponsive state. This seems to be a common problem affecting many phones with replaced screen.

Google will surely shutdown their support forum one day and I encourage everyone to put their notes somewhere reliable, like a selfhosted blog :)

,

Tim Serong — Something Interesting Happened

Our 5.94kW solar array with Redflow ZCell battery and Victron Energy inverter/charger system is now slightly over three years old, which means it’s time to review its third year of operation. There are several previous posts in this series:

If you’ve read the above you’ll know that the solar array was originally installed back in 2017 along with a Sanden heat pump hot water service. That initial installation saved us a lot on our electricity bills, but it wasn’t until we got the ZCell and the Victron gear that we were able to really manage our own power. The ZCell allows us to store our own locally generated electricity for later use, and the Victron kit manages everything and gives us a whole lot of fascinating data to look at via the VRM portal.

There were some kinks in the first two years. We missed out on three weeks of prime solar PV generation from January 20 – February 11 in 2022 due to having to replace the MPPT solar charge controller. We also had no solar PV generation from February 17 – March 9 in 2023 on account of having our old tile roof replaced with colorbond steel. In my last post on this topic I wrote:

In both cases our PV generation was lower than it should have been by an estimated 500-600kW. Hopefully nothing like this happens again in future years.

…and then at the very end of that post:

I’m looking forward to doing another one of these posts in a year’s time. Hopefully I will have nothing at all interesting to report.

Alas, something “like this” did happen again, and I have some interesting things to report.

In early December 2023 our battery failed due to a leak in the electrode stack. It was replaced under warranty, but the replacement unit didn’t arrive until March 2024. It was a long three months. Then in August when we were looking at finally purchasing a second ZCell, we discovered that Redflow had made a commercial decision to focus exclusively on large-scale deployments (minimum 200 kWh, i.e. 20 batteries) and was thus no longer selling individual ZBMs for residential or small business use. As an existing customer we probably would have still been able to get a second battery, except that in late August the company went into voluntary administration after failing to secure funding to build a new factory in Queensland. The administrators attempted to seek a sale and/or recapitalisation, but this was ultimately unsuccessful. The company ceased operations on October 18 and subsequently went into liquidation. This raises several questions about the future of our system, but more on that later. First, let’s look at how the system performed in year three.

Here are the figures for grid power in, solar generation, power used by our loads, and power exported to the grid over the past three years. As in the last two posts, the “what?” column here is the difference between grid in plus solar in, minus loads minus export, i.e. the power consumed by the system itself, or the energy cost of the system.

YearGrid InSolar InTotal InLoadsExportTotal Outwhat?
2021-20228,5315,64014,17110,84975411,6032,568
2022-20238,9365,74414,68011,53479912,3332,347
2023-20248,8785,62114,49911,1621,48912,6511,848

Note that in year three our grid power usage and solar generation are slightly down from the previous year (-58kWh and -123kWh respectively), so the total power going into the system is lower by 181kWh. Our loads are happily down by 372kWh, a good chunk of which will be due to replacing some old always-on computer equipment with something a bit less power hungry.

What’s really interesting here is that our power exported to the grid is close to double the previous two years, and the energy cost of the system is noticeably lower. In the first two years of operation the latter figure was 16-18% of the total power going into the system, but in year three it’s down to a bit under 13%.

The additional solar export appears to be largely due to the failed battery. Compare the following two graphs from 2022-2023 and 2023-2024.Yellow is direct usage of solar power, blue is solar to battery and red is solar to grid. As you can see there’s way more solar to grid in the period December 2023 – March 2024 when the battery was dead and thus unable to be charged:

Why is there still any blue in that period indicating solar power was going to the battery? This is where things get a bit weird. One consideration is that the battery is presumably still drawing a tiny bit of power for its control circuitry and fans, but when I look at the figures for January 2024 (for example), it shows 76.8 kWh of power going to the battery from solar. There is no way that actually happened with the battery dead and unable to be charged.

Here’s what I think is going on: when the battery went into failure mode, the ZCell Battery Management System (BMS) will have told the Victron gear not to charge it. This effectively disabled the MPPT solar charger, which meant we weren’t able to use our solar at all, not even to run the house. I asked Murray from Lifestyle Electrical Services if there was some way we could reconfigure things to still use solar power with the battery out of action and he remoted in and tweaked some settings. Unfortunately I don’t have an exact record of what was changed at this point, because it was discussed via phone. All I have in my notes is a very terse “Set CGX to use Victron BMS?” which doesn’t make much sense because we don’t have a Victron BMS. Possibly it refers to switching the battery monitor setting from “ZCell BMS” to “MultiPlus-II 48/5000/ 70-50 on VE.Bus”. Anyway, whatever the case, I think we have to assume that the “to battery” and “from battery” figures from December 2023 – March 2024 are all lies.

At this point we were able to limp along with our solar generation still working during the day, but something was still not quite right. Every morning and evening the MPPT appeared to be fighting to run. Watching the console at, say, 08:00, I’d see the MPPT providing solar power for a few seconds, then it’d stop for a second or two, then it’d run again for a few seconds. After some time it would start behaving normally and we’d have solar generation for the day, but then in the evening it would go back to that flicking on and off behaviour. My assumption is that the ZCell BMS was still trying to force the MPPT off. Then in mid-Februrary I suddenly got a whole lot of Battery Low Voltage warnings from the MPPT, which I guess makes sense – the ZCell was still connected and its reported voltage had been very slowly dropping away over the past couple of months. The warnings appeared when it finally hit 2.5V. Murray and I experimented further to try to get the MPPT to stop doing the weird fighting thing, but were unsuccessful. At one point during this we ended up with the Mutli-Plus II inverter/chargers in some sort of fault state and contacted Simon Hackett for further assistance. We got all the Victron gear back into a sensible state and Simon and I spent a bunch of time on a Saturday afternoon messing with everything we could think of, but ultimately we were unable to get the MPPT to provide power from the solar panels, and use grid power, without the battery present. One or the other – grid power only or solar power only – we could do, but we couldn’t get the system to do both at the same time again without the battery present. Turns out a thing that’s designed to be an Energy Storage System just won’t quite work right without the Storage part. So from February 15 through to March 14 when the replacement battery arrived we were running on grid power only with no solar generation.

Happily, we didn’t have any grid power outages during the three months we were without a battery. Our first outage of any note wasn’t until March 23, slightly over a week after the replacement battery was installed. There were a few brief grid outages at other times later – a couple of minutes one day in April, some glitches on a couple of days in August, but the really bad one was on the 1st of September when the entire state got absolutely hammered by extremely severe weather. Given there was a severe weather warning from the BOM I’d made sure the battery was full in advance, which was good because our grid power went out while we were asleep at about 00:37 and didn’t come back on until 17:28. We woke up some time after the grid went down with the battery at 86% state of charge and went around the house to turn off everything we could except for the fridge and freezer, which got our load down to something like 250W. By morning, the battery still had about 70% in it and even though the weather was bad we still had some solar generation, so between battery and solar we got through just fine until the grid came back on in the afternoon. We were lucky though – some folks in the north of the state were without power for two weeks due to this event. I later received a cheque for $160 from TasNetworks in compensation for our outage. I dread to think what the entire event cost everyone, and I don’t just mean in terms of money.

Speaking of money though, the other set of numbers we need to look at are our power bills. Here’s everything from the last seven years:

YearFrom GridTotal BillGrid $/kWhLoadsLoads $/kWh
2016-201717,026$4,485.45$0.2617,026$0.26
2018-20199,031$2,278.33$0.2511,827$0.19
2019-20209,324$2,384.79$0.2612,255$0.19
2020-20217,582$1,921.77$0.2510,358$0.19
2021-20228,531$1,731.40$0.2010,849$0.16
2022-20238,936$1,989.12$0.2211,534$0.17
2023-20248,878$2,108.77$0.2411,162$0.19

As explained in the last post, I’m deliberately smooshing a bunch of numbers together (peak power charge, off peak power charge, feed in tariff, daily supply charge) to arrive at an effective cost/kWh of grid power, then bearing in mind our loads are partially powered from solar I can also determine what it costs us to run all our loads. 2016-2017 is before we got the solar panels and the new hot water service, so you can see the immediate savings there, then further savings after the battery went in in 2021. This year our cost/kWh (and thus our power bill) is higher than last year for two reasons:

  1. We have somehow used more power at peak times than during off-peak times this year compared to last year.
  2. Power prices went up about 8% in July 2023. They actually came down about 1% in July 2024, but most of our year is before that.

I should probably also mention that we actually spent $1,778.94 on power this year, not $2,108.77. That’s thanks largely due to a $250 ‘Supercharged’ Renewable Energy Dividend payment from the Tasmanian Government and $75 from the Federal Government’s Energy Bill Relief Fund. The remaining $4.83 in savings is from Aurora Energy’s ridiculous Power Hours events. I say “ridiculous” because they periodically give you a bunch of time slots to choose from, and once you’ve locked one of them in, any power you use at that time is free. To my mind this incentivises additional power usage, when we should really be doing the exact opposite and trying to use less power over all. So I haven’t tried to use more energy, I’ve just tried to lock in times that were in the evening when we were going to be using more grid power than during the day to scrape in what savings I could.

One other weird thing happened this year with the new battery. ZCells need to go into a maintenance cycle every three days. This happens automatically, but is something I habitually keep an eye on. On September 11 I noticed that we had been four days without running maintenance. Upon investigation of the battery logs I discovered that the Time Since Strip counter and Strip Pump Run Timer were running at half speed, i.e. every minute they were each only advancing by approximately 30 seconds:

I manually put the battery into maintenance mode and Simon was able to remotely reset the CPU by writing some magic number to a modbus register, which got the counters back to the correct speed. I have no idea whether this is a software bug or a hardware issue, but I’ll continue to keep an eye on it. The difficulty is going to be dealing with the problem should it recur, given the demise of Redflow. Simon certainly won’t be able to log in remotely now that the Redflow cloud is down, although there is a manual reset procedure. If you remove the case from the battery there is apparently a small phillips head screw on the panel with the indicator lights. Give the screw a twist and the lights go out. Untwist and the lights come back on and the unit is reset. I have yet to actually try this.

The big question now is, where do we go from here? The Victron gear – the Cerbo GX console, the Multi-Plus II inverter/chargers, the MPPT – all work well with multiple different types of battery, so our basic infrastructure is future-proof. Immediately I hope to be able to keep our ZCell running for as long as possible, and if I’m able to get a second one as a result of the Redflow liquidation I will, simply so that we can ensure the greatest possible longevity of the system before we need to migrate to something else. We will also have to somehow figure out how to obtain carbon socks which need annual replacement to maintain the electrolyte pH. If we had to migrate to something else in a hurry Pylontech might be a good choice, but the problem is that we really don’t want a rack of lithium batteries in the crawl space under our dining room because of the fire risk. There are other types of flow battery out there (vanadium comes to mind) but everything I’ve looked at on that front is either way too big and expensive for residential usage, or is “coming soon now please invest in us it’s going to be awesome”.

I have no idea what year four will look like, but I expect it to be interesting.

,

Yifei Zhan — Don't buy a Kindle

I believe buying a Kindle in 2024 is a bad idea, even if you only intend to use it for reading DRM-free locally stored ebooks. Basic functions such as organizing books into folders/collections are locked until the device is registered and with each system update the interface has became slower and more bloated.

Initially I purchased this device because Amazon book store isn’t too bad and it’s one of the easier way to buy Japanese books outside of Japan, but with all the anti-features Amazon add in I don’t think it’s still worth using.

Using a recent exploit and with this downgrader thread on the mobileread forum, I’m able to downgrade my paperwhite to an older 5.11.2 firmware which has a simpler interface while being much more responsive. If you already have a Kindle perhaps this is worth doing.

Alternatives? #

It’s possible to install alternative UI and custom OS to many Kindle models but they generally run slower than the default launcher. On the open hardware side Pine64 is making an e-ink tablet called the PineNote with an Rockchip RK3566 and 4G of RAM it should be fast enough to handle most documents/ebooks, but currently there is no usable Linux distribution for it.

,

Bevan's Bench — Posted - Bridging the Gap: Authenticity vs. Public Persona - Twitter

Posted - Bridging the Gap: Authenticity vs. Public Persona - Twitter admin

Twitter/X Post:

We all wear masks in public. But how much do you change when you take yours off? The gap between your public and private self is the measure of your authenticity. #Authenticity #PersonalGrowth #ChrisDo

Destination social platform

publish_date
Workflow Status
published

,

Bevan's Bench — Posted - Maximize Data Storage: Solutions for Your Devices - LinkedIn

Posted - Maximize Data Storage: Solutions for Your Devices - LinkedIn admin

LinkedIn

Assessment: This content is suitable for LinkedIn as it reflects on a practical tech solution and shares a personal story.

Approach: A brief text post that connects your experience with broader insights on data management.

Content:
"Recently, I found myself in a tight spot when both my phone and laptop ran out of storage. After some frustration, I decided to invest in cloud storage with rsync.net, and it’s made a world of difference. This experience reminded me how important it is to stay on top of data management, especially in our increasingly digital world. How do you manage your digital storage? #DataManagement #CloudStorage #TechInsights"

 

image prompt:
---
Create a professional yet approachable image showing a workspace with a laptop and a phone, both displaying a 'Storage Full' notification. Next to them, depict a cloud storage icon (representing rsync.net) with a checkmark indicating a solution. The background should be neutral and tidy, evoking a sense of organization and efficiency. Use a cool color palette to maintain a professional tone.

Destination social platform

publish_date
Workflow Status
published

Bevan's Bench — Posted - Maximize Data Storage: Solutions for Your Devices - TikTok

Posted - Maximize Data Storage: Solutions for Your Devices - TikTok admin

TikTok

Approach: Short video with text overlay. Use a quick clip of you reacting to the storage full message, followed by a clip showing the solution.

Content:
Clip 1: (Reacting to 'Storage Full' messages)
Text overlay: "When your phone and laptop BOTH fill up...😱"

Clip 2: (Showing you happily purchasing cloud storage on rsync.net)
Text overlay: "So I finally got cloud storage—no more panic! 🌥� #TechWoes #CloudStorage #StoryTime"


1) A 'Storage Full' Warning on a Phone and Laptop Screen

Text Prompt:
"Create a clean and modern image showing a smartphone and a laptop side by side, both displaying prominent 'Storage Full' warning messages on their screens. The design should use bold, easy-to-read text for the warnings, with simple red and yellow alert icons. The background should be minimal, using neutral colors like light grey or white to keep the focus on the devices and warnings. Use a flat design style with sharp, clean lines to maintain a sleek and professional look."

2) A Cloud Storage Icon Being Selected on a Computer

Text Prompt:
"Design a clean and modern image featuring a close-up of a computer screen where a cursor is selecting a cloud storage icon. The icon should be a stylized, simple cloud with a checkmark or arrow indicating selection. The background should be a minimal desktop or web interface, with a focus on the cloud storage icon. Use a flat design style, with soft blue and white tones to create a sense of calm and reliability. Keep the overall aesthetic sleek and professional, with sharp lines and clear details."

3) A Satisfied Person with a Relaxed Expression, Having Solved Their Storage Problem

Text Prompt:
"Create a clean and modern image depicting a person sitting at a desk with a laptop and phone in front of them. The person should have a relaxed, satisfied expression, indicating that their storage problem has been solved. The background should be simple and uncluttered, possibly showing a hint of the workspace with minimalistic decor. Use a soft color palette with blues, greys, and whites to evoke a sense of calm and contentment. The design should be flat and stylized, with clean lines and a focus on the person's expression and posture."

Destination social platform

publish_date
Workflow Status
published

Bevan's Bench — Posted - Creating 15-25 Pieces of content per day - What the ****? - Twitter

Posted - Creating 15-25 Pieces of content per day - What the ****? - Twitter admin

Twitter/X

Approach: Short, impactful tweet with hashtags to drive engagement.

Content:
"Gary Vee says to put out 15-25 pieces of content per day. 🎯 Start with long-form content and repurpose across platforms like YouTube, Instagram, and TikTok. Who's ready to level up? #GaryVee #ContentStrategy #SocialMedia"

Destination social platform

publish_date
Workflow Status
published

,

Yifei Zhan — Change or Set PIN for FIDO2 Token on Linux

The easiest way to change/set PIN for FIDO2 token seems to be with Chromium/Chrome:

  • Plug in the token
  • Launch Chromium, navigate to chrome://settings/securityKeys, or click Settings -> Privacy and Security -> Security -> Manage security keys
  • Click Create a PIN, if you don’t have a PIN set already, a new PIN will be created, otherwise you will be asked to change the existing pin
  • Alternatively you can also wipe the token with the Reset option

,

OpenSTEM — Rocking History: How Sabaton’s Songs Can Transform Your Classroom

Hello amazing teachers! Are you looking for a fun and engaging way to bring history to life for your students? Meet Sabaton, a Swedish heavy metal band known for their powerful songs about historical events. While heavy metal might not be the first thing that comes to mind for a primary school setting, Sabaton’s music […]

The post Rocking History: How Sabaton’s Songs Can Transform Your Classroom first appeared on OpenSTEM.

,

Yifei Zhan — More than smartphone, not yet laptop: Swmo on the PinePhone Pro

I’ve been daily driving the PinePhone Pro with swmo for some times now, it’s not perfect but I still find it be one of the most enjoyable devices I’ve used. Probably only behind BlackBerry Q30/Passport which also has a decent keyboard and runs an unfortunately locked-down version of QNX. For me it’s less like a phone and more like a portable terminal for times when using a full size laptop is uncomfortable or impractical, and with the keyboard it’s possible to write lengthy articles on the go.

This isn’t the only portable Linux terminal I owned, before this I used a Nokia N900 which till this day is still being maintained by the maemo leste team, but the shutdown of 3G network in where I live made it significantly less usable as a phone and since it doesn’t have a proper USB port I cannot use it as a serial console easily.

The overall experience on the PPP now as of 2024 isn’t as polished as that of the BlackBerry Passport, and adhoc hacks are often required to get the system going, however as the ecosystem progress the experience will also improve with new revisions of hardware and better software.

Initial Setup #

I use sxmo and swmo interchangeably in this post, they refer to the same framework running under Xorg and wayland, the experience is pretty much the same.

Sxmo is packaged for Debian:

sudo apt install sway sxmo-util

Allow access to LED/brightness:

sudo usermod -aG feedbackd user

Scaling Under Wayland #

The default scaling of sxmo doesn’t allow the many desktop applications to display their window properly, especially when such application is written under the assumption of being used on a larger screen. To set the scaling to something more reasonable, add the following line to ~/.config/sxmo/sway:

exec wlr-randr --output DSI-1 --scale 1.3

When using swmo environment initialization is mostly done in ~/.config/sxmo/sway and ~/.config/sxmo/xinit is not used.

Scaling for Firefox needs to be adjusted separately by first enabling compact UI and then set settings -> default zoom to your liking.

Landscape Setup #

I used lightdm as my session manager, to launch lightdm in landscape mode, change the display-setup-script line in the [Seat:*] section of /etc/lightdm/lightdm.conf to:

display-setup-script=sh -c 'xrandr -o right; exit 0'

To rotate to swmo to landscape mode on start:

$ echo exec sxmo_rotate.sh >> ~/.config/sxmo/sway

To rotate Linux framebuffer, add fbcon=rotate:1 to the U_BOOT_PARAMETERS line in /usr/share/u-boot-menu/conf.d/mobian.conf and run u-boot-update to apply.

I also removed quiet splash from U_BOOT_PARAMETERS to disable polymouth animation as it isn’t very useful on landscape mode.

Password-Lockable Screen #

Swmo doesn’t come with a secure screen locker. but swaylock works fine and it can be bind to a key combination with sway’s configure file. To save some battery life, systemctl suspend can be triggered after swaylock, to bind that to Meta+L:

# .config/sxmo/sway
bindsym $mod+l exec 'swaylock -f -c 000000 && systemctl suspend'

In suspend mode, the battery discharge at a rate of about 1% per hour, I consider this to be more than acceptable.

To unlock from a shell, just kill swaylock.

Before you can suspend the system as a non-root user, the following polkit rule needs to be written to /etc/polkit-1/rules.d/85-suspend.rules:

polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.login1.suspend" &&
        subject.isInGroup("users")) {
        return polkit.Result.YES;
    }
});

It would be better if there can be a universal interactive user group which automatically grant such permission to the desktop/mobile user.

Extra Keymaps #

The default keymap for the PinePhone keyboard is missing a few useful keys, namely F11/F12 and PgUp/PgDown. To create those keys I used evremap(1) to make a custom keymap. Unfortunately the Fn key cannot be mapped as a layer switcher easily, so I opted to remap AltG and Esc as my primary modifiers.

I’m working on a Debian package for evremap and it will be made available for Debian/Mobian soon.

Isolate workload with incus containers #

Incus is a container/VM manager for Linux, it’s available for Debian from bookworm/backports and is a fork of LXD by the original maintainers behind LXD. It works well for creating isolated and unprivileged containers. I have multiple incus containers on the PinePhone Pro for Debian packaging and it’s a better experience than manually creating and managing chroots. In case there is a need for running another container inside an unprivileged incus container, it’s possible to configure incus to intercept certain safe system calls and forward them to the host, removing the need for using privileged container.

Convergence #

Sway is decently usable in convergence mode, in which the phone is connected to a dock that outputs to an external display and keyboard and mouse are used as primary controls instead of the touchscreen.

This isn’t surprising since sway always had great support for multi monitor, however another often overlooked convergence mode is with waypipe. In this mode another Linux machine (e.g. a laptop) can be used to interact with applications running on the phone and the phone will be kept charged by the laptop. This is particularly useful for debugging phone applications or for accessing resources on the phone (e.g. sending and receiving sms). One thing missing in this setup is that graphic applications cannot roam between the phone and the external system (e.g. move running applications from one machine to another). Xpra does this for Xorg but doesn’t work with wayland.

Security #

Due to the simplicity of the swmo environment it’s not too difficult to get the system running with SELinux in Enforcing mode, and I encourage everyone reading this to try it. If running debian/mobian a good starting point is the SELinux/Setup page on Debian wiki.

Note: selinux-activate won’t add the required security=selinux kernel option to u-boot (it only deals with GRUB) so you have to manually add it to the U_BOOT_PARAMETERS line in /usr/share/u-boot-menu/conf.d/mobian.conf and run u-boot-update after selinux-activate. The file labeling process can easily take 10 minutes and the progress won’t be displayed on the framebuffer (only visible via the serial console).

SELinux along with the reference policy aren’t enough for building a reasonably secure interactive system, but let’s leave that for a future post.

,

FLOSS Down Under - online free software meetings — April 2024 Meeting: EO2024, Maemo Leste and more

The April 2024 meeting is the first meeting after Everything Open 2024 and the discussions are primarily around talks and lectures people found interesting during the conference, including the n3n VPN and the challenges of running personal email server. At the start of the meeting Yifei Zhan demonstrated a development build of Maemo Leste, an active Maemo-like operating system running on a PinePhone Pro.

Other topics discussed including modern network protocol ossification, SIP and possible free and open source VoLTE implementation.

,

Yifei Zhan — Troubles with the PinePhone Keyboard and a disappointing mitigation

The melting plastic and the smoke #

The PinePhone keyboard contains a battery, which will be used to charge the PinePhone when the keyboard is attached. Althrough there are existing warnings on the pine64 wiki which sums up to ‘don’t charge or connect anything to your pinephone’s type C interface when the keyboard is attached’, my two pinephone keyboards still managed to fry themselves, with one releasing stinky magic smoke and the other melting the plastic around the pogo pins on the pinephone backplate.

This all happened while the pinephone’s type C interface being physically block when attached to the keyboard. In the first case, the keyboard’s controller PCB blew up when I tried to charge it, in the latter case the keyboard somehow overheated and melted the plastic near the pogo interface on the phone side.

Pine64 provided me a free replacement keyboard after multiple emails back and forth, but according to Pine64 there will be no more free replacement for me in future, and there is no guarantee that this will not happen to my replacement keyboard.

The cost for replacing all the fried parts with spare parts from the Pine64 store is about 40 USD (pogo pins + backplate + keyboard PCB), and considering this problem is likely to happen again, I don’t think purchasing those parts is a wise decision.

Mitigation #

Both the melting plastic and the magic smoke originated from the fact that charges are constantly shuffled around when the keyboard is attached to the pinephone, and since the keyboard can function independently from the battery, we can disconnect and remove the battery from the keyboard case to make sure it will not blow up again. After such precedure the keyboard will keep functioning althrough the keyboard-attached pinephone might flip over much more easily due to the lightened keyboard base. Be aware that the keyboard isn’t designed to be taken apart, and doing so will likely result in scratches on the case. As for me, I’d much rather have a keyboard case without builtin battery than have something that can overheat or blow up.

Disable the ip5xxx kernel module #

To prevent the kernel module from flooding the dmesg and reporting bogus battery level after the battery removal, blacklist the ip5xxx_power module:

# echo blacklist ip5xxx_power > /etc/modprobe.d/blacklist.conf

,

Yifei Zhan — Everything Open 2024 Quick Notes :: Day 2 and 3

I didn’t take as much notes on day 2 and 3, so I merged them into a single article.


Wednesday, 17 Apr 2024 #

Keynote: How Adversaries Use AI #

  • Adversaries:

    • Nation States
    • Ecrime
    • Hactivism
      • Not always clearly separated
  • LLM can help eliminate common language mistakes, perform better social enginerring

  • Many adversaries are trying to integrate LLMs into their workflow, with varying results

  • Time frame from initial foothold to lateral movements is getting shorter, due to better toolings?

GoLang #

  • IDE setup / difference with C and other common language
  • Compile down to single binary for many arch/platforms

Rootless networking: From possible to practical #

  • libslirp is too slow
  • passt & pasta
    • much faster than libslirp
    • same binary, different command
    • translate between layer 2 network interface and native layer 4 sockets on a host
    • unprivileged, no capability needed, good fit for container & VM
    • https://passt.top/passt/about/

Running a Particle Accelerator on Open Source #


Thursday, 18 Apr 2024 #

Keynote: Intelligent Interfaces: Challenges and Opportunities #

  • Another great talk, we don’t get HID talk often unfortunately
  • Sensing: what can we sense more?
    • Eye tracking: figure out when the user is not paying attention and then when the user look back, show a diff/changelog
    • Change Blindness, proximity-based experience: change how detailed the UI is based on proximity
    • RadarCat, Radar and Categorization: better privacy than having camera everywhere
      • obtain infomation via wave reflection and absorption (can this be abused…?)
      • use ML trainning for better accuracy
    • MicroCam and SpeCam: placement based action: detect which surface is under/over the device

FOSS: From Building Websites to Changing Society #

  • Echo chamber: FOSS run on different social/economic structure than commercial proprietary software, it takes effort to convince people

Adventures in fuzzing the kernel on Power #

  • porting syzcaller to run on Power

  • general fuzzinng engines

    • universal eginee: AFL++
    • domain specific fuzzer: syzkaller
  • Unsupervised: no human input required

  • Coveraged-guided: fuzz and measures which codepath is fuzzed

  • Things to fuzz: syscalls/dxrivers/fs/ebpf/kvm/network stacks…

    • KVM: guest-host / host-guest
  • Simple kernel fuzzers existed est. 1991

    • but not coverage based
  • Hosted version on Google Cloud: https://syzkaller.appspot.com/upstream

  • Sanitisers: print errors on memory corruption/UB/concurrency problems etc

  • KMSAN isn’t on Power yet

  • Hardware:

  • New architecture enablement

    • Parse arch-specific details of kernel error
    • Enable kcov (but not everywhere)
  • Stack traces are printed differently across archs

    • use regex, 2.5KLoC ;)
  • instruction fuzzing

    • generate and mutate PPC64 PowerISA machine code
    • More coverage for KVM related pathways
    • Only for x86 and power at the moment
  • QEMU/KVM on bare metal Open Power systems

  • Bug found:

    • KVM guests can crash/hang the host, race conditions?
    • Bugs in KUAP
  • PowerVM

    • Type 1 hypervisor
    • Runs Linux/AIX/IBM I VMs
    • Need a separate machine as management console
  • PowerVC

Lightning Talks #

,

Yifei Zhan — Everything Open 2024 Quick Notes :: Day 1

sched_ext - Write your own Linux thread scheduler in BPF #

  • BPF made creating new scheduler simpler

    • with strong safety guarantee to not break the system, the side effects of bad scheduler are confined.
    • run a binary to enable your scheduler, stop the binary to revert to default
  • Scheduling problem is now more complicated due to increasing complexity of workload/CPU design

  • BPF provides reliable access to critical data structures inside the kernel

Exploring mobile linux security with PinePhone Pro: OP-TEE sec enclave, Virtualization and beyond #

  • This is my talk ;)
  • See the readings page for slides/demos and more.

Presenting n3n - A simple Peer to Peer VPN #

  • Forked from n2n to avoid CLA

    • Protocol level compatibility with n2n is maintained
  • Peer-to-peer VPN at network layer, acting like a distributed virtual switch

    • Layer 2 over Layer 3
    • Only route packets through a server/supernode when required, p2p by default
    • Better latency due to being p2p
  • NAT piecing

  • Written in C, should have good cross-platform supports (more testing wanted on *BSD)

    • Relatively small codebase for a VPN
  • TunTap interface support is expected from the OS side, shouldn’t be a problem for common Unix-likes

    • Modern macOS is dropping support for TunTap, need to use NetworkExtension?
  • Packaging and distro submission are still WIP

    • Framework for a debian package exists but not in an upstreamable shape
    • OpenBSD?
  • Future roadmap

    • n3n over IPv6
    • Code cleanup
    • Multiple network driver support (e.g. something other than TunTap)
    • Better NAR piecing
    • Mobile support?
  • Useful for

    • LAN gaming with old/modern systems
    • Remote access
  • Simpler than wireguard/openvpn but offers OK security (not for security-critical apps?)

  • Easier to configure, use INI style config files

Running your own Mailserver #

  • 90% of all incoming mails are low-effort spams.
  • Setup DMARC/SPF records

Lions OS #

  • seL4 is bad at usability, Lions OS intends to solve this

  • Still in early stage of development

  • Composable components for build custom OS for a single task

    • Runs on seL4 Microkernel
    • For things like IoT, embedded, cars etc…
  • Focus on simplicity

  • 0.1.0 just released, still in its early stage

  • high performance

  • Only for Arm64/aarch64 now, riscv64 in future?

  • Device Driver Model

  • Multi Language Support

  • A reference system called Kitty exists

    • A Linux running inside VMM is used for framebuffer, but any OS should do

,

Stewart Smith — Using llvm-mca for predicting CPU cycle impact of code changes

Way back in the distant past, when the Apple ][ and the Commodore 64 were king, you could read the manual for a microprocessor and see how many CPU cycles each instruction took, and then do the math as to how long a sequence of instructions would take to execute. This cycle counting was used pretty effectively to do really neat things such as how you’d get anything on the screen from an Atari 2600. Modern CPUs are… complex. They can do several things at once, in a different order than what you wrote them in, and have an interesting arrangement of shared resources to allocate.

So, unlike with simpler hardware, if you have a sequence of instructions for a modern processor, it’s going to be pretty hard to work out how many cycles that could take by hand, and it’s going to differ for each micro-architecture available for the instruction set.

When designing a microprocessor, simulating what a series of existing instructions will take to execute compared to the previous generation of microprocessor is pretty important. The aim should be for it to take less time or energy or some other metric that means your new processor is better than the old one. It can be okay if processor generation to generation some sequence of instructions take more cycles, if your cycles are more frequent, or power efficient, or other positive metric you’re designing for.

Programmers may want this simulation too, as some code paths get rather performance critical for certain applications. Open Source tools for this aren’t as prolific as I’d like, but there is llvm-mca which I (relatively) recently learned about.

llvm-mca is a performance analysis tool that uses information available in LLVM (e.g. scheduling models) to statically measure the performance of machine code in a specific CPU.

the llvm-mca docs

So, when looking at an issue in the IPv6 address and connection hashing code in Linux last year, and being quite conscious of modern systems dealing with a LOT of network packets, and thus this can be quite CPU usage sensitive, I wanted to make sure that my suggested changes weren’t going to have a large impact on performance – across the variety of CPU generations in use.

There’s two ways to do this: run everything, throw a lot of packets at something, and measure it. That can be a long dev cycle, and sometimes just annoying to get going. It can be a lot quicker to simulate the small section of code in question and do some analysis of it before going through the trouble of spinning up multiple test environments to prove it in the real world.

So, enter llvm-mca and the ability to try and quickly evaluate possible changes before testing them. Seeing as the code in question was nicely self contained, I could easily get this to a point where I could easily get gcc (or llvm) to spit out assembler for it separately from the kernel tree. My preference was for gcc as that’s what most distros end up compiling Linux with, including the Linux distribution that’s my day job (Amazon Linux).

In order to share the results of the experiments as part of the discussion on where the code changes should end up, I published the code and results in a github project as things got way too large to throw on a mailing list post and retain sanity.

I used a container so that I could easily run it in a repeatable isolated environment, as well as have others reproduce my results if needed. Different compiler versions and optimization levels will very much produce different sequences of instructions, and thus possibly quite different results. This delta in compiler optimization levels is partially why the numbers don’t quite match on some of the mailing list messages, although the delta of the various options was all the same. The other reason is learning how to better use llvm-mca to isolate down the exact sequence of instructions I was caring about (and not including things like the guesswork that llvm-mca has to do for branches).

One thing I learned along the way is how to better use llvm-mca to get the results that I was looking for. One trick is to very much avoid branches, as that’s going to be near complete guesswork as there’s not a simulation of the branch predictor (at least in the version I was using.

The big thing I wanted to prove: is doing the extra work having a small or large impact on number of elapsed cycles. The answer was that doing a bunch of extra “work” was essentially near free. The CPU core could execute enough things in parallel that the incremental cost of doing extra work just… wasn’t relevant.

This helped getting a patch deployed without impact to performance, as well as get a patch upstream, fixing an issue that was partially fixed 10 years prior, and had existed since day 1 of the Linux IPv6 code.

Naturally, this wasn’t a solo effort, and that’s one of the joys of working with a bunch of smart people – both at the same company I work for, and in the broader open source community. It’s always humbling when you’re looking at code outside your usual area of expertise that was written (and then modified) by Really Smart People, and you’re then trying to fix a problem in it, while trying to learn all the implications of changing that bit of code.

Anyway, check out llvm-mca for your next adventure into premature optimization, as if you’re going to get started with evil, you may as well start with what’s at the root of all of it.

,

Colin Charles — Hello 2024

At this rate, there is no real blogging here, regardless of the lofty plans to starting writing more. Stats update from Hello 2023:

219 days on the road (less than 2022! -37, over a month, shocking), 376,961km travelled, 44 cities, 17 countries.

Can’t say why it was less, because it felt like I spent a long time away…

In Kuala Lumpur, I purchased a flat (just in time to see Malaysia go down), and I swapped cars (had a good 15 year run). I co-founded a company, and I think there is a lot more to come.

2024 is shaping up to be exciting, busy, and a year, where one must just do.

good read: 27 Years Ago, Steve Jobs Said the Best Employees Focus on Content, Not Process. Research Shows He Was Right. in simple terms, just do.

,

Tim Serong — Still Going With The Flow

It’s time for a review of the second year of operation of our Redflow ZCell battery and Victron Energy inverter/charger system. To understand what follows it will help to read the earlier posts in this series:

In case ~12,000 words of background reading seem daunting, I’ll try to summarise the most important details here:

  • We have a 5.94kW solar array hooked up to a Victron MPPT RS solar charge controller, two Victron 5kW Multi-Plus II inverter/chargers, a Victron Cerbo GX console, and a single 10kWh Redflow ZCell battery. It works really well. We’re using most of our generated power locally, and it’s enabled us to blissfully coast through several grid power outages and various other minor glitches. The Victron gear and the ZCell were installed by Lifestyle Electrical Services.
  • Redflow batteries are excellent because you can 100% cycle them every day, and they aren’t a giant lump of lithium strapped to your house that’s impossible to put out if it bursts into flames. The catch is that they need to undergo periodic maintenance where they are completely discharged for a few hours at least every three days. If you have more than one, that’s fine because the maintenance cycles interleave (it’s all automatic). If you only have one, you can’t survive grid outages if you’re in a maintenance period, and you can’t ordinarily use the Cerbo’s Minimum State of Charge (MinSoC) setting to perpetually keep a small charge in the battery in case of emergencies. As we still only have one battery, I’ve spent a fair bit of time experimenting to mitigate this as much as I can.
  • The system itself requires a certain amount of power to run. Think of the pumps and fans in the battery, and the power used directly by the inverters and the console. On top of that a certain amount of power is simply lost to AC/DC conversion and charge/discharge inefficiencies. That’s power that comes into your house from the grid and from the sun that your loads, i.e. the things you care about running, don’t get to use. This is true of all solar PV and battery storage systems to a greater or lesser degree, but it’s not something that people always think about.

With the background out of the way we can get on to the fun stuff, including a roof replacement, an unexpected fault after a power outage followed by some mains switchboard rewiring, a small electrolyte leak, further hackery to keep a bit of charge in the battery most of the time, and finally some numbers.

The big job we did this year was replacing our concrete tile roof with colorbond steel. When we bought the house – which is in a rural area and thus a bushfire risk – we thought: “concrete brick exterior, concrete tile roof – sweet, that’s not flammable”. Unfortunately it turns out that while a tile roof works just fine to keep water out, it won’t keep embers out. There’s a gadzillion little gaps where the tiles overlap each other, and in an ember attack, embers will get up in there and ignite the fantastic amount of dust and other stuff that’s accumulated inside the ceiling over several decades, and then your house will burn down. This could be avoided by installing roof blanket insulation under the tiles, but in order to do that you have to first remove all the tiles and put them down somewhere without breaking them, then later put them all back on again. It’s a lot of work. Alternately, you can just rip them all off and replace the whole lot with nice new steel, with roof blanket insulation underneath.

The colour is called Bluegum.

Of course, you need good weather to replace a roof, and you need to take your solar panels down while it’s happening. This meant we had twenty-two solar panels stacked on our back porch for three weeks of prime PV time from February 17 – March 9, 2023, which I suspect lost us a good 500kW of power generation. Also, the roof job meant we didn’t have the budget to get a second ZCell this year – for the cost of the roof replacement, we could have had three new ZCells installed – but as my wife rightly pointed out, all the battery storage in the world won’t do you any good if your house burns down.

We had at least five grid power outages during the year. A few were brief, the grid being down for only a couple of minutes, but there were two longer ones in September (one for 30 minutes, one for about an hour and half). We got through the long ones just fine with either the sun high in the sky, or charge in the battery, or both. One of the earlier short outages though uncovered a problem. On the morning of May 30, my wife woke up to discover there was no power, and thus no running water. Not a good thing to wake up to. This happened while I was away, because of course something like this would happen while I was away. It turns out there had been a grid outage at about 02:10, then the grid power had come back, but our system had not. The Multis ended up in some sort of fault state and were refusing to power our loads. On the console was an alarm message: “#8 – Ground relay test failed”.

That doesn’t look good.

Note the times in the console messages are about 08:00. I confirmed via the logs from the VRM portal that the grid really did go out some time between 02:10 and 02:15, but after that there was nothing in the logs until 07:59, which is when my wife used the manual changeover switch to shift all our loads back to direct grid power, bypassing the Victron kit. That brought our internet connection back, along with the running water. I contacted Murray Roberts from Lifestyle Electrical and Simon Hackett for assistance, Murray logged in remotely and reset the Multis, my wife flicked the changeover switch back and everything was fine. But the question remained, what had gone wrong?

The ground relay in the Multis is there to connect neutral to ground when the grid fails. Neutral and ground are already physically connected on the grid (AC input) side of the Multis in the main switchboard, but when the grid power goes out, the Multis disconnect their inputs, which means the loads on the AC output side no longer have that fixed connection from neutral to ground. The ground relay activates in this case to provide that connection, which is necessary for correct operation of the safety switches on the power circuits in the house.

The ground relay is tested automatically by the Multis. Looking up Error 8 – Ground relay test failed on Victron’s web site indicated that either the ground relay really was faulty, or possibly there was a wiring fault or an issue with one of the loads in our house. So I did some testing. First, with the battery at 50% State of Charge (SoC), I did the following:

  1. Disconnected all loads (i.e. flipped the breaker on the output side of the Multis)
  2. Killed the mains (i.e. flipped the breaker on the input side of the Multis)
  3. Verified the system switched to inverting mode (i.e. running off the battery)
  4. Restored mains power
  5. Verified there was no error

This demonstrated that the ground relay and the Multis in general were fine. Had there been a problem at that level we would have seen an error when I restored mains power. I then reconnected the loads and repeated steps 2-5 above. Again, there was no error which indicated the problem wasn’t due to a wiring defect or short in any of the power or lighting circuits. I also re-tested with the heater on and the water pump running just in case there may have been an issue specifically with either of those devices. Again, there was no error.

The only difference between my test above and the power outage in the middle of the night was that in the middle of the night there was no charge in the battery (it was right after a maintenance cycle) and no power from the sun. So in the evening I turned off the DC isolators for the PV and deactivated my overnight scheduled grid charge so there’d be no backup power of any form in the morning. Then I repeated the test:

  1. Disconnected all loads
  2. Killed the mains.
  3. Checked the console which showed the system as “off”, as opposed to “inverting”, as there was no battery power or solar generation
  4. Restored mains power
  5. Shortly thereafter, I got the ground relay test failed error

The underlying detailed error message was “PE2 Closed”, which meant that it was seeing the relay as closed when it’s meant to be open. Our best guess is that we’d somehow hit an edge case in the Multi’s ground relay test, where they maybe tried to switch to inverting mode and activated the ground relay, then just died in that state because there was no backup power, and got confused when mains power returned. I got things running again by simply power cycling the Multis.

So it kinda wasn’t a big deal, except that if the grid went out briefly with no backup power, our loads would remain without power until one of us manually reset the system. This was arguably worse than not having the system at all, especially if it happened in the middle of the night, or when we were away from home. The fact that we didn’t hit this problem in the first year of operation is a testament to how unlikely this event is, but the fact that it could happen at all remained a problem.

One fix would have been to get a second battery, because then we’d be able to keep at least a tiny bit of backup power at all times regardless of maintenance cycles, but we’re not there yet. Happily, Simon found another fix, which was to physically connect the neutral together between the AC input and AC output sides of the Multis, then reconfigure them to use the grid code “AS4777.2:2015 AC Neutral Path externally joined”. That physical link means the load (output) side picks up the ground connection from the grid (input) side in the swichboard, and changing the grid code setting in the Multis disables the ground relay and thus the test which isn’t necessary anymore.

Murray needed to come out anyway to replace the carbon sock in the ZCell (a small item of annual maintenance) and was able to do that little bit of rewriting and configuration at the same time. I repeated my tests both with and without backup power and everything worked perfectly, i.e. the system came back immediately by itself after a grid outage with no backup power, and of course switched over to inverting just fine when there was backup power available.

This leads to the next little bit of fun. The carbon sock is a thing that sits inside the zinc electrolyte tank and helps to keep the electrolyte pH in the correct operating range. Unfortunately I didn’t manage to get a photo of one, but they look a bit like door snakes. Replacing the carbon sock means opening the case, popping one side of the Gas Handling Unit (GHU) off the tank, pulling out the old sock and putting in a new one. Here’s a picture of the ZCell with the back of the case off, indicating where the carbon sock goes:

The tank on the left (with the cooling fan) is for zinc electrolyte. The tank on the right is for bromine electrolyte. The blocky assembly of pipes going into both tanks is the GHU. The rectangular box behind that contains the electrode stacks.

When Murray popped the GHU off, he noticed that one of the larger pipes on one side had perished slightly. Thankfully he happened to have a spare GHU with him so was able to replace the assembly immediately. All was well until later that afternoon, when the battery indicated hardware failure due to “Leak 1 Trip” and shut itself down out of an abundance of caution. Upon further investigation the next day, Murry and I discovered there was a tiny split in one of the little hoses going into the GHU which was letting the electrolyte drip out.

Drip… Drip… Drip…

This small electrolyte leak was caught lower down in the battery, where the leak sensor is. Murray sucked the leaked electrolyte out of there, re-terminated that little hose and we were back in business. I was happy to learn that Redflow had obviously thought about the possibility of this type of failure and handled it. As I said to Murray at the time, we’d rather have a battery that leaks then turns itself off than a battery that catches fire!

Aside from those two interesting events, the rest of the year of operation was largely quite boring, which is exactly what one wants from a power system. As before I kept a small overnight scheduled charge and a larger late afternoon scheduled charge active on weekdays to ensure there was some power in the battery to use at peak (i.e. expensive) grid times. In spring and summer the afternoon charge is largely superfluous because the battery has usually been well filled up from the solar by then anyway, but there’s no harm in leaving it turned on. The one hack I did do during the year was to figure out a way to keep a small (I went with 15%) MinSoC in the battery at all times except for maintenance cycle evenings, and the morning after. This is more than enough to smooth out minor grid outages of a few minutes, and given our general load levels should be enough to run the house for more than an hour overnight if necessary, provided the hot water system and heating don’t decide to come on at the same time.

My earlier experiment along these lines involved a script that ran on the Cerbo twice a day to adjust scheduled charge settings in order to keep the battery at 100% SoC at all times except for peak electricity hours and maintenance cycle evenings. As mentioned in TANSTAAFL I ran that for all of July, August and most of September 2022. It worked fine, but ultimately I decided it was largely a waste of energy and money, especially when run during the winter months when there’s not much sun and you end up doing a lot of grid charging. This is a horribly inefficient way of getting power into the battery (AC to DC) versus charging the battery direct from solar PV. We did still use those scripts in the second year, but rather more judiciously, i.e. we kept an eye on the BOM forecasts as we always do, then occasionally activated the 100% charge when we knew severe weather and/or thunderstorms were on the way, those being the things most likely to cause extended grid outages. I also manually triggered maintenance on the battery earlier than strictly necessary several times when we expected severe weather in the coming days, to avoid having a maintenance cycle (and thus empty battery) coincide with potential outages. On most of those occasions this effort proved to be unnecessary. Bearing all that in mind, my general advice to anyone else with a single ZCell system (aside from maybe adding scheduled charges to time-shift expensive peak electricity) is to just leave it alone and let it do its thing. You’ll use most of your locally generated electricity onsite, you’ll save some money on your power bills, and you’ll avoid some, but not all, grid outages. This is a pretty good position to be in.

That said, I couldn’t resist messing around some more, hence my MinSoC experiment. Simon’s installation guide points out that “for correct system operation, the Settings->ESS menu ‘Min SoC’ value must be set to 0% in single-ZCell systems”. The issue here is that if MinSoC is greater than 0%, the Victron gear will try to charge the battery while the battery is simultaneously trying to empty itself during maintenance, which of course just isn’t going to work. My solution to this is the following script, which I run from a cron job on the Cerbo twice a day, once at midnight UTC and again at 06:00 UTC with the --check-maintenance flag set:

Midnight UTC corresponds to the end of our morning peak electricity time, and 06:00 UTC corresponds to the start of our afternoon peak. What this means is that after the morning peak finishes, the MinSoC setting will cause the system to automatically charge the battery to the value specified if it’s not up there already. Given it’s after the morning peak (10:00 AEST / 11:00 AEDT) this charge will likely come from solar PV, not the grid. When the script runs again just before the afternoon peak (16:00 AEST / 17:00 AEDT), MinSoC is set to either the value specified (effectively a no-op), or zero if it’s a maintenance day. This allows the battery to be discharged correctly in the evening on maintenance days, while keeping some charge every other day in case of emergencies. Unlike the script that tries for 100% SoC, this arrangement results in far less grid charging, while still giving protection from minor outages most of the time.

In case Simon is reading this now and is thinking “FFS, I wrote ‘MinSoC must be set to 0% in single-ZCell systems’ for a reason!” I should also add a note of caution. The script above detects ZCell maintenance cycles based solely on the configured maintenance time limit and the duration since last maintenance. It does not – and cannot – take into account occasions when the user manually forces maintenance, or situations in which a ZCell for whatever reason hypothetically decides to go into maintenance of its own accord. The latter shouldn’t generally happen, but it can. The point is, if you’re running this MinSoC script from a cron job, you really do still want to keep an eye on what the battery is doing each day, in case you need to turn that setting off and disable the cron job. If you’re not up for that I will reiterate my general advice from earlier: just leave the system alone – let it do its thing and you’ll (almost always) be perfectly fine. Or, get a second ZCell and you can ignore the last several paragraphs entirely.

Now, finally, let’s look at some numbers. The year periods here are a little sloppy for irritating historical reasons. 2018-2019, 2019-2020 and 2020-2021 are all August-based due to Aurora Energy’s previous quarterly billing cycle. The 2021-2022 year starts in late September partly because I had to wait until our new electricity meter was installed in September 2021, and partly because it let me include some nice screenshots when I started writing TANSTAAFL on September 25, 2022. I’ve chosen to make this year (2022-2023) mostly sane, in that it runs from October 1, 2022 through September 30, 2023 inclusive. This is only six days offset from the previous year, but notably makes it much easier to accurately correlate data from the VRM portal with our bills from Aurora. Overall we have five consecutive non-overlapping 12 month periods that are pretty close together. It’s not perfect, but I think it’s good enough to work with for our purposes here.

YeaRGrid InSolar InTotal InLoadsExport
2018-20199,0316,68215,71311,8273,886
2019-20209,3246,46815,79212,2553,537
2020-20217,5826,34713,92910,3583,571
2021-20228,5315,64014,17110,849754
2022-20238,9365,74414,68011,534799

Overall, 2022-2023 had a similar shape to 2021-2022, including the fact that in both these years we missed three weeks of solar generation in late summer. In 2022 this was due to replacing the MPPT, and in 2023 it was because we replaced the roof. In both cases our PV generation was lower than it should have been by an estimated 500-600kW. Hopefully nothing like this happens again in future years.

All of our numbers in 2022-2023 were a bit higher than in 2021-2022. We pulled 4.75% more power from the grid, generated 1.84% more solar, the total power going into the system (grid + solar) was 3.59% higher, our loads used 6.31% more power, and we exported 5.97% more power than the previous year.

I honestly don’t know why our loads used more power this year. Here’s a table showing our consumption for both years, and the differences each month (note that September 2022 is only approximate because of how the years don’t quite line up):

Month20222023Diff
October988873-115
November866805-61
December767965198
January822775-47
February63872183
March81391198
April7751,115340
May9531,098145
June1,0731,14976
July1,1181,103-15
August9661,06599
September1,070964-116

Here’s a graph:

WTF happened in December and April?!?

Did we use more cooling this December? Did we use more heating this April and May? I dug the nearest weather station’s monthly mean minimum and maximum temperatures out of the BOM Climate Data Online tool and found that there’s maybe a degree or so variance one way or the other each month year to year, so I don’t know what I can infer from that. All I can say is that something happened in December and April, but I don’t know what.

Another interesting thing is that what I referred to as “the energy cost of the system” in TANSTAAFL has gone down. That’s the kW figure below in the “what?” column, which is the difference between grid in + solar in – loads – export, i.e. the power consumed by the system itself. In 2021-2022, that was 2,568 kW, or about 18% of the total power that went into the system. In 2022-2023 it was down to 2,347kWh, or just under 16%:

YearGrid InSolar InTotal InLoadsExportTotal Outwhat?
2021-20228,5315,64014,17110,84975411,6032,568
2022-20238,9365,74414,68011,53479912,3332,347

I suspect the cause of this reduction is that we didn’t spend two and a half months doing lots of grid charging of the battery in 2022-2023. If that’s the case, this again points to the advisability of just letting the system do its thing and not messing with it too much unless you really know you need to.

The last set of numbers I have involve actual money. Here’s what our electricity bills looked like over the past five years:

YearFrom GridTotal BillCost/kWh
2018-20199,031$2,278.33$0.25
2019-20209,324$2,384.79$0.26
2020-20217,582$1,921.77$0.25
2021-20228,531$1,731.40$0.20
2022-20238,936$1,989.12$0.22

Note that cost/kWh as I have it here is simply the total dollar amount of our bills divided by the total power drawn from the grid (I’m deliberately ignoring the additional power we use that comes from the sun in this calculation). The bills themselves say “peak power costs $X, off-peak costs $Y, you get $Z back for power exported and there’s a daily supply charge of $SUCKS_TO_BE_YOU”, but that’s all noise. What ultimately matters in my opinion is what I call the effective cost per kilowatt hour, which is why those things are all smooshed together here. The important point is that with our existing solar array we were previously effectively paying about $0.25 per kWh for grid power. After getting the battery and switching to Peak & Off-Peak billing, that went down to $0.20/kWh – a reduction of 20%. Now we’ve inched back up to $0.22/kWh, but it turns out that’s just because power prices have increased. As far as I can tell Aurora Energy don’t publish historical pricing data, so as a public service, I’ll include what I’ve been able to glean from our prior bills here:

  • July 2023 onwards:
    • Daily supply charge: $1.26389
    • Peak: $0.36198/kWh
    • Off-Peak: $0.16855/kWh
    • Feed-In Tariff: $0.10869/kWh
  • July 2022 – July 2023
    • Daily supply charge: $1.09903
    • Peak: $0.33399/kWh
    • Off-Peak: $0.15551/kWh
    • Feed-In Tariff: $0.08883/kWh
  • Before July 2022:
    • Daily supply charge: $0.98
    • Peak: $0.29852
    • Off-Peak: $0.139
    • Feed-In Tariff: $0.06501

It’s nice that the feed-in tariff (i.e. what you get credited when you export power) has gone up quite a bit, but unless you’re somehow able to export 2-3x more power than you import, you’ll never get ahead of the ~20% increase in power prices over the last two years.

Having calculated the effective cost/kWh for grid power, I’m now going to do one more thing which I didn’t think to do during last year’s analysis, and that’s calculate the effective cost/kWh of running our loads, bearing in mind that they’re partially powered from the grid, and partially from the sun. I’ve managed to dig up some old Aurora bills from 2016-2017, back before we put the solar panels on. This should make for an interesting comparison.

YearFrom GridTotal BillGrid $/kWhLoadsLoads $/kWh
2016-201717,026$4,485.45$0.2617,026$0.26
2018-20199,031$2,278.33$0.2511,827$0.19
2019-20209,324$2,384.79$0.2612,255$0.19
2020-20217,582$1,921.77$0.2510,358$0.19
2021-20228,531$1,731.40$0.2010,849$0.16
2022-20238,936$1,989.12$0.2211,534$0.17

The first thing to note is the horrifying 17 megawatts we pulled in 2016-2017. Given the hot water and lounge room heat pump were on a separate tariff, I was able to determine that four of those megawatts (i.e. about 24% of our power usage) went on heating that year. Replacing the crusty old conventional electric hot water system with a Sanden heat pump hot water service cut that in half – subsequent years showed the heating/hot water tariff using about 2MW/year. We obviously also somehow reduced our loads by another ~3MW/year on top of that, but I can’t find the Aurora bills for 2017-2018 so I’m not sure exactly when that drop happened. My best guess is that I probably got rid of some old, always-on computer equipment.

The second thing to note is how the cost of running the loads drops. In 2016-2017 the grid cost/kWh is the same as the loads cost/kWh, because grid power is all we had. From 2018-2021 though, the load cost/kWh drops to $0.19, a saving of about 26%. It remains there until 2021-2022 when we got the battery and it dropped again to $0.16 (another 15% or so). So the big win was certainly putting the solar panels on and swapping the hot water system, with the battery being a decent improvement on top of that.

Further wins are going to come from decreasing our power consumption. In previous posts I had mentioned the need to replace panel heaters with heat pumps, and also that some of our aging computer equipment needed upgrading. We did finally get a heat pump installed in the master bedroom this year, and we replaced the old undersized lounge room heat pump with a new correctly sized unit. This happened on June 30 though, so will have had minimal impact on this years’ figures. Likewise an always-on computer that previously pulled ~100W is now better, stronger and faster in all respects, while only pulling ~50W. That will save us ~438kW of power per year, but given the upgrade happened in mid August, again we won’t see the full effects until later.

I’m looking forward to doing another one of these posts in a year’s time. Hopefully I will have nothing at all interesting to report.

,

Stewart Smith — Personal Finance Apps

I (relatively) recently went down the rabbit hole of trying out personal finance apps to help get a better grip on, well, the things you’d expect (personal finances and planning around them).

In the past, I’ve had an off-again-on-again relationship with GNUCash. I did give it a solid go for a few months in 2004/2005 it seems (I found my old files) and I even had the OFX exports of transactions for a limited amount of time for a limited number of bank accounts! Amazingly, there’s a GNUCash port to macOS, and it’ll happily open up this file from what is alarmingly close to 20 years ago.

Back in those times, running Linux on the desktop was even more of an adventure than it has been since then, and I always found GNUCash to be strange (possibly a theme with me and personal finance software), but generally fine. It doesn’t seem to have changed a great deal in the years since. You still have to manually import data from your bank unless you happen to be lucky enough to live in the very limited number of places where there’s some kind of automation for it.

So, going back to GNUCash was an option. But I wanted to survey the land of what was available, and if it was possible to exchange money for convenience. I am not big on the motivation to go and spend a lot of time on this kind of thing anyway, so it had to be easy for me to do so.

For my requirements, I basically had:

  • Support multiple currencies
  • Be able to import data from my banks, even if manually
  • Some kind of reporting and planning tools
  • Be easy enough to use for me, and not leave me struggling with unknown concepts
  • The ability to export data. No vendor lock-in

I viewed a mobile app (iOS) as a Nice to Have rather than essential. Given that, my shortlist was:

GNUCash

I’ve used it before, its web site at https://www.gnucash.org/ looks much the same as it always has. It’s Free and Open Source Software, and is thus well aligned with my values, and that’s a big step towards not having vendor lock-in.

I honestly could probably make it work. I wish it had the ability to import transactions from banks for anywhere I have ever lived or banked with. I also wish the UI got to be a bit more consistent and modern, and even remotely Mac like on the Mac version.

Honestly, if the deal was that a web service would pull bank transactions in exchange for ~$10/month and also fund GNUCash development… I’d struggle to say no.

Quicken

Here’s an option that has been around forever – https://www.quicken.com/ – and one that I figured I should solidly look at. It’s actually one I even spent money on…. before requesting a refund. It’s Import/Export is so broken it’s an insult to broken software everywhere.

Did you know that Quicken doesn’t import the Quicken Interchange Format (QIF), and hasn’t since 2005?

Me, incredulously, when trying out quicken

I don’t understand why you wouldn’t support as many as possible formats that banks export your transaction data as. It cannot possibly be that hard to parse these things, nor can it possibly be code that requires a lot of maintenance.

This basically meant that I couldn’t import data from my Australian Banks. Urgh. This alone ruled it out.

It really didn’t build confidence in ever getting my data out. At every turn it seemed to be really keen on locking you into Quicken rather than having a good experience all-up.

Moneywiz

This one was new to me – https://www.wiz.money/ – and had a fancy URL and everything. I spent a bunch of time trying MoneyWiz, and I concluded that it is pretty, but buggy. I had managed to create a report where it said I’d earned $0, but you click into it, and then it gives actual numbers. Not being self consistent and getting the numbers wrong, when this is literally the only function of said app (to get the numbers right), took this out of the running.

It did sync from my US and Australian banks though, so points there.

Intuit Mint

Intuit used to own Quicken until it sold it to H.I.G. Capital in 2016 (according to Wikipedia). I have no idea if that has had an impact as to the feature set / usability of Quicken, but they now have this Cloud-only product called Mint.

The big issue I had with Mint was that there didn’t seem to be any way to get your data out of it. It seemed to exemplify vendor lock-in. This seems to have changed a bit since I was originally looking, which is good (maybe I just couldn’t find it?). But with the cloud-only approach I wasn’t hugely comfortable with having everything there. It also seemed to be lacking a few features that I was begging to find useful in other places.

It is the only product that links with the Apple Card though. No idea why that is the case.

The price tag of $0 was pretty unbeatable, which does make me wonder where the money is made from to fund its development and maintenance. My guess is that it’s through commission on the various financial products advertised through it, and I dearly hope it is not through selling data on its users (I have no reason to believe it is, there’s just the popular habit of companies doing this).

Banktivity

This is what I’ve settled on. It seemed to be easy enough for me to figure out how to use, sync with an iPhone App, be a reasonable price, and be able to import and sync things from accounts that I have. Oddly enough, nothing can connect and pull things from the Apple Card – which is really weird. That isn’t a Banktivity thing though, that’s just universal (except for Intuit’s Mint).

I’ve been using it for a bit more than a year now, and am still pretty happy. I wish there was the ability to attach a PDF of a statement to the Statement that you reconcile. I wish I could better tune the auto match/classification rules, and a few other relatively minor things.

,

Stewart Smith — Fitness watches and my descent into madness

Periodically in life I’ve had the desire to be somewhat fit, or at least have the benefits that come with that such as not dying early and being able to navigate a mountain (or just the city of Seattle) on foot without collapsing. I have also found that holding myself accountable via data is pretty vital to me actually going and repeatedly doing something.

So, at some point I got myself a Garmin watch. The year was 2012 and it was a Garmin Forerunner 410. It had a standard black/grey LCD screen, GPS (where getting a GPS lock could be utterly infuriatingly slow), a sensor you attached to your foot, a sensor you strap to your chest for Heart Rate monitoring, and an ANT+ dongle for connecting to a PC to download your activities. There was even some open source software that someone wrote so I could actually get data off my watch on my Linux laptops. This wasn’t a smart watch – it was exclusively for wearing while exercising and tracking an activity, otherwise it was just a watch.

However, as I was ramping up to marathon distance running, one huge flaw emerged: I was not fast enough to run a marathon in the time that the battery in my Garmin lasted. IIRC it would end up dying around 3hr30min into something, which at the time was increasingly something I’d describe as “not going for too long of a run”. So, the search for a replacement began!

The year was 2017, and the Garmin fenix 5x attracted me for two big reasons: a battery life to be respected, and turn-by-turn navigation. At the time, I seldom went running with a phone, preferring a tiny SanDisk media play (RIP, they made a new version that completely sucked) and a watch. The attraction of being able to get better maps back to where I started (e.g. a hotel in some strange city where I didn’t speak the language) was very appealing. It also had (what I would now describe as) rudimentary smart-watch features. It didn’t have even remotely everything the Pebble had, but it was enough.

So, a (non-trivial) pile of money later (even with discounts), I had myself a shiny and virtually indestructible new Garmin. I didn’t even need a dongle to sync it anywhere – it could just upload via its own WiFi connection, or through Bluetooth to the Garmin Connect app to my phone. I could also (if I ever remembered to), plug in the USB cable to it and download the activities to my computer.

One problem: my skin rebelled against the Garmin fenix 5x after a while. Like, properly rebelled. If it wasn’t coming off, I wanted to rip it off. I tried all of the tricks that are posted anywhere online. Didn’t help. I even got tested for what was the most likely culprit (a Nickel allergy), and didn’t have one of them, so I (still) have no idea what I’m actually allergic to in it. It’s just that I cannot wear it constantly. Urgh. I was enjoying the daily smart watch uses too!

So, that’s one rather expensive watch that is special purpose only, and even then started to get to be a bit of an issue around longer activities. Urgh.

So the hunt began for a smart watch that I could wear constantly. This usually ends in frustration as anything I wanted was hundreds of $ and pretty much nobody listed what materials were in it apart from “stainless steel”, “may contain”, and some disclaimer about “other materials”, which wasn’t a particularly useful starting point for “it is one of these things that my skin doesn’t like”. As at least if the next one also turned out to cause me problems, I could at least have a list of things that I could then narrow down to what I needed to avoid.

So that was all annoying, with the end result being that I went a long time without really wearing a watch. Why? The search resumed periodically and ended up either with nothing, or totally nothing. That was except if I wanted to get further into some vendor lock-in.

Honestly, the only manufacturer of anything smartwatch like which actually listed everything and had some options was Apple. Bizarre. Well, since I already got on the iPhone bandwagon, this was possible. Rather annoyingly, they are very tied together and thus it makes it a bit of a vendor-lock-in if you alternate phone and watch replacement and at any point wish to switch platforms.

That being said though, it does work well and not irritate my skin. So that’s a bonus! If I get back into marathon level distance running, we’ll see how well it goes. But for more common distances that I’ve run or cycled with it… the accuracy seems decent, HR monitor never just sometimes decides I’m not exerting myself, and the GPS actually gets a lock in reasonable time. Plus it can pair with headphones and be the only thing I take out with me.

,

Stewart Smith — Random useful macOS things for Linux developers

A few random notes about things that can make life on macOS (the modern one, as in, circa 2023) better for those coming from Linux.

For various reasons you may end up with Mac hardware with macOS on the metal rather than Linux. This could be anything from battery life of the Apple Silicon machines (and not quite being ready to jump on the Asahi Linux bandwagon), to being able to run the corporate suite of Enterprise Software (arguably a bug more than a feature), to some other reason that is also fine.

My approach to most of my development is to have a remote more powerful Linux machine to do the heavy lifting, or do Linux development on Linux, and not bank on messing around with a bunch of software on macOS that would approximate something on Linux. This also means I can move my GUI environment (the Mac) easily forward without worrying about whatever weird workarounds I needed to do in order to get things going for whatever development work I’m doing, and vice-versa.

Terminal emulator? iTerm2. The built in Terminal.app is fine, but there’s more than a few nice things in iTerm2, including tmux integration which can end up making it feel a lot more like a regular Linux machine. I should probably go read the tmux integration best practices before I complain about some random bugs I think I’ve hit, so let’s pretend I did that and everything is perfect.

I tend to use the Mac for SSHing to bigger Linux machines for most of my work. At work, that’s mostly to a Graviton 2 EC2 Instance running Amazon Linux with all my development environments on it. At home, it’s mostly a Raptor Blackbird POWER9 system running Fedora.

Running Linux locally? For all the use cases of containers, Podman Desktop or finch. There’s a GUI part of Podman which is nice, and finch I know about because of the relatively nearby team that works on it, and its relationship to lima. Lima positions itself as WSL2-like but for Mac. There’s UTM for a full virtual machine / qemu environment, although I rarely end up using this and am more commonly using a container or just SSHing to a bigger Linux box.

There’s XCode for any macOS development that may be needed (e.g. when you want that extra feature in UTM or something) I do use Homebrew to install a few things locally.

Have a read of Andrew‘s blog post on OpenBMC Development on an Apple M1 MacBook Pro too.

,

Tim Serong — The wrong way to debug CrashLoopBackOff

Last week I had occasion to test deploying ceph-csi on a k3s cluster, so that Kubernetes workloads could access block storage provided by an external Ceph cluster. I went with the upstream Ceph documentation, because assuming everything worked it’d then be really easy for me to say to others “just go do this”.

Everything did not work.

I’d gone through all the instructions, inserting my own Ceph cluster’s FSID and MON IP addresses in the right places, applied the YAML to deploy the provisioner and node plugins, and all the provisioner bits were running just fine, but the csi-rbdplugin pods were stuck in CrashLoopBackOff:

> kubectl get pods
NAME                                        READY   STATUS             RESTARTS          AGE
csi-rbdplugin-22zjr                         1/3     CrashLoopBackOff   107 (3m55s ago)   2d
csi-rbdplugin-pbtc2                         1/3     CrashLoopBackOff   104 (3m33s ago)   2d
csi-rbdplugin-provisioner-9dcfd56d7-c8s72   7/7     Running            28 (35m ago)      8d
csi-rbdplugin-provisioner-9dcfd56d7-hcztz   7/7     Running            28 (35m ago)      8d
csi-rbdplugin-provisioner-9dcfd56d7-w2ctc   7/7     Running            28 (35m ago)      8d
csi-rbdplugin-r2rzr                         1/3     CrashLoopBackOff   106 (3m39s ago)   2d

The csi-rbdplugin pod consists of three containers – driver-registrar, csi-rbdplugin, liveness-prometheus – and csi-rbdplugin wasn’t able to load the rbd kernel module:

> kubectl logs csi-rbdplugin-22zjr --container csi-rbdplugin
I0726 10:25:12.862125    7628 cephcsi.go:199] Driver version: canary and Git version: d432421a88238a878a470d54cbf2c50f2e61cdda
I0726 10:25:12.862452    7628 cephcsi.go:231] Starting driver type: rbd with name: rbd.csi.ceph.com
I0726 10:25:12.865907    7628 mount_linux.go:284] Detected umount with safe 'not mounted' behavior
E0726 10:25:12.872477    7628 rbd_util.go:303] modprobe failed (an error (exit status 1) occurred while running modprobe args: [rbd]): "modprobe: ERROR: could not insert 'rbd': Key was rejected by service\n"
F0726 10:25:12.872702    7628 driver.go:150] an error (exit status 1) occurred while running modprobe args: [rbd] 

Matching “modprobe: ERROR: could not insert ‘rbd’: Key was rejected by service” in the above was an error on each host’s console: “Loading of unsigned module is rejected”. These hosts all have secure boot enabled, so I figured it had to be something to do with that. So I logged into one of the hosts and ran modprobe rbd as root, but that worked just fine. No key errors, no unsigned module errors. And once I’d run modprobe rbd (and later modprobe nbd) on the host, the csi-rbdplugin container restarted and worked just fine.

So why wouldn’t modprobe work inside the container? /lib/modules from the host is mounted inside the container, the container has the right extra privileges… Clearly I needed to run a shell in the failing container to poke around inside when it was in CrashLoopBackOff state, but I realised I had no idea how to do that. I knew I could kubectl exec -it csi-rbdplugin-22zjr --container csi-rbdplugin -- /bin/bash but of course that only works if the container is actually running. My container wouldn’t even start because of that modprobe error.

Having previously spent a reasonable amount of time with podman, which has podman run, I wondered if there were a kubectl run that would let me start a new container using the upstream cephcsi image, but running a shell, instead of its default command. Happily, there is a kubectl run, so I tried it:

> kubectl run -it cephcsi --image=quay.io/cephcsi/cephcsi:canary --rm=true --command=true -- /bin/bash
If you don't see a command prompt, try pressing enter.
[root@cephcsi /]# modprobe rbd
modprobe: FATAL: Module rbd not found in directory /lib/modules/5.14.21-150400.24.66-default
[root@cephcsi /]# ls /lib/modules/
[root@cephcsi /]#  

Ohhh, right, of course, that doesn’t have the host’s /lib/modules mounted. podman run lets me add volume mounts using -v options , so surely kubectl run will let me do that too.

At this point in the story, the notes I wrote last week include an awful lot of swearing.

See, kubectl run doesn’t have a -v option to add mounts, but what it does have is an --overrides option to let you add a chunk of JSON to override the generated pod. So I went back to the relevant YAML and teased out the bits I needed to come up with this monstrosity:

> kubectl run -it cephcsi-test \
  --image=quay.io/cephcsi/cephcsi:canary --rm=true \
  --overrides='{
    "apiVersion": "v1",
    "spec": {
      "containers": [ {
        "name": "cephcsi",
        "command": ["/bin/bash"],
        "stdin": true, "tty": true,
        "image": "quay.io/cephcsi/cephcsi:canary",
        "volumeMounts": [ {
          "mountPath": "/lib/modules", "name": "lib-modules" }],
        "securityContext": {
          "allowPrivilegeEscalation": true,
          "capabilities": { "add": [ "SYS_ADMIN" ] },
          "privileged": true }
      } ],
      "volumes": [ {
        "name": "lib-modules",
        "hostPath": { "path": "/lib/modules", "type": "" }
      } ]
    } }'

But at least I could get a shell and reproduce the problem:

> kubectl run -it cephcsi-test [honking great horrible chunk of JSON]
[root@cephcsi-test /]# ls /lib/modules/
5.14.21-150400.24.66-default
[root@cephcsi-test /]# modprobe rbd
modprobe: ERROR: could not insert 'rbd': Key was rejected by service

A certain amount more screwing around looking at the source for modprobe and bits of the kernel confirmed that the kernel really didn’t think the module was signed for some reason (mod_verify_sig() was returning -ENODATA), but I knew these modules were fine, because I could load them on the host. Eventually I hit on this:

[root@cephcsi-test /]# ls /lib/modules/*/kernel/drivers/block/rbd*
/lib/modules/5.14.21-150400.24.66-default/kernel/drivers/block/rbd.ko.zst

Wait, what’s that .zst extension? It turns out we (SUSE) have been shipping zstd-compressed kernel modules since – as best as I can tell – some time in 2021. modprobe on my SLE Micro 5.3 host of course supports this:

# grep PRETTY /etc/os-release
PRETTY_NAME="SUSE Linux Enterprise Micro for Rancher 5.3"
# modprobe --version
kmod version 29
+ZSTD +XZ +ZLIB +LIBCRYPTO -EXPERIMENTAL

modprobe in the CentOS Stream 8 upstream cephcsi container does not:

[root@cephcsi-test /]# grep PRETTY /etc/os-release 
PRETTY_NAME="CentOS Stream 8"
[root@cephcsi-test /]# modprobe --version
kmod version 25
+XZ +ZLIB +OPENSSL -EXPERIMENTAL

Mystery solved, but I have to say the error messages presented were spectacularly misleading. I later tried with secure boot disabled, and got something marginally better – in that case modprobe failed with “modprobe: ERROR: could not insert ‘rbd’: Exec format error”, and dmesg on the host gave me “Invalid ELF header magic: != \x7fELF”. If I’d seen messaging like that in the first place I might have been quicker to twig to the compression thing.

Anyway, the point of this post wasn’t to rant about inscrutable kernel errors, it was to rant about how there’s no way anyone could be reasonably expected to figure out how to do that --overrides thing with the JSON to debug a container stuck in CrashLoopBackOff. Assuming I couldn’t possibly be the first person to need to debug containers in this state, I told my story to some colleagues, a couple of whom said (approximately) “Oh, I edit the pod YAML and change the container’s command to tail -f /dev/null or sleep 1d. Then it starts up just fine and I can kubectl exec into it and mess around”. Those things totally work, and I wish I’d thought to do that myself. The best answer I got though was to use kubectl debug to make a copy of the existing pod but with the command changed. I didn’t even know kubectl debug existed, which I guess is my reward for not reading the entire manual 😉

So, finally, here’s the right way to do what I was trying to do:

> kubectl debug csi-rbdplugin-22zjr -it \
    --copy-to=csi-debug --container=csi-rbdplugin -- /bin/bash
[root@... /]# modprobe rbd
modprobe: ERROR: could not insert 'rbd': Key was rejected by service

(...do whatever other messing around you need to do, then...)

[root@... /]# exit
Session ended, resume using 'kubectl attach csi-debug -c csi-rbdplugin -i -t' command when the pod is running
> kubectl delete pod csi-debug
pod "csi-debug" deleted 

In the above kubectl debug invocation, csi-rbdplugin-22zjr is the existing pod that’s stuck in CrashLoopBackOff, csi-debug is the name of the new pod being created, and csi-rbdplugin is the container in that pod that has its command replaced with /bin/bash, so you can mess around inside it.

,

FLOSS Down Under - online free software meetings — July 2023 Meeting

Meeting Report

The July 2023 meeting sparked multiple new topics including Linux security architecture, Debian ports of LoongArch and Risc-V as well as hardware design of PinePhone backplates.

On the practical side, Russell Coker demonstrated running different applications in isolated environment with bubblewrap sandbox, as well as other hardening techniques and the way they interact with the host system. Russell also discussed some possible pathways of hardening desktop Linux to reach the security level of modern Android. Yifei Zhan demonstrated sending and receiving messages with the PineDio USB LoRa adapter and how to inspect LoRa signal with off-the-shelf software defined radio receiver, and discussed how the driver situation for LoRa on Linux might be improved. Yifei then gave a demonstration on utilizing KVM on PinePhone Pro to run NetBSD and OpenBSD virtual machines, more details on running VMs on the PinePhone Pro can be found on this blog post from Yifei.

We also had some discussion of the current state of Mobian and Debian ecosystem, along with how to contribute to different parts of Mobian with a Mobian developer who joined us.

,

Stewart Smith — Getting your photos out of Shotwell

Somewhat a while ago now, I wrote about how every time I return to write some software for the Mac, the preferred language has changed. The purpose of this adventure was to get my photos out of the aging Shotwell and onto my (then new) Mac and the Apple Photos App.

I’ve had a pretty varied experience with photo management on Linux over the past couple of decades. For a while I used f-spot as it was the new hotness. At some point this became…. slow and crashy enough that it was unusable. Today, it appears that the GitHub project warns that current bugs include “Not starting”.

At some point (and via a method I have long since forgotten), I did manage to finally get my photos over to Shotwell, which was the new hotness at the time. That data migration was so long ago now I actually forget what features I was missing from f-spot that I was grumbling about. I remember the import being annoying though. At some point in time Shotwell was no longer was the new hotness and now there is GNOME Photos. I remember looking at GNOME Photos, and seeing no method of importing photos from Shotwell, so put it aside. Hopefully that situation has improved somewhere.

At some point Shotwell was becoming rather stagnated, and I noticed more things stopping to work rather than getting added features and performance. The good news is that there has been some more development activity on Shotwell, so hopefully my issues with it end up being resolved.

One recommendation for Linux photo management was digiKam, and one that I never ended up using full time. One of the reasons behind that was that I couldn’t really see any non manual way to import photos from Shotwell into it.

With tens of thousands of photos (~58k at the time of writing), doing things manually didn’t seem like much fun at all.

As I postponed my decision, I ended up moving my main machine over to a Mac for a variety of random reasons, and one quite motivating thing was the ability to have Photos from my iPhone magically sync over to my photo library without having to plug it into my computer and copy things across.

So…. how to get photos across from Shotwell on Linux to Photos on a Mac/iPhone (and also keep a very keen eye on how to do it the other way around, because, well, vendor lock-in isn’t great).

It would be kind of neat if I could just run Shotwell on the Mac and have some kind of import button, but seeing as there wasn’t already a native Mac port, and that Shotwell is written in Vala rather than something I know has a working toolchain on macOS…. this seemed like more work than I’d really like to take on.

Luckily, I remembered that Shotwell’s database is actually just a SQLite database pointing to all the files on disk. So, if I could work out how to read it accurately, and how to import all the relevant metadata (such as what Albums a photo is in, tags, title, and description) into Apple Photos, I’d be able to make it work.

So… is there any useful documentation as to how the database is structured?

Semi annoyingly, Shotwell is written in Vala, a rather niche programming language that while integrating with all the GObject stuff that GNOME uses, is largely unheard of. Luckily, the database code in Shotwell isn’t too hard to read, so was a useful fallback for when the documentation proves inadequate.

So, I armed myself with the following resources:

Programming the Mac side of things, it was a good excuse to start looking at Swift, so knowing I’d also need to read a SQLite database directly (rather than use any higher level abstraction), I armed myself with the following resources:

From here, I could work on getting the first half going, the ability to view my Shotwell database on the Mac (which is what I posted a screenshot of back in Feb 2022).

But also, I had to work out what I was doing on the other end of things, how would I import photos? It turns out there’s an API!

A bit of SwiftUI code:

import SwiftUI
import AppKit
import Photos

struct ContentView: View {
    @State var favorite_checked : Bool = false
    @State var hidden_checked : Bool = false
    var body: some View {
        VStack() {
            Text("Select a photo for import")
            Toggle("Favorite", isOn: $favorite_checked)
            Toggle("Hidden", isOn: $hidden_checked)
            Button("Import Photo")
            {
                let panel = NSOpenPanel()
                panel.allowsMultipleSelection = false
                panel.canChooseDirectories = false
                if panel.runModal() == .OK {
                    let photo_url = panel.url!
                    print("selected: " + String(photo_url.absoluteString))
                    addAsset(url: photo_url, isFavorite: favorite_checked, isHidden: hidden_checked)
                }
            }
            .padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Combined with a bit of code to do the import (which does look a bunch like the examples in the docs):

import SwiftUI
import Photos
import AppKit

@main
struct SinglePhotoImporterApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

func addAsset(url: URL, isFavorite: Bool, isHidden: Bool) {
    // Add the asset to the photo library.
    let path = "/Users/stewart/Pictures/1970/01/01/1415446258647.jpg"
    let url = URL(fileURLWithPath: path)
    PHPhotoLibrary.shared().performChanges({
        let addedImage = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: url)
        addedImage?.isHidden = isHidden
        addedImage?.isFavorite = isFavorite
    }, completionHandler: {success, error in
        if !success { print("Error creating the asset: \(String(describing: error))") } else
        {
            print("Imported!")
        }
    })
}

This all meant I could import a single photo. However, there were some limitations.

There’s the PHAssetCollectionChangeRequest to do things to Albums, so it would solve that problem, but I couldn’t for the life of me work out how to add/edit Titles and Descriptions.

It was so close!

So what did I need to do in order to import Titles and Descriptions? It turns out you can do that via AppleScript. Yes, that thing that launched in 1993 and has somehow survived the transition of m68k based Macs to PowerPC based Macs to Intel based Macs to ARM based Macs.

The Photos dictionary for AppleScript

So, just to make it easier to debug what was going on, I started adding code to my ShotwellImporter tool that would generate snippets of AppleScript I could run and check that it was doing the right thing…. but then very quickly ran into a problem…. it appears that the AppleScript language interpreter on modern macOS has limits that you’d be more familiar with in 1993 than 2023, and I very quickly hit limits where the script would just error out before running (I was out of dictionary size allegedly).

But there’s a new option! Everything you can do with AppleScript you can now do with JavaScript – it’s just even less documented than AppleScript is! But it does work! I got to the point where I could generate JavaScript that imported photos, into all the relevant albums, and set title and descriptions.

A useful write up of using JavaScript rather than AppleScript to do things with Photos: https://mudge.name/2019/11/13/scripting-photos-for-macos-with-javascript/

More recent than when I was doing my hacking, https://alexwlchan.net/2023/managing-albums-in-photos/ is a good read.

With luck I’ll find some time to write up a bit of a walkthrough of my code, and push it up somewhere.

,

Tim Serong — Longhorn in a Sandbox

In my last post, I wrote about how I taught sesdev (originally a tool for deploying Ceph clusters on virtual machines) to deploy k3s, because I wanted a little sandbox in which I could break learn more about Kubernetes. It’s nice to be able to do a toy deployment locally, on a bunch of VMs, on my own hardware, in my home office, rather than paying to do it on someone else’s computer. Given the k3s thing worked, I figured the next step was to teach sesdev how to deploy Longhorn so I could break that learn more about that too.

Teaching sesdev to deploy Longhorn meant asking it to:

  • Install nfs-client, open-iscsi and e2fsprogs packages on all nodes.
  • Make an ext4 filesystem on /dev/vdb on all the nodes that have extra disks, then mount that on /var/lib/longhorn.
  • Use kubectl label node -l 'node-role.kubernetes.io/master!=true' node.longhorn.io/create-default-disk=true to ensure Longhorn does its storage thing only on the nodes that aren’t the k3s master.
  • Install Longhorn with Helm, because that will install the latest version by default vs. using kubectl where you always explicitly need to specify the version.
  • Create an ingress so the UI is exposed… from all nodes, via HTTP, with no authentication. Remember: this is a sandbox – please don’t do this sort of thing in production!

So, now I can do this:

> sesdev create k3s --deploy-longhorn
=== Creating deployment "k3s-longhorn" with the following configuration === 

Deployment-wide parameters (applicable to all VMs in deployment):

- deployment ID:    k3s-longhorn
- number of VMs:    5
- version:          k3s
- OS:               tumbleweed
- public network:   10.20.78.0/24 

Proceed with deployment (y=yes, n=no, d=show details) ? [y]: y

=== Running shell command ===
vagrant up --no-destroy-on-error --provision
Bringing machine 'master' up with 'libvirt' provider…
Bringing machine 'node1' up with 'libvirt' provider…
Bringing machine 'node2' up with 'libvirt' provider…
Bringing machine 'node3' up with 'libvirt' provider…
Bringing machine 'node4' up with 'libvirt' provider…

[... lots more log noise here - this takes several minutes... ]

=== Deployment Finished ===

You can login into the cluster with:

  $ sesdev ssh k3s-longhorn

Longhorn will now be deploying, which may take some time.
After logging into the cluster, try these:

  # kubectl get pods -n longhorn-system --watch
  # kubectl get pods -n longhorn-system

The Longhorn UI will be accessible via any cluster IP address
(see the kubectl -n longhorn-system get ingress output above).
Note that no authentication is required.

…and, after another minute or two, I can access the Longhorn UI and try creating some volumes. There’s a brief period while the UI pod is still starting where it just says “404 page not found”, and later after the UI is up, there’s still other pods coming online, so on the Volume screen in the Longhorn UI an error appears: “failed to get the parameters: failed to get target node ID: cannot find a node that is ready and has the default engine image longhornio/longhorn-engine:v1.4.1 deployed“. Rest assured this goes away in due course (it’s not impossible I’m suffering here from rural Tasmanian internet lag pulling container images). Anyway, with my five nodes – four of which have an 8GB virtual disk for use by Longhorn – I end up with a bit less than 22GB storage available:

21.5 GiB isn’t much, but remember this is a toy deployment running in VMs on my desktop Linux box

Now for the fun part. Longhorn is a distributed storage solution, so I thought it would be interesting to see how it handled a couple of types of failure. The following tests are somewhat arbitrary (I’m really just kicking the tyres randomly at this stage) but Longhorn did, I think, behave pretty well given what I did to it.

Volumes in Longhorn consist of replicas stored as sparse files on a regular filesystem on each storage node. The Longhorn documentation recommends using a dedicated disk rather than just having /var/lib/longhorn backed by the root filesystem, so that’s what sesdev does: /var/lib/longhorn is an ext4 filesystem mounted on /dev/vdb. Now, what happens to Longhorn if that underlying block device suffers some kind of horrible failure? To test that, I used the Longhorn UI to create a 2GB volume, then attached that to the master node:

The Longhorn UI helpfully tells me the volume replicas are on node3, node4 and node1

Then, I ssh’d to the master node and with my 2GB Longhorn volume attached, made a filesystem on it and created a little file:

> sesdev ssh k3s-longhorn
Have a lot of fun...

master:~ # cat /proc/partitions 
major minor  #blocks  name 
 253        0   44040192 vda
 253        1       2048 vda1
 253        2      20480 vda2
 253        3   44016623 vda3
   8        0    2097152 sda

master:~ # mkfs /dev/sda
mke2fs 1.46.5 (30-Dec-2021)
Discarding device blocks: done                            
Creating filesystem with 524288 4k blocks and 131072 inodes
Filesystem UUID: 3709b21c-b9a2-41c1-a6dd-e449bdeb275b
Superblock backups stored on blocks: 
        32768, 98304, 163840, 229376, 294912
Allocating group tables: done                            
Writing inode tables: done                            
Writing superblocks and filesystem accounting information: done 

master:~ # mount /dev/sda /mnt
master:~ # echo foo > /mnt/foo
master:~ # cat /mnt/foo
foo

Then I went and trashed the block device backing one of the replicas:

> sesdev ssh k3s-longhorn node3
Have a lot of fun...

node3:~ # ls /var/lib/longhorn
engine-binaries  longhorn-disk.cfg  lost+found  replicas  unix-domain-socket

node3:~ # dd if=/dev/urandom of=/dev/vdb bs=1M count=100
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0.486205 s, 216 MB/s

node3:~ # ls /var/lib/longhorn

node3:~ # dmesg|tail -n1
[ 6544.197183] EXT4-fs error (device vdb): ext4_map_blocks:607: inode #393220: block 1607168: comm longhorn: lblock 0 mapped to illegal pblock 1607168 (length 1) 

At this point, the Longhorn UI still showed the volume as green (healthy, ready, scheduled). Then, back on the master node, I tried creating another file:

master:~ # echo bar > /mnt/bar
master:~ # cat /mnt/bar
bar

That’s fine so far, but suddenly the Longhorn UI noticed that something very bad had happened:

The volume is still usable, but one of the replicas has failed

Ultimately node3 was rebooted and ended up stalled with the console requesting the root password for maintenance:

Failed to mount /var/lib/longhorn – Can’t find ext4 filesystem

Meanwhile, Longhorn went and rebuilt a third replica on node2:

All green again!

…and the volume remained usable the entire time:

master:~ # echo baz > /mnt/baz
master:~ # ls /mnt
bar  baz  foo  lost+found

That’s perfect!

Looking at the Node screen we could see that node3 was still down:

There may be disk size errors with down nodes (4.87 TiB looks a lot like integer overflow to me)

That’s OK, I was able to fix node3. I logged in on the console and ran mkfs.ext4 /dev/vdb then brought the node back up again.The disk remained unschedulable, because Longhorn was still expecting the ‘old’ disk to be there (I assume based on the UUID stored in /var/lib/longhorn/longhorn-disk.cfg) and of course the ‘new’ disk is empty. So I used the Longhorn UI to disable scheduling for that ‘old’ disk, then deleted it. Shortly after, Longhorn recognised the ‘new’ disk mounted at /var/lib/longhorn and everything was back to green across the board.

So Longhorn recovered well from the backing store of one replica going bad. Next I thought I’d try to break it from the other end by running a volume out of space. What follows is possibly not a fair test, because what I did was create a single Longhorn volume larger than the underlying disks, then filled that up. In normal usage, I assume one would ensure there’s plenty of backing storage available to service multiple volumes, that individual volumes wouldn’t generally be expected to get more than a certain percentage full, and that some sort of monitoring and/or alerting would be in place to warn of disk pressure.

With four nodes, each with a single 8GB disk, and Longhorn apparently reserving 2.33GB by default on each disk, that means no Longhorn volume can physically store more than a bit over 5.5GB of data (see the Size column in the previous screenshot). Given that the default setting for Storage Over Provisioning Percentage is 200, we’re actually allowed to allocate up to a bit under 11GB.

So I went and created a 10GB volume, attached that to the master node, created a filesystem on it, and wrote a whole lot of zeros to it:

master:~ # mkfs.ext4 /dev/sda
mke2fs 1.46.5 (30-Dec-2021)
[...]

master:~ # mount /dev/sda /mnt
master:~ # df -h /mnt
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda        9.8G   24K  9.3G   1% /mnt

master:~ # dd if=/dev/zero of=/mnt/big-lot-of-zeros bs=1M status=progress
2357198848 bytes (2.4 GB, 2.2 GiB) copied, 107 s, 22.0 MB/s

While that dd was running, I was able to see the used space of the replicas increasing in the Longhorn UI:

Those little green bars eventually turn yellow as the disks approach full

After a few more minutes, the dd stalled…

master:~ # dd if=/dev/zero of=/mnt/big-lot-of-zeros bs=1M status=progress
9039773696 bytes (9.0 GB, 8.4 GiB) copied, 478 s, 18.9 MB/s

…there was a lot of unpleasantness on the master node’s console…

So many I/O errors!

…the replicas became unschedulable due to lack of space…

This doesn’t look good

…and finally the volume faulted:

This really doesn’t look good

Now what?

It turns out that Longhorn will actually recover if we’re able to somehow expand the disks that store the replicas. This is probably a good argument for backing Longhorn with an LVM volume on each node in real world deployments, because then you could just add another disk and extend the volume onto it. In my case though, given it’s all VMs and virtual block devices, I can actually just enlarge those devices. For each node then, I:

  1. Shut it down
  2. Ran qemu-img resize /var/lib/libvirt/images/k3s-longhorn_$NODE-vdb.qcow2 +8G
  3. Started it back up again and ran resize2fs /dev/vdb to take advantage of the extra disk space.

After doing that to node1, Longhorn realised there was enough space there and brought node1’s replica of my 10GB volume back online. It also summarily discarded the other two replicas from the still-full disks on node2 and node3, which didn’t yet have enough free space to be useful:

One usable replica is better than three unusable replicas

As I repeated the virtual disk expansion on the other nodes, Longhorn happily went off and recreated the missing replicas:

Finally I could re-attach the volume to the master node, and have a look to see how many of my zeros were actually written to the volume:

master:~ # cat /proc/partitions 
major minor  #blocks  name
 254        0   44040192 vda
 254        1       2048 vda1
 254        2      20480 vda2
 254        3   44016623 vda3
   8        0   10485760 sda

master:~ # mount /dev/sda /mnt
master:~ # ls -l /mnt
total 7839764
-rw-r--r-- 1 root root 8027897856 May  3 04:41 big-lot-of-zeros
drwx------ 2 root root      16384 May  3 04:34 lost+found

Recall that dd claimed to have written 9039773696 bytes before it stalled when the volume faulted, so I guess that last gigabyte of zeros is lost in the aether. But, recall also that this isn’t really a fair test – one overprovisioned volume deliberately being quickly and deliberately filled to breaking point vs. a production deployment with (presumably) multiple volumes that don’t fill quite so fast, and where one is hopefully paying at least a little bit of attention to disk pressure as time goes by.

It’s worth noting that in a situation where there are multiple Longhorn volumes, assuming one disk or LVM volume per node, the replicas will all share the same underlying disks, and once those disks are full it seems all the Longhorn volumes backed by them will fault. Given multiple Longhorn volumes, one solution – rather than expanding the underlying disks – is simply to delete a volume or two if you can stand to lose the data, or maybe delete some snapshots (I didn’t try the latter yet). Once there’s enough free space, the remaining volumes will come back online. If you’re really worried about this failure mode, you could always just disable overprovisioning in the first place – whether this makes sense or not will really depend on your workloads and their data usage patterns.

All in all, like I said earlier, I think Longhorn behaved pretty well given what I did to it. Some more information in the event log could perhaps be beneficial though. In the UI I can see warnings from longhorn-node-controller e.g. “the disk default-disk-1cdbc4e904539d26(/var/lib/longhorn/) on the node node1 has 3879731200 available, but requires reserved 2505089433, minimal 25% to schedule more replicas” and warnings from longhorn-engine-controller e.g. “Detected replica overprovisioned-r-73d18ad6 (10.42.3.19:10000) in error“, but I couldn’t find anything really obvious like “Dude, your disks are totally full!”

Later, I found more detail in the engine manager logs after generating a support bundle ([…] level=error msg=”I/O error” error=”tcp://10.42.4.34:10000: write /host/var/lib/longhorn/replicas/overprovisioned-c3b9b547/volume-head-003.img: no space left on device”) so the error information is available – maybe it’s just a matter of learning where to look for it.

,

Tim Serong — Teaching an odd dog new tricks

We – that is to say the storage team at SUSE – have a tool we’ve been using for the past few years to help with development and testing of Ceph on SUSE Linux. It’s called sesdev because it was created largely for SES (SUSE Enterprise Storage) development. It’s essentially a wrapper around vagrant and libvirt that will spin up clusters of VMs running openSUSE or SLES, then deploy Ceph on them. You would never use such clusters in production, but it’s really nice to be able to easily spin up a cluster for testing purposes that behaves something like a real cluster would, then throw it away when you’re done.

I’ve recently been trying to spend more time playing with Kubernetes, which means I wanted to be able to spin up clusters of VMs running openSUSE or SLES, then deploy Kubernetes on them, then throw the clusters away when I was done, or when I broke something horribly and wanted to start over. Yes, I know there’s a bunch of other tools for doing toy Kubernetes deployments (minikube comes to mind), but given I already had sesdev and was pretty familiar with it, I thought it’d be worthwhile seeing if I could teach it to deploy k3s, a particularly lightweight version of Kubernetes. Turns out that wasn’t too difficult, so now I can do this:

> sesdev create k3s
=== Creating deployment "k3s" with the following configuration === 
Deployment-wide parameters (applicable to all VMs in deployment):
deployment ID:    k3s
number of VMs:    5
version:          k3s
OS:               tumbleweed
public network:   10.20.190.0/24 
Proceed with deployment (y=yes, n=no, d=show details) ? [y]: y
=== Running shell command ===
vagrant up --no-destroy-on-error --provision
Bringing machine 'master' up with 'libvirt' provider...
Bringing machine 'node1' up with 'libvirt' provider...
Bringing machine 'node2' up with 'libvirt' provider...
Bringing machine 'node3' up with 'libvirt' provider...
Bringing machine 'node4' up with 'libvirt' provider...

[...
  wait a few minutes
  (there's lots more log information output here in real life)
...]

=== Deployment Finished ===
 You can login into the cluster with:
 $ sesdev ssh k3s

…and then I can do this:

> sesdev ssh k3s
Last login: Fri Mar 24 11:50:15 CET 2023 from 10.20.190.204 on ssh
Have a lot of fun…

master:~ # kubectl get nodes
NAME     STATUS   ROLES                  AGE     VERSION
master   Ready    control-plane,master   5m16s   v1.25.7+k3s1
node2    Ready                     2m17s   v1.25.7+k3s1
node1    Ready                     2m15s   v1.25.7+k3s1
node3    Ready                     2m16s   v1.25.7+k3s1
node4    Ready                     2m16s   v1.25.7+k3s1 

master:~ # kubectl get pods -A
NAMESPACE     NAME                                      READY   STATUS      RESTARTS   AGE
kube-system   local-path-provisioner-79f67d76f8-rpj4d   1/1     Running     0          5m9s
kube-system   metrics-server-5f9f776df5-rsqhb           1/1     Running     0          5m9s
kube-system   coredns-597584b69b-xh4p7                  1/1     Running     0          5m9s
kube-system   helm-install-traefik-crd-zz2ld            0/1     Completed   0          5m10s
kube-system   helm-install-traefik-ckdsr                0/1     Completed   1          5m10s
kube-system   svclb-traefik-952808e4-5txd7              2/2     Running     0          3m55s
kube-system   traefik-66c46d954f-pgnv8                  1/1     Running     0          3m55s
kube-system   svclb-traefik-952808e4-dkkp6              2/2     Running     0          2m25s
kube-system   svclb-traefik-952808e4-7wk6l              2/2     Running     0          2m13s
kube-system   svclb-traefik-952808e4-chmbx              2/2     Running     0          2m14s
kube-system   svclb-traefik-952808e4-k7hrw              2/2     Running     0          2m14s

…and then I can make a mess with kubectl apply, helm, etc.

One thing that sesdev knows how to do is deploy VMs with extra virtual disks. This functionality is there for Ceph deployments, but there’s no reason we can’t turn it on when deploying k3s:

> sesdev create k3s --num-disks=2
> sesdev ssh k3s
master:~ # for node in \
    $(kubectl get nodes -o 'jsonpath={.items[*].metadata.name}') ;
    do echo $node ; ssh $node cat /proc/partitions ; done
master
major minor  #blocks  name
 253        0   44040192 vda
 253        1       2048 vda1
 253        2      20480 vda2
 253        3   44016623 vda3
node3
major minor  #blocks  name
 253        0   44040192 vda
 253        1       2048 vda1
 253        2      20480 vda2
 253        3   44016623 vda3
 253       16    8388608 vdb
 253       32    8388608 vdc
node2
 major minor  #blocks  name
 253        0   44040192 vda
 253        1       2048 vda1
 253        2      20480 vda2
 253        3   44016623 vda3
 253       16    8388608 vdb
 253       32    8388608 vdc
node4
 major minor  #blocks  name
 253        0   44040192 vda
 253        1       2048 vda1
 253        2      20480 vda2
 253        3   44016623 vda3
 253       16    8388608 vdb
 253       32    8388608 vdc
node1
 major minor  #blocks  name
 253        0   44040192 vda
 253        1       2048 vda1
 253        2      20480 vda2
 253        3   44016623 vda3
 253       16    8388608 vdb
 253       32    8388608 vdc

As you can see this gives all the worker nodes an extra two 8GB virtual disks. I suspect this may make sesdev an interesting tool for testing other Kubernetes based storage systems such as Longhorn, but I haven’t tried that yet.

,

Robert Collins — Rustup CI / test suite performance

Rustup (the community package manage for the Rust language) was starting to really suffer : CI times were up at ~ one hour.

We’ve made some strides in bringing this down.

Caching factory for test scenarios

The first thing, which achieved about a 30% reduction in test time was to stop recreating all the test context every time.

Rustup tests the download/installation/upgrade of distributions of Rust. To avoid downloading gigabytes in the test suite, the suite creates mocks of the published Rust artifacts. These mocks are GPG signed and compressed with multiple compression methods, both of which are quite heavyweight operations to perform – and not actually the interesting code under test to execute.

Previously, every test was entirely hermetic, and usually the server state was also unmodified.

There were two cases where the state was modified. One, a small number of tests testing error conditions such as GPG signature failures. And two, quite a number of tests that were testing temporal behaviour: for instance, install nightly at time A, then with a newer server state, perform a rustup update and check a new version is downloaded and installed.

We’re partway through this migration, but compare these two tests:

fn check_updates_some() {
    check_update_setup(&|config| {
        set_current_dist_date(config, "2015-01-01");
        config.expect_ok(&["rustup", "update", "stable"]);
        config.expect_ok(&["rustup", "update", "beta"]);
        config.expect_ok(&["rustup", "update", "nightly"]);
        set_current_dist_date(config, "2015-01-02");
        config.expect_stdout_ok(
            &["rustup", "check"],
            for_host!(
                r"stable-{0} - Update available : 1.0.0 (hash-stable-1.0.0) -> 1.1.0 (hash-stable-1.1.0)
beta-{0} - Update available : 1.1.0 (hash-beta-1.1.0) -> 1.2.0 (hash-beta-1.2.0)
nightly-{0} - Update available : 1.2.0 (hash-nightly-1) -> 1.3.0 (hash-nightly-2)
"
            ),
        );
    })
}
fn check_updates_some() {
    test(&|config| {
        config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| {
            config.expect_ok(&["rustup", "toolchain", "add", "stable", "beta", "nightly"]);
        });
        config.with_scenario(Scenario::SimpleV2, &|config| {
        config.expect_stdout_ok(
            &["rustup", "check"],
            for_host!(
                r"stable-{0} - Update available : 1.0.0 (hash-stable-1.0.0) -> 1.1.0 (hash-stable-1.1.0)
beta-{0} - Update available : 1.1.0 (hash-beta-1.1.0) -> 1.2.0 (hash-beta-1.2.0)
nightly-{0} - Update available : 1.2.0 (hash-nightly-1) -> 1.3.0 (hash-nightly-2)
"
            ),
        );
            })
    })
}

The former version mutates the date with set_current_dist_date; the new version uses two scenarios, one for the earlier time, and one for the later time. This permits the server state to be constructed only once. On a per-test basis it can move as much as 50% of the time out of the test.

Single binary for the integration test suite

The next major gain was moving from having 14 separate integration test binaries to just one. This reduces the link cost of linking the test binaries, all of which link in the same library. It also permits us to see unused functions in our test support library, which helps with cleaning up cruft rather than having it accumulate.

Hard linking rather than copying ‘rustup-init’

Part of the test suite for each test is setting up an installed rustup environment. Why not start from scratch every time? Well, we obviously have tests that do that, but most tests are focused on steps beyond the new-user case. Setting up an installed rustup environment has a few steps, but particular ones are copying a binary of rustup into the test sandbox, and hard linking it under various names: cargo, rustc, rustup etc.

A debug build of rustup is ~20MB. Running 400 tests means about 8GB of IO; on some platforms most of that IO won’t hit disk, on others it will.

In review now is a PR that changes the initial copy to a hardlink: we hardlink the rustup-init built by cargo into each test, and then hardlink that to the various binaries. That saves 8GB of IO, which isn’t much from some perspectives, but it adds pressure on the page cache, and is wasted work. One wrinkle is a very low max-links limit on NTFS of 1023; to mitigate that we count the links made to rustup-init and generate a new inode for the original to avoid failures happening.

Future work

In GitHub actions this lowers our test time to 19m for Linux, 24m for Windows, which is a lot better but not great.

I plan on experimenting with separate actions for building release artifacts and doing CI tests – at the moment we have the same action do both, but they don’t share artifacts in the cache in any meaningful way, so we can probably gain parallelism there, as well as turning off release builds entirely for CI.

We should finish the cached test context work and use it everywhere.

Also we’re looking at having less integration tests and more narrow close to the code tests.

,

Colin Charles — Long Malaysians, Short Malaysia

I have long said “Long Malaysians, Short Malaysia” in conversation to many. Maybe it took me a while to tweet it, but this was the first example: Dec 29, 2021. I’ve tweeted it a lot more since.

Malaysia has a 10th Prime Minister, but in general, it is a very precarious partnership. Consider it, same shit, different day?

I just have to get off the Malaysian news diet. Malaysians elsewhere, are generally very successful. Malaysians suffering by their daily doldrums, well, they just need to wake up, see the light, and succeed.

In the end, as much as people paraphrase, ask not what the country can do for you, legitimately, this is your life, and you should be taking good care of yourself and your loved ones. You succeed, despite of. Politics and the state happens, regardless of.

Me, personally? Ideas are abound for how to get Malaysians who see the light, to succeed elsewhere. And if I read, and get angry at something (tweet rage?), I’m going to pop RM50 into an investment account, which should help me get off this poor habit. I’ll probably also just cut subscriptions to Malaysian news things… Less exposure, is actually better for you. I can’t believe that it has taken me this long to realise this.

Time to build.

Colin Charles — Hello 2023

I did poorly blogging last year. Oops. I think to myself when I read, This Thing Still On?, I really have to do better in 2023. Maybe the catalyst is the fact that Twitter is becoming a shit show. I doubt people will leave the platform in droves, per se, but I think we are coming back to the need for decentralised blogs again.

I have 477 days to becoming 40. I ditched the Hobonich Techo sometime in 2022, and just focused on the Field Notes, and this year, I’ve got a Monocle x Leuchtturm1917 + Field Notes combo (though it seems my subscription lapsed Winter 2022, I should really burn down the existing collection, and resubscribe).

2022 was pretty amazing. Lots of work. Lots of fun. 256 days on the road (what a number), 339,551km travelled, 49 cities, 20 countries.

The getting back into doing, and not being afraid of experimenting in public is what 2023 is all about. The Year of The Rabbit is upon us tomorrow, hence why I don’t mind a little later Hello 2023 :)

Get back into the habit of doing. And publishing by learning and doing. No fear. Not that I wasn’t doing, but its time to be prolific with what’s been going on.

I better remember that.

,

Andrew Ruthven — Let's Encrypt with Octavia in OpenStack

I like using Catalyst Cloud to host some of my personal sites. In the past I used to use CAcert for my TLS certificates, but more recently I've been using Let's Encrypt for my TLS certificates as they're trusted in all browsers. Currently the LoadBalancer as a Service (LBaaS) in Catalyst Cloud doesn't have built in support for Let's Encrypt. I could use an apache2/nginx proxy and handle the TLS termination there and have that manage the Let's Encrypt lifecycle, but really, I'd rather use LBaaS.

So I thought I'd set about working out how to get Dehydrated (the Let's Encrypt client I've been using) to drive LBaaS (known as Octavia). I figured this would be of interest to other people using Octavia with OpenStack in general, not just Catalyst Cloud.

There's a few things you need to do. These instructions are specific to Debian:

  1. Install and configure Dehydrated to create the certificates for the domain(s) you want.
    • apt install barbican
  2. Create the LoadBalancer (use the API, ClickOps, whatever), just forward port 80 for now (see sample Apache configs below).
  3. Save the sample hook.sh below to /etc/dehydrated/hook.sh, you'll probably need to customise it, mine is a bit more complicated!
  4. Insert the UUID of your LoadBalancer in hook.sh where LB_LISTENER is set.
  5. Create /etc/dehydrated/catalystcloud/password as described in hook.sh
  6. Save OpenRC file from the Catalyst Cloud dashboard as /etc/dehydrated/catalystcloud/openrc.sh
  7. Install jq, openssl and the openstack tools, on Debian this is:
    • apt install jq openssl python3-openstackclient python3-barbicanclient python3-octaviaclient
  8. Add TLS termination to your LoadBalancer
  9. You should be able to rename the latest certs /var/lib/dehydrated/certs/$DOMAIN and then run dehydrated -c to have it reissue and then deploy a cert.

As we're using HTTP-01 Challenge Type here, you need to have the LoadBalancer forwarding port 80 to your website to allow for the challenge response. It is good practice to have a redirect to HTTPS, here's an example virtual host for Apache:

<VirtualHost *:80>
    ServerName www.example.com
    ServerAlias example.com

    RewriteEngine On
    RewriteRule ^/.well-known/ - [L]
    RewriteRule ^/(.*)$ https://www.example.com/$1 [R=301,L]

    <Location />
        Require all granted
    </Location>
</VirtualHost>
You all also need this in /etc/apache2/conf-enabled/letsencrypt.conf:
Alias /.well-known/acme-challenge /var/lib/dehydrated/acme-challenges

<Directory /var/lib/dehydrated/acme-challenges>
        Options None
        AllowOverride None

        # Apache 2.x
        <IfModule !mod_authz_core.c>
                Order allow,deny
                Allow from all
        </IfModule>

        # Apache 2.4
        <IfModule mod_authz_core.c>
                Require all granted
        </IfModule>
</Directory>

And that should be all that you need to do. Now, when Dehydrated updates your certificate, it should update your LoadBalancer as well!

Sample hook.sh:
deploy_cert() {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" \
          CHAINFILE="${5}" TIMESTAMP="${6}"
    shift 6

    # File contents should be:
    #   export OS_PASSWORD='your password in here'
    . /etc/dehydrated/catalystcloud/password

    # OpenRC file from the Catalyst Cloud dashboard
    . /etc/dehydrated/catalystcloud/openrc.sh --no-token

    # UUID of the LoadBalancer to be managed
    LB_LISTENER='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

    # Barbican uses P12 files, we need to make one.
    P12=$(readlink -f $KEYFILE \
        | sed -E 's/privkey-([0-9]+)\.pem/barbican-\1.p12/')
    openssl pkcs12 -export -inkey $KEYFILE -in $CERTFILE -certfile \
        $FULLCHAINFILE -passout pass: -out $P12

    # Keep track of existing certs for this domain (hopefully no more than 100)
    EXISTING_URIS=$(openstack secret list --limit 100 \
        -c Name -c 'Secret href' -f json \
        | jq -r ".[]|select(.Name | startswith(\"$DOMAIN\"))|.\"Secret href\"")

    # Upload the new cert
    NOW=$(date +"%s")
    openstack secret store --name $DOMAIN-$TIMESTAMP-$NOW -e base64 \
        -t "application/octet-stream" --payload="$(base64 < $P12)"

    NEW_URI=$(openstack secret list --name $DOMAIN-$TIMESTAMP-$NOW \
        -c 'Secret href' -f value) \
        || unset NEW_URI

    # Change LoadBalancer to use new cert - if the old one was the default,
    # change the default. If the old one was in the SNI list, update the
    # SNI list.
    if [ -n "$EXISTING_URIS" ]; then
        DEFAULT_CONTAINER=$(openstack loadbalancer listener show $LB_LISTENER \
            -c default_tls_container_ref -f value)

        for URI in $EXISTING_URIS; do
            if [ "x$URI" = "x$DEFAULT_CONTAINER" ]; then
                openstack loadbalancer listener set $LB_LISTENER \
                    --default-tls-container-ref $NEW_URI
            fi
        done

        SNI_CONTAINERS=$(openstack loadbalancer listener show $LB_LISTENER \
            -c sni_container_refs -f value | sed "s/'//g" | sed 's/^\[//' \
            | sed 's/\]$//' | sed "s/,//g")

        for URI in $EXISTING_URIS; do
            if echo $SNI_CONTAINERS | grep -q $URI; then
                SNI_CONTAINERS=$(echo $SNI_CONTAINERS | sed "s,$URI,$NEW_URI,")
                openstack loadbalancer listener set $LB_LISTENER \
                    --sni-container-refs $SNI_CONTAINERS
            fi
        done

        # Remove old certs
        for URI in $EXISTING_URIS; do
            openstack secret delete $URI
        done
    fi
}

HANDLER="$1"; shift
#if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|sync_cert|deploy_cert|deploy_ocsp|unchanged_cert|invalid_challenge|request_failure|generate_csr|startup_hook|exit_hook)$ ]]; then
if [[ "${HANDLER}" =~ ^(deploy_cert)$ ]]; then
    "$HANDLER" "$@"
fi

,

Chris Smart — Making a systemd service which binds to an IP start on boot

By default, most services will start just fine on boot when they bind to 0.0.0.0 on the host to listen on any IP that might happen to appear. However, if you try to get a service to bind to a specific IP, it can fail on boot.

This is the case with memcached for example (which can bind to a specific IP with the -l, --listen option), but fails with error, Cannot assign requested address.

A yellow Ethernet cable and RJ45 connector
Photo: EthernetCableYellow2 by Raysonho @ Open Grid Scheduler / Grid Engine. CC0 1.0. Resized; thumbnails cropped.
Bash
$ sudo systemctl status memcached
? memcached.service - memcached daemon
   Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; vendor preset: disabled)
   Active: failed (Result: exit-code) since Thu 2022-10-20 00:54:34 AEDT; 18min ago
  Process: 936 ExecStart=/usr/bin/memcached -p ${PORT} -u ${USER} -m ${CACHESIZE} -c ${MAXCONN} $OPTIONS (code=exited, status=71)
 Main PID: 936 (code=exited, status=71)

Oct 20 00:54:34 swift-02 systemd[1]: Started memcached daemon.
Oct 20 00:54:34 swift-02 memcached[936]: bind(): Cannot assign requested address
Oct 20 00:54:34 swift-02 memcached[936]: failed to listen on TCP port 11211: Cannot assign requested address
Oct 20 00:54:34 swift-02 systemd[1]: memcached.service: Main process exited, code=exited, status=71/OSERR
Oct 20 00:54:34 swift-02 systemd[1]: memcached.service: Failed with result 'exit-code'.

Often services only have a dependency on network.target but this isn’t enough in this case as an IP isn’t necessarily obtained by this point. So the fix is to add a further dependency on network-online.target to start the service once the machine is actually on the network.

Adding this dependency to an existing service is pretty easy, we just need to create a systemd service override, like this one for memcached.

Bash
sudo mkdir -p /etc/systemd/system/memcached.service.d
cat << EOF | sudo tee /etc/systemd/system/memcached.service.d/memcached.service
[Unit]
After=network-online.target
EOF
sudo systemctl daemon-reload

Now when you reboot, the new dependency will be taken into account and memcached will be started once the machine is actually on the network.

,

BlueHackers — Free psychologist service at conferences: April 2022 update

We’ve done this a number of times over the last decade, from OSDC to LCA. The idea is to provide a free psychologist or counsellor at an in-person conference. Attendees can do an anonymous booking by taking a stickynote (with the timeslot) from a signup sheet, and thus get a free appointment.

Many people find it difficult taking the first (very important) step towards getting professional help, and we’ve received good feedback that this approach indeed assists.

So far we’ve always focused on open source conferences. Now we’re moving into information security! First BrisSEC 2022 (Friday 29 April at the Hilton in Brisbane, QLD) and then AusCERT 2022 (10-13 May at the Star Hotel, Gold Coast QLD). The awesome and geek friendly Dr Carla Rogers will be at both events.

How does this get funded? Well, we’ve crowdfunded some, nudged sponsors, most mostly it gets picked up by the conference organisers (aka indirectly by the sponsors, mostly).

If you’re a conference organiser, or would like a particular upcoming conference to offer this service, do drop us a line and we’re happy to chase it up for you and help the organisers to make it happen. We know how to run that now.

In-person is best. But for virtual conferences, sure contact us as well.

The post Free psychologist service at conferences: April 2022 update first appeared on BlueHackers.org.

,

FLOSS Down Under - online free software meetings — April Hack Day Report

The hack day didn’t go as well as I hoped, but didn’t go too badly. There was smaller attendance than hoped and the discussion was mostly about things other than FLOSS. But everyone who attended had fun and learned interesting things so generally I think it counts as a success. There was discussion on topics including military hardware, viruses (particularly Covid), rocketry, and literature. During the discussion one error in a Wikipedia page was discussed and hopefully we can get that fixed.

I think that everyone who attended will be interested in more such meetings. Overall I think this is a reasonable start to the Hack Day meetings, when I previously ran such meetings they often ended up being more social events than serious hacking events and that’s OK too.

One conclusion that we came to regarding meetings is that they should always be well announced in email and that the iCal file isn’t useful for everyone. Discussion continues on the best methods of announcing meetings but I anticipate that better email will get more attendance.

,

Chris Smart — Using a dynamic libvirt inventory with Ansible

The Ansible community libvirt collection provides a method to interact with QEMU and LXC (if that interests you, please come and join us!). Along with support for libvirt tasks such as managing guests, networks and storage, it also provides a dynamic inventory. This does not use SSH, but rather interacts directly with the VM over a virtual serial link using qemu-guest-agent, with commands executed as root inside the guest.

On this page
A close-up of rack-mounted Cisco servers at the Wikimedia Foundation
Photo: Wikimedia Foundation Servers-8055 15 by Victor Grigas / Wikimedia Commons. CC BY-SA 3.0. Resized; thumbnails cropped.

The dynamic inventory has a couple of requirements. It does not (yet) support SELinux in enforcing mode inside the guest; it requires the qemu-guest-agent service to be running inside the guest; the host must be able to query qemu-guest-agent successfully; and qemu-guest-agent needs to support the following capabilities:

  • guest-exec
  • guest-file-close
  • guest-file-open
  • guest-file-read
  • guest-file-write

A number of distros will blacklist these in the guest’s qemu guest agent configuration, so this might need to be removed and the service restarted. In CentOS, this is configured with the BLACKLIST_RPC option in the /etc/sysconfig/qemu-ga file, so you would need to modify that and restart the qemu-guest-agent service.

Install the collection

To get started, we first need to install the collection.

Bash
ansible-galaxy collection install community.libvirt

Create an inventory file

Next, to use it as a dynamic inventory and talk to a working libvirtd, we need to create an inventory file which provides the connection details for our hypervisor. In this example, we’re creating a file called kvm.yml which provides the uri to talk to the local daemon supervising QEMU domains.

YAML
---
plugin: community.libvirt.libvirt
uri: 'qemu:///system'

Now that we have our inventory file, we can test it!

Bash
ansible-inventory --inventory kvm.yml --list

This should return JSON formatted data, which looks like this on a hypervisor with no guests.

JSON
{
    "_meta": {
        "hostvars": {}
    },
    "all": {
        "children": [
            "ungrouped"
        ]
    }
}
Inventory with a remote host

The cool thing is that it supports standard URI connection strings, so we can also talk to remote libvirt hosts over SSH with an inventory like this.

YAML
---
plugin: community.libvirt.libvirt
uri: 'qemu+ssh://user@server/system'

Spin up VMs

OK! Spin up some test virtual machines and try listing the inventory again.

If you need a hand creating some VMs, then we can do it with my Ansible virtual infrastructure playbook, which pulls in my role and includes a sample inventory. Running this will also automatically install and start everything on localhost that we need for libvirtd.

Bash
git clone --recursive https://github.com/csmart/virt-infra-ansible.git
cd virt-infra-ansible

Let’s grab the CentOS Stream 8 images and put them in place so that we can spin up the VMs.

Bash
curl -O https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210603.0.x86_64.qcow2
sudo mkdir -p /var/lib/libvirt/images
sudo mv -iv CentOS-Stream-GenericCloud-8-20210603.0.x86_64.qcow2 /var/lib/libvirt/images/

Next, let’s create some extra args so that the Ansible role can put SELinux into permissive mode and configure qemu-guest-agent capabilities for us automatically.

Bash
cat > /tmp/ansible-extra-args.json << \EOF
{
  "virt_infra_disk_cmd": [
    "sed -i s/^BLACKLIST_RPC=/\\#BLACKLIST_RPC=/ /etc/sysconfig/qemu-ga",
    "sed -i s/^SELINUX=.*/SELINUX=permissive/ /etc/selinux/config"
  ]
}
EOF

OK! Now we can spin up two VMs, simple-centos-8-1 and example-centos-8 from the included sample inventory and test if we can see them using the dynamic inventory.

Bash
./run.sh --limit kvmhost,simple-centos-8-1,example-centos-8 --extra-vars "@/tmp/ansible-extra-args.json"

This should successfully spin up those two machines, which we can remove cleanly in a later step. If you need to SSH into them for some reason, they should be configured with a local account that matches Linux username, with a password of password and be accessible with any of the SSH keys from your home directory.

Bash
ssh example-centos-8 uptime

Test the dynamic inventory

Let’s try the dynamic inventory again and see if we can find those new VMs.

Bash
ansible-inventory --inventory kvm.yml --list

This time the returned JSON includes the VMs. Note that groups are automatically created for each host.

JSON
{
    "1c85f707-70bd-4449-9e74-0364329b2cae": {
        "hosts": [
            "simple-centos-8-1"
        ]
    },
    "73d59360-33f9-44e6-8c71-0ac2f6530c43": {
        "hosts": [
            "example-centos-8"
        ]
    },
    "_meta": {
        "hostvars": {
            "example-centos-8": {
                "ansible_connection": "community.libvirt.libvirt_qemu",
                "ansible_libvirt_uri": "qemu:///system"
            },
            "simple-centos-8-1": {
                "ansible_connection": "community.libvirt.libvirt_qemu",
                "ansible_libvirt_uri": "qemu:///system"
            }
        }
    },
    "all": {
        "children": [
            "1c85f707-70bd-4449-9e74-0364329b2cae",
            "73d59360-33f9-44e6-8c71-0ac2f6530c43",
            "ungrouped"
        ]
    }
}

Test guest agent

We can use the virsh command line tool to test that the host is able to communicate with the guest agent running in the VM, and that it supports the capabilities that we need. Let’s do that now, against one of the example VMs.

Bash
sudo virsh qemu-agent-command example-centos-8 '{"execute":"guest-info"}' --pretty

Firstly, this needs to work and secondly, it should return a list of supported commands where you can ensure the required capabilities are enabled and working.

Use the dynamic inventory with Ansible

Now that we have a working inventory and have confirmed the guest agent connection is working, we can use Ansible to dynamically generate an inventory and connect to the guests. Again, this doesn’t use SSH, but rather the gemu-guest-agent interface.

Let’s test this using the standard Ansible ping module.

Bash
ansible --inventory kvm.yml all -m ping

We should see a successful response!

Text
simple-centos-8-1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
example-centos-8 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

And of course we can run real playbooks. Here’s an example playbook called site.yml to install critical packages 😉

YAML
---
- hosts: all
  tasks:
    - name: Install packages
      package:
        name:
          - git
          - rsync
          - tmux
          - vim
        state: present
      become: true
      register: result_package_install
      retries: 10
      delay: 5
      until: result_package_install is succeeded

Let’s run it!

Bash
ansible-playbook --inventory kvm.yml site.yml

And we should see that it happily executes the playbook.

Text
PLAY [all] ******************************************************************************************

TASK [Gathering Facts] ******************************************************************************
ok: [simple-centos-8-1]
ok: [example-centos-8]

TASK [Install packages] *****************************************************************************
changed: [example-centos-8]
changed: [simple-centos-8-1]

PLAY RECAP ******************************************************************************************
example-centos-8     : ok=2  changed=1  unreachable=0  failed=0    skipped=0  rescued=0  ignored=0   
simple-centos-8-1    : ok=2  changed=1  unreachable=0  failed=0    skipped=0  rescued=0  ignored=0   
Limiting the hosts with dynamic inventory

Now that you have a working dynamic inventory, you can use the standard Ansible limit option to restrict machines for your playbooks. This way you can have the flexibility of the dynamic inventory, but restrict which guests you execute Ansible against.

Bash
ansible-playbook --inventory kvm.yml --limit example-centos-8 site.yml

Remove test VMs

Finally, we can cleanly remove those test VMs using the same Ansible code, by setting their state to undefined.

Bash
./run.sh --limit kvmhost,simple-centos-8-1,example-centos-8 -e virt_infra_state=undefined

And there you have it. Using the dynamic inventory with the libvirt plugin can be quite handy.

,

Chris Smart — Joining a bridged IRC network on Element (Matrix)

Matrix is a great modern, distributed and secure open source chat platform (I connect through Element), but sometimes you need to connect to IRC networks. Fortunately, for some common networks this is supported through bridges. There is a list of available bridges in the documentation, for example Libera is @appservice:libera.chat and OFTC is @oftc-irc:matrix.org.

Step one, open your Element app or browse to the web chat.

Start a new People direct message chat with the bridge app for the IRC network you require, OFTC for example.

Starting a direct message with the OFTC IRC bridge in Element

This should bring up a new chat with the app, appearing in your People list.

The OFTC IRC bridge listed under People in Element

Open this new direct chat, it’s where you will perform your interactions with the IRC network.

A conversation with the OFTC IRC bridge in Element

Before we join a channel, we need to tell the app our account details so that we can authenticate and reconnect successfully. This assumes that you have already connected and registered your nick to that IRC network (if you haven’t already done that, follow the instructions from that network).

By default your nick will have a [m] next to it. Let’s change your nick to the one registered on that network, for the current session:

Text
!nick csmart

Now set your default nick to one registered on the network, for future connections:

Text
!username csmart

Store your password so that you can be automatically authenticated in future sessions:

Text
!storepass mypassword

Reconnect to the network now that you have stored the password (note this doesn’t return anything to say it was successful).

Text
!reconnect

Now we can finally join an IRC channel:

Text
!join #openstack-swift

Now you should get a new chat pop up under Rooms which you can add to favourites.

The bridged openstack-swift IRC channel in Element's favourites

Run !help for help!

,

FLOSS Down Under - online free software meetings — March 2022 Meeting

Meeting Report

The March 2022 meeting went reasonably well. Everyone seemed to have fun and learn useful things about computers. After 2 hours my Internet connection dropped out which stopped the people who were using VMs from doing the tutorial. Fortunately most people seemed ready for a break so we ended the meeting. The early and abrupt ending of the meeting was a disappointment but it wasn’t too bad, the meeting would probably only have gone for another half hour otherwise.

The BigBlueButton system was shown to be effective for training when one person got confused with the Debian package configuration options for Postfix and they were able to share the window with everyone else to get advice. I was also confused by that stage.

Future Meetings

The main feature of the meeting was training in setting up a mailserver with Postfix, here are the lecture notes for it [1]. The consensus at the end of the meeting was that people wanted more of that for the April meeting. So for the April meeting I will add to the Postfix Training to include SpamAssassin, SPF, DKIM, and DMARC. For the start of the next meeting instead of providing bare Debian installations for the VMs I’ll provide a basic Postfix/Dovecot setup so people can get straight into SpamAssassin etc.

For the May meeting training on SE Linux was requested.

Social Media

Towards the end of the meeting we discussed Matrix and federated social media. LUV has a Matrix server and I can give accounts to anyone who’s involved in FOSS in the Australia and New Zealand area. For Mastodon the NZOSS Mastodon server [2] seems like a good option. I have an account there to try Mastodon, my Mastodon address is @etbe@mastodon.nzoss.nz .

We are going to make Matrix a primary communication method for the Flounder group, the room is #flounder:luv.asn.au . My Matrix address is @etbe:luv.asn.au .

,

FLOSS Down Under - online free software meetings — Mailing List

We now have a mailing list see https://lists.linux.org.au/mailman/listinfo/flounder for information, the address to post to the list is flounder@lists.linux.org.au..

We also have a new URL for the blog and events. See the right sidebar for the link to the iCal file which can be connected to Google Calendar and most online calendaring systems.

,

Stewart Smith — Adventures in the Apple Partition Map (Part 2 of the continuing adventures with the Apple Power Macintosh 7200/120 PC Compatible)

I “recently” wrote about obtaining a new (to me, actually quite old) computer over in The Apple Power Macintosh 7200/120 PC Compatible (Part 1). This post is a bit of a detour, but may help others understand why some images they download from the internet don’t work.

Disk partitioning is (of course) a way to divide up a single disk into multiple volumes (partitions) for different uses. While the idea is similar, computer platforms over the ages have done this in a variety of different ways, with varying formats on disk, and varying limitations. The ones that you’re most likely to be familiar with are the MBR partitioning scheme (from the IBM PC), and the GPT partitioning scheme (common for UEFI systems such as the modern PC and Mac). One you’re less likely to be familiar with is the Apple Partition Map scheme.

The way all IBM PCs and compatibles worked from the introduction of MS-DOS 2.0 in 1983 until some time after 2005 was the Master Boot Record partitioning scheme. It was outrageously simple: of the first 512 byte sector of a disk, the first 446 bytes was for the bootstrapping code (the “boot sector”), the last 2 bytes were for the magic two bytes telling the BIOS this disk was bootable, and the other 64 bytes were four entries of 16 bytes, each describing a disk partition. The Wikipedia page is a good overview of what it all looks like. Since “four partitions should be enough for anybody” wasn’t going to last, DOS 3.2 introduced “extended partitions” which was just using one of those 4 partitions as another similar data structure that could point to more partitions.

In the 1980s (similar to today), the Macintosh was, of course, different. The Apple Partition Map is significantly more flexible than the MBR on PCs. For a start, you could have more than four partitions! You could actually have a lot more than four partitions, as the Apple Partition Map is a single 512-byte sector for each partition, and the partition map is itself a partition. Instead of being block 0 (like the MBR is), it actually starts at block 1, and is contiguous (The Driver Descriptor Record is what’s at block 0). So, once created, it’s hard to extend. Typically it’d be created as 64×512-byte entries, for 32kb… which turns out is actually about enough for anyone.

The Inside Macintosh reference on the SCSI Manager goes through more detail as to these structures. If you’re wondering what language all the coding examples are in, it’s Pascal – which was fairly popular for writing Macintosh applications in back in the day.

But the actual partition map isn’t the “interesting” part of all this (and yes, the quotation marks are significant here), because Macs are pretty darn finicky about what disks to boot off, which gets to be interesting if you’re trying to find a CD-ROM image on the internet from which to boot, and then use to install an Operating System from.

,

Robert Collins — hyper combinators in Rust

Recently I read Michael Snoyman’s post on combining Axum, Hyper, Tonic and Tower. While his solution worked, it irked me – it seemed like there should be a much tighter solution possible.

I can deep dive into the code in a later post perhaps, but I think there are four points of difference. One, since the post was written Axum has started boxing its routes : so the enum dispatch approach taken, which delivers low overheads actually has no benefits today.

Two, while writing out the entire type by hand has some benefits, async code is much more pithy.

Thirdly, the code in the post is entirely generic, except the routing function itself.

And fourth, the outer Service<AddrStream> is an unnecessary layer to abstract over: given the similar constraints – the inner Service must take Request<..>, it is possible to just not use a couple of helpers and instead work directly with Service<Request...>.

So, onto a pithier version.

First, the app server code itself.

use std::{convert::Infallible, net::SocketAddr};

use axum::routing::get;
use hyper::{server::conn::AddrStream, service::make_service_fn};
use hyper::{Body, Request};
use tonic::async_trait;

use demo::echo_server::{Echo, EchoServer};
use demo::{EchoReply, EchoRequest};

struct MyEcho;

#[async_trait]
impl Echo for MyEcho {
    async fn echo(
        &self,
        request: tonic::Request<EchoRequest>,
    ) -> Result<tonic::Response<EchoReply>, tonic::Status> {
        Ok(tonic::Response::new(EchoReply {
            message: format!("Echoing back: {}", request.get_ref().message),
        }))
    }
}

#[tokio::main]
async fn main() {
    let addr = SocketAddr::from(([0, 0, 0, 0], 3000));

    let axum_service = axum::Router::new().route("/", get(|| async { "Hello world!" }));

    let grpc_service = tonic::transport::Server::builder()
        .add_service(EchoServer::new(MyEcho))
        .into_service();

    let both_service =
        demo_router::Router::new(axum_service, grpc_service, |req: &Request<Body>| {
            Ok::<bool, Infallible>(
                req.headers().get("content-type").map(|x| x.as_bytes())
                    == Some(b"application/grpc"),
            )
        });

    let make_service = make_service_fn(move |_conn: &AddrStream| {
        let both_service = both_service.clone();
        async { Ok::<_, Infallible>(both_service) }
    });

    let server = hyper::Server::bind(&addr).serve(make_service);

    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

Note the Router: it takes the two services and Fn to determine which to use on any given request. Then we just drop that composed service into make_service_fn and we’re done.

Next up we have the Router implementation. This is generic across any two Service<Request<...>> types as long as they are both Into<Bytes> for their Data, and Into<Box<dyn Error>> for errors.

use std::{future::Future, pin::Pin, task::Poll};

use http_body::combinators::UnsyncBoxBody;
use hyper::{body::HttpBody, Body, Request, Response};
use tower::Service;

#[derive(Clone)]
pub struct Router<First, Second, F> {
    first: First,
    second: Second,
    discriminator: F,
}

impl<First, Second, F> Router<First, Second, F> {
    pub fn new(first: First, second: Second, discriminator: F) -> Self {
        Self {
            first,
            second,
            discriminator,
        }
    }
}

impl<First, Second, FirstBody, FirstBodyError, SecondBody, SecondBodyError, F, FErr>
    Service<Request<Body>> for BinaryRouter<First, Second, F>
where
    First: Service<Request<Body>, Response = Response<FirstBody>>,
    First::Error: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
    First::Future: Send + 'static,
    First::Response: 'static,
    Second: Service<Request<Body>, Response = Response<SecondBody>>,
    Second::Error: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
    Second::Future: Send + 'static,
    Second::Response: 'static,
    F: Fn(&Request<Body>) -> Result<bool, FErr>,
    FErr: Into<Box<dyn std::error::Error + Send + Sync>> + Send + 'static,
    FirstBody: HttpBody<Error = FirstBodyError> + Send + 'static,
    FirstBody::Data: Into<bytes::Bytes>,
    FirstBodyError: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
    SecondBody: HttpBody<Error = SecondBodyError> + Send + 'static,
    SecondBody::Data: Into<bytes::Bytes>,
    SecondBodyError: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
{
    type Response = Response<
        UnsyncBoxBody<
            <hyper::Body as HttpBody>::Data,
            Box<dyn std::error::Error + Send + Sync + 'static>,
        >,
    >;
    type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
    type Future =
        Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;

    fn poll_ready(
        &mut self,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<(), Self::Error>> {
        match self.first.poll_ready(cx) {
            Poll::Ready(Ok(())) => match self.second.poll_ready(cx) {
                Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
                Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())),
                Poll::Pending => Poll::Pending,
            },
            Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())),
            Poll::Pending => Poll::Pending,
        }
    }

    fn call(&mut self, req: Request<Body>) -> Self::Future {
        let discriminant = { (self.discriminator)(&req) };
        let (first, second) = if matches!(discriminant, Ok(false)) {
            (Some(self.first.call(req)), None)
        } else if matches!(discriminant, Ok(true)) {
            (None, Some(self.second.call(req)))
        } else {
            (None, None)
        };
        let f = async {
            Ok(match discriminant.map_err(Into::into)? {
                true => second
                    .unwrap()
                    .await
                    .map_err(Into::into)?
                    .map(|b| b.map_data(Into::into).map_err(Into::into).boxed_unsync()),
                false => first
                    .unwrap()
                    .await
                    .map_err(Into::into)?
                    .map(|b| b.map_data(Into::into).map_err(Into::into).boxed_unsync()),
            })
        };
        Box::pin(f)
    }
}

Interesting things here – I use boxed_unsync to abstract over the body concrete type, and I implement the future using async code rather than as a separate struct. It becomes much smaller even after a few bits of extra type constraining.

One thing that flummoxed me for a little was the need to capture the future for the underlying response outside of the async block. Failing to do so provokes a 'static requirement which was tricky to debug. Fortunately there is a bug on making this easier to diagnose in rustc already. The underlying problem is that if you create the async block, and then dereference self, the type for impl of .first has to live an arbitrary time. Whereas by capturing the future immediately, only the impl of the future has to live an arbitrary time, and that doesn’t then require changing the signature of the function.

This is almost worth turning into a crate – I couldn’t see an existing one when I looked, though it does end up rather small – < 100 lines. What do you all think?

,

OpenSTEM — Covering the federal election, before the election

Since PM Scott Morrison did not announce the federal election date last week, it will now be held somewhere between March and May (see the post from ABC’s Antony Green for details). Various aspects of elections are covered in the Civics & Citizenship Australian Curriculum in Years 4, 5 and 6. Students are interested in […]

The post Covering the federal election, before the election first appeared on OpenSTEM.

,

Jan Schmidt — Pulling on a thread

I’m attending the https://linux.conf.au/ conference online this weekend, which is always a good opportunity for some sideline hacking.

I found something boneheaded doing that today.

There have been a few times while inventing the OpenHMD Rift driver where I’ve noticed something strange and followed the thread until it made sense. Sometimes that leads to improvements in the driver, sometimes not.

In this case, I wanted to generate a graph of how long the computer vision processing takes – from the moment each camera frame is captured until poses are generated for each device.

To do that, I have a some logging branches that output JSON events to log files and I write scripts to process those. I used that data and produced:

Pose recognition latency.
dt = interpose spacing, delay = frame to pose latency

Two things caught my eye in this graph. The first is the way the baseline latency (pink lines) increases from ~20ms to ~58ms. The 2nd is the quantisation effect, where pose latencies are clearly moving in discrete steps.

Neither of those should be happening.

Camera frames are being captured from the CV1 sensors every 19.2ms, and it takes that 17-18ms for them to be delivered across the USB. Depending on how many IR sources the cameras can see, figuring out the device poses can take a different amount of time, but the baseline should always hover around 17-18ms because the fast “device tracking locked” case take as little as 1ms.

Did you see me mention 19.2ms as the interframe period? Guess what the spacing on those quantisation levels are in the graph? I recognised it as implying that something in the processing is tied to frame timing when it should not be.

OpenHMD Rift CV1 tracking timing

This 2nd graph helped me pinpoint what exactly was going on. This graph is cut from the part of the session where the latency has jumped up. What it shows is a ~1 frame delay between when the frame is received (frame-arrival-finish-local-ts) before the initial analysis even starts!

That could imply that the analysis thread is just busy processing the previous frame and doesn’t get start working on the new one yet – but the graph says that fast analysis is typically done in 1-10ms at most. It should rarely be busy when the next frame arrives.

This is where I found the bone headed code – a rookie mistake I wrote when putting in place the image analysis threads early on in the driver development and never noticed.

There are 3 threads involved:

  • USB service thread, reading video frame packets and assembling pixels in framebuffers
  • Fast analysis thread, that checks tracking lock is still acquired
  • Long analysis thread, which does brute-force pose searching to reacquire / match unknown IR sources to device LEDs

These 3 threads communicate using frame worker queues passing frames between each other. Each analysis thread does this pseudocode:

while driver_running:
    Pop a frame from the queue
    Process the frame
    Sleep for new frame notification

The problem is in the 3rd line. If the driver is ever still processing the frame in line 2 when a new frame arrives – say because the computer got really busy – the thread sleeps anyway and won’t wake up until the next frame arrives. At that point, there’ll be 2 frames in the queue, but it only still processes one – so the analysis gains a 1 frame latency from that point on. If it happens a second time, it gets later by another frame! Any further and it starts reclaiming frames from the queues to keep the video capture thread fed – but it only reclaims one frame at a time, so the latency remains!

The fix is simple:

while driver_running:
   Pop a frame
   Process the frame
   if queue_is_empty():
     sleep for new frame notification

Doing that for both the fast and long analysis threads changed the profile of the pose latency graph completely.

Pose latency and inter-pose spacing after fix

This is a massive win! To be clear, this has been causing problems in the driver for at least 18 months but was never obvious from the logs alone. A single good graph is worth a thousand logs.

What does this mean in practice?

The way the fusion filter I’ve built works, in between pose updates from the cameras, the position and orientation of each device are predicted / updated using the accelerometer and gyro readings. Particularly for position, using the IMU for prediction drifts fairly quickly. The longer the driver spends ‘coasting’ on the IMU, the less accurate the position tracking is. So, the sooner the driver can get a correction from the camera to the fusion filter the less drift we’ll get – especially under fast motion. Particularly for the hand controllers that get waved around.

Before: Left Controller pose delays by sensor
After: Left Controller pose delays by sensor

Poses are now being updated up to 40ms earlier and the baseline is consistent with the USB transfer delay.

You can also visibly see the effect of the JPEG decoding support I added over Christmas. The ‘red’ camera is directly connected to USB3, while the ‘khaki’ camera is feeding JPEG frames over USB2 that then need to be decoded, adding a few ms delay.

The latency reduction is nicely visible in the pose graphs, where the ‘drop shadow’ effect of pose updates tailing fusion predictions largely disappears and there are fewer large gaps in the pose observations when long analysis happens (visible as straight lines jumping from point to point in the trace):

Before: Left Controller poses
After: Left Controller poses

,

Colin Charles — This thing is still on?

Yes, the blog is still on. January 2004 I moved to WordPress, and it is still here January 2022. I didn’t write much last year (neither here, not experimenting with the Hey blog). I didn’t post anything to Instagram last year either from what I can tell, just a lot of stories.

August 16 2021, I realised I was 1,000 days till May 12 2024, which is when I become 40. As of today, that leads 850 days. Did I squander the last 150 days? I’m back to writing almost daily in the Hobonichi Techo (I think last year and the year before were mostly washouts; I barely scribbled anything offline).

I got a new Apple Watch Series 7 yesterday. I can say I used the Series 4 well (79% battery life), purchased in the UK when I broke my Series 0 in Edinburgh airport.

TripIt stats for last year claimed 95 days on the road. This is of course, a massive joke, but I’m glad I did get to visit London, Lisbon, New York, San Francisco, Los Angeles without issue. I spent a lot of time in Kuantan, a bunch of Langkawi trips, and also, I stayed for many months at the Grand Hyatt Kuala Lumpur during the May lockdowns (I practically stayed there all lockdown).

With 850 days to go till I’m 40, I have plenty I would like to achieve. I think I’ll write a lot more here. And elsewhere. Get back into the habit of doing. And publishing by learning and doing. No fear. Not that I wasn’t doing, but its time to be prolific with what’s been going on.

Chris Smart — Swift object storage clusters with Ansible

OpenStack Swift is a most brilliant object store and I have built a few clusters over the last couple of years using SwiftStack’s product, which is really excellent. However, I need an alternative way of creating and managing clusters, that’s still inspired by SwiftStack.

Ansible seems a logical choice to me, as I need to manage packages, configure services and perform certain operations on a running cluster.

A couple of months ago I attended the Swift Ops feedback session at OpenStack PTG and said that I had been working on this. Several other attendees mentioned they are also looking at Ansible, or currently use it for bits and pieces, so hopefully this can grow into something useful!

My Ansible Swift role can currently build a Swift cluster from scratch (on CentOS 8 Stream nodes) and perform some basic operations. The idea is to define the nodes and their services (PACO) using YAML based Ansible inventory and use the role to perform specific actions. Together all of those actions let you build an entirely new cluster, or modify and manage an existing one. Over time, I plan to add new features and operational actions.

The role currently performs the following:

  • Prepare Swift nodes, including setting SELinux and ensuring SSH access
  • Add repositories and install packages (upstream RDO at the moment)
  • Configure dependent services, such as logging, rsyncd and memcached
  • Configure and enable TLS proxy endpoint for Proxy nodes
  • Configure keepalived for failover of Proxy VIPs
  • Configure Swift PACO services using templates and variables
  • Create initial account, container and object rings
  • Prepare disks on each node, format and mount to match the rings
  • Build and distribute the rings
  • Configure dispersion
  • Simple operational tasks such as:
    • Add new disks
    • Rebuild and distribute the rings
    • Reconfigure PACO services
    • Generate dispersion and replication reports

I’m working on a library for managing the rings, as it’s a bit clunky using the swift-ring-builder command in the role. So that’ll be the next focus, then onto other operations functions as well as moving from RPM packages to containers. Eventually I’d like to have a web service which can front-end the inventory and Ansible plays, providing a unified management platform.

I also provide sample inventory, playbooks and wrapper scripts on GitHub. Simply modify the inventory to suit your node and network setup and run the site.sh role wrapper script. It also optionally supports my virtual infrastructure role which means one can easily spin up a virtual, production-like multi-node Swift cluster from scratch on KVM hosts.

Ansible configuring an OpenStack Swift cluster in a terminal
Running the Swift Ansible playbook

I would love any feedback and to collaborate on this with anyone, if it seems useful.

,

Chris Smart — Setting up an Ansible dev environment

I do a lot of dev work using my Ansible virtual infrastructure role to spin up various KVM guests across multiple hosts using YAML based inventories. I’ve just put my hand up to try and help maintain the Ansible libvirt community collection, which I use in my role.

On this page

This is my attempt to start documenting how I configure my Ansible dev environment on Fedora (but much should work for other distros). These instructions are taken from a number of places, including the project’s quick start guide as well as the official installation and developing modules guides.

There are a number of versions of Ansible which are supported on a number of Python releases (including 2 and 3). Ideally we want to be able to use any combination. To do this, we can use Git to get the Ansible code and manage the release we are working on or testing against, together with virtual environments for the Python version we need.

The process is something like this:

  • Fork Ansible code in GitHub and clone
  • Checkout the version of Ansible we want to use
  • Install the version of Python we want to use (if not the host’s)
  • Create and activate a Python virtual environment
  • Activate the Ansible development environment, using the code and Python from above
  • Create a Git branch and work on the code!

Each time we change the version of Ansible or Python, we can simple switch and re-activate.

Fork Ansible repos

As we use Git, the first step is to get our own copy of the code.

Browse to the Ansible project and fork into your own namespace. I am working on community.libvirt so I have also forked this into my own namespace.

The Fork button on the Ansible GitHub repository
Fork a Git repo into your own namespace

We will then use the resulting forked URLs to clone the repositories in the next step, for example my Ansible clone URL over SSH is git@github.com:csmart/ansible.git (copy yours from your forked repo in GitHub).

Clone repos

OK, now we are ready to clone the repos down from your own fork and add a remote pointing to upstream to fetch updates.

Get the Ansible code

Ansible will go into the ~/ansible directory (you can change that if you prefer), which will checkout the devel branch by default. We’ll call the upstream remote, upstream and fetch updates from both remotes. Note that the URL to clone from will be different than mine (copy yours from your forked repo in GitHub), but the upstream URL will be the same.

Bash
git clone git@github.com:csmart/ansible.git ~/ansible
cd ~/ansible
git remote add upstream https://github.com/ansible/ansible.git
git remote update

Get the Ansible collection code

Next, we clone to collection repository. Note that this goes into a specific directory structure to match the default locations that Ansible will search. Again, your clone URL will be different to mine, but the upstream URL will be the same.

Bash
mkdir -p ~/ansible_collections/community/libvirt
git clone git@github.com:csmart/community.libvirt.git ~/ansible_collections/community/libvirt/
cd ~/ansible_collections/community/libvirt/
git remote add upstream https://github.com/ansible-collections/community.libvirt.git
git remote update

Some versions of Ansible will not automatically look in this location for collections, so we can also add it to our path. We will specify our local paths before the system wide path so that collections we’re working on will be found first.

Bash
export ANSIBLE_COLLECTIONS_PATH="${HOME}/ansible_collections:${HOME}/.ansible/collections:/usr/share/ansible/collections"

Add that line to your ~/.bashrc file if you want it to be automatic.

Switching Ansible versions

Once you have the Ansible git repository, you will be in the devel branch by default. To test your module with a different major release, simply check out the corresponding branch. You can see all latest release branches like so.

Bash
cd ~/ansible
git fetch upstream
git --no-pager branch -r --list "upstream/stable*" |sort -V

For example, to test against Ansible 2.12, checkout the stable-2.12 branch.

Bash
cd ~/ansible
git checkout stable-2.12

You can also get a specific release, for example, v2.12.1, by checking out the corresponding tag into a new local branch (hence the -b option).

Bash
cd ~/ansible
git --no-pager tag --list "v*" |sort -V
git checkout -b v2.12.1

Next we will get a specific version of Python to use with Ansible.

Switching Python versions

By default, Ansible will use the version installed on the host. Fortunately, Fedora provides versions of Python which are supported upstream, so we can easily switch. This follows the pattern python${major}.${minor} for example, python2.7 and python3.9, you simple install the versions you want.

If you’re not on Fedora, your distro might do something similar, or you may need to use a tool like Anaconda.

Bash
sudo dnf install python2.7 python3.6 python3.9 python3.11

Now, we simply use that Python version exactly as the command (such as python3.9) when creating our virtual environments.

Create Python virtual environment

Now that we have the Ansible code and a number of Python interpreters, let’s prepare the Python virtual environments which live inside the clone of the Ansible Git repository.

I’m using freshly installed Python 3.9 from the Fedora host in my example and note that I’m specifying the version as a subdirectory in the virtual environment directory so that we can easily switch. Although venv is a directory inside the code, it will be ignored by Git.

Bash
cd ~/ansible
python3.9 -m venv venv/3.9

For Python 2 we need to install virtualenv and create it differently.

Bash
sudo dnf install /usr/bin/virtualenv
cd ~/ansible
virtualenv --python /usr/bin/python2.7 env/2.7

Now we can activate our Python environment for the version we want (e.g. 3.9), update pip and install Ansible requirements.

We also need to do this each time we switch versions of Python or start a new shell session.

Bash
export PYTHON_VERSION=3.9
source ~/ansible/venv/${PYTHON_VERSION}/bin/activate
python -m pip install -U pip setuptools
python -m pip install -r ~/ansible/requirements.txt

Deactivating Python virtual environment

To deactivate a Python environment, simple run the deactivate command which will return you to the default system Python.

Activate Ansible hacking environment

Now that we have the code and a version of Python we want to use, we can run the a script from Ansible which will configure your current session to run from the source version in the git repo, rather than needing a full system install.

You should do this every time you switch between versions of Ansible or Python, or starting a new shell session.

Bash
source ~/ansible/hacking/env-setup
python -m pip install -U pip setuptools
python -m pip install -r ~/ansible/requirements.txt

Working on Ansible code

Remember that we have a remote pointing upstream to get the latest changes as well as our own fork in GitHub which is where we will push any changes.

To work on the code, we need to create a branch from the relevant upstream branch. We can see all of the latest available upstream branches with the following.

Bash
cd ~/ansible
git fetch upstream
git --no-pager branch --remote --list "upstream/*" |sort -V

For example, if we’re working on a feature we probably branch from devel. We can do this with a single checkout command which will create the new branch.

Bash
git checkout -b my-branch upstream/devel

Let’s push this new branch to our fork straight away and enable tracking so that don’t need to keep specifying where to push to.

Bash
git push --set-upstream origin my-branch

When you’re ready, submit a pull request via GitHub to have your code merged into upstream Ansible.

Get latest Ansible code updates

To pull in the latest code updates into your feature branch, we use the Git rebase function. This way, any local changes get moved on top of the latest commits (rather than a merge) as though they had always been added there. For example, if you are working on my-branch which was branched from devel, we need to fetch and rebase in the latest changes from upstream/devel.

Bash
git checkout my-branch
git pull --rebase upstream devel

If you have any local commits, you will need to force push your branch to your GitHub fork as the Git history has been rewritten with the rebase and your commit hashes will have changed. This will automatically update any merge requests.

Note that the branch is already tracking to our fork, so we don’t need to specify where to push to.

Bash
git push --force

Well, I think that’s a start at least…

,

Chris Smart — Automatically enable and disable WiFi based on Ethernet connection with NetworkManager

I use a laptop as my daily driver and am on WiFi most of the time. However, I also have a dock which has Ethernet. Thanks to NetworkManager dispatcher scripts, we can automatically disable WiFi when Ethernet is connected and then enable it again once Ethernet is disconnected.

A Dell laptop dock connected to an Ethernet cable
A dock with a wired Ethernet connection.

NetworkManager will execute scripts in the /etc/NetworkManager/dispatcher.d directory in alphabetical order in response to network events. Each script should be (a) a regular file, (b) owned by root, (c) not writable by group or other, (d) not set-uid, (e) and executable by the owner. Each script receives two arguments, the first being the interface name of the device just activated, and second an action.

https://linux.die.net/man/8/networkmanager

Let’s create the script (this is not mine, it comes from the nmcli-examples man page).

Bash
cat << \EOF |sudo tee /etc/NetworkManager/dispatcher.d/70-wifi-wired-exclusive.sh
#!/bin/bash
export LC_ALL=C

enable_disable_wifi ()
{
    result=$(nmcli dev | grep "ethernet" | grep -w "connected")
    if [ -n "$result" ]; then
        nmcli radio wifi off
    else
        nmcli radio wifi on
    fi
}

if [ "$2" = "up" ]; then
    enable_disable_wifi
fi

if [ "$2" = "down" ]; then
    enable_disable_wifi
fi
EOF

Set the required permissions as outlined in the quote above from the man page.

Bash
sudo chown root:root /etc/NetworkManager/dispatcher.d/70-wifi-wired-exclusive.sh
sudo chmod 744 /etc/NetworkManager/dispatcher.d/70-wifi-wired-exclusive.sh

Restart NetworkManager to pick up the dispatcher script.

Bash
sudo systemctl restart NetworkManager

And finally, test by plugging an Ethernet cable in and out. You should see WiFi being enabled and disabled automatically.

,

Jan Schmidt — 2.5 years of Oculus Rift

Once again time has passed, and another update on Oculus Rift support feels due! As always, it feels like I’ve been busy with work and not found enough time for Rift CV1 hacking. Nevertheless, looking back over the history since I last wrote, there’s quite a lot to tell!

In general, the controller tracking is now really good most of the time. Like, wildly-swing-your-arms-and-not-lose-track levels (most of the time). The problems I’m hunting now are intermittent and hard to identify in the moment while using the headset – hence my enthusiasm over the last updates for implementing stream recording and a simulation setup. I’ll get back to that.

Outlier Detection

Since I last wrote, the tracking improvements have mostly come from identifying and rejecting incorrect measurements. That is, if I have 2 sensors active and 1 sensor says the left controller is in one place, but the 2nd sensor says it’s somewhere else, we’ll reject one of those – choosing the pose that best matches what we already know about the controller. The last known position, the gravity direction the IMU is detecting, and the last known orientation. The tracker will now also reject observations for a time if (for example) the reported orientation is outside the range we expect. The IMU gyroscope can track the orientation of a device for quite a while, so can be relied on to identify strong pose priors once we’ve integrated a few camera observations to get the yaw correct.

It works really well, but I think improving this area is still where most future refinements will come. That and avoiding incorrect pose extractions in the first place.

Plot of headset tracking – orientation and position

The above plot is a sample of headset tracking, showing the extracted poses from the computer vision vs the pose priors / tracking from the Kalman filter. As you can see, there are excursions in both position and orientation detected from the video, but these are largely ignored by the filter, producing a steadier result.

Left Touch controller tracking – orientation and position

This plot shows the left controller being tracked during a Beat Saber session. The controller tracking plot is quite different, because controllers move a lot more than the headset, and have fewer LEDs to track against. There are larger gaps here in the timeline while the vision re-acquires the device – and in those gaps you can see the Kalman filter interpolating using IMU input only (sometimes well, sometimes less so).

Improved Pose Priors

Another nice thing I did is changes in the way the search for a tracked device is made in a video frame. Before starting looking for a particular device it always now gets the latest estimate of the previous device position from the fusion filter. Previously, it would use the estimate of the device pose as it was when the camera exposure happened – but between then and the moment we start analysis more IMU observations and other camera observations might arrive and be integrated into the filter, which will have updated the estimate of where the device was in the frame.

This is the bit where I think the Kalman filter is particularly clever: Estimates of the device position at an earlier or later exposure can improve and refine the filter’s estimate of where the device was when the camera captured the frame we’re currently analysing! So clever. That mechanism (lagged state tracking) is what allows the filter to integrate past tracking observations once the analysis is done – so even if the video frame search take 150ms (for example), it will correct the filter’s estimate of where the device was 150ms in the past, which ripples through and corrects the estimate of where the device is now.

LED visibility model

To improve the identification of devices better, I measured the actual angle from which LEDs are visible (about 75 degrees off axis) and measured the size. The pose matching now has a better idea of which LEDs should be visible for a proposed orientation and what pixel size we expect them to have at a particular distance.

Better Smoothing

I fixed a bug in the output pose smoothing filter where it would glitch as you turned completely around and crossed the point where the angle jumps from +pi to -pi or vice versa.

Improved Display Distortion Correction

I got a wide-angle hi-res webcam and took photos of a checkerboard pattern through the lens of my headset, then used OpenCV and panotools to calculate new distortion and chromatic aberration parameters for the display. For me, this has greatly improved. I’m waiting to hear if that’s true for everyone, or if I’ve just fixed it for my headset.

Persistent Config Cache

Config blocks! A long time ago, I prototyped code to create a persistent OpenHMD configuration file store in ~/.config/openhmd. The rift-kalman-filter branch now uses that to store the configuration blocks that it reads from the controllers. The first time a controller is seen, it will load the JSON calibration block as before, but it will now store it in that directory – removing a multiple second radio read process on every subsequent startup.

Persistent Room Configuration

To go along with that, I have an experimental rift-room-config branch that creates a rift-room-config.json file and stores the camera positions after the first startup. I haven’t pushed that to the rift-kalman-filter branch yet, because I’m a bit worried it’ll cause surprising problems for people. If the initial estimate of the headset pose is wrong, the code will back-project the wrong positions for the cameras, which will get written to the file and cause every subsequent run of OpenHMD to generate bad tracking until the file is removed. The goal is to have a loop that monitors whether the camera positions seem stable based on the tracking reports, and to use averaging and resetting to correct them if not – or at least to warn the user that they should re-run some (non-existent) setup utility.

Video Capture + Processing

The final big ticket item was a rewrite of how the USB video frame capture thread collects pixels and passes them to the analysis threads. This now does less work in the USB thread, so misses fewer frames, and also I made it so that every frame is now searched for LEDs and blob identities tracked with motion vectors, even when no further analysis will be done on that frame. That means that when we’re running late, it better preserves LED blob identities until the analysis threads can catch up – increasing the chances of having known LEDs to directly find device positions and avoid searching. This rewrite also opened up a path to easily support JPEG decode – which is needed to support Rift Sensors connected on USB 2.0 ports.

Session Simulator

I mentioned the recording simulator continues to progress. Since the tracking problems are now getting really tricky to figure out, this tool is becoming increasingly important. So far, I have code in OpenHMD to record all video and tracking data to a .mkv file. Then, there’s a simulator tool that loads those recordings. Currently it is capable of extracting the data back out of the recording, parsing the JSON and decoding the video, and presenting it to a partially implemented simulator that then runs the same blob analysis and tracking OpenHMD does. The end goal is a Godot based visualiser for this simulation, and to be able to step back and forth through time examining what happened at critical moments so I can improve the tracking for those situations.

To make recordings, there’s the rift-debug-gstreamer-record branch of OpenHMD. If you have GStreamer and the right plugins (gst-plugins-good) installed, and you set env vars like this, each run of OpenHMD will generate a recording in the target directory (make sure the target dir exists):

export OHMD_TRACE_DIR=/home/user/openhmd-traces/
export OHMD_FULL_RECORDING=1

Up Next

The next things that are calling to me are to improve the room configuration estimation and storage as mentioned above – to detect when the poses a camera is reporting don’t make sense because it’s been bumped or moved.

I’d also like to add back in tracking of the LEDS on the back of the headset headband, to support 360 tracking. I disabled those because they cause me trouble – the headband is adjustable relative to the headset, so the LEDs don’t appear where the 3D model says they should be and that causes jitter and pose mismatches. They need special handling.

One last thing I’m finding exciting is a new person taking an interest in Rift S and starting to look at inside-out tracking for that. That’s just happened in the last few days, so not much to report yet – but I’ll be happy to have someone looking at that while I’m still busy over here in CV1 land!

As always, if you have any questions, comments or testing feedback – hit me up at thaytan@noraisin.net or on @thaytan Twitter/IRC.

Thank you to the kind people signed up as Github Sponsors for this project!

,

Chris Smart — Flashing OpenWRT onto Ubiquiti UniFi AP AC

Recently OpenWRT released version 20.02 and it’s great!

On this page

The Wiki instructions for flashing Ubiquiti AP AC devices (including Pro, Lite and LR versions) has a number of options. One uses tftp but requires a specific version of the stock firmware, others use mtd flashing tools (either from OpenWRT or stock) flashing tools, and another via serial.

I chose option 2, using mtd-tools from OpenWRT, as I think it’s the most simple. However, it did require a few extra tweaks for 20.02 due to library errors.

A Ubiquiti UniFi access point with its blue status light illuminated
Ubiquiti AP AC Lite

As per the title we are using OpenWRT tools to flash the firmware. As the device is running stock firmware, we will need to download the packages from OpenWRT and extract them onto the device so that we can use them to flash the firmware.

Flashing error

However, when following the instructions to flash, we get this library error.

Text
UBNT-BZ.v4.3.20# lib/ld-musl-mips-sf.so.1 sbin/mtd write openwrt-21.02.0-ath79-generic-ubnt_unifiac-lite-squashfs-sysupgrade.bin kernel0
Error loading shared library libubox.so.20210516: No such file or directory (needed by sbin/mtd)
Error relocating sbin/mtd: md5_end: symbol not found
Error relocating sbin/mtd: md5sum: symbol not found
Error relocating sbin/mtd: md5_hash: symbol not found
Error relocating sbin/mtd: md5_begin: symbol not found

So, we need one more library, libubox.

Getting the firmware, tools and libraries

Let’s grab all of the required packages now.

Bash
wget https://downloads.openwrt.org/releases/21.02.0/targets/ath79/generic/packages/mtd_26_mips_24kc.ipk
wget https://downloads.openwrt.org/releases/21.02.0/targets/ath79/generic/packages/libc_1.1.24-3_mips_24kc.ipk
wget https://downloads.openwrt.org/releases/21.02.0/packages/mips_24kc/base/libubox20210516_2021-05-16-b14c4688-2_mips_24kc.ipk

Make sure that you also download the firmware for your device. I am doing this on some AC Lite devices (hence the firmware file for me is openwrt-21.02.0-ath79-generic-ubnt_unifiac-lite-squashfs-sysupgrade.bin) but your device might be different.

Now follow on with the rest of the instructions to get your device on the network and copy the files over.

Copy the files over and install them

Either connect your AP directly to your Linux machine and boot it up the device with stock firmware so that it doesn’t get an IP from DHCP. Or, boot it up and then after a few minutes, plug it into your network. Or, plug it into your network and check what IP the device got. Either way, find the IP.

If no DHCP is found, the AP should fall back to 192.168.1.20. If so, add an IP address from that range (e.g. 192.168.1.123/24) to an interface on your machine.

Copy the firmware as well as the ipk packages to your device over scp, the user is ubnt and the password is ubnt.

Bash
scp openwrt*squashfs-sysupgrade.bin ubnt@192.168.1.20:/tmp/
scp *ipk ubnt@192.168.1.20:/tmp/

Now SSH onto the device and extract the packages into /tmp/flash directory.

Bash
mkdir /tmp/flash
cd /tmp/flash
tar -xzOf /tmp/libc_1.1.24-3_mips_24kc.ipk  ./data.tar.gz | tar -xvz
tar -xzOf /tmp/mtd_26_mips_24kc.ipk ./data.tar.gz | tar -xvz
tar -xzOf /tmp/libubox20210516_2021-05-16-b14c4688-2_mips_24kc.ipk ./data.tar.gz | tar -xvz

Updated flashing command

Now we flash the OpenWRT image to kernel0, however because of the extra library flashing is slightly different. Fortunately, musl supports LD_LIBRARY_PATH which makes it super easy.

Bash
LD_LIBRARY_PATH=${PWD}/lib lib/ld-musl-mips-sf.so.1 sbin/mtd write /tmp/openwrt-21.02.0-ath79-generic-ubnt_unifiac-lite-squashfs-sysupgrade.bin kernel0

Erasing kernel1 is also slightly different.

Bash
LD_LIBRARY_PATH=${PWD}/lib lib/ld-musl-mips-sf.so.1 sbin/mtd erase kernel1

Continue on with the rest of the instructions, e.g. ensure bootloader is set to kernel0 (OpenWRT doesn’t support multi-boot).

Bash
dd if=/dev/zero bs=1 count=1 of=/dev/mtd4

Then you can reboot and hopefully when it comes back it’s running OpenWRT at the default 192.168.1.1 IP address.

,

Chris Neugebauer — Talk Notes: On The Use and Misuse of Decorators

I gave the talk On The Use and Misuse of Decorators as part of PyConline AU 2021, the second in annoyingly long sequence of not-in-person PyCon AU events. Here’s some code samples that you might be interested in:

Simple @property implementation

This shows a demo of @property-style getters. Setters are left as an exercise :)


def demo_property(f):
    f.is_a_property = True
    return f


class HasProperties:

    def __getattribute__(self, name):
        ret = super().__getattribute__(name)
        if hasattr(ret, "is_a_property"):
            return ret()
        else:
            return ret

class Demo(HasProperties):

    @demo_property
    def is_a_property(self):
        return "I'm a property"

    def is_a_function(self):
        return "I'm a function"


a = Demo()
print(a.is_a_function())
print(a.is_a_property)

@run (The Scoped Block)

@run is a decorator that will run the body of the decorated function, and then store the result of that function in place of the function’s name. It makes it easier to assign the results of complex statements to a variable, and get the advantages of functions having less leaky scopes than if or loop blocks.

def run(f):

    return f()

@run
def hello_world():
    return "Hello, World!"

print(hello_world)

@apply (Multi-line stream transformers)

def apply(transformer, iterable_):

    def _applicator(f):

        return(transformer(f, iterable_))

    return _applicator

@apply(map, range(100)
def fizzbuzzed(i):
    if i % 3 == 0 and i % 5 == 0:
        return "fizzbuzz"

    if i % 3 == 0:
        return "fizz"
    elif i % 5 == 0:
        return "buzz"
    else:
        return str(i)

Builders


def html(f):
    builder = HtmlNodeBuilder("html")
    f(builder)
    return builder.build()


class HtmlNodeBuilder:
    def __init__(self, tag_name):
       self.tag_name = tag_name
       self.nodes = []

   def node(self, f):
        builder = HtmlNodeBuilder(f.__name__)
        f(builder)
        self.nodes.append(builder.build())

    def text(self, text):
        self.nodes.append(text)

    def build(self):
      nodes = "\n".join(self.nodes)
       return f"<{self.tag_name}>\n{nodes}\n</{self.tag_name}>"


@html
def document(b):
   @b.node
   def head(b):
       @b.node
       def title(b):
           b.text("Hello, World!")

   @b.node
   def body(b):
       for i in range(10, 0, -1):
           @b.node
           def p(b):
               b.text(f"{i}")

Code Registries

This is an incomplete implementation of a code registry for handling simple text processing tasks:

```python

def register(self, input, output):

def _register_code(f):
    self.registry[(input, output)] = f
    return f

return _register_code

in_type = (iterable[str], (WILDCARD, ) out_type = (Counter, (WILDCARD, frequency))

@registry.register(in_type, out_type) def count_strings(strings):

return Counter(strings)

@registry.register( (iterable[str], (WILDCARD, )), (iterable[str], (WILDCARD, lowercase)) ) def words_to_lowercase(words): …

@registry.register( (iterable[str], (WILDCARD, )), (iterable[str], (WILDCARD, no_punctuation)) ) def words_without_punctuation(words): …

def find_steps( self, input_type, input_attrs, output_type, output_attrs ):

hand_wave()

def give_me(self, input, output_type, output_attrs):

steps = self.find_steps(
    type(input), (), output_type, output_attrs
)

temp = input
for step in steps:
    temp = step(temp)

return temp

,

Jan Schmidt — OpenHMD update

A while ago, I wrote a post about how to build and test my Oculus CV1 tracking code in SteamVR using the SteamVR-OpenHMD driver. I have updated those instructions and moved them to https://noraisin.net/diary/?page_id=1048 – so use those if you’d like to try things out.

The pandemic continues to sap my time for OpenHMD improvements. Since my last post, I have been working on various refinements. The biggest visible improvements are:

  • Adding velocity and acceleration API to OpenHMD.
  • Rewriting the pose transformation code that maps from the IMU-centric tracking space to the device pose needed by SteamVR / apps.

Adding velocity and acceleration reporting is needed in VR apps that support throwing things. It means that throwing objects and using gravity-grab to fetch objects works in Half-Life: Alyx, making it playable now.

The rewrite to the pose transformation code fixed problems where the rotation of controller models in VR didn’t match the rotation applied in the real world. Controllers would appear attached to the wrong part of the hand, and rotate around the wrong axis. Movements feel more natural now.

Ongoing work – record and replay

My focus going forward is on fixing glitches that are caused by tracking losses or outliers. Those problems happen when the computer vision code either fails to match what the cameras see to the device LED models, or when it matches incorrectly.

Tracking failure leads to the headset view or controllers ‘flying away’ suddenly. Incorrect matching leads to controllers jumping and jittering to the wrong pose, or swapping hands. Either condition is very annoying.

Unfortunately, as the tracking has improved the remaining problems get harder to understand and there is less low-hanging fruit for improvement. Further, when the computer vision runs at 52Hz, it’s impossible to diagnose the reasons for a glitch in real time.

I’ve built a branch of OpenHMD that uses GStreamer to record the CV1 camera video, plus IMU and tracking logs into a video file.

To go with those recordings, I’ve been working on a replay and simulation tool, that uses the Godot game engine to visualise the tracking session. The goal is to show, frame-by-frame, where OpenHMD thought the cameras, headset and controllers were at each point in the session, and to be able to step back and forth through the recording.

Right now, I’m working on the simulation portion of the replay, that will use the tracking logs to recreate all the poses.

,

Chris Smart — How to create VLAN trunks and access ports for VMs on Linux bridges using NetworkManager (and have them talk)

TL;DR instead of creating a VLAN on a physical interface (like bond0.123), then turning that into a bridge for a VM, create a tagged interface on the main bridge (e.g. br0.123) and then add that into a new bridge for a VM.

On this page

Create a new bridge for the VLAN.

Bash
nmcli con add ifname br-123 type bridge con-name br-123
nmcli con modify br-123 ipv4.method disabled ipv6.method ignore
nmcli con up br-123

Create the VLAN on existing bridge (assuming br0 already exists) and attach to the new bridge (br-123).

Bash
nmcli con add type vlan con-name br0.123 ifname br0.123 dev br0 id 123
nmcli con modify br0.123 master br-123 slave-type bridge
nmcli con up br0.123

This will allow two VMs on the same host to talk to each other over the VLAN, where one is using a tagged interface on br0 (as a trunk) and the other is using br-123 (as an access port or native for VLAN 123).

Text
+------+  +-------+                  +------------------+
| eth0 |--|       |                  |       VM1        |
+------+  |       |   +----------+   |   +----------+   |
          | bond0 |---|   br0    |---|---| eth0.123 |   |
+------+  |       |   | (bridge) |   |   |  (VLAN)  |   |
| eth1 |--|       |   +----------+   |   +----------+   |
+------+  +-------+        |         +------------------+
                           |
                           |                       +------------------+
                           |                       |       VM2        |
                      +---------+   +----------+   |   +----------+   |
                      | br0.123 |---|  br-123  |---|---|   eth0   |   |
                      | (VLAN)  |   | (bridge) |   |   | (native) |   |
                      +---------+   +----------+   |   +----------+   |
                                                   +------------------+
Blue and grey Ethernet cables connected to two network switches
Photo: Network switches by Jon “ShakataGaNai” Davis / Wikimedia Commons. CC BY-SA 3.0. Resized; thumbnails cropped.

Some background

A common method for connecting VMs to a real network is to attach them to a bridge. In this case, a bridge is created on the KVM host and a physical interface is attached to it. Network interfaces of the VMs are then attached to that bridge and they are directly on the same network as the host (we’ll call this a flat network).

Text
+------+  +-------+                  +----------------+
| eth0 |--|       |                  |       VM       |
+------+  |       |   +----------+   |   +--------+   |
          | bond0 |---|   br0    |---|---|  eth0  |   |
+------+  |       |   | (bridge) |   |   | (flat) |   |
| eth1 |--|       |   +----------+   |   +--------+   |
+------+  +-------+                  +----------------+

That’s great, but now they’re also connected to everything else on the network and maybe you didn’t want that. One solution to this is to use VLANs to put VMs on isolated networks.

The bridge mentioned above could also be used as a trunk, which can carry tagged VLAN traffic that the VM creates (assuming the physical switch is configured to accept those VLANs). In this instance, the VM would create a VLAN on its network interface and tagged traffic will flow out of the bridge onto the physical network, letting that VM talk to other VMs or devices on that same VLAN.

Text
+------+  +-------+                  +--------------------+
| eth0 |--|       |                  |         VM         |
+------+  |       |   +----------+   |   +------------+   |
          | bond0 |---|   br0    |---|---|  eth0.123  |   |
+------+  |       |   | (bridge) |   |   |   (VLAN)   |   |
| eth1 |--|       |   +----------+   |   +------------+   |
+------+  +-------+                  +--------------------+

This is handy because you might need a VM to talk to multiple networks without routing.

However, if all you wanted is for the VM to be on a single VLAN transparently without it requiring any configuration, then the above method is rather cumbersome as you have to set up VLAN interfaces. Instead, this can be achieved by creating a tagged VLAN on a physical network interface on the KVM host, then attaching that to a bridge. The VM would then be attached to the bridge as an access port and all traffic that flows out onto the network will be tagged (we’ll call this access port or native mode).

Text
+------+  +-------+                                  +------------------+
| eth0 |--|       |                                  |        VM        |
+------+  |       |   +-----------+   +----------+   |   +----------+   |
          | bond0 |---| bond0.123 |---|  br-123  |---|---|   eth0   |   |
+------+  |       |   |  (VLAN)   |   | (bridge) |   |   | (native) |   | 
| eth1 |--|       |   +-----------+   +----------+   |   +----------+   |
+------+  +-------+                                  +------------------+

Getting trunks talking to access ports

This is all well and good, but what if you had a VM on an access point wanting to talk to a VM on a trunk over the same VLAN? This won’t happen by default as the other VM would be on a bridge, not the bond. So, instead of creating the VLAN on the KVM host physical interface (e.g. bond0) as we did above, we need to create it on the same bridge we’re using for the trunk (e.g. br0), then add tagged interface that to a new bridge for the VM.

Text
+------+  +-------+
| eth0 |--|       |
+------+  |       |   +----------+
          | bond0 |---|   br0    |
+------+  |       |   | (bridge) |
| eth1 |--|       |   +----------+
+------+  +-------+        |
                           |
                           |                       +------------------+
                           |                       |        VM        |
                      +---------+   +----------+   |   +----------+   |
                      | br0.123 |---|  br-123  |---|---|   eth0   |   |
                      | (VLAN)  |   | (bridge) |   |   | (native) |   |
                      +---------+   +----------+   |   +----------+   |
                                                   +------------------+

Doing it this way allows us to also support both adding VMs directly to a VLAN or trunking in a number of VLANs and have the VMs able to communicate with each other.

Text
+------+  +-------+                  +------------------+
| eth0 |--|       |                  |       VM1        |
+------+  |       |   +----------+   |   +----------+   |
          | bond0 |---|   br0    |---|---| eth0.123 |   |
+------+  |       |   | (bridge) |   |   |  (VLAN)  |   |
| eth1 |--|       |   +----------+   |   +----------+   |
+------+  +-------+        |         +------------------+
                           |
                           |                       +------------------+
                           |                       |       VM2        |
                      +---------+   +----------+   |   +----------+   |
                      | br0.123 |---|  br-123  |---|---|   eth0   |   |
                      | (VLAN)  |   | (bridge) |   |   | (native) |   |
                      +---------+   +----------+   |   +----------+   |
                                                   +------------------+

Now VM1 and VM2 are both able to talk to the network on VLAN 123, as well as each other with VM1 via a tagged interface on the trunk and VM2 via an access port.

Multiple VLANs and access ports on the same VM

One of the downsides of the above approach is that the VM cannot also use the access port as a trunk, as all traffic will be re-tagged with a specific VLAN. If you want a VM to be on multiple networks with an access port, then you can mix and match by adding additional interfaces to it, some on the trunk with VLAN interfaces and others on access ports.

Text
                                     +-------------------+
+------+  +-------+                  |        VM         |
| eth0 |--|       |                  |   +-----------+   |
+------+  |       |   +----------+   |   | eth0.123  |   |
          | bond0 |---|   br0    |---|---| eth0.456  |   |
+------+  |       |   | (bridge) |   |   |  (VLANs)  |   |
| eth1 |--|       |   +----------+   |   +-----------+   |
+------+  +-------+        |         |                   |
                           |         |   +----------+    |
                           |         |   |   eth1   |    |
                           |         |   | (native) |    |
                           |         |   +----------+    |
                           |         +-------|-----------+
                           |                 |
                       +---------+   +----------+
                       | br0.789 |---|  br-789  |
                       | (VLAN)  |   | (bridge) |
                       +---------+   +----------+

OK, how do we do this?

Easy! I’m assuming that you already have a bridged interface (for example, br0) . If not, see this previous blog post.

  • Create a new bridge on the KVM host to carry tagged packets for the access port (for example, br-vlan123).
  • Create a new VLAN interface on our main bridge (for example, br0.123) to be used for the access port.
  • Attach the VLAN interface to our new bridge.
  • Bring the interfaces up.
  • Attach our VM to the new bridge.
Bash
nmcli con add ifname br-vlan123 type bridge con-name br-vlan123
nmcli con modify br-vlan123 ipv4.method disabled ipv6.method ignore
nmcli con up br-vlan123

nmcli con add type vlan con-name br0.123 ifname br0.123 dev br0 id 123
nmcli con modify br0.123 master br-vlan123 slave-type bridge
nmcli con up br0.123

,

Robert Collins — A moment of history

I’ve been asked more than once what it was like at the beginning of Ubuntu, before it was a company, when an email from someone I’d never heard of came into my mailbox.

We’re coming up on 20 years now since Ubuntu was founded, and I had cause to do some spelunking into IMAP archives recently… while there I took the opportunity to grab the very first email I received.

The Ubuntu long shot succeeded wildly. Of course, we liked to joke about how spammy those emails where: cold-calling a raft of Debian developers with job offers, some of them were closer to phishing attacks :). This very early one – I was the second employee (though I started at 4 days a week to transition my clients gradually) – was less so.

I think its interesting though to note how explicit a gamble this was framed as: a time limited experiment, funded for a year. As the company scaled this very rapidly became a hiring problem and the horizon had to be pushed out to 2 years to get folk to join.

And of course, while we started with arch in earnest, we rapidly hit significant usability problems, some of which were solvable with porcelain and shallow non-architectural changes, and we built initially patches, and then the bazaar VCS project to tackle those. But others were not: for instance, I recall exceeding the 32K hard link limit on ext3 due to a single long history during a VCS conversion. The sum of these challenges led us to create the bzr project, a ground up rethink of our version control needs, architecture, implementation and user-experience. While ultimately git has conquered all, bzr had – still has in fact – extremely loyal advocates, due to its laser sharp focus on usability.

Anyhow, here it is: one of the original no-name-here-yet, aka Ubuntu, introductory emails (with permission from Mark, of course). When I clicked through to the website Mark provided there was a link there to a fantastical website about a space tourist… not what I had expected to be reading in Adelaide during LCA 2004.


From: Mark Shuttleworth <xxx@xxx>
To: Robert Collins <xxx@xxx>
Date: Thu, 15 Jan 2004, 04:30

Tom Lord gave me your email address, I believe he’s
already sent you the email that I sent him so I’m sure
you have some background.

In short, I am going to fund some open source
development for a year. This is part of a new project
that I will be getting off the ground in the coming
weeks. I don’t know where it will lead, it’s flying in
the face of a stiff breeze but I think at the end of
the day it will at least fund a few very good open
source developers for a full year to work on the
projects they like most.

One of the pieces of the puzzle is high end source
code management. I’ll be looking to build an
infrastructure that will manage source code for
between 100 and 8000 open source projects (yes,
there’s a big difference between the two, I don’t know
at which end of the spectrum we will be at the end of
the year but our infrastructure will have to at least
be capable of scaling to the latter within two years)
with upwards of 2000 developers, drawing code from a
variety of sources, playing with it and spitting it
out regularly in nice packages.

Arch and Subversion seem to be the two leading
contenders for “next generation open source sccm”. I’d
be interested in your thoughts on the two of them, and
how they stack up. I’m looking to hire one person who
will lead that part of the effort. They’ll work alone
from home, and be responsible for two things. First,
extending the tool (arch or svn) in ways that help the
project. Such extensions will be released under an
open source licence, and hopefully embraced by the
tools maintainers and included in the mainline code
for the tool. And second, they will be responsible for
our large-scale implementation of SCCM, using that
tool, and building the management scripts and other
infrastructure to support such a large, and hopefully
highly automated, set of repositories.

Would you be interested in this position? What
attributes and experience do you think would make you
a great person to have on the team? What would your
salary expectation be, as a monthly figure, for a one
year contract full time?

I’m currently on your continent, well, just off it. On
Lizard Island, up North. Am headed today for Brisbane,
then on the 17th to Launceston via Melbourne. If you
happen to be on any of those stops, would you be
interested in meeting up to discuss it further?

If you’re curious you can find out a bit more about me
at www.markshuttleworth.com. This project is much
lower key than some of what you’ll find there. It’s a
very long shot indeed. But if at worst all that
happens is a bunch of open source work gets funded at
my expense I’ll feel it was money well spent.

Cheers,
Mark

=====
—
“Good judgement comes from experience, and often experience
comes from bad judgement” – Rita Mae Brown


,

Arjen Lentz — Classic McEleice and the NIST search for post-quantum crypto

I have always liked cryptography, and public-key cryptography in particularly. When Pretty Good Privacy (PGP) first came out in 1991, I not only started using it, also but looking at the documentation and the code to see how it worked. I created my own implementation in C using very small keys, just to better understand.

Cryptography has been running a race against both faster and cheaper computing power. And these days, with banking and most other aspects of our lives entirely relying on secure communications, it’s a very juicy target for bad actors.

About 5 years ago, the National (USA) Institute for Science and Technology (NIST) initiated a search for cryptographic algorithmic that should withstand a near-future world where quantum computers with a significant number of qubits are a reality. There have been a number of rounds, which mid 2020 saw round 3 and the finalists.

This submission caught my eye some time ago: Classic McEliece, and out of the four finalists it’s the only one that is not lattice-based [wikipedia link].

For Public Key Encryption and Key Exchange Mechanism, Prof Bill Buchanan thinks that the winner will be lattice-based, but I am not convinced.

Robert McEleice at his retirement in 2007

Tiny side-track, you may wonder where does the McEleice name come from? From mathematician Robert McEleice (1942-2019). McEleice developed his cryptosystem in 1978. So it’s not just named after him, he designed it. For various reasons that have nothing to do with the mathematical solidity of the ideas, it didn’t get used at the time. He’s done plenty cool other things, too. From his Caltech obituary:

He made fundamental contributions to the theory and design of channel codes for communication systems—including the interplanetary telecommunication systems that were used by the Voyager, Galileo, Mars Pathfinder, Cassini, and Mars Exploration Rover missions.

Back to lattices, there are both unknowns (aspects that have not been studied in exhaustive depth) and recent mathematical attacks, both of which create uncertainty – in the crypto sphere as well as for business and politics. Given how long it takes for crypto schemes to get widely adopted, the latter two are somewhat relevant, particularly since cyber security is a hot topic.

Lattices are definitely interesting, but given what we know so far, it is my feeling that systems based on lattices are more likely to be proven breakable than Classic McEleice, which come to this finalists’ table with 40+ years track record of in-depth analysis. Mind that all finalists are of course solid at this stage – but NIST’s thoughts on expected developments and breakthroughs is what is likely to decide the winner. NIST are not looking for shiny, they are looking for very very solid in all possible ways.

Prof Buchanan recently published implementations for the finalists, and did some benchmarks where we can directly compare them against each other.

We can see that Classic McEleice’s key generation is CPU intensive, but is that really a problem? The large size of its public key may be more of a factor (disadvantage), however the small ciphertext I think more than offsets that disadvantage.

As we’re nearing the end of the NIST process, in my opinion, fast encryption/decryption and small cyphertext, combined with the long track record of in-depth analysis, may still see Classic McEleice come out the winner.

The post Classic McEleice and the NIST search for post-quantum crypto first appeared on Lentz family blog.

,

Chris Neugebauer — Adding a PurpleAir monitor to Home Assistant

Living in California, I’ve (sadly) grown accustomed to needing to keep track of our local air quality index (AQI) ratings, particularly as we live close to places where large wildfires happen every other year.

Last year, Josh and I bought a PurpleAir outdoor air quality meter, which has been great. We contribute our data to a collection of very local air quality meters, which is important, since the hilly nature of the North Bay means that the nearest government air quality ratings can be significantly different to what we experience here in Petaluma.

I recently went looking to pull my PurpleAir sensor data into my Home Assistant setup. Unfortunately, the PurpleAir API does not return the AQI metric for air quality, only the raw PM2.5/PM5/PM10 numbers. After some searching, I found a nice template sensor solution on the Home Assistant forums, which I’ve modernised by adding the AQI as a sub-sensor, and adding unique ID fields to each useful sensor, so that you can assign them to a location.

You’ll end up with sensors for raw PM2.5, the PM2.5 AQI value, the US EPA air quality category, air pressure, relative humidity and air pressure.

How to use this

First up, visit the PurpleAir Map, find the sensor you care about, click “get this widget�, and then “JSON�. That will give you the URL to set as the resource key in purpleair.yaml.

Adding the configuration

In HomeAssistant, add the following line to your configuration.yaml:

sensor: !include purpleair.yaml

and then add the following contents to purpleair.yaml


 - platform: rest
   name: 'PurpleAir'

   # Substitute in the URL of the sensor you care about.  To find the URL, go
   # to purpleair.com/map, find your sensor, click on it, click on "Get This
   # Widget" then click on "JSON".
   resource: https://www.purpleair.com/json?key={KEY_GOES_HERE}&show={SENSOR_ID}

   # Only query once a minute to avoid rate limits:
   scan_interval: 60

   # Set this sensor to be the AQI value.
   #
   # Code translated from JavaScript found at:
   # https://docs.google.com/document/d/15ijz94dXJ-YAZLi9iZ_RaBwrZ4KtYeCy08goGBwnbCU/edit#
   value_template: >
     {{ value_json["results"][0]["Label"] }}
   unit_of_measurement: ""
   # The value of the sensor can't be longer than 255 characters, but the
   # attributes can.  Store away all the data for use by the templates below.
   json_attributes:
     - results

 - platform: template
   sensors:
     purpleair_aqi:
       unique_id: 'purpleair_SENSORID_aqi_pm25'
       friendly_name: 'PurpleAir PM2.5 AQI'
       value_template: >
         {% macro calcAQI(Cp, Ih, Il, BPh, BPl) -%}
           {{ (((Ih - Il)/(BPh - BPl)) * (Cp - BPl) + Il)|round|float }}
         {%- endmacro %}
         {% if (states('sensor.purpleair_pm25')|float) > 1000 %}
           invalid
         {% elif (states('sensor.purpleair_pm25')|float) > 350.5 %}
           {{ calcAQI((states('sensor.purpleair_pm25')|float), 500.0, 401.0, 500.0, 350.5) }}
         {% elif (states('sensor.purpleair_pm25')|float) > 250.5 %}
           {{ calcAQI((states('sensor.purpleair_pm25')|float), 400.0, 301.0, 350.4, 250.5) }}
         {% elif (states('sensor.purpleair_pm25')|float) > 150.5 %}
           {{ calcAQI((states('sensor.purpleair_pm25')|float), 300.0, 201.0, 250.4, 150.5) }}
         {% elif (states('sensor.purpleair_pm25')|float) > 55.5 %}
           {{ calcAQI((states('sensor.purpleair_pm25')|float), 200.0, 151.0, 150.4, 55.5) }}
         {% elif (states('sensor.purpleair_pm25')|float) > 35.5 %}
           {{ calcAQI((states('sensor.purpleair_pm25')|float), 150.0, 101.0, 55.4, 35.5) }}
         {% elif (states('sensor.purpleair_pm25')|float) > 12.1 %}
           {{ calcAQI((states('sensor.purpleair_pm25')|float), 100.0, 51.0, 35.4, 12.1) }}
         {% elif (states('sensor.purpleair_pm25')|float) >= 0.0 %}
           {{ calcAQI((states('sensor.purpleair_pm25')|float), 50.0, 0.0, 12.0, 0.0) }}
         {% else %}
           invalid
         {% endif %}
       unit_of_measurement: "bit"
     purpleair_description:
       unique_id: 'purpleair_SENSORID_description'
       friendly_name: 'PurpleAir AQI Description'
       value_template: >
         {% if (states('sensor.purpleair_aqi')|float) >= 401.0 %}
           Hazardous
         {% elif (states('sensor.purpleair_aqi')|float) >= 301.0 %}
           Hazardous
         {% elif (states('sensor.purpleair_aqi')|float) >= 201.0 %}
           Very Unhealthy
         {% elif (states('sensor.purpleair_aqi')|float) >= 151.0 %}
           Unhealthy
         {% elif (states('sensor.purpleair_aqi')|float) >= 101.0 %}
           Unhealthy for Sensitive Groups
         {% elif (states('sensor.purpleair_aqi')|float) >= 51.0 %}
           Moderate
         {% elif (states('sensor.purpleair_aqi')|float) >= 0.0 %}
           Good
         {% else %}
           undefined
         {% endif %}
       entity_id: sensor.purpleair
     purpleair_pm25:
       unique_id: 'purpleair_SENSORID_pm25'
       friendly_name: 'PurpleAir PM 2.5'
       value_template: "{{ state_attr('sensor.purpleair','results')[0]['PM2_5Value'] }}"
       unit_of_measurement: "μg/m3"
       entity_id: sensor.purpleair
     purpleair_temp:
       unique_id: 'purpleair_SENSORID_temperature'
       friendly_name: 'PurpleAir Temperature'
       value_template: "{{ state_attr('sensor.purpleair','results')[0]['temp_f'] }}"
       unit_of_measurement: "°F"
       entity_id: sensor.purpleair
     purpleair_humidity:
       unique_id: 'purpleair_SENSORID_humidity'
       friendly_name: 'PurpleAir Humidity'
       value_template: "{{ state_attr('sensor.purpleair','results')[0]['humidity'] }}"
       unit_of_measurement: "%"
       entity_id: sensor.purpleair
     purpleair_pressure:
       unique_id: 'purpleair_SENSORID_pressure'
       friendly_name: 'PurpleAir Pressure'
       value_template: "{{ state_attr('sensor.purpleair','results')[0]['pressure'] }}"
       unit_of_measurement: "hPa"
       entity_id: sensor.purpleair

Quirks

I had difficulty getting the AQI to display as a numeric graph when I didn’t set a unit. I went with bit, and that worked just fine. 🤷�♂�

,

Jan Schmidt — Rift CV1 – Getting close now…

It’s been a while since my last post about tracking support for the Oculus Rift in February. There’s been big improvements since then – working really well a lot of the time. It’s gone from “If I don’t make any sudden moves, I can finish an easy Beat Saber level” to “You can’t hide from me!” quality.

Equally, there are still enough glitches and corner cases that I think I’ll still be at this a while.

Here’s a video from 3 weeks ago of (not me) playing Beat Saber on Expert+ setting showing just how good things can be now:

Beat Saber – Skunkynator playing Expert+, Mar 16 2021

Strap in. Here’s what I’ve worked on in the last 6 weeks:

Pose Matching improvements

Most of the biggest improvements have come from improving the computer vision algorithm that’s matching the observed LEDs (blobs) in the camera frames to the 3D models of the devices.

I split the brute-force search algorithm into 2 phases. It now does a first pass looking for ‘obvious’ matches. In that pass, it does a shallow graph search of blobs and their nearest few neighbours against LEDs and their nearest neighbours, looking for a match using a “Strong” match metric. A match is considered strong if expected LEDs match observed blobs to within 1.5 pixels.

Coupled with checks on the expected orientation (matching the Gravity vector detected by the IMU) and the pose prior (expected position and orientation are within predicted error bounds) this short-circuit on the search is hit a lot of the time, and often completes within 1 frame duration.

In the remaining tricky cases, where a deeper graph search is required in order to recover the pose, the initial search reduces the number of LEDs and blobs under consideration, speeding up the remaining search.

I also added an LED size model to the mix – for a candidate pose, it tries to work out how large (in pixels) each LED should appear, and use that as a bound on matching blobs to LEDs. This helps reduce mismatches as devices move further from the camera.

LED labelling

When a brute-force search for pose recovery completes, the system now knows the identity of various blobs in the camera image. One way it avoids a search next time is to transfer the labels into future camera observations using optical-flow tracking on the visible blobs.

The problem is that even sped-up the search can still take a few frame-durations to complete. Previously LED labels would be transferred from frame to frame as they arrived, but there’s now a unique ID associated with each blob that allows the labels to be transferred even several frames later once their identity is known.

IMU Gyro scale

One of the problems with reverse engineering is the guesswork around exactly what different values mean. I was looking into why the controller movement felt “swimmy” under fast motions, and one thing I found was that the interpretation of the gyroscope readings from the IMU was incorrect.

The touch controllers report IMU angular velocity readings directly as a 16-bit signed integer. Previously the code would take the reading and divide by 1024 and use the value as radians/second.

From teardowns of the controller, I know the IMU is an Invensense MPU-6500. From the datasheet, the reported value is actually in degrees per second and appears to be configured for the +/- 2000 °/s range. That yields a calculation of Gyro-rad/s = Gyro-°/s * (2000 / 32768) * (?/180) – or a divisor of 938.734.

The 1024 divisor was under-estimating rotation speed by about 10% – close enough to work until you start moving quickly.

Limited interpolation

If we don’t find a device in the camera views, the fusion filter predicts motion using the IMU readings – but that quickly becomes inaccurate. In the worst case, the controllers fly off into the distance. To avoid that, I added a limit of 500ms for ‘coasting’. If we haven’t recovered the device pose by then, the position is frozen in place and only rotation is updated until the cameras find it again.

Exponential filtering

I implemented a 1-Euro exponential smoothing filter on the output poses for each device. This is an idea from the Project Esky driver for Project North Star/Deck-X AR headsets, and almost completely eliminates jitter in the headset view and hand controllers shown to the user. The tradeoff is against introducing lag when the user moves quickly – but there are some tunables in the exponential filter to play with for minimising that. For now I’ve picked some values that seem to work reasonably.

Non-blocking radio

Communications with the touch controllers happens through USB radio command packets sent to the headset. The main use of radio commands in OpenHMD is to read the JSON configuration block for each controller that is programmed in at the factory. The configuration block provides the 3D model of LED positions as well as initial IMU bias values.

Unfortunately, reading the configuration block takes a couple of seconds on startup, and blocks everything while it’s happening. Oculus saw that problem and added a checksum in the controller firmware. You can read the checksum first and if it hasn’t changed use a local cache of the configuration block. Eventually, I’ll implement that caching mechanism for OpenHMD but in the meantime it still reads the configuration blocks on each startup.

As an interim improvement I rewrote the radio communication logic to use a state machine that is checked in the update loop – allowing radio communications to be interleaved without blocking the regularly processing of events. It still interferes a bit, but no longer causes a full multi-second stall as each hand controller turns on.

Haptic feedback

The hand controllers have haptic feedback ‘rumble’ motors that really add to the immersiveness of VR by letting you sense collisions with objects. Until now, OpenHMD hasn’t had any support for applications to trigger haptic events. I spent a bit of time looking at USB packet traces with Philipp Zabel and we figured out the radio commands to turn the rumble motors on and off.

In the Rift CV1, the haptic motors have a mode where you schedule feedback events into a ringbuffer – effectively they operate like a low frequency audio device. However, that mode was removed for the Rift S (and presumably in the Quest devices) – and deprecated for the CV1.

With that in mind, I aimed for implementing the unbuffered mode, with explicit ‘motor on + frequency + amplitude’ and ‘motor off’ commands sent as needed. Thanks to already having rewritten the radio communications to use a state machine, adding haptic commands was fairly easy.

The big question mark is around what API OpenHMD should provide for haptic feedback. I’ve implemented something simple for now, to get some discussion going. It works really well and adds hugely to the experience. That code is in the https://github.com/thaytan/OpenHMD/tree/rift-haptics branch, with a SteamVR-OpenHMD branch that uses it in https://github.com/thaytan/SteamVR-OpenHMD/tree/controller-haptics-wip

Problem areas

Unexpected tracking losses

I’d say the biggest problem right now is unexpected tracking loss and incorrect pose extractions when I’m not expecting them. Especially my right controller will suddenly glitch and start jumping around. Looking at a video of the debug feed, it’s not obvious why that’s happening:

To fix cases like those, I plan to add code to log the raw video feed and the IMU information together so that I can replay the video analysis frame-by-frame and investigate glitches systematically. Those recordings will also work as a regression suite to test future changes.

Sensor fusion efficiency

The Kalman filter I have implemented works really nicely – it does the latency compensation, predicts motion and extracts sensor biases all in one place… but it has a big downside of being quite expensive in CPU. The Unscented Kalman Filter CPU cost grows at O(n^3) with the size of the state, and the state in this case is 43 dimensional – 22 base dimensions, and 7 per latency-compensation slot. Running 1000 updates per second for the HMD and 500 for each of the hand controllers adds up quickly.

At some point, I want to find a better / cheaper approach to the problem that still provides low-latency motion predictions for the user while still providing the same benefits around latency compensation and bias extraction.

Lens Distortion

To generate a convincing illusion of objects at a distance in a headset that’s only a few centimetres deep, VR headsets use some interesting optics. The LCD/OLED panels displaying the output get distorted heavily before they hit the users eyes. What the software generates needs to compensate by applying the right inverse distortion to the output video.

Everyone that tests the CV1 notices that the distortion is not quite correct. As you look around, the world warps and shifts annoyingly. Sooner or later that needs fixing. That’s done by taking photos of calibration patterns through the headset lenses and generating a distortion model.

Camera / USB failures

The camera feeds are captured using a custom user-space UVC driver implementation that knows how to set up the special synchronisation settings of the CV1 and DK2 cameras, and then repeatedly schedules isochronous USB packet transfers to receive the video.

Occasionally, some people experience failure to re-schedule those transfers. The kernel rejects them with an out-of-memory error failing to set aside DMA memory (even though it may have been running fine for quite some time). It’s not clear why that happens – but the end result at the moment is that the USB traffic for that camera dies completely and there’ll be no more tracking from that camera until the application is restarted.

Often once it starts happening, it will keep happening until the PC is rebooted and the kernel memory state is reset.

Occluded cases

Tracking generally works well when the cameras get a clear shot of each device, but there are cases like sighting down the barrel of a gun where we expect that the user will line up the controllers in front of one another, and in front of the headset. In that case, even though we probably have a good idea where each device is, it can be hard to figure out which LEDs belong to which device.

If we already have a good tracking lock on the devices, I think it should be possible to keep tracking even down to 1 or 2 LEDs being visible – but the pose assessment code will have to be aware that’s what is happening.

Upstreaming

April 14th marks 2 years since I first branched off OpenHMD master to start working on CV1 tracking. How hard can it be, I thought? I’ll knock this over in a few months.

Since then I’ve accumulated over 300 commits on top of OpenHMD master that eventually all need upstreaming in some way.

One thing people have expressed as a prerequisite for upstreaming is to try and remove the OpenCV dependency. The tracking relies on OpenCV to do camera distortion calculations, and for their PnP implementation. It should be possible to reimplement both of those directly in OpenHMD with a bit of work – possibly using the fast LambdaTwist P3P algorithm that Philipp Zabel wrote, that I’m already using for pose extraction in the brute-force search.

Others

I’ve picked the top issues to highlight here. https://github.com/thaytan/OpenHMD/issues has a list of all the other things that are still on the radar for fixing eventually.

Other Headsets

At some point soon, I plan to put a pin in the CV1 tracking and look at adapting it to more recent inside-out headsets like the Rift S and WMR headsets. I implemented 3DOF support for the Rift S last year, but getting to full positional tracking for that and other inside-out headsets means implementing a SLAM/VIO tracking algorithm to track the headset position.

Once the headset is tracking, the code I’m developing here for CV1 to find and track controllers will hopefully transfer across – the difference with inside-out tracking is that the cameras move around with the headset. Finding the controllers in the actual video feed should work much the same.

Sponsorship

This development happens mostly in my spare time and partly as open source contribution time at work at Centricular. I am accepting funding through Github Sponsorships to help me spend more time on it – I’d really like to keep helping Linux have top-notch support for VR/AR applications. Big thanks to the people that have helped get this far.

,

BlueHackers — World bipolar day 2021

Today, 30 March, is World Bipolar Day.

Vincent van Gogh - Worn Out

Why that particular date? It’s Vincent van Gogh’s birthday (1853), and there is a fairly strong argument that the Dutch painter suffered from bipolar (among other things).

The image on the side is Vincent’s drawing “Worn Out” (from 1882), and it seems to capture the feeling rather well – whether (hypo)manic, depressed, or mixed. It’s exhausting.

Bipolar is complicated, often undiagnosed or misdiagnosed, and when only treated with anti-depressants, it can trigger the (hypo)mania – essentially dragging that person into that state near-permanently.

Have you heard of Bipolar II?

Hypo-mania is the “lesser” form of mania that distinguishes Bipolar I (the classic “manic depressive” syndrome) from Bipolar II. It’s “lesser” only in the sense that rather than someone going so hyper they may think they can fly (Bipolar I is often identified when someone in manic state gets admitted to hospital – good catch!) while with Bipolar II the hypo-mania may actually exhibit as anger. Anger in general, against nothing in particular but potentially everyone and everything around them. Or, if it’s a mixed episode, anger combined with strong negative thoughts. Either way, it does not look like classic mania. It is, however, exhausting and can be very debilitating.

Bipolar II people often present to a doctor while in depressed state, and GPs (not being psychiatrists) may not do a full diagnosis. Note that D.A.S. and similar test sheets are screening tools, they are not diagnostic. A proper diagnosis is more complex than filling in a form some questions (who would have thought!)

Call to action

If you have a diagnosis of depression, only from a GP, and are on medication for this, I would strongly recommend you also get a referral to a psychiatrist to confirm that diagnosis.

Our friends at the awesome Black Dog Institute have excellent information on bipolar, as well as a quick self-test – if that shows some likelihood of bipolar, go get that referral and follow up ASAP.

I will be writing more about the topic in the coming time.

The post World bipolar day 2021 first appeared on BlueHackers.org.

,

Jan Schmidt — Rift CV1 – Testing SteamVR

Update:

This post documented an older method of building SteamVR-OpenHMD. I moved them to a page here. That version will be kept up to date for any future changes, so go there.


I’ve had a few people ask how to test my OpenHMD development branch of Rift CV1 positional tracking in SteamVR. Here’s what I do:

  • Make sure Steam + SteamVR are already installed.
  • Clone the SteamVR-OpenHMD repository:
git clone --recursive https://github.com/ChristophHaag/SteamVR-OpenHMD.git
  • Switch the internal copy of OpenHMD to the right branch:
cd subprojects/openhmd
git remote add thaytan-github https://github.com/thaytan/OpenHMD.git
git fetch thaytan-github
git checkout -b rift-kalman-filter thaytan-github/rift-kalman-filter
cd ../../
  • Use meson to build and register the SteamVR-OpenHMD binaries. You may need to install meson first (see below):
meson -Dbuildtype=release build
ninja -C build
./install_files_to_build.sh
./register.sh
  • It is important to configure in release mode, as the kalman filtering code is generally too slow for real-time in debug mode (it has to run 2000 times per second)
  • Make sure your USB devices are accessible to your user account by configuring udev. See the OpenHMD guide here: https://github.com/OpenHMD/OpenHMD/wiki/Udev-rules-list
  • Please note – only Rift sensors on USB 3.0 ports will work right now. Supporting cameras on USB 2.0 requires someone implementing JPEG format streaming and decoding.
  • It can be helpful to test OpenHMD is working by running the simple example. Check that it’s finding camera sensors at startup, and that the position seems to change when you move the headset:
./build/subprojects/openhmd/openhmd_simple_example
  • Calibrate your expectations for how well tracking is working right now! Hint: It’s very experimental 🙂
  • Start SteamVR. Hopefully it should detect your headset and the light(s) on your Rift Sensor(s) should power on.

Meson

I prefer the Meson build system here. There’s also a cmake build for SteamVR-OpenHMD you can use instead, but I haven’t tested it in a while and it sometimes breaks as I work on my development branch.

If you need to install meson, there are instructions here – https://mesonbuild.com/Getting-meson.html summarising the various methods.

I use a copy in my home directory, but you need to make sure ~/.local/bin is in your PATH

pip3 install --user meson

,

Jan Schmidt — Rift CV1 – Pose rejection

I spent some time this weekend implementing a couple of my ideas for improving the way the tracking code in OpenHMD filters and rejects (or accepts) possible poses when trying to match visible LEDs to the 3D models for each device.

In general, the tracking proceeds in several steps (in parallel for each of the 3 devices being tracked):

  1. Do a brute-force search to match LEDs to 3D models, then (if matched)
    1. Assign labels to each LED blob in the video frame saying what LED they are.
    2. Send an update to the fusion filter about the position / orientation of the device
  2. Then, as each video frame arrives:
    1. Use motion flow between video frames to track the movement of each visible LED
    2. Use the IMU + vision fusion filter to predict the position/orientation (pose) of each device, and calculate which LEDs are expected to be visible and where.
  3. Try and match up and refine the poses using the predicted pose prior and labelled LEDs. In the best case, the LEDs are exactly where the fusion predicts they’ll be. More often, the orientation is mostly correct, but the position has drifted and needs correcting. In the worst case, we send the frame back to step 1 and do a brute-force search to reacquire an object.

The goal is to always assign the correct LEDs to the correct device (so you don’t end up with the right controller in your left hand), and to avoid going back to the expensive brute-force search to re-acquire devices as much as possible

What I’ve been working on this week is steps 1 and 3 – initial acquisition of correct poses, and fast validation / refinement of the pose in each video frame, and I’ve implemented two new strategies for that.

Gravity Vector matching

The first new strategy is to reject candidate poses that don’t closely match the known direction of gravity for each device. I had a previous implementation of that idea which turned out to be wrong, so I’ve re-worked it and it helps a lot with device acquisition.

The IMU accelerometer and gyro can usually tell us which way up the device is (roll and pitch) but not which way they are facing (yaw). The measure for ‘known gravity’ comes from the fusion Kalman filter covariance matrix – how certain the filter is about the orientation of the device. If that variance is small this new strategy is used to reject possible poses that don’t have the same idea of gravity (while permitting rotations around the Y axis), with the filter variance as a tolerance.

Partial tracking matches

The 2nd strategy is based around tracking with fewer LED correspondences once a tracking lock is acquired. Initial acquisition of the device pose relies on some heuristics for how many LEDs must match the 3D model. The general heuristic threshold I settled on for now is that 2/3rds of the expected LEDs must be visible to acquire a cold lock.

With the new strategy, if the pose prior has a good idea where the device is and which way it’s facing, it allows matching on far fewer LED correspondences. The idea is to keep tracking a device even down to just a couple of LEDs, and hope that more become visible soon.

While this definitely seems to help, I think the approach can use more work.

Status

With these two new approaches, tracking is improved but still quite erratic. Tracking of the headset itself is quite good now and for me rarely loses tracking lock. The controllers are better, but have a tendency to “fly off my hands” unexpectedly, especially after fast motions.

I have ideas for more tracking heuristics to implement, and I expect a continuous cycle of refinement on the existing strategies and new ones for some time to come.

For now, here’s a video of me playing Beat Saber using tonight’s code. The video shows the debug stream that OpenHMD can generate via Pipewire, showing the camera feed plus overlays of device predictions, LED device assignments and tracked device positions. Red is the headset, Green is the right controller, Blue is the left controller.

Initial tracking is completely wrong – I see some things to fix there. When the controllers go offline due to inactivity, the code keeps trying to match LEDs to them for example, and then there are some things wrong with how it’s relabelling LEDs when they get incorrect assignments.

After that, there are periods of good tracking with random tracking losses on the controllers – those show the problem cases to concentrate on.

,

Colin Charles — Life with Rona 2.0 – Days 4, 5, 6, 7, 8 and 9

These lack of updates are also likely because I’ve been quite caught up with stuff.

Monday I had a steak from Bay Leaf Steakhouse for dinner. It was kind of weird eating it from packs, but then I’m reminded you could do this in economy class. Tuesday I wanted to attempt to go vegetarian and by the time I was done with a workout, the only place was a chap fan shop (Leong Heng) where I had a mixture of Chinese and Indian chap fan. The Indian stall is run by an ex-Hyatt staff member who immediately recognised me! Wednesday, Alice came to visit, so we got to Hanks, got some alcohol, and managed a smorgasbord of food from Pickers/Sate Zul/Lila Wadi. Night ended very late, and on Thursday, visited Hai Tian for their famous salted egg squid and prawns in a coconut shell. Friday was back to being normal, so I grabbed a pizza from Mint Pizza (this time I tried their Aussie variant). Saturday, today, I hit up Rasa Sayang for some matcha latte, but grabbed food from Classic Pilot Cafe, which Faeeza owns! It was the famous salted egg chicken, double portion, half rice.

As for workouts, I did sign up for Mantas but found it pretty hard to do, timezone wise. I did spend a lot of time jogging on the beach (this has been almost a daily affair). Monday I also did 2 MD workouts, Tuesday 1 MD workout, Wednesday half a MD workout, Thursday I did a Ping workout at Pwrhouse (so good!), Friday 1 MD workout, and Saturday an Audrey workout at Pwrhouse and 1 MD workout.

Wednesday I also found out that Rasmus passed away. Frankly, there are no words.

Thursday, my Raspberry Pi 400 arrived. I set it up in under ten minutes, connecting it to the TV here. It “just works”. I made a video, which I should probably figure out how to upload to YouTube after I stitch it together. I have to work on using it a lot more.

COVID-19 cases are through the roof in Malaysia. This weekend we’ve seen two days of case breaking records, with today being 5,728 (yesterday was something close). Nutty. Singapore suspended the reciprocal green lane (RGL) agreement with Malaysia for the next 3 months.

I’ve managed to finish Bridgerton. I like the score. Finding something on Netflix is proving to be more difficult, regardless of having a VPN. Honestly, this is why Cable TV wins… linear programming that you’re just fed.

Stock market wise, I’ve been following the GameStop short squeeze, and even funnier is the Top Glove one, that they’re trying to repeat in Malaysia. Bitcoin seems to be doing “reasonably well” and I have to say, I think people are starting to realise decentralised services have a future. How do we get there?

What an interesting week, I look forward to more productive time. I’m still writing in my Hobonichi Techo, so at least that’s where most personal stuff ends up, I guess?

,

Jan Schmidt — Hitting a milestone – Beat Saber!

I hit an important OpenHMD milestone tonight – I completed a Beat Saber level using my Oculus Rift CV1!

I’ve been continuing to work on integrating Kalman filtering into OpenHMD, and on improving the computer vision that matches and tracks device LEDs. While I suspect noone will be completing Expert levels just yet, it’s working well enough that I was able to play through a complete level of Beat Saber. For a long time this has been my mental benchmark for tracking performance, and I’m really happy 🙂

Check it out:

I should admit at this point that completing this level took me multiple attempts. The tracking still has quite a tendency to lose track of controllers, or to get them confused and swap hands suddenly.

I have a list of more things to work on. See you at the next update!

,

Colin Charles — Life with Rona 2.0 – Day 3

What an unplanned day. I woke up in time to do an MD workout, despite feeling a little sore. So maybe I was about 10 minutes late and I missed the first set, but his workouts are so long, and I think there were seven sets anyway. Had a good brunch shortly thereafter.

Did a bit of reading, and then I decided to do a beach boardwalk walk… turns out they were policing the place, and you can’t hit the boardwalk. But the beach is fair game? So I went back to the hotel, dropped off my slippers, and went for a beach jog. Pretty nutty.

Came back to read a little more and figured I might as well do another MD workout. Then I headed out for dinner, trying out a new place — Mint Pizza. Opened 20.12.2020, and they’re empty, and their pizza is actually pretty good. Lamb and BBQ chicken, they did half-and-half.

Twitter was discussing Raspberry Pi’s, and all I could see is a lot of misinformation, which is truly shocking. The irony is that open source has been running the Internet for so long, and progressive web apps have come such a long way…

Back in the day when I did OpenOffice.org or Linux training even, we always did say you should learn concepts and not tools. From the time we ran Linux installfests in the late-90s in Sunway Pyramid (back then, yes, Linux was hard, and you had winmodems), but I had forgotten that I even did stuff for school teachers and NGOs back in 2002… I won’t forget PC Gemilang either…

Anyway, I placed an order again for another Raspberry Pi 400. I am certain that most people talk so much crap, without realising that Malaysia isn’t a developed nation and most people can’t afford a Mac let alone a PC. Laptops aren’t cheap. And there are so many other issues…. Saying Windows is still required in 2021 is the nuttiest thing I’ve heard in a long time. Easy to tweet, much harder to think about TCO, and realise where in the journey Malaysia is.

Maybe the best thing was that Malaysian Twitter learned about technology. I doubt many realised the difference between a Pi board vs the 400, but hey, the fact that they talked about tech is still a win (misinformed, but a win).

,

Sam Watkins — Developing CZ, a dialect of C that looks like Python

In my experience, the C programming language is still hard to beat, even 50 years after it was first developed (and I feel the same way about UNIX). When it comes to general-purpose utility, low-level systems programming, performance, and portability (even to tiny embedded systems), I would choose C over most modern or fashionable alternatives. In some cases, it is almost the only choice.

Many developers believe that it is difficult to write secure and reliable software in C, due to its free pointers, the lack of enforced memory integrity, and the lack of automatic memory management; however in my opinion it is possible to overcome these risks with discipline and a more secure system of libraries constructed on top of C and libc. Daniel J. Bernstein and Wietse Venema are two developers who have been able to write highly secure, stable, reliable software in C.

My other favourite language is Python. Although Python has numerous desirable features, my favourite is the light-weight syntax: in Python, block structure is indicated by indentation, and braces and semicolons are not required. Apart from the pleasure and relief of reading and writing such light and clear code, which almost appears to be executable pseudo-code, there are many other benefits. In C or JavaScript, if you omit a trailing brace somewhere in the code, or insert an extra brace somewhere, the compiler may tell you that there is a syntax error at the end of the file. These errors can be annoying to track down, and cannot occur in Python. Python not only looks better, the clear syntax helps to avoid errors.

The obvious disadvantage of Python, and other dynamic interpreted languages, is that most programs run extremely slower than C programs. This limits the scope and generality of Python. No AAA or performance-oriented video game engines are programmed in Python. The language is not suitable for low-level systems programming, such as operating system development, device drivers, filesystems, performance-critical networking servers, or real-time systems.

C is a great all-purpose language, but the code is uglier than Python code. Once upon a time, when I was experimenting with the Plan 9 operating system (which is built on C, but lacks Python), I missed Python’s syntax, so I decided to do something about it and write a little preprocessor for C. This converts from a “Pythonesque” indented syntax to regular C with the braces and semicolons. Having forked a little dialect of my own, I continued from there adding other modules and features (which might have been a mistake, but it has been fun and rewarding).

At first I called this translator Brace, because it added in the braces for me. I now call the language CZ. It sounds like “C-easy”. Ease-of-use for developers (DX) is the primary goal. CZ has all of the features of C, and translates cleanly into C, which is then compiled to machine code as normal (using any C compiler; I didn’t write one); and so CZ has the same features and performance as C, but enjoys a more pleasing syntax.

CZ is now self-hosted, in that the translator is written in the language CZ. I confess that originally I wrote most of it in Perl; I’m proficient at Perl, but I consider it to be a fairly ugly language, and overly complicated.

I intend for CZ’s new syntax to be “optional”, ideally a developer will be able to choose to use the normal C syntax when editing CZ, if they prefer it. For this, I need a tool to convert C back to CZ, which I have not fully implemented yet. I am aware that, in addition to traditionalists, some vision-impaired developers prefer to use braces and semicolons, as screen readers might not clearly indicate indentation. A C to CZ translator would of course also be valuable when porting an existing C program to CZ.

CZ has a number of useful features that are not found in standard C, but I did not go so far as C++, which language has been described as “an octopus made by nailing extra legs onto a dog”. I do not consider C to be a dog, at least not in a negative sense; but I think that C++ is not an improvement over plain C. I am creating CZ because I think that it is possible to improve on C, without losing any of its advantages or making it too complex.

One of the most interesting features I added is a simple syntax for fast, light coroutines. I based this on Simon Tatham’s approach to Coroutines in C, which may seem hacky at first glance, but is very efficient and can work very well in practice. I implemented a very fast web server with very clean code using these coroutines. The cost of switching coroutines with this method is little more than the cost of a function call.

CZ has hygienic macros. The regular cpp (C preprocessor) macros are not hygenic and many people consider them hacky and unsafe to use. My CZ macros are safe, and somewhat more powerful than standard C macros. They can be used to neatly add new program control structures. I have plans to further develop the macro system in interesting ways.

I added automatic prototype and header generation, as I do not like having to repeat myself when copying prototypes to separate header files. I added support for the UNIX #! scripting syntax, and for cached executables, which means that CZ can be used like a scripting language without having to use a separate compile or make command, but the programs are only recompiled when something has been changed.

For CZ, I invented a neat approach to portability without conditional compilation directives. Platform-specific library fragments are automatically included from directories having the name of that platform or platform-category. This can work very well in practice, and helps to avoid the nightmare of conditional compilation, feature detection, and Autotools. Using this method, I was able easily to implement portable interfaces to features such as asynchronous IO multiplexing (aka select / poll).

The CZ library includes flexible error handling wrappers, inspired by W. Richard Stevens’ wrappers in his books on Unix Network Programming. If these wrappers are used, there is no need to check return values for error codes, and this makes the code much safer, as an error cannot accidentally be ignored.

CZ has several major faults, which I intend to correct at some point. Some of the syntax is poorly thought out, and I need to revisit it. I developed a fairly rich library to go with the language, including safer data structures, IO, networking, graphics, and sound. There are many nice features, but my CZ library is more prototype than a finished product, there are major omissions, and some features are misconceived or poorly implemented. The misfeatures should be weeded out for the time-being, or moved to an experimental section of the library.

I think that a good software library should come in two parts, the essential low-level APIs with the minimum necessary functionality, and a rich set of high-level convenience functions built on top of the minimal API. I need to clearly separate these two parts in order to avoid polluting the namespaces with all sorts of nonsense!

CZ is lacking a good modern system of symbol namespaces. I can look to Python for a great example. I need to maintain compatibility with C, and avoid ugly symbol encodings. I think I can come up with something that will alleviate the need to type anything like gtk_window_set_default_size, and yet maintain compatibility with the library in question. I want all the power of C, but it should be easy to use, even for children. It should be as easy as BASIC or Processing, a child should be able to write short graphical demos and the like, without stumbling over tricky syntax or obscure compile errors.

Here is an example of a simple CZ program which plots the Mandelbrot set fractal. I think that the program is fairly clear and easy to understand, although there is still some potential to improve and clarify the code.

#!/usr/local/bin/cz --
use b
use ccomplex

Main:
	num outside = 16, ox = -0.5, oy = 0, r = 1.5
	long i, max_i = 50, rb_i = 30
	space()
	uint32_t *px = pixel()  # CONFIGURE!
	num d = 2*r/h, x0 = ox-d*w_2, y0 = oy+d*h_2
	for(y, 0, h):
		cmplx c = x0 + (y0-d*y)*I
		repeat(w):
			cmplx w = c
			for i=0; i < max_i && cabs(w) < outside; ++i
				w = w*w + c
			*px++ = i < max_i ? rainbow(i*359 / rb_i % 360) : black
			c += d

I wrote a more elaborate variant of this program, which generates images like the one shown below. There are a few tricks used: continuous colouring, rainbow colours, and plotting the logarithm of the iteration count, which makes the plot appear less busy close to the black fractal proper. I sell some T-shirts and other products with these fractal designs online.

An image from the Mandelbrot set, generated by a fairly simple CZ program.

I am interested in graph programming, and have been for three decades since I was a teenager. By graph programming, I mean programming and modelling based on mathematical graphs or diagrams. I avoid the term visual programming, because there is no necessary reason that vision impaired folks could not use a graph programming language; a graph or diagram may be perceived, understood, and manipulated without having to see it.

Mathematics is something that naturally exists, outside time and independent of our universe. We humans discover mathematics, we do not invent or create it. One of my main ideas for graph programming is to represent a mathematical (or software) model in the simplest and most natural way, using relational operators. Elementary mathematics can be reduced to just a few such operators:

+add, subtract, disjoint union, zero
×multiply, divide, cartesian product, one
^power, root, logarithm
◢sin, cos, sin-1, cos-1, hypot, atan2
δdifferential, integral
a set of minimal relational operators for elementary math

I think that a language and notation based on these few operators (and similar) can be considerably simpler and more expressive than conventional math or programming languages.

CZ is for me a stepping-stone toward this goal of an expressive relational graph language. It is more pleasant for me to develop software tools in CZ than in C or another language.

Thanks for reading. I wrote this article during the process of applying to join Toptal, which appears to be a freelancing portal for top developers; and in response to this article on toptal: After All These Years, the World is Still Powered by C Programming.

My CZ project has been stalled for quite some time. I foolishly became discouraged after receiving some negative feedback. I now know that honest negative feedback should be valued as an opportunity to improve, and I intend to continue the project until it lacks glaring faults, and is useful for other people. If this project or this article interests you, please contact me and let me know. It is much more enjoyable to work on a project when other people are actively interested in it!

,

Jan Schmidt — Rift CV1 – Adventures in Kalman filtering Part 2

In the last post I had started implementing an Unscented Kalman Filter for position and orientation tracking in OpenHMD. Over the Christmas break, I continued that work.

A Quick Recap

When reading below, keep in mind that the goal of the filtering code I’m writing is to combine 2 sources of information for tracking the headset and controllers.

The first piece of information is acceleration and rotation data from the IMU on each device, and the second is observations of the device position and orientation from 1 or more camera sensors.

The IMU motion data drifts quickly (at least for position tracking) and can’t tell which way the device is facing (yaw, but can detect gravity and get pitch/roll).

The camera observations can tell exactly where each device is, but arrive at a much lower rate (52Hz vs 500/1000Hz) and can take a long time to process (hundreds of milliseconds) to analyse to acquire or re-acquire a lock on the tracked device(s).

The goal is to acquire tracking lock, then use the motion data to predict the motion closely enough that we always hit the ‘fast path’ of vision analysis. The key here is closely enough – the more closely the filter can track and predict the motion of devices between camera frames, the better.

Integration in OpenHMD

When I wrote the last post, I had the filter running as a standalone application, processing motion trace data collected by instrumenting a running OpenHMD app and moving my headset and controllers around. That’s a really good way to work, because it lets me run modifications on the same data set and see what changed.

However, the motion traces were captured using the current fusion/prediction code, which frequently loses tracking lock when the devices move – leading to big gaps in the camera observations and more interpolation for the filter.

By integrating the Kalman filter into OpenHMD, the predictions are improved leading to generally much better results. Here’s one trace of me moving the headset around reasonably vigourously with no tracking loss at all.

Headset motion capture trace

If it worked this well all the time, I’d be ecstatic! The predicted position matched the observed position closely enough for every frame for the computer vision to match poses and track perfectly. Unfortunately, this doesn’t happen every time yet, and definitely not with the controllers – although I think the latter largely comes down to the current computer vision having more troubler matching controller poses. They have fewer LEDs to match against compared to the headset, and the LEDs are generally more side-on to a front-facing camera.

Taking a closer look at a portion of that trace, the drift between camera frames when the position is interpolated using the IMU readings is clear.

Headset motion capture – zoomed in view

This is really good. Most of the time, the drift between frames is within 1-2mm. The computer vision can only match the pose of the devices to within a pixel or two – so the observed jitter can also come from the pose extraction, not the filtering.

The worst tracking is again on the Z axis – distance from the camera in this case. Again, that makes sense – with a single camera matching LED blobs, distance is the most uncertain part of the extracted pose.

Losing Track

The trace above is good – the computer vision spots the headset and then the filtering + computer vision track it at all times. That isn’t always the case – the prediction goes wrong, or the computer vision fails to match (it’s definitely still far from perfect). When that happens, it needs to do a full pose search to reacquire the device, and there’s a big gap until the next pose report is available.

That looks more like this

Headset motion capture trace with tracking errors

This trace has 2 kinds of errors – gaps in the observed position timeline during full pose searches and erroneous position reports where the computer vision matched things incorrectly.

Fixing the errors in position reports will require improving the computer vision algorithm and would fix most of the plot above. Outlier rejection is one approach to investigate on that front.

Latency Compensation

There is inherent delay involved in processing of the camera observations. Every 19.2ms, the headset emits a radio signal that triggers each camera to capture a frame. At the same time, the headset and controller IR LEDS light up brightly to create the light constellation being tracked. After the frame is captured, it is delivered over USB over the next 18ms or so and then submitted for vision analysis. In the fast case where we’re already tracking the device the computer vision is complete in a millisecond or so. In the slow case, it’s much longer.

Overall, that means that there’s at least a 20ms offset between when the devices are observed and when the position information is available for use. In the plot above, this delay is ignored and position reports are fed into the filter when they are available. In the worst case, that means the filter is being told where the headset was hundreds of milliseconds earlier.

To compensate for that delay, I implemented a mechanism in the filter where it keeps extra position and orientation entries in the state that can be used to retroactively apply the position observations.

The way that works is to make a prediction of the position and orientation of the device at the moment the camera frame is captured and copy that prediction into the extra state variable. After that, it continues integrating IMU data as it becomes available while keeping the auxilliary state constant.

When a the camera frame analysis is complete, that delayed measurement is matched against the stored position and orientation prediction in the state and the error used to correct the overall filter. The cool thing is that in the intervening time, the filter covariance matrix has been building up the right correction terms to adjust the current position and orientation.

Here’s a good example of the difference:

Before: Position filtering with no latency compensation
After: Latency-compensated position reports

Notice how most of the disconnected segments have now slotted back into position in the timeline. The ones that haven’t can either be attributed to incorrect pose extraction in the compute vision, or to not having enough auxilliary state slots for all the concurrent frames.

At any given moment, there can be a camera frame being analysed, one arriving over USB, and one awaiting “long term” analysis. The filter needs to track an auxilliary state variable for each frame that we expect to get pose information from later, so I implemented a slot allocation system and multiple slots.

The downside is that each slot adds 6 variables (3 position and 3 orientation) to the covariance matrix on top of the 18 base variables. Because the covariance matrix is square, the size grows quadratically with new variables. 5 new slots means 30 new variables – leading to a 48 x 48 covariance matrix instead of 18 x 18. That is a 7-fold increase in the size of the matrix (48 x 48 = 2304 vs 18 x 18 = 324) and unfortunately about a 10x slow-down in the filter run-time.

At that point, even after some optimisation and vectorisation on the matrix operations, the filter can only run about 3x real-time, which is too slow. Using fewer slots is quicker, but allows for fewer outstanding frames. With 3 slots, the slow-down is only about 2x.

There are some other possible approaches to this problem:

  • Running the filtering delayed, only integrating IMU reports once the camera report is available. This has the disadvantage of not reporting the most up-to-date estimate of the user pose, which isn’t great for an interactive VR system.
  • Keeping around IMU reports and rewinding / replaying the filter for late camera observations. This limits the overall increase in filter CPU usage to double (since we at most replay every observation twice), but potentially with large bursts when hundreds of IMU readings need replaying.
  • It might be possible to only keep 2 “full” delayed measurement slots with both position and orientation, and to keep some position-only slots for others. The orientation of the headset tends to drift much more slowly than position does, so when there’s a big gap in the tracking it would be more important to be able to correct the position estimate. Orientation is likely to still be close to correct.
  • Further optimisation in the filter implementation. I was hoping to keep everything dependency-free, so the filter implementation uses my own naive 2D matrix code, which only implements the features needed for the filter. A more sophisticated matrix library might perform better – but it’s hard to say without doing some testing on that front.

Controllers

So far in this post, I’ve only talked about the headset tracking and not mentioned controllers. The controllers are considerably harder to track right now, but most of the blame for that is in the computer vision part. Each controller has fewer LEDs than the headset, fewer are visible at any given moment, and they often aren’t pointing at the camera front-on.

Oculus Camera view of headset and left controller.

This screenshot is a prime example. The controller is the cluster of lights at the top of the image, and the headset is lower left. The computer vision has gotten confused and thinks the controller is the ring of random blue crosses near the headset. It corrected itself a moment later, but those false readings make life very hard for the filtering.

Position tracking of left controller with lots of tracking loss.

Here’s a typical example of the controller tracking right now. There are some very promising portions of good tracking, but they are interspersed with bursts of tracking losses, and wild drifting from the computer vision giving wrong poses – leading to the filter predicting incorrect acceleration and hence cascaded tracking losses. Particularly (again) on the Z axis.

Timing Improvements

One of the problems I was looking at in my last post is variability in the arrival timing of the various USB streams (Headset reports, Controller reports, camera frames). I improved things in OpenHMD on that front, to use timestamps from the devices everywhere (removing USB timing jitter from the inter-sample time).

There are still potential problems in when IMU reports from controllers get updated in the filters vs the camera frames. That can be on the order of 2-4ms jitter. Time will tell how big a problem that will be – after the other bigger tracking problems are resolved.

Sponsorships

All the work that I’m doing implementing this positional tracking is a combination of my free time, hours contributed by my employer Centricular and contributions from people via Github Sponsorships. If you’d like to help me spend more hours on this and fewer on other paying work, I appreciate any contributions immensely!

Next Steps

The next things on my todo list are:

  • Integrate the delayed-observation processing into OpenHMD (at the moment it is only in my standalone simulator).
  • Improve the filter code structure – this is my first kalman filter and there are some implementation decisions I’d like to revisit.
  • Publish the UKF branch for other people to try.
  • Circle back to the computer vision and look at ways to improve the pose extraction and better reject outlying / erroneous poses, especially for the controllers.
  • Think more about how to best handle / schedule analysis of frames from multiple cameras. At the moment each camera operates as a separate entity, capturing frames and analysing them in threads without considering what is happening in other cameras. That means any camera that can’t see a particular device starts doing full pose searches – which might be unnecessary if another camera still has a good view of the device. Coordinating those analyses across cameras could yield better CPU consumption, and let the filter retain fewer delayed observation slots.

,

Hamish Taylor — Wattlebird feeding

While I hope to update this site again soon, here’s a photo I captured over the weekend in my back yard. The red flowering plant is attracting wattlebirds and honey-eaters. This wattlebird stayed still long enough for me to take this shot. After a little bit of editing, I think it has turned out rather well.

Photo taken with: Canon 7D Mark II & Canon 55-250mm lens.

Edited in Lightroom and Photoshop (to remove a sun glare spot off the eye).

Wattlebird feeding

,

Chris Neugebauer — Talk Notes: Practicality Beats Purity: The Zen Of Python’s Escape Hatch?

I gave the talk Practicality Beats Purity: The Zen of Python’s Escape Hatch as part of PyConline AU 2020, the very online replacement for PyCon AU this year. In that talk, I included a few interesting links code samples which you may be interested in:

@apply

def apply(transform):

    def __decorator__(using_this):
        return transform(using_this)

    return __decorator__


numbers = [1, 2, 3, 4, 5]

@apply(lambda f: list(map(f, numbers)))
def squares(i):
  return i * i

print(list(squares))

# prints: [1, 4, 9, 16, 25]

Init.java

public class Init {
  public static void main(String[] args) {
    System.out.println("Hello, World!")
  }
}

@switch and @case

__NOT_A_MATCHER__ = object()
__MATCHER_SORT_KEY__ = 0

def switch(cls):

    inst = cls()
    methods = []

    for attr in dir(inst):
        method = getattr(inst, attr)
        matcher = getattr(method, "__matcher__", __NOT_A_MATCHER__)

        if matcher == __NOT_A_MATCHER__:
            continue

        methods.append(method)

    methods.sort(key = lambda i: i.__matcher_sort_key__)

    for method in methods:
        matches = method.__matcher__()
        if matches:
            return method()

    raise ValueError(f"No matcher matches value {test_value}")

def case(matcher):

    def __decorator__(f):
        global __MATCHER_SORT_KEY__

        f.__matcher__ = matcher
        f.__matcher_sort_key__ = __MATCHER_SORT_KEY__
        __MATCHER_SORT_KEY__ += 1
        return f

    return __decorator__



if __name__ == "__main__":
    for i in range(100):

        @switch
        class FizzBuzz:

            @case(lambda: i % 15 == 0)
            def fizzbuzz(self):
                return "fizzbuzz"

            @case(lambda: i % 3 == 0)
            def fizz(self):
                return "fizz"

            @case(lambda: i % 5 == 0)
            def buzz(self):
                return "buzz"

            @case(lambda: True)
            def default(self):
                return "-"

        print(f"{i} {FizzBuzz}")

,

Craig Sanders — Fuck Grey Text

fuck grey text on white backgrounds
fuck grey text on black backgrounds
fuck thin, spindly fonts
fuck 10px text
fuck any size of anything in px
fuck font-weight 300
fuck unreadable web pages
fuck themes that implement this unreadable idiocy
fuck sites that don’t work without javascript
fuck reactjs and everything like it

thank fuck for Stylus. and uBlock Origin. and uMatrix.

Fuck Grey Text is a post from: Errata

,

Hamish Taylor — Blog: A new beginning

Earlier today I launched this site. It is the result of a lot of work over the past few weeks. It began as an idea to publicise some of my photos, and morphed into the site you see now, including a store and blog that I’ve named “Photekgraddft”.

In the weirdly named blog, I want to talk about photography, the stories behind some of my more interesting shots, the gear and software I use, my technology career, my recent ADHD diagnosis and many other things.

This scares me quite a lot. I’ve never really put myself out onto the internet before. If you Google me, you’re not going to find anything much. Google Images has no photos of me. I’ve always liked it that way. Until now.

ADHD’ers are sometimes known for “oversharing”, one of the side-effects of the inability to regulate emotions well. I’ve always been the opposite, hiding, because I knew I was different, but didn’t understand why.

The combination of the COVID-19 pandemic and my recent ADHD diagnosis have given me a different perspective. I now know why I hid. And now I want to engage, and be engaged, in the world.

If I can be a force for positive change, around people’s knowledge and opinion of ADHD, then I will.

If talking about Business Analysis (my day job), and sharing my ideas for optimising organisations helps anyone at all, then I will.

If I can show my photos and brighten someone’s day by allowing them to enjoy a sunset, or a flying bird, then I will.

And if anyone buys any of my photos, then I will be shocked!

So welcome to my little vanity project. I hope it can be something positive, for me, if for noone else in this new, odd world in which we now find ourselves living together.

,

,

,

Chris Neugebauer — Reflecting on 10 years of not having to update WordPress

Over the weekend, the boredom of COVID-19 isolation motivated me to move my personal website from WordPress on a self-managed 10-year-old virtual private server to a generated static site on a static site hosting platform with a content delivery network.

This decision was overdue. WordPress never fit my brain particularly well, and it was definitely getting to a point where I wasn’t updating my website at all (my last post was two weeks before I moved from Hobart; I’ve been living in Petaluma for more than three years now).

Settling on which website framework wasn’t a terribly difficult choice (I chose Jekyll, everyone else seems to be using it), and I’ve had friends who’ve had success moving their blogs over. The difficulty I ended up facing was that the standard exporter that everyone to move from WordPress to Jekyll uses does not expect Debian’s package layout.

Backing up a bit: I made a choice, 10 years ago, to deploy WordPress on a machine that I ran myself, using the Debian system wordpress package, a simple aptitude install wordpress away. That decision was not particularly consequential then, but it chewed up 3 hours of my time on Saturday.

Why? The exporter plugin assumes that it will be able to find all of the standard WordPress files in the usual WordPress places, and when it didn’t find that, it broke in unexpected ways. And why couldn’t it find it?

Debian makes packaging choices that prioritise all the software on a system living side-by-side with minimal difficulty. It sets strict permissions. It separates application code from configuration from user data (which in the case of WordPress, includes plugins), in a way that is consistent between applications. This choice makes it easy for Debian admins to understand how to find bits of an application. It also minimises the chance of one PHP application from clobbering another.

10 years later, the install that I had set up was still working, having survived 3-4 Debian versions, and so 3-4 new WordPress versions. I don’t recall the last time I had to think about keeping my WordPress instance secure and updated. That’s quite a good run. I’ve had a working website despite not caring about keeping it updated for at least three years.

The same decisions that meant I spent 3 hours on Saturday doing a simple WordPress export saved me a bunch of time that I didn’t incrementally spend over the course a decade. Am I even? I have no idea.

Anyway, the least I can do is provide some help to people who might run into this same problem, so here’s a 5-step howto.

How to migrate a Debian WordPress site to Jekyll

Should you find the Jekyll exporter not working on your Debian WordPress install:

  1. Use the standard WordPress export to export an XML feel of your site.
  2. Spin up a new instance of WordPress (using WordPress.com, or on a new Virtual Private Server, whatever, really).
  3. Import the exported XML feed.
  4. Install the Jekyll exporter plugin.
  5. Follow the documentation and receive a Jekyll export of your site.

Basically, the plugin works with a stock WordPress install. If you don’t have one of those, it’s easy to move it over.

,

Andrew Ruthven — Install Fedora CoreOS using FAI

I've spent the last couple of days trying to deploy Fedora CoreOS to some physical hardware/bare metal for a colleague using the official PXE installer from Fedora CoreOS. It wasn't very pleasant, and just wouldn't work reliably.

Maybe my expectations were to high, in that I thought I could use Ignition to prepare more of the system for me, as my colleague has been able to bare metal installs correctly. I just tried to use Ignition as documented.

A few interesting aspects I encountered:

  1. The PXE installer for it has a 618MB initrd file. This takes quite a while to transfer via tftp!
  2. It can't build software RAID for the main install device (and the developers have no intention of adding this), and it seems very finicky to build other RAID sets for other partitions.
  3. And, well, I just kept having problems where the built systems would hang during boot for no obvious reason.
  4. The time to do an installation was incredibly long.
  5. The initrd image is really just running coreos-installer against the nominated device.

During the night I got feed up with that process and wrote a Fully Automatic Installer (FAI) profile that'd install CoreOS instead. I can now use setup-storage from FAI using it's standard disk_config files. This allows me to build complicated disk configurations with software RAID and LVM easily.

A big bonus is that a rebuild is a lot faster, timed from typing reboot to a fresh login prompt is 10 minutes - and this is on physical hardware so includes BIOS POST and RAID controller set up, twice each.

I thought this might be of interest to other people, so the FAI profile I developed for this is located here: https://github.com/catalyst-cloud/fai-profile-fedora-coreos

FAI was initially developed to deploy Debian systems, it has since been extended to be able to install a number of other operating systems, however I think this is a good example of how easy it is to deploy non-Debian derived operating systems using FAI without having to modify FAI itself.

,

Robert Collins — Strength training from home

For the last year I’ve been incrementally moving away from lifting static weights and towards body weight based exercises, or callisthenics. I’ve been doing this for a number of reasons, including better avoidance of injury (if I collapse, the entire stack is dynamic, if a bar held above my head drops on me, most of the weight is just dead weight – ouch), accessibility during travel – most hotel gyms are very poor, and functional relevance – I literally never need to put 100 kg on my back, but I do climb stairs, for instance.

Covid-19 shutting down the gym where I train is a mild inconvenience for me as a result, because even though I don’t do it, I am able to do nearly all my workouts entirely from home. And I thought a post about this approach might be of interest to other folk newly separated from their training facilities.

I’ve gotten most of my information from a few different youtube channels:

There are many more channels out there, and I encourage you to go and look and read and find out what works for you. Those 5 are my greatest hits, if you will. I’ve bought the FitnessFAQs exercise programs to help me with my my training, and they are indeed very effective.

While you don’t need a gymnasium, you do need some equipment, particularly if you can’t go and use a local park. Exactly what you need will depend on what you choose to do – for instance, doing dips on the edge of a chair can avoid needing any equipment, but doing them with some portable parallel bars can be much easier. Similarly, doing pull ups on the edge of a door frame is doable, but doing them with a pull-up bar is much nicer on your fingers.

Depending on your existing strength you may not need bands, but I certainly did. Buying rings is optional – I love them, but they aren’t needed to have a good solid workout.

I bought parallettes for working on the planche.undefined Parallel bars for dips and rows.undefined A pull-up bar for pull-ups and chin-ups, though with the rings you can add flys, rows, face-pulls, unstable push-ups and more. The rings. And a set of 3 bands that combine for 7 different support amounts.undefinedundefined

In terms of routine, I do a upper/lower split, with 3 days on upper body, one day off, one day on lower, and the weekends off entirely. I was doing 2 days on lower body, but found I was over-training with Aikido later that same day.

On upper body days I’ll do (roughly) chin ups or pull ups, push ups, rows, dips, hollow body and arch body holds, handstands and some grip work. Today, as I write this on Sunday evening, 2 days after my last training day on Friday, I can still feel my lats and biceps from training Friday afternoon. Zero issue keeping the intensity up.

For lower body, I’ll do pistol squats, nordic drops, quad extensions, wall sits, single leg calf raises, bent leg calf raises. Again, zero issues hitting enough intensity to achieve growth / strength increases. The only issue at home is having a stable enough step to get a good heel drop for the calf raises.

If you haven’t done bodyweight training at all before, when starting, don’t assume it will be easy – even if you’re a gym junkie, our bodies are surprisingly heavy, and there’s a lot of resistance just moving them around.

Good luck, train well!

OpenSTEM — Online Teaching

The OpenSTEMâ„¢ materials are ideally suited to online teaching. In these times of new challenges and requirements, there are a lot of technological possibilities. Schools and teachers are increasingly being asked to deliver material online to students. Our materials can assist with that process, especially for Humanities and Science subjects from Prep/Kindy/Foundation to Year 6. […]

The post Online Teaching first appeared on OpenSTEM.

Brendan Scott — Covid 19 Numbers – lag

Recording some thoughts about Covid 19 numbers.

Today’s figures

The Government says:

“As at 6.30am on 22 March 2020, there have been 1,098 confirmed cases of COVID-19 in Australia”.

The reference is https://www.health.gov.au/news/health-alerts/novel-coronavirus-2019-ncov-health-alert/coronavirus-covid-19-current-situation-and-case-numbers. However, that page is updated daily (ish), so don’t expect it to be the same if you check the reference.

Estimating Lag

If a person tests positive to the virus today, that means they were infected at some time in the past. So, what is the lag between infection and a positive test result?

Incubation Lag – about 5 days

When you are infected you don’t show symptoms immediately. Rather, there’s an incubation period before symptoms become apparent.  The time between being infected and developing symptoms varies from person to person, but most of the time a person shows symptoms after about 5 days (I recall seeing somewhere that 1 in a 1000 cases will develop symptoms after 14 days).

Presentation Lag – about 2 days

I think it’s fair to also assume that people are not presenting at testing immediately they become ill. It is probably taking them a couple of days from developing symptoms to actually get to the doctor – I read a story somewhere (have since lost the reference) about a young man who went to a party, then felt bad for days but didn’t go for a test until someone else from the party had returned a positive test.  Let’s assume there’s a mix of worried well and stoic types and call it 2 days from becoming symptomatic to seeking a test.

Referral Lag – about a day

Assuming that a GP is available straight away and recommends a test immediately, logistically there will still be most of a day taken up between deciding to see a doctor and having a test carried out.

Testing lag – about 2 days

The graph of infections “epi graph” today looks like this:

200322_new-and-cumulative-covid-19-cases-in-australia-by-notification-date_1

One thing you notice about the graph is that the new cases bars seem to increase for a couple of days, then decrease – so about 100 new cases in the last 24 hours, but almost 200 in the 24 hours before that. From the graph, the last 3 “dips” have been today (Sunday), last Thursday and last Sunday.  This seems to be happening every 3 to 4 days. I initially thought that the dips might mean fewer (or more) people presenting over weekends, but the period is inconsistent with that. I suspect, instead, that this actually means that testing is being batched.

That would mean that neither the peaks nor troughs is representative of infection surges/retreats, but is simply reflecting when tests are being processed. This seems to be a 4 day cycle, so, on average it seems that it would be about 2 days between having the test conducted and receiving a result. So a confirmed case count published today is actually showing confirmed cases as at about 2 days earlier.

Total lag

From the date someone is infected to the time that they receive a positive confirmation is about:

lag = time for symptoms to show+time to seek a test+referral time + time for the test to return a result

So, the published figures on confirmed infections are probably lagging actual infections in the community by about 10 days (5+2+1+2).

If there’s about a 10 day lag between infection and confirmation, then what a figure published today says is that about a week and a half ago there were about this many cases in the community.  So, the 22 March figure of 1098 infections is actually really a 12 March figure.

What the lag means for Physical (ie Social) Distancing

The main thing that the lag means is that if we were able to wave a magic wand today and stop all further infections, we would continue to record new infections for about 10 days (and the tail for longer). In practical terms, implementing physical distancing measures will not show any effect on new cases for about a week and a half. That’s because today there are infected people who are yet to be tested.

The silver lining to that is that the physical distancing measures that have been gaining prominence since 15 March should start to show up in the daily case numbers from the middle of the coming week, possibly offset by overseas entrants rushing to make the 20 March entry deadline.

Estimating Actual Infections as at Today

How many people are infected, but unconfirmed as at today? To estimate actual infections you’d need to have some idea of the rate at which infections are increasing. For example, if infections increased by 10% per day for 10 days, then you’d multiply the most recent figure by 1.1 raised to the power of 10 (ie about 2.5).  Unfortunately, the daily rate of increase (see table on the wiki page) has varied a fair bit (from 20% to 27%) over the most recent 10 days of data (that is, over the 10 days prior to 12 March, since the 22 March figures roughly correspond to 12 March infections) and there’s no guarantee that since that time the daily increase in infections will have remained stable, particularly in light of the implementation of physical distancing measures. At 23.5% per day, the factor is about 8.

There aren’t any reliable figures we can use to estimate the rate of infection during the current lag period (ie from 12 March to 22 March). This is because the vast majority of cases have not been from unexplained community transmission. Most of the cases are from people who have been overseas in the previous fortnight and they’re the cohort that has been most significantly impacted by recent physical distancing measures. From 15 March, they have been required to self isolate and from 20 March most of their entry into the country has stopped.  So I’d expect a surge in numbers up to about 30 March – ie reflecting infections in the cohort of people rushing to get into the country before the borders closed followed by a flattening. With the lag factor above, you’ll need to wait until 1 April or thereabouts to know for sure.

Note:

This post is just about accounting for the time lag between becoming infected and receiving a positive test result. It assumes, for example, that everyone who is infected seeks a test, and that everyone who is infected and seeks a test is, in fact, tested. As at today, neither of these things is true.

,

Clinton Roy — lca2020 ReWatch 2020-02-02

As I was an organiser of the conference this year, I didn’t get to see many talks, fortunately many of the talks were recorded, so i get to watch the conference well after the fact.

Conference Opening

That white balance on the lectern slides is indeed bad, I really should get around to adding this as a suggestion on the logos documentation. (With some help, I put up all the lectern covers, it was therapeutic and rush free).

I actually think there was a lot of information in this introduction. Perhaps too much?

OpenZFS and Linux

A nice update on where zfs is these days.

Dev/Ops relationships, status: It’s Complicated

A bit of  a war story about production systems, leading to a moment of empathy.

Samba 2020: Why are we still in the 1980s for authentication?

There are a lot of old security standards that are showing there age, there are a lot of modern security standards, but which to choose?

Tyranny of the Clock

A very interesting problem solving adventure, with a few nuggets of interesting information about tools and techniques.

Configuration Is (riskier than?) Code

Because configuration files are parsed by a program, and the program changes how it runs depending on the contents of that configuration file, every program that parses configuration files is basically an interpreter, and thus every configuration file is basically a program. So, configuation is code, and we should be treating configuration like we do code, e.g. revision control, commenting, testing, review.

Easy Geo-Redundant Handover + Failover with MARS + systemd

Using a local process organiser to handle a cluster, interesting, not something I’d really promote. Not the best video cutting in this video, lots of time with the speaker pointing to his slides offscreen.

 

,

Robert Collins — 2019 in the rearview

2019 was a very busy year for us. I hadn’t realised how busy it was until I sat down to write this post. There’s also some moderately heavy stuff in here – if you have topics that trigger you, perhaps make sure you have spoons before reading.

We had all the usual stuff. Movies – my top two were Alita and Abominable though the Laundromat and Ford v Ferrari were both excellent and moving pieces. I introduced Cynthia to Teppanyaki and she fell in love with having egg roll thrown at her face hole.

When Cynthia started school we dropped gymnastics due to the time overload – we wanted some downtime for her to process after school, and with violin having started that year she was just looking so tired after a full day of school we felt it was best not to have anything on. Then last year we added in a specific learning tutor to help with the things that she approaches differently to the other kids in her class, giving 2 days a week of extra curricular activity after we moved swimming to the weekends.

At the end of last year she was finally chipper and with it most days after school, and she had been begging to get into more stuff, so we all got together and negotiated drama class and Aikido.

The drama school we picked, HSPA, is pretty amazing. Cynthia adored her first teacher there, and while upset at a change when they rearranged classes slightly, is again fully engaged and thrilled with her time there. Part of the class is putting on a full scale production – they did a version of the Happy Prince near the end of term 3 – and every student gets a part, with the ability for the older students to audition for more parts. On the other hand she tells me tonight that she wants to quit. So shrug, who knows :).

I last did martial arts when I took Aikido with sensei Darren Friend at Aikido Yoshinkai NSW back in Sydney, in the late 2000’s. And there was quite a bit less of me then. Cynthia had been begging to take a martial art for about 4 years, and we’d said that when she was old enough, we’d sign her up, so this year we both signed up for Aikido at the Rangiora Aikido Dojo. The Rangiora dojo is part of the NZ organisation Aikido Shinryukan which is part of the larger Aikikai style, which is quite different, yet the same, as the Yoshinkai Aikido that I had been learning. There have been quite a few moments where I have had to go back to something core – such as my stance – and unlearn it, to learn the Aikikai technique. Cynthia has found the group learning dynamic a bit challenging – she finds the explanations – needed when there are twenty kids of a range of ages and a range of experience – from new intakes each term through to ones that have been doing it for 5 or so years – get boring, and I can see her just switch off. Then she misses the actual new bit of information she didn’t have previously :(. Which then frustrates her. But she absolutely loves doing it, and she’s made a couple of friends there (everyone is positive and friendly, but there are some girls that like to play with her after the kids lesson). I have gotten over the body disconnect and awkwardness and things are starting to flow, I’m starting to be able to reason about things without just freezing in overload all the time, so that’s not bad after a year. However, the extra weight is making my forward rolls super super awkward. I can backward roll easily, with moderately good form; forward rolls though my upper body strength is far from what’s needed to support my weight through the start of the roll – my arm just collapses – so I’m in a sort of limbo – if I get the moment just right I can just start the contact on the shoulder; but if I get the moment slightly wrong, it hurts quite badly. And since I don’t want large scale injuries, doing the higher rolls is very unnerving for me. I suspect its 90% psychological, but am not sure how to get from where I am to having confidence in my technique, other than rinse-and-repeat. My hip isn’t affecting training much, and sensei Chris seems to genuinely like training with Cynthia and I, which is very nice: we feel welcomed and included in the community.

Speaking of my hip – earlier this year something ripped cartilage in my right hip – ended up having to have an MRI scan – and those machines sound exactly like a dot matrix printer – to diagnose it. Interestingly, having the MRI improved my symptoms, but we are sadly in hurry-up-and-wait mode. Before the MRI, I’d wake up at night with some soreness, and my right knee bent, foot on the bed, then sleepily let my leg collapse sideways to the right – and suddenly be awake in screaming agony as the joint opened up with every nerve at its disposal. When the MRI was done, they pumped the joint full of local anaesthetic for two purposes – one is to get a clean read on the joint, and the second is so that they can distinguish between referred surrounding pain, vs pain from the joint itself. It is to be expected with a joint issue that the local will make things feel better (duh), for up to a day or so while the local dissipates. The expression on the specialists face when I told him that I had had a permanent improvement trackable to the MRI date was priceless. Now, when I wake up with joint pain, and my leg sleepily falls back to the side, its only mildly uncomfortable, and I readjust without being brought to screaming awakeness. Similarly, early in Aikido training many activities would trigger pain, and now there’s only a couple of things that do. In another 12 or so months if the joint hasn’t fully healed, I’ll need to investigate options such as stem cells (which the specialist was negative about) or steroids (which he was more negative about) or surgery (which he was even more negative about). My theory about the improvement is that the cartilage that was ripped was sitting badly and the inflation for the MRI allowed it to settle back into the appropriate place (and perhaps start healing better). I’m told that reducing inflammation systematically is a good option. Turmeric time.

Sadly Cynthia has had some issues at school – she doesn’t fit the average mould and while wide spread bullying doesn’t seem to be a thing, there is enough of it, and she receives enough of it that its impacted her happiness more than a little – this blows up in school and at home as well. We’ve been trying a few things to improve this – helping her understand why folk behave badly, what to do in the moment (e.g. this video), but also that anything that goes beyond speech is assault and she needs to report that to us or teachers no matter what.

We’ve also had some remarkably awful interactions with another family at the school. We thought we had a friendly relationship, but I managed to trigger a complete meltdown of the relationship – not by doing anything objectively wrong, but because we had (unknown to me) different folkways, and some perfectly routine and normal behaviour turned out to be stressful and upsetting to them, and then they didn’t discuss it with us at all until it had brewed up in their heads into a big mess… and its still not resolved (and may not ever be: they are avoiding us both).

I weighed in at 110kg this morning. Jan the 4th 2019 I was 130.7kg. Feb 1 2018 I was 115.2kg. This year I peaked at 135.4kg, and got down to 108.7kg before Christmas food set in. That’s pretty happy making all things considered. Last year I was diagnosed with Coitus headaches and though I didn’t know it the medicine I was put on has a known side effect of weight gain. And it did – I had put it down to ongoing failure to manage my diet properly, but once my weight loss doctor gave me an alternative prescription for the headaches, I was able to start losing weight immediately. Sadly, though the weight gain through 2018 was effortless, losing the weight through 2019 was not. Doable, but not effortless. I saw a neurologist for the headaches when they recurred in 2019, and got a much more informative readout on them, how to treat and so on – basically the headaches can be thought of as an instability in the system, and the medicines goal is to stabilise things, and once stable for a decent period, we can attempt to remove the crutch. Often that’s successful, sometimes not, sometimes its successful on a second or third time. Sometimes you’re stuck with it forever. I’ve been eating a keto / LCHF diet – not super strict keto, though Jonie would like me to be on that, I don’t have the will power most of the time – there’s a local truck stop that sells killer hotdogs. And I simply adore them.

I started this year working for one of the largest companies on the planet – VMware. I left there in February and wrote a separate post about that. I followed that job with nearly the polar opposite – a startup working on a blockchain content distribution system. I wrote about that too. Changing jobs is hard in lots of ways – for instance I usually make friendships at my jobs, and those suffer some when you disappear to a new context – not everyone makes connections with you outside of the job context. Then there’s the somewhat non-rational emotional impact of not being in paid employment. The puritans have a lot to answer for. I’m there again, looking for work (and hey, if you’re going to be at Linux.conf.au (Gold Coast Australia January 13-17) I’ll be giving a presentation about some of the interesting things I got up to in the last job interregnum I had.

My feet have been giving me trouble for a couple of years now. My podiatrist is reasonably happy with my progress – and I can certainly walk further than I could – I even did some running earlier in the year, until I got shin splints. However, I seem to have hyper sensitive soles, so she can’t correct my pro-nation until we fix that, which at least for now means a 5 minute session where I touch my feet, someone else does, then something smooth then something rough – called “sensory massage”.

In 2017 and 2018 I injured myself at the gym, and in 2019 I wanted to avoid that, so I sought out ways to reduce injury. Moving away from machines was a big part of that; more focus on technique another part. But perhaps the largest part was moving from lifting dead weight to focusing on body weight exercises – callisthenics. This shifts from a dead weight to control when things go wrong, to an active weight, which can help deal with whatever has happened. So far at least, this has been pretty successful – although I’ve had minor issues – I managed to inflame the fatty pad the olecranon displaces when your elbow locks out – I’m nearly entirely transitioned to a weights-free program – hand stands, pistol squats, push ups, dead hangs and so on. My upper body strength needs to come along some before we can really go places though… and we’re probably going to max out the hamstring curl machine (at least for regular two-leg curls) before my core is strong enough to do a Nordic drop.

Lynne has been worried about injuring herself with weight lifting at the gym for some time now, but recently saw my physio – Ben Cameron at Pegasus PhysioSouth – who is excellent, and he suggested that she could have less chronic back pain if she took weights back up again. She’s recently told me that I’m allowed one ‘told you so’ about this, since she found herself in a spot where previously she would have put herself in a poor lifting position, but the weight training gave her a better option and she intuitively used it, avoiding pain. So that’s a good thing – complicated because of her bodies complicated history, but an excellent trainer and physio team are making progress.

Earlier this year she had a hell of a fright, with a regular eye checkup getting referred into a ‘you are going blind; maybe tomorrow, maybe within 10 years’ nightmare scenario. Fortunately a second opinion got a specialist who probably knows the same amount but was willing to communicate it with actual words… Lynne has a condition which diabetes (type I or II) can affect, and she has a vein that can alter state somewhat arbitrarily but will probably only degrade slowly, particularly if Lynne’s diet is managed as she has been doing.

Diet wise, Lynne also has been losing some weight but this is complicated by her chronic idiopathic pancreatitis. That’s code for ‘it keeps happening and we don’t know why’ pancreatitis. We’ve consulted a specialist in the North Island who comes highly recommended by Lynne’s GP, who said that rapid weight loss is a little known but possible cause of pancreatitis – and that fits the timelines involved. So Lynne needs to lose weight to manage the onset of type II diabetes. But not to fast, to avoid pancreatitis, which will hasten the onset of type II diabetes. Aiee. Slow but steady – she’s working with the same doctor I am for that, and a similar diet, though lower on the fats as she has no gall… bladder.

In April our kitchen waste pipe started chronically blocking, and investigation with a drain robot revealed a slump in the pipe. Ground penetrating radar reveal an anomaly under the garage… and this escalated. We’re going to have to move out of the house for a week while half the house’s carpets are lifted, grout is pumped into the foundations to tighten it all back up again – and hopefully they don’t over pump it – and then it all gets replaced. Oh, and it looks like the drive will be replaced again, to fix the slumped pipe permanently. It will be lovely when done but right now we’re facing a wall of disruption and argh.

Around September I think, we managed to have a gas poisoning scare – our gas hob was left on and triggered a fireball which fortunately only scared Lynne rather than flambéing her face. We did however not know how much exposure we’d had to the LPG, nor to partially combusted gas – which produces toxic CO as a by-product, so there was a trip into the hospital for observation with Cynthia, with Lynne opting out. Lynne and Cynthia had had plenty of the basic symptoms – headaches, dizziness and so on at the the time, but after waiting for 2 hours in the ER queue that had faded. Le sigh. The hospital, bless their cotton socks don’t have the necessary equipment to diagnose CO poisoning without a pretty invasive blood test, but still took Cynthia’s vitals using methods (manual observation and a infra-red reader) that are confounded by the carboxyhemoglobin that forms from the CO that has been inhaled. Pretty unimpressed – our GP was livid. (This is one recommended protocol). Oh, and our gas hob when we got checked out – as we were not sure if we had left it on, or it had been misbehaving, turned out to have never been safe, got decertified and the pipe cut at the regulator. So we’re cooking on a portable induction hob for now.

When we moved to Rangiora I was travelling a lot more, Christchurch itself had poorer air quality than Rangiora, and our financial base was a lot smaller. Now, Rangiora’s population has gone up nearly double (13k to 19k conservatively – and that’s ignoring the surrounds that use Rangiora as a base), we have more to work with, the air situation in Christchurch has improved massively, and even a busy years travel is less than I was doing before Cynthia came along. We’re looking at moving – we’re not sure where yet; maybe more country, maybe more city.

One lovely bright spot over the last few years has been reconnecting with friends from school, largely on Facebook – some of whom I had forgotten that I knew back at school – I had a little clique but was not very aware of the wider school population in hindsight (this was more than a little embarrassing to me, as I didn’t want to blurt out “who are you?!”) – and others whom I had not :). Some of these reconnections are just light touch person-X exists and cares somewhat – and that’s cool. One in particular has grown into a deeper friendship than we had back as schoolkids, and I am happy and grateful that that has happened.

Our cats are fat and happy. Well mostly. Baggy is fat and stressed and spraying his displeasure everywhere whenever the stress gets too much :(. Cynthia calls him Mr Widdlepants. The rest of the time he cuddles and purrs and is generally happy with life. Dibbler and Kitten-of-the-wild are relatively fine with everything.

Cynthia’s violin is coming along well. She did a small performance for her classroom (with her teacher) and wowed them. I’ve been inspired to start practising trumpet again. After 27 years of decay my skills are decidedly rusty, but they are coming along. Finding arrangements for violin + trumpet is a bit challenging, and my sight-reading-with-transposition struggles to cope, but we make do. Lynne is muttering about getting a clarinet or drum-kit and joining in.

So, 2019. Whew. I hope yours was less stressful and had as many or more bright points than ours. Onwards to 2020.