<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="rss.xsl" media="screen"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
  <title>iio.ie</title>
  <description>a rambling personal blog of a techie</description>
  <link>http://iio.ie</link>
  <ttl>1380</ttl>
  <atom:link rel="self" href="https://iio.ie/rss" type="application/rss+xml"/>
  <item><title>techbunker: I wish small computer clubs existed</title><pubDate>Mon, 06 Apr 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;When I was a kid, I used to hang out at a cybercafe near my aunt&amp;#39;s place.
Places like this were later dubbed &amp;#34;internet cafes&amp;#34;, but back then the internet wasn&amp;#39;t really a thing.
The machines were connected to each other and people played Quake II, Red Alert II, and typical LAN games like that.
Lacking pocket money, I mostly just watched other people playing.
It was quite fun.
Such local play has a completely different vibe than online gaming.&lt;/p&gt;

&lt;p&gt;Later I hung out at my high school&amp;#39;s computer room.
This was much better because I didn&amp;#39;t need to pay to use the machines there.
But the machines were older and not connected to a network.
We mostly played local multiplayer games.
Eventually we got more powerful, networked machines and then we could play shooters too.
We had a blast!&lt;/p&gt;

&lt;p&gt;These experiences died down during my university years even though the university had a much bigger computer lab.
I don&amp;#39;t fully remember why.
We probably started having our own laptops, so we could play anywhere we wanted, and thus we didn&amp;#39;t need the lab machines.
Maybe we had rules against installing games too.&lt;/p&gt;

&lt;p&gt;But at university I had another local activity: programming contests like ACM.
Those were thrilling too: you tried hard to solve the problems and afterwards you hung around and discussed solutions.
Even I could somewhat socialize there as a person who is otherwise incapable of even rudimentary social interactions.&lt;/p&gt;

&lt;p&gt;This atmosphere is completely gone from my workplace nowadays.
We can&amp;#39;t even reinstate it.
Everything has to be locked down, everything has to be secure, just because the internet has become such a crazily dangerous place.&lt;/p&gt;

&lt;p&gt;These computer labs were like a third place for me.
They are gone and I realized I quite miss them.
So let me braindump what I wish existed.&lt;/p&gt;

&lt;p class=cBold&gt;## Description&lt;/p&gt;

&lt;p&gt;I wish that bigger cities had a small, non-profit, offline computer club that anybody could join.
Imagine a big room with about 12 machines mostly for playing simple video and coding games together.&lt;/p&gt;

&lt;p&gt;I want this place to be an escape from the modern distracting world, a technology bunker.
The machines are networked with each other; people can interact with each other within the room, but not with the outside internet.
The room is a Faraday cage; the wifi modules on the machines are physically disabled.
If people are there, I want them to be present, not chatting on their smartphones with outsiders.&lt;/p&gt;

&lt;p&gt;The machines would be cheap Raspberry Pis with a monitor, keyboard, mouse, and two gamepads attached.
They would run some niche OS like a BSD or similar, just to avoid mainstream stuff.
The OS would be ephemeral so if you reboot a machine, you get back a pristine machine.
Games, compilers, documentation, user data could be stored on a NAS that is also accessible by all the machines.&lt;/p&gt;

&lt;p&gt;Ideally it would be near the city&amp;#39;s transport hub but somewhat hard to find.
I don&amp;#39;t want to attract foot traffic; I just want it to be at an easily accessible place so I have fewer excuses not to hang out there.
Knowledge about the club would spread via word of mouth or people could discover it when they searched online for old-school LAN party places or local coding competitions.&lt;/p&gt;

&lt;p class=cBold&gt;## Usecases&lt;/p&gt;

&lt;p&gt;Old networked games like Doom, Warcraft, Red Alert would run just fine on old machines.
Emulated SNES games could also be played with the two attached controllers.
These games are still quite fun with the right people!&lt;/p&gt;

&lt;p&gt;Or this would be the perfect place to host small, local programming competition parties.
For example, host a weekly 45-minute programming competition with some AI generated tasks.
You don&amp;#39;t even need to have strong testcases.
Just have another 20-minute section after the coding part where you can read other people&amp;#39;s code and submit testcases to break it.
I love this, and I bet you can find 12 interested people in a big city for this to be a regular weekly event.&lt;/p&gt;

&lt;p&gt;People could hold local game jams or coding hackathons here.
Educators should be able to reserve the room to teach programming basics to interested people.
You don&amp;#39;t need the internet for this either.
Believe it or not, it is possible to code without internet and AI.
If you didn&amp;#39;t know something, you would need to ask ... the human mentor in the session!
It&amp;#39;s enjoyable to learn together, which is another experience that we are losing through the internet and AI.&lt;/p&gt;

&lt;p class=cBold&gt;## Rules&lt;/p&gt;

&lt;p&gt;I want to have some strict rules to maintain the retro bunker / sanctuary vibe for the place:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; Initiation: you need to register, do some short training, and accept the rules of the bunker to become a member and gain access.&lt;/li&gt;
&lt;li&gt; Crumb-free zone: no eating / drinking in the bunker.&lt;/li&gt;
&lt;li&gt; Player two: you can bring one visitor but you are fully responsible for the person.&lt;/li&gt;
&lt;li&gt; WYSIWYG: no plugging in outside devices or tweaking or hacking the machines in the bunker; you have to embrace the limitations.
  Keep your smartphones and laptops tucked away too.&lt;/li&gt;
&lt;li&gt; Sauron: you accept that cameras watch over you.&lt;/li&gt;
&lt;li&gt; Airgap: let the admins know if you need a package installed, a file downloaded, or a file emailed to you from the NAS.
  They will fulfill the request after three days.
  The delay is there to reduce the dependency on online connectivity and to teach people to plan ahead.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Anyway, this is just a raw collection of ideas.
The point is that this space could be a nice third place for folks with the above described niche interests.
I would love to regularly attend such a place, especially for the low-tech coding jams.
Plz, start such a club if you have the means for it.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;published on 2026-04-06&lt;/i&gt;&lt;/p&gt;

</description><link>https://iio.ie/techbunker</link><guid>https://iio.ie/techbunker</guid></item>
  <item><title>dorms: cities should offer the shared dorm rooms experience</title><pubDate>Mon, 02 Mar 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;During my undergraduate studies I lived in a cheap, state-subsidized student dormitory about 120 km away from my home.
It was Soviet-style: many small rooms, 4 people per room, bunk beds, 1 kitchen + 1 study room + 2 bathroom blocks per floor (one for each gender).
The room was small, you only had a very small desk space and a small storage cabinet.
It was all right because you weren&amp;#39;t meant to live there anyway; most people went home for the weekend.&lt;/p&gt;

&lt;p&gt;Living in a tight space with 3 other people is an interesting experience.
It can be:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; meh: You just work and sleep next to each other but otherwise don&amp;#39;t interact with each other.&lt;/li&gt;
&lt;li&gt; bad: You could have incompatible roommates.
  Your roommate might never clean up after themselves, be smelly, etc.
  Or maybe you are an early bird but your roommate is a night owl, owns a clicky keyboard, and has furious chatting sessions at midnight.
  Then you never sleep well, you are grumpy all the time, and you have a lot of conflicts with your roommate.&lt;/li&gt;
&lt;li&gt; amazing: You have fully compatible roommates.
  Maybe you have the same work schedule and same interests.
  Then you can study together, have LAN parties together, etc.
  It&amp;#39;s truly an amazing experience.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;I think the 3rd experience is very rare.
For this reason people demand more isolation, single rooms, often with private bathrooms, etc.
Students aren&amp;#39;t forced to interact with others, or to learn respect and communication with others through the daily struggle.
This robs students of important life experiences and social connections needed for a healthy adult life.&lt;/p&gt;

&lt;p&gt;Fortunately, I was lucky enough to experience all three.
I wish this were more common though.
So in this post I daydream about some ideas on how a good dorm experience could be more mainstream.&lt;/p&gt;

&lt;p class=cBold&gt;## Buildings&lt;/p&gt;

&lt;p&gt;In my dream the government provides these apartments for free.
The government hires the maintenance staff directly and gives them a fixed budget to operate with.
Rather than seeking profit, the goal is fostering community.
It&amp;#39;s an investment in the future because a good community is likely to lead to a more prosperous future.&lt;/p&gt;

&lt;p&gt;Ideally, there would be 3-5 such buildings in different parts of the city.
They should be managed independently so that dorms can compete on the quality of life.
The maintenance staff would get bonuses from the government based on resident satisfaction surveys (a monthly survey), and perhaps other stuff like maintenance velocity (repair time for showers and elevators), community engagement (inhabitant engagement rate in dorm events), eco-friendliness (how much water/energy is used).
So they have an incentive to do a good job.&lt;/p&gt;

&lt;p&gt;Living in such a room wouldn&amp;#39;t be free for the inhabitants though.
They would need to pay a small fixed monthly fee, e.g. $100 or something.
This doesn&amp;#39;t go back to the government or management, it goes to a per-dorm money pool.
The inhabitants can use this pool to sponsor equipment or events that the maintenance staff otherwise wouldn&amp;#39;t.&lt;/p&gt;

&lt;p&gt;I&amp;#39;m imagining here a 10-floor building not unlike a hotel.
The basement has a laundry room, a ping pong table, a small fitness corner.
The ground floor has some food vending machines, large conference room / event space.
The first floor has some meeting rooms of various sizes where the inhabitants can work together on stuff.
The roof is parkified with outdoor gym equipment (or just solar panels if a park would be too expensive).
The rest is for living.
Each floor has: 1 free room that can be used as a study room, 1 kitchen, 1 bathroom for each gender, and a dozen 4-person rooms.&lt;/p&gt;

&lt;p class=cBold&gt;## Themes&lt;/p&gt;

&lt;p&gt;If there are multiple dorms in the city, then each of them might as well come with some unique perks.
Here are some ideas:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; The Maker Dorm: the ground floor has a big workshop with 3D printers and other tools.&lt;/li&gt;
&lt;li&gt; The Library Dorm: the rooms are soundproofed and library rules apply to hallways after 8 PM.&lt;/li&gt;
&lt;li&gt; The Active Dorm: has lots of fitness rooms, and group workout events.&lt;/li&gt;
&lt;li&gt; The Social Dorm: has large rooms, shared dinners, lots of board game nights, language clubs.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Each dorm would be resident-governed.
The dorm residents could vote for their representatives who would then work with the management to ensure that the required tools get bought or events get organized.
This allows the residents to practice community governance.&lt;/p&gt;

&lt;p class=cBold&gt;## Eligibility&lt;/p&gt;

&lt;p&gt;In order to be eligible for the application, you need to:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; be between 18 and 30 years old,&lt;/li&gt;
&lt;li&gt; score some predetermined minimum on a standardized general knowledge test (not too high though; just enough to confirm that the applicants can read/write and do basic math),&lt;/li&gt;
&lt;li&gt; have a clean criminal record for the past 2 years,&lt;/li&gt;
&lt;li&gt; maintain an academic or some community-oriented business in the city for the duration of the stay.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;The system selects from the applicant pool the residents to onboard based on a public lottery to ensure fairness.
The draw is seeded by a verifiable randomness beacon (like NIST).
Every applicant gets one ticket.
But good academic grades, community contributions, distant home address, poor financial situation grant you extra tickets so you have a greater chance to get in.
Once in, you won&amp;#39;t be kicked out for 4 years unless you behave antisocially or are not actually living there much.&lt;/p&gt;

&lt;p&gt;I&amp;#39;d limit max residency to 4 years.
The purpose of these dorms is not to provide cheap, long-term living but rather give people a pure community experience without the financial stress.
You cannot use your dorm as a permanent home address.&lt;/p&gt;

&lt;p class=cBold&gt;## Matching&lt;/p&gt;

&lt;p&gt;If you don&amp;#39;t have a room assigned or you would like to leave your current room then you can participate in a reshuffle.
There would be 3 reshuffles per year (August, November, April).
If you like your room and nobody is leaving, then you don&amp;#39;t need to do anything, you can skip the next reshuffle and everything stays the same.
Otherwise fill out a lifestyle compatibility profile:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; chronotype: early bird ... night owl&lt;/li&gt;
&lt;li&gt; sensory tolerance: need absolute silence ... need to hear music all the time&lt;/li&gt;
&lt;li&gt; cleanliness: clean ... messy&lt;/li&gt;
&lt;li&gt; social battery: room is for sleeping ... I like to chat a lot&lt;/li&gt;
&lt;li&gt; temperature: cold ... warm&lt;/li&gt;
&lt;li&gt; preferences: major:maths, classical-music, age&amp;gt;=26, ... (other arbitrary tags)&lt;/li&gt;
&lt;li&gt; dealbreakers: smoking, snoring, loud-music, age&amp;lt;=23 ... (other arbitrary tags)&lt;/li&gt;
&lt;li&gt; friends: bob, charlie, ... (the people who you would prefer to share a room with)&lt;/li&gt;
&lt;li&gt; foes: dave, eric, ... (the people who you would prefer to not share a room with)&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;You also rate the previous experiences and explain why you want to move out from your current room.&lt;/p&gt;

&lt;p&gt;The system then tries to match you with different roommates and a different room.
It will try to honor the above compatibilities but otherwise it&amp;#39;s relatively random.
Random is good because it mixes people with different backgrounds together and reduces the chances for social cliques to appear.&lt;/p&gt;

&lt;p&gt;If the new roommate is too much, the system also allows you to swap around with other people at any time.
But you have to find a person to swap with manually.&lt;/p&gt;

&lt;p&gt;Note that the frequent reshuffles are disruptive and lower the chance of deep bonds emerging with any single person.
However at the same time it increases the social network and through that the chance you meet someone with whom you can establish a deep bond quickly.
I think the increase of the latter is bigger than the decrease from the former so the disruptions are worth it.&lt;/p&gt;

&lt;p class=cBold&gt;## Expectation&lt;/p&gt;

&lt;p&gt;I&amp;#39;d expect that after about 3 years most people would find their cool roommates and end their residency with positive memories.
They would gain experience in how to live together in tight spaces with minimal conflicts.
Their social network would also be greater, so if they are in trouble, they have more options now in finding support, rather than relying on the state supporting them.
Overall, I think it would be a net benefit for society, and well worth the taxpayers&amp;#39; money indirectly.&lt;/p&gt;

&lt;p&gt;Will there be conflict and stress when you live with 3 new, untrusted strangers every 4 months in a tight space?
Yes.
But keep in mind that you would be in such a place voluntarily and you can leave any time.
Try to learn from the conflicts and stress and grow from it; it&amp;#39;s the cost of not living in a bubble.&lt;/p&gt;

&lt;p&gt;Also note that I&amp;#39;m not advocating that this is the only possible cheap accommodation that the government should subsidize.
There&amp;#39;s no single type of accommodation that works for everyone.
All I&amp;#39;m saying is that this should also be an option.
Society should not swing entirely into single-occupancy rooms and capsule hotels for everyone.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;published on 2026-03-02&lt;/i&gt;&lt;/p&gt;

</description><link>https://iio.ie/dorms</link><guid>https://iio.ie/dorms</guid></item>
  <item><title>cmdmatch: match commands with simple syntax</title><pubDate>Mon, 02 Feb 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;Imagine you are implementing sudo or you have an AI worker / intern.
You want to allow them to run commands on your local or production machines.
For most commands you want a confirmation, justification, or your explicit approval.
At the same time you want to allow running a set of safe commands without any roadblocks.
How would you specify this list of safe commands?&lt;/p&gt;

&lt;p&gt;First, what not to do: use a regex for the whole commandline.
For example, suppose you want to allow listing the files in an arbitrary directory.
While you might use a regex like `ls .*`, this inadvertently lets your intern also run `ls .; rm -rf /`.
Similarly, you might want to allow listing logs like `cat /var/log/cups/access_log.1`.
For this you might use a regex like `cat /var/log/[a-z0-9_./]*` but now your intern can also run `cat /var/log/../../root/.secretpassword`.&lt;/p&gt;

&lt;p&gt;sudo allows globs and regexes, but the globs match whitespace of the space-concatenated invocation, so the grant &amp;#34;myuser ALL = /bin/cat /var/log/*&amp;#34; would allow running &amp;#34;sudo cat /var/log/secure /etc/shadow&amp;#34;.
Furthermore, the regex gets hard to maintain very quickly once you want to allow various combinations of flags.&lt;/p&gt;

&lt;p&gt;So what would I do if I could start the universe from scratch?&lt;/p&gt;

&lt;p class=cBold&gt;# Grant syntax&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s call the rules that allow specific invocations &amp;#34;grants&amp;#34;.
A single grant is a list of matchrules.
A matchrule is a `[count][type][pattern]` string.
The argv array is then matched greedily against the matchrules.
Skip to the examples below to see it in action.&lt;/p&gt;

&lt;p&gt;Cardinalities:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; {number} means this has to match exactly the given number of times.&lt;/li&gt;
&lt;li&gt; {min,max} means this has to match at least min and at most max times.&lt;/li&gt;
&lt;li&gt; {min,} means this has to match at least min times.&lt;/li&gt;
&lt;li&gt; {,max} means this has to match at most max times.&lt;/li&gt;
&lt;li&gt; (no cardinality) means this has to match exactly once, same as {1}.&lt;/li&gt;
&lt;li&gt; ? means this has to match zero or once, same as {0,1}.&lt;/li&gt;
&lt;li&gt; + means this has to match at least once, same as {1,}.&lt;/li&gt;
&lt;li&gt; * means this can match any number of times, same as {0,}.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Types:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; = means the string has to match exactly.&lt;/li&gt;
&lt;li&gt; ~ means the string is an anchored regex (i.e. the regex has to match the full parameter).&lt;/li&gt;
&lt;li&gt; : means the string is a glob. The wildcards * and ** will never match `..`.&lt;/li&gt;
&lt;li&gt; --: this is a special marker that signifies that flags end here.
  It means that either a -- must follow or the next argument doesn&amp;#39;t start with a dash.&lt;/li&gt;&lt;/ul&gt;

&lt;p class=cBold&gt;# Grant examples&lt;/p&gt;

&lt;pre&gt;  # Match journalctl without any args.
  # = on its own without cardinality means the string has to match exactly once.
  # The list [&amp;#34;journalctl&amp;#34;] matches but [&amp;#34;journalctl&amp;#34; &amp;#34;journalctl&amp;#34;] doesn&amp;#39;t.
  =/bin/journalctl

  # Match ls with some flags but then anything as long as it&amp;#39;s not a flag.
  # Notice that the last part starts with the * cardinality.
  # It means there can be an arbitrary number of positional arguments.
  =/bin/ls   # argv[0] must be /bin/ls
  ?~-[lth]+  # argv[1] can optionally be a flag like -lh
  *~[^-].*   # accept non-flag-like arguments for the rest of argv

  # Allow printing the logs with globs.
  # Here the grant uses : for globs, so the user can specify only files under this directory.
  # Globs can be generally less error-prone than regexes.
  =/bin/cat
  +:/var/log/**  # ** in globs means match anything recursively (cannot go upwards via ..)

  # Allow dumping a USB disk to /tmp.
  =/bin/dd
  ?:bs=*
  :if=/dev/mmcblk*
  :of=/tmp/usbdump.*

  # Allow disk usage/free utility with some flags (https://github.com/muesli/duf).
  # ? means the flags are optional.
  # Also notice that flags are specified in a sorted order; see below why.
  =/bin/duf
  ?=-all(=(true|false))?
  ?~-output=.*
  ?~-sort=.*
  --
  *:**&lt;/pre&gt;

&lt;p class=cBold&gt;# Flags&lt;/p&gt;

&lt;p&gt;This simple matching syntax relies on a strict constraint: predictable commandline parsing.
The problem is that most tools weren&amp;#39;t designed for adversarial invocations, so it&amp;#39;s still easy to get things wrong.
In order to fully embrace the simplicity of the above syntax, you need to simplify and restrict how you invoke commands.&lt;/p&gt;

&lt;p&gt;I recommend using this only for tools that fully embrace &lt;a href=&#39;https://iio.ie/flagstyle&#39;&gt;@/flagstyle&lt;/a&gt;.
Here&amp;#39;s a recap of my suggested flag syntax:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; All flags must be before the positional arguments.
  All arguments are positional after the first positional argument even if they look like a flag.
  That&amp;#39;s why -- only scans the next argument to determine whether flags are done.
  Refer to the command wrapping section in &lt;a href=&#39;https://iio.ie/flagstyle&#39;&gt;@/flagstyle&lt;/a&gt; to see why this simplifies commands.&lt;/li&gt;
&lt;li&gt; All flags must be specified with a single - dash (though OK to allow --flag style too for compatibility).&lt;/li&gt;
&lt;li&gt; The value for all flags must be specified via the = syntax. &amp;#34;-flag=value&amp;#34; is OK, &amp;#34;-flag value&amp;#34; is not (otherwise bool flags are too hard).&lt;/li&gt;
&lt;li&gt; The flags configure global variables in the program, so it should be fine to reorder them.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;If you accept those restrictions, then you can sort the user&amp;#39;s flags and match them alphabetically.
The duf grant in the above example relies on this.
So after sorting it would accept the following invocation too:&lt;/p&gt;

&lt;pre&gt;  duf --sort=size -output=size,usage /tmp /home&lt;/pre&gt;

&lt;p&gt;Notice that --sort (with two dashes!) appears before -output.
-all doesn&amp;#39;t appear at all.
You can specify multiple positional arguments thanks to the `*:**` part of the grant.
This part can no longer match flags due to the -- marker.
All the while the grant syntax remains a simple list of strings.&lt;/p&gt;

&lt;p class=cBold&gt;# Recommendation&lt;/p&gt;

&lt;p&gt;I recommend writing all tools that are meant to be used in such a security context with &lt;a href=&#39;https://iio.ie/flagstyle&#39;&gt;@/flagstyle&lt;/a&gt; in mind.
Their usage is less user-friendly, but in exchange you gain a lot of simplification in your security configuration.
I wouldn&amp;#39;t recommend using the above syntax for general-purpose commandline matching because GNU flags are too flexible.
But if you are writing a small Go tool with many subcommands, then the above approach could be a simple way to specify which commands should not require extra verification.&lt;/p&gt;

&lt;p&gt;Also in general don&amp;#39;t trust your AI workers or interns with full, unreviewed access to production.
List explicitly the common safe commands and then you will sleep better when you are on vacation.
As you can see above, the syntax doesn&amp;#39;t have to be too cumbersome.&lt;/p&gt;

&lt;p class=cBold&gt;# Notes&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; The sudoers glob example is from &lt;a href=&#39;https://www.sudo.ws/posts/2022/03/sudo-1.9.10-using-regular-expressions-in-the-sudoers-file/&#39;&gt;https://www.sudo.ws/posts/2022/03/sudo-1.9.10-using-regular-expressions-in-the-sudoers-file/&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;&lt;i&gt;published on 2026-02-02&lt;/i&gt;&lt;/p&gt;

</description><link>https://iio.ie/cmdmatch</link><guid>https://iio.ie/cmdmatch</guid></item>
  <item><title>cliusage: use the Go package doc as the usage string</title><pubDate>Mon, 12 Jan 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;Writing a nice -help message for a CLI tool is often a hassle.
But in Go I found a nice technique: use the package comment (the top block) as the usage string.
You can achieve this easily by embedding the source with //go:embed and parsing out the top comment block:&lt;/p&gt;

&lt;pre&gt;  // The cliusage tool demonstrates using the main package comment as the usage string.
  //
  // Usage: cliusage -input=[filename] -output=[filename]
  //
  // Some description and examples go here.
  package main

  import (
    &amp;#34;context&amp;#34;
    &amp;#34;flag&amp;#34;
    &amp;#34;fmt&amp;#34;
    &amp;#34;os&amp;#34;
    &amp;#34;strings&amp;#34;

    _ &amp;#34;embed&amp;#34;
  )

  var (
    flagInput  = flag.String(&amp;#34;input&amp;#34;, &amp;#34;&amp;#34;, &amp;#34;The input file.&amp;#34;)
    flagOutput = flag.String(&amp;#34;output&amp;#34;, &amp;#34;&amp;#34;, &amp;#34;The output file.&amp;#34;)
  )

  //go:embed cliusage.go
  var source string

  func usage(fs *flag.FlagSet) {
    for line := range strings.Lines(source) {
      if len(line) == 0 || line[0] != &amp;#39;/&amp;#39; {
        break
      }
      fmt.Fprint(fs.Output(), strings.TrimPrefix(strings.TrimPrefix(line, &amp;#34;//&amp;#34;), &amp;#34; &amp;#34;))
    }
    fmt.Fprintf(fs.Output(), &amp;#34;\nFlags:\n&amp;#34;)
    fs.PrintDefaults()
  }

  func run(ctx context.Context) error {
    return nil
  }

  func main() {
    flag.Usage = func() { usage(flag.CommandLine) }
    flag.Parse()
    if err := run(context.Background()); err != nil {
      fmt.Fprintf(os.Stderr, &amp;#34;Error: %v\n&amp;#34;, err)
      os.Exit(1)
    }
  }&lt;/pre&gt;

&lt;p&gt;The output looks like this:&lt;/p&gt;

&lt;pre&gt;  $ go run cliusage.go -help
  The cliusage tool demonstrates using the main package comment as the usage string.

  Usage: cliusage -input=[filename] -output=[filename]

  Some description and examples go here.

  Flags:
    -input string
          The input file.
    -output string
          The output file.&lt;/pre&gt;

&lt;p&gt;This keeps the documentation natural and readable for people reading the source directly.
Note that the source doesn&amp;#39;t have to be the package comment.
It could be a section from the README or other documentation files; the concept remains the same.&lt;/p&gt;

&lt;p&gt;One downside is that embedding the full source (not just the top comment) increases the binary size.
But if you keep the actual logic in a library and treat main.go as a thin wrapper, the overhead is negligible.
It&amp;#39;s a small trade-off for documentation that rarely goes out of sync.&lt;/p&gt;

&lt;p&gt;This practice also helps keep the usage string short.
-help shouldn&amp;#39;t be a full reference anyway.
It should just describe what the tool does and contain example invocations for the common use cases.
The goal is to jog the memory, not to teach.
Detailed docs should live in a markdown file; -help should simply link to it rather than dumping pages of text.&lt;/p&gt;

&lt;p&gt;Edit from 2026-04-07: In general always put the doc comment in a file named after the package or a file named doc.go.
This predictable pattern makes it easy to find it when browsing the code.
If you plan to embed the source just for the doc comment, then put it into doc.go.
Then the overhead will be quite small.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;published on 2026-01-12, last modified on 2026-04-07&lt;/i&gt;&lt;/p&gt;

</description><link>https://iio.ie/cliusage</link><guid>https://iio.ie/cliusage</guid></item>
  <item><title>huelights: I switched my lights to Philips Hue lights</title><pubDate>Mon, 01 Dec 2025 00:00:00 +0000</pubDate><description>&lt;p&gt;&lt;i&gt;this post has non-textual or interactive elements that were snipped from rss. see the full content at &lt;a href=https://iio.ie/huelights&gt;@/huelights&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;


&lt;p&gt;When I was a kid in the late 2000s, we had those earlier versions of the Osram Duo Click lightbulbs.
Switch once, and the lamp is at full brightness.
Switch twice, and it has very low brightness, ideal when waking up in the night.
I&amp;#39;ve missed that feature ever since I moved out, though I never actively looked for such a bulb.&lt;/p&gt;

&lt;p&gt;One day a friend showed me his Philips Hue lights.
I really liked that you can adjust the brightness.
I realized I could recreate my old experience with it.
So as an experiment I bought a few lights and a 4 button switch, this one:&lt;/p&gt;

&lt;p&gt;&lt;i&gt;[non-text content snipped]&lt;/i&gt;&lt;/p&gt;


&lt;p&gt;Hue also needs a bridge but I managed to snag one from a friend who ditched the Hue ecosystem for Home Assistant.
(I did look into Home Assistant too but it was just too much for me; I wanted something simple to start with.)
Hue doesn&amp;#39;t need internet or a central service either.
Everything can be controlled from the local network, so that sounded good enough for me.
Plus, I only want controllable lights; I&amp;#39;m currently not looking for any other smart home widget.&lt;/p&gt;

&lt;p&gt;I played around with Hue and I ended up liking it.
I really like that I can put switches all around the place and configure them however I want.&lt;/p&gt;

&lt;p&gt;I understand that the lights are always on and listening for commands from the bridge.
So I&amp;#39;m wasting some electricity even when they are off and this bothered me at first.
But they don&amp;#39;t seem to add up to a lot on my electricity bill so I decided I can sort of live with this in exchange for the convenience.&lt;/p&gt;

&lt;p class=cBold&gt;# My configuration&lt;/p&gt;

&lt;p&gt;I configured each of the four buttons to be an independent switch for a specific room, with this scene cycling logic:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; First press when the light is on: turns it completely off.&lt;/li&gt;
&lt;li&gt; First press when the light is off: sets the light to low brightness.
  I use this during the night when I don&amp;#39;t really need light but I don&amp;#39;t want to bump into things either.&lt;/li&gt;
&lt;li&gt; Double press: sets the light to mid brightness.
  This is the general setting when I need some light.&lt;/li&gt;
&lt;li&gt; Triple press: full brightness.
  I use it when reading or writing.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;I used the official app to configure this.
However, I don&amp;#39;t use it for controlling the lights because it takes ages to start up.
For my wife I installed Hue Essentials (available in Play Store) and for myself I sideloaded &lt;a href=&#39;https://f-droid.org/en/packages/io.github.domi04151309.home/&#39;&gt;https://f-droid.org/en/packages/io.github.domi04151309.home/&lt;/a&gt; because that one looked even simpler.&lt;/p&gt;

&lt;p class=cBold&gt;# Configuration changes&lt;/p&gt;

&lt;p&gt;I have not found a way to configure scene switches in apps other than in the official one.
And the official app allows you to configure only the top and bottom buttons for scene cycling.
The middle buttons can be configured only for dimming.
I don&amp;#39;t understand why.
Maybe to sell more switches?&lt;/p&gt;

&lt;p&gt;I started wondering how the various apps communicate and configure the system.
It turns out the bridge exposes a REST API for this and that allows you to configure all buttons to scene cycle.
You can go to /debug/clip.html of your bridge&amp;#39;s https port and play with it.
There&amp;#39;s a v1 and a v2 API.
I suspect the scene cycle stuff is specific to v2, and that&amp;#39;s why most apps don&amp;#39;t support it.&lt;/p&gt;

&lt;p&gt;Unfortunately the API is underdocumented and the bridge only returns a generic HTTP error code without any explanation if you make a mistake.
You can find a short reference of the endpoints on the Philips webpage (needs registration).
But it doesn&amp;#39;t tell you how to use it or give you usage examples.
I couldn&amp;#39;t find a good article or forum post explaining how to use it either.
And I searched a lot.&lt;/p&gt;

&lt;p&gt;Eventually I gave up and asked an AI to provide me sample code.
And to my surprise it managed to come up with an almost working JSON that the bridge accepts.
This really cemented my belief that AI is truly a transformative approach to how humans work with information.
So I managed to get started thanks to AI.&lt;/p&gt;

&lt;p&gt;Now I have an intent file at $HOME/.config/hue.cfg with content like below.
In general the bottom three buttons of all the switches control the same main lamps in my apartment.
Only the top button controls switch-specific rooms based on the switch&amp;#39;s location.
This way the buttons remain easy to remember for me.
With more lamps I&amp;#39;d probably label the buttons on the physical switches, e.g. when I get Hue bulbs for the kitchen too.&lt;/p&gt;

&lt;pre&gt;  hueaddress https://192.168.1.ADDRESS/
  huekey     SOME_RANDOM_CHARACTERS_THE_BRIDGE_NEEDS

  KidSwitch 0 Kid
  KidSwitch 1 Office
  KidSwitch 2 TV
  KidSwitch 3 Hallway

  BedSwitch 0 Bed
  BedSwitch 1 Office
  BedSwitch 2 TV
  BedSwitch 3 Hallway

  # The switch on the bathroom entrance switches the bedroom too.
  # I don&amp;#39;t have Hue lights in my bathroom yet.
  BathSwitch 0 Bed
  BathSwitch 1 Office
  BathSwitch 2 TV
  BathSwitch 3 Hallway&lt;/pre&gt;

&lt;p&gt;This describes my 3 switches and the 5 lamps they control.
If I want to add a new switch or reorder the buttons then I just edit the file and run `huepush -apply`.
The tool reads the full bridge configuration so it understands which entities the references in the config file refer to.
Then it generates a new switch configuration and pushes it.
Without the `-apply` flag it just prints the switches that are not matching the intent.
The code is this: &lt;a href=&#39;https://github.com/ypsu/cfg/blob/main/huepush/huepush.go&#39;&gt;https://github.com/ypsu/cfg/blob/main/huepush/huepush.go&lt;/a&gt;.
Requires no dependencies other than the Go standard library.
Quite easy to use for me.&lt;/p&gt;

&lt;p&gt;Note that I configure the actual brightness levels in the app itself.
I save the desired levels as scenes named &amp;#34;low&amp;#34;, &amp;#34;mid&amp;#34;, or &amp;#34;high&amp;#34;.
The switch buttons are then mapped to cycle those scenes.&lt;/p&gt;

&lt;p&gt;(Sidenote: when I was still experimenting with various apps, one switch got into a bad state.
One button was always turning off the lights and I couldn&amp;#39;t override that behavior with the v2 API.
Maybe it was a v1 API setting.
Resetting the switch via removing it from the bridge and readding it solved the issue.)&lt;/p&gt;

&lt;p class=cBold&gt;# Switching from my desktop&lt;/p&gt;

&lt;p&gt;I also wrote myself another small tool to adjust the light levels from my desktop.
I named it lll as in &amp;#34;Light Level Lever&amp;#34; just to make it easy to type with my dominant hand.
If I run it without arguments, then it displays the light levels of all my lamps.&lt;/p&gt;

&lt;p&gt;I can use it like this to switch the lamps: `lll o3t1h0`.
The letters identify the light and the number sets the desired brightness.
The above sets the Office light to level 3 (full brightness), the Tv light to level 1 (minimum brightness), and turns off the Hallway light (level 0).&lt;/p&gt;

&lt;p&gt;I also configured it to control the display backlight brightness of my desktop using DDC.
I wanted this because I use different monitor brightness and system theme depending on how much background light I have.
Previously I did this via `ddcutil setvcp 10 $BRIGHTNESS` but thought I would simplify this for myself.
So now I can use `lll d3` for full backlight or `lll d0` to set backlight level to 0% which is still a usable backlight level on my Thinkpad display.
I needed AI assistance for this too because the ioctl magic was too hardcore for me to figure out.
I asked AI to generate a code example to set brightness via ioctl and then I based my code on that.&lt;/p&gt;

&lt;p&gt;I often use this tool because it&amp;#39;s nicer than fiddling with the phone or walking to a switch to adjust the light.
The code for this one is at &lt;a href=&#39;https://github.com/ypsu/cfg/blob/main/lll/lll.go&#39;&gt;https://github.com/ypsu/cfg/blob/main/lll/lll.go&lt;/a&gt;.&lt;/p&gt;

&lt;p class=cBold&gt;# Recommendation&lt;/p&gt;

&lt;p&gt;Are you happy with your traditional lights?
Then I recommend sticking with them.&lt;/p&gt;

&lt;p&gt;But if you are looking for flexibility in brightness then I can wholeheartedly recommend the Hue ecosystem.
I&amp;#39;d expect that other similar brands could work just as well, e.g. IKEA.
I went with Hue just because it&amp;#39;s simple to start with, it has a broad selection of lights, and has a REST API for the complex needs later on.
As far as I understand, the switches and lights use standardized technology so you can put them into other systems too if you change your mind later.&lt;/p&gt;

&lt;p&gt;Buy one such light+switch+bridge combo for experimenting and then decide.
That&amp;#39;s how I started too.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;published on 2025-12-01&lt;/i&gt;&lt;/p&gt;

</description><link>https://iio.ie/huelights</link><guid>https://iio.ie/huelights</guid></item>
  <item><title>exercise: do the minimum effective dose of exercise every day</title><pubDate>Mon, 03 Nov 2025 00:00:00 +0000</pubDate><description>&lt;p&gt;I want to avoid being overly unfit without spending too much time on exercise.
So for the past few years I have done a small &amp;lt;10 minute exercise almost every day.
The exact approach varied over time but now there are two types of exercises:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; Hard ones where I can do only a few reps, for example pushups, pullups, curls with heavy weights.
  These I count manually to my desired target and do 2 sets of reps.&lt;/li&gt;
&lt;li&gt; Easy ones that I can do for minutes, for example plank, squats, situps, curls with light weights.
  For these I set the phone timer for 2 minutes and do them for that long.
  Then I can daydream during the exercise.
  I quite enjoy these.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;I pick 2 exercises every day and do them.
It&amp;#39;s different every day so the muscles get plenty of breaks.
I won&amp;#39;t bulk up from these but it makes me feel much better about myself.
My mood is better for the day.
And I no longer feel unfit or get out of breath from a bit of stair climbing.&lt;/p&gt;

&lt;p&gt;If I feel unmotivated, sad, or otherwise constrained (for example while traveling) then I only do one set only, for example squat hold because I can do that anywhere, even in the toilet.
Rule: never break the habit.
No zero days.
If it&amp;#39;s the end of the day and I&amp;#39;m in bed, I can still do some situps.
This way the habit stays in my subconscious and the momentum keeps me going.
The habit gets easier over time.
I don&amp;#39;t even think about it much.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;published on 2025-11-03&lt;/i&gt;&lt;/p&gt;

</description><link>https://iio.ie/exercise</link><guid>https://iio.ie/exercise</guid></item>
  <item><title>smartgo: I wish for a Go-like language with Rust-like pointers</title><pubDate>Mon, 06 Oct 2025 00:00:00 +0000</pubDate><description>&lt;p&gt;I&amp;#39;m using Go now for over 5 years and I really like it.
But the properties of Rust&amp;#39;s model are quite seductive:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; No GC, no runtime.&lt;/li&gt;
&lt;li&gt; More optimization opportunities compared to C-like languages thanks to the lack of aliasing for anything writable.&lt;/li&gt;
&lt;li&gt; Race-free coding by default.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;I tried learning Rust a few times but I always failed to get traction in it.
I like that Go&amp;#39;s structure and idioms are simple so it doesn&amp;#39;t require advanced IDEs to be productive with it.&lt;/p&gt;

&lt;p&gt;For instance you cannot define new types within a type.
You can have only top level types.
And for functions you can only have top level functions or methods on a type.
No deeply nested structures.
Those always make my head hurt because they are often messily organized.
In Go you have to organize on the package level.
This makes documentation lookup trivial because I can just run &amp;#34;go doc packagename.Symbol&amp;#34; to look up stuff.
And there are other small quality of life things with Go that make it easy to work with, e.g. I love the simplicity of the interfaces or the imports system.&lt;/p&gt;

&lt;p&gt;My point is that my problem is not with the borrow checker or lifetime analysis but basic programming language ergonomics.
I argue that some complexity in Rust is not due to its memory model and I want a language that gets rid of that part of complexity.&lt;/p&gt;

&lt;p&gt;What I&amp;#39;m envisioning here is having a Go variant with the GC replaced with smart pointers inspired by Rust and everything else kept Go-like.
There would be 3 pointers:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; *: star represents the read-only pointers.&lt;/li&gt;
&lt;li&gt; +: plus represents mutable pointers.&lt;/li&gt;
&lt;li&gt; ^: hat represents mutable pointers with ownership.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Here are the main rules:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt; You can borrow ^ or + as a * pointer to a struct or function as long as nothing else accesses a ^ or + variant of the pointer during the lifetime of that borrow.&lt;/li&gt;
&lt;li&gt; When a ^ pointer goes out of scope, the resource behind it will be auto-released.
  If the pointer type has a release() function then that will be automatically called before the free.&lt;/li&gt;
&lt;li&gt; A struct can contain any combination of ^+* pointers.
  But all those fields downgrade to * when accessing the struct through a read-only * pointer.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;Slices contain pointers so slices would have 3 variants too.
Similar for closures (^ and + represent closure, * represents a free-form function).&lt;/p&gt;

&lt;p&gt;Oh, and while we&amp;#39;re at it, let&amp;#39;s make all the pointers non-nil by default.
Use something like ?*ptr to express the fact that ptr can be nil too.&lt;/p&gt;

&lt;p&gt;I&amp;#39;ll now use code examples written in this hypothetical language to better express what I mean.
I don&amp;#39;t have a compiler so apologies for syntax and logical errors.
Hopefully the gist comes across.&lt;/p&gt;

&lt;p class=cBold&gt;# Binary tree example&lt;/p&gt;

&lt;p&gt;A binary tree looks quite complex in Rust but could be relatively simple here.
I&amp;#39;ll demo this with an integer tree because I don&amp;#39;t want to overcomplicate this with generics for now.&lt;/p&gt;

&lt;pre&gt;  type Tree struct {
    // ?^Node means this pointer is either empty or it owns a Node pointer.
    root ?^Node
  }

  type Node struct {
    value  int

    // ?^Node means this pointer is either empty or it owns a Node pointer.
    lt, rt ?^Node
  }

  // In this implementation only the owner can add elements (mostly for demo purposes).
  // That&amp;#39;s because the receiver is a ^ pointer.
  // If a function receives a ^ pointer then it must be returned or it will be released.
  // In this function it is returned so the node is kept alive.
  func (n ?^Node) Add(v int) ^Node {
    if n == nil {
      return &amp;amp;Node{value: v}
    }
    // At this point n cannot be nil so its type is the same as ^Node.
    if v &amp;lt;= n.value {
      // Here&amp;#39;s an example for an explicit borrow with a tuple assignment.
      // The struct&amp;#39;s lt field is borrowed in the tmp local variable.
      // Setting n.lt is not really needed here with a smart enough compiler with a borrow checker.
      // Such a compiler could recognize that n.lt is not used anywhere in this block,
      // and it will be overwritten eventually anyway.
      // So you could have a usable language even without a borrow checker.
      // Though I do want a borrow checker so that I can keep the code compact.
      var tmp ?^Node
      tmp, n.lt = n.lt, nil
      n.lt = t.Add(v)
    } else {
      // Here it is the same while relying on the borrow checker to prove this is fine.
      n.rt = n.rt.Add(v)
    }
    return n
  }

  func (t *Tree) Add(v int) {
    t.root = t.root.Add(v)
  }

  func main() {
    var t ^Tree = &amp;amp;Tree{}
    t.Add(3)
    t.Add(4)
    t.Add(5)
    // Here the whole tree gets auto-released because t goes out of scope.
  }&lt;/pre&gt;

&lt;p class=cBold&gt;# Flags example&lt;/p&gt;

&lt;p&gt;I really like Go&amp;#39;s solution to flags.
It&amp;#39;s simple to use for simple functions too.
It relies on global flags though.
But that&amp;#39;s not a problem with the right building blocks: a smart mutex type in this case.&lt;/p&gt;

&lt;pre&gt;  package sync

  // This is a standard mutex.
  // Note that Lock/Unlock modifies it yet it takes a read-only receiver.
  // Internally it is implemented via unsafe magic to bypass the language constraints.
  // But this unsafeness doesn&amp;#39;t get exposed to the users of the mutex.
  type Mutex struct { ... }
  func (mu *Mutex) Lock() { ... }
  func (mu *Mutex) Unlock() { ... }

  // RAII type for auto-unlocking mutexes.
  type MutexLock struct { mu *Mutex }
  func (mu MutexLock) release() { mu.Unlock() }

  type Guarded[T any] struct {
    mu    Mutex
    value ^T
  }

  // Takes a read-only guard and converts it to a mutable reference.
  // Again relies on unsafe magic.
  func Lock[T any](g *Guard[T]) (+T, ^MutexLock) { ... }&lt;/pre&gt;

&lt;p&gt;Now you can write this:&lt;/p&gt;

&lt;pre&gt;  package flag
  func Int(name string, value int, usage string) *sync.Guarded[int] { ... }

  package main

  // Globals can be only read-only (*) pointers.
  // But *sync.Guarded[] happens to be that.
  var flagPort = flag.Int(&amp;#34;port&amp;#34;, 8080, &amp;#34;The server port.&amp;#34;)

  func main() {
    flag.Parse()
    // Use RAII to get exclusive access to the flag.
    // The lock will be autoreleased when the scope ends.
    port, _ := sync.Lock(flagPort)
    http.Listen(*port)
  }&lt;/pre&gt;

&lt;p&gt;It&amp;#39;s a bit cumbersome to access global vars through locks but should work out quite reasonably.&lt;/p&gt;

&lt;p class=cBold&gt;# Other notes&lt;/p&gt;

&lt;p&gt;I wouldn&amp;#39;t mind not having goroutines or coroutines either.
IMO normal threads could scale well once Linux starts supporting userspace scheduling.&lt;/p&gt;

&lt;p&gt;Also I&amp;#39;m not optimizing for max performance.
For instance accessing a global variable would need to go through a mutex lock.
But that&amp;#39;s fine: the main optimization is that there&amp;#39;s no runtime and that&amp;#39;s enough for most things.
More unsafe constructs can be used for parts that need to be truly performant.&lt;/p&gt;

&lt;p&gt;There is more to all this: how to handle slices, closures, interfaces.
Probably not worth going into those details given I&amp;#39;m not fully sure I got the pointers down to a usable state.
But my main point here is that I strongly believe there&amp;#39;s a not-yet invented language that has Go&amp;#39;s simplicity but Rust&amp;#39;s memory semantics.
If someone manages to pull this off well, then I think they have a good chance of disrupting the programming language world yet again.&lt;/p&gt;

&lt;p class=cBold&gt;# Edit 2025-10-10: Linear types&lt;/p&gt;

&lt;p&gt;I came across the concept of &amp;#34;linear types&amp;#34; as defined at &lt;a href=&#39;https://borretti.me/article/introducing-austral#linear&#39;&gt;https://borretti.me/article/introducing-austral#linear&lt;/a&gt; or in more detail at &lt;a href=&#39;https://austral-lang.org/spec/spec.html&#39;&gt;https://austral-lang.org/spec/spec.html&lt;/a&gt;.
This looks like a simplified version of the Rust semantics.
Borrows are explicit in this language.
I haven&amp;#39;t digested these ideas yet, just wanted to save the link here for now.
My first reaction is leaning dislike because I don&amp;#39;t like too much verbosity.
But it certainly proves that borrow checking doesn&amp;#39;t need to be expensive.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;published on 2025-10-06, last modified on 2025-10-10&lt;/i&gt;&lt;/p&gt;

</description><link>https://iio.ie/smartgo</link><guid>https://iio.ie/smartgo</guid></item>
</channel>
</rss>
