OpenRiot v6.9 — Gödel, Escher, Wi-Fi
“The Wi-Fi works on every operating system except the one you actually use.” — Ancient laptop proverb, whispered in coffee shops worldwide
Release Overview
v6.9 is the release where we looked at OpenBSD’s wireless stack and decided
that nmcli and iwctl and wpa_supplicant and D-Bus and all the Linux
baggage could stay on Linux. We built our own network manager. From scratch.
For OpenBSD. In a terminal. With Bubble Tea.
It speaks ifconfig, hostname.if, and netstart. It parses scan output
with regex. It handles quoted SSIDs, hidden networks, signal percentages,
and — because someone in your building definitely named their network
“Gödel” in UTF-8 — it hex-decodes 0x47c3b664656c back into human text.
Meanwhile, the offline installer image we have been teasing for six releases
actually boots now. It installs. It shows the welcome message. The ls -lT
formatting bug that made site79.tgz invisible to the installer is dead.
And CopyFile no longer reads multi-hundred-megabyte tarballs into RAM like
a script-kiddie parsing XML with regex.
v6.8 was clean. v6.9 is connected.
Total changes: Wi-Fi TUI, offline installer end-to-end, memory-conscious copying, 4 architectural essays, 14 unit tests, 1 Gödel.
🛜 openriot –nmtui: A Wi-Fi Manager Born on OpenBSD
The Problem: Every Wi-Fi manager for Unix-like systems assumes you are
running NetworkManager, or wpa_supplicant, or iwd, or at minimum a D-Bus
session. OpenBSD has none of these. It has ifconfig. It has
hostname.if(5). It has netstart. The Linux tools will not save you.
The Solution: We wrote one.
What It Does
Launch with openriot --nmtui (or pick Select WiFi from Rofi or the
Settings menu). It opens a floating Alacritty window and:
- Auto-detects your wireless interface (looks for
ieee80211inifconfig -a) - Auto-scans on launch (
ifconfig <iface> scan) - Lists networks with signal bars (
●○○○) and security badges - Prompts for WPA2 passwords with masked input
- Connects via
doas ifconfig … wpakey …thendoas sh /etc/netstart - Shows active connection info (IP, gateway, DNS, MAC, state)
- Disconnects with
doas ifconfig <iface> down
All mutations require doas. The TUI checks for it and displays a clear
error if missing. It does not try to self-elevate. This is OpenBSD. We
do not sudo ourselves.
The Parser
ifconfig scan output is not a data format. It is a human-readable text
stream that varies by driver (iwm, iwn, athn, iwx, urtwn). Our
backend treats every line as a bag of space-delimited tokens and uses regex
to extract fields in order-independent fashion. This is defensive against:
- Different field ordering across drivers
- Missing fields (some drivers omit
bssid) - Comma-separated security flags (
privacy,wpa2vsprivacy,wpa3,wpa2) - Signal as percentage (
83%) not dBm
The Gödel Bug
ifconfig scan outputs non-ASCII SSIDs as hex: nwid 0x47c3b664656c.
The bytes 47 c3 b6 64 65 6c decode to UTF-8 “Gödel”. Our first parser
treated 0x47c3b664656c as the literal SSID. Users saw empty strings
where they expected Gödel. v6.9 detects ^0x([0-9a-fA-F]+)$, hex-decodes,
validates UTF-8, and returns the decoded string. Gödel is restored.
Files Added
| File | Lines | Responsibility |
|---|---|---|
source/nmtui/backend.go |
~390 | Primitives: scan, connect, disconnect, info |
source/nmtui/model.go |
~140 | Bubble Tea model, states, key map, Init() |
source/nmtui/update.go |
~250 | Update(), tea.Msg, tea.Cmd generators |
source/nmtui/view.go |
~300 | View(), lipgloss styles, pagination, bars |
source/nmtui/nmtui.go |
~30 | Run() entry point: alt-screen + mouse |
source/nmtui/backend_test.go |
~300 | 13 tests: scan, SSID, signal, connection |
source/nmtui/unicode_test.go |
~30 | Hex-decode validation (Gödel, Café, etc.) |
docs/Network-TUI.md |
~280 | Full functional specification |
Integration
source/commands/commands.go—--nmtuiunder “Network & Battery”config/rofi/apps.txt— Select WiFi at the end of the app listsource/settings/settings.go— Select WiFi in Polybar Settings menuconfig/i3/config—for_window [class="^openriot_wifi$"]floatingconfig/window/icons.toml—openriot_wifi = ""(no?in workspaces)
📀 The Installer Image Works
Since v6.4 we have been building openriot.img — a bootable USB image that
bundles OpenBSD 7.9 with all OpenRiot packages pre-downloaded. The theory:
install offline, then run setup.sh after first login. The reality: it did
not boot, or it booted and could not find the packages, or the install script
did not run.
v6.9 fixes every known blocker:
-
index.txtformatting — The installer parsesls -lToutput to discover install sets. We were hand-crafting afmt.Sprintfline that looked likels -lTbut was notls -lT. The installer silently ignoredsite79.tgz. We now run actualls -lTon the copied file and append the output verbatim. The set appears. -
syncbeforeumount— The image builder was unmounting before sync, truncatingsite79.tgzat the FFS boundary. Data loss without error. Fixed:syncis now explicit and blocking before everyumount. -
End-to-end validated — The image boots from USB. The installer discovers
site79.tgz.install.siteexecutes. Packages install via bulkpkg_add -I *.tgz. The first-login welcome message appears. It is real. -
Firmware bundling — Non-free firmware (Intel wireless, etc.) is now bundled in the site tarball. OpenBSD’s
fw_updatecan find it locally during post-install. -
install.sitesimplified — User creation, group modification, and shell changes were removed from the install script. These are runtime concerns handled bysetup.shafter first login. The install script now focuses on package installation and MOTD setup only.
Status: Image builder spec updated in docs/Image-Builder-Spec.md. New
architectural spec added in docs/Image-Spec.md (231 lines).
💾 CopyFile: Stop Eating RAM
fsutil.CopyFile was reading the entire source file into memory with
os.ReadFile, then writing it with os.WriteFile. For a 781MB
site79.tgz, this allocates 781MB of heap. During image building, this
happens multiple times.
Rewritten to use io.Copy with streaming os.Open/os.Create. Same-size
skipping now compares os.Stat sizes instead of bytes.Equal (no need to
read either file). The fix is in source/fsutil/copy.go.
📚 WhySeries: Four Architectural Essays
OpenRiot makes a lot of opinionated choices. Users ask why. We wrote the
answers down in assets/WhySeries/:
| Essay | Question Answered |
|---|---|
Binary.md |
Why a single Go binary instead of shell scripts? |
fish-helix-crush.md |
Why fish + Helix + crush instead of bash/Vim/?? |
i3-X11.md |
Why i3 + Xenocara instead of Sway/Wayland? |
polybar.md |
Why Polybar instead of something else? |
Each is a short, direct argument for the choice. No evangelism. Just the tradeoffs and why we picked the side we picked.
🧾 Files Changed
| File | Nature of Change |
|---|---|
source/nmtui/backend.go |
New — WiFi detection, scan parser, connection, hex SSID decode |
source/nmtui/model.go |
New — Bubble Tea model, states, key map, Init() |
source/nmtui/update.go |
New — Update(), tea.Msg, tea.Cmd generators |
source/nmtui/view.go |
New — View(), lipgloss styles, pagination, bars |
source/nmtui/nmtui.go |
New — Run() entry point |
source/nmtui/backend_test.go |
New — 13 tests for parser, extractor, signal, connection |
source/nmtui/unicode_test.go |
New — Hex-decoding tests (Gödel, Café, Hütte, Röd) |
source/commands/commands.go |
--nmtui registered under Network & Battery |
source/settings/settings.go |
Select WiFi entry added to Polybar Settings menu |
source/fsutil/copy.go |
Rewritten: io.Copy streaming, size-based skip |
source/imaging/build.go |
Image builder: validated status, firmware bundling |
source/imaging/download.go |
Image builder: download handling improvements |
source/imaging/runner.go |
Image builder: execution flow fixes |
source/imaging/site.go |
Image builder: install.site simplification, sync fix |
config/i3/config |
Floating window rule for openriot_wifi class |
config/rofi/apps.txt |
Select WiFi entry added |
config/window/icons.toml |
openriot_wifi → mapping |
docs/Network-TUI.md |
New — Functional specification for nmtui |
docs/Image-Builder-Spec.md |
Updated: end-to-end validation status, firmware |
docs/Image-Spec.md |
New — Architectural specification for installer image |
assets/WhySeries/Binary.md |
New — Why a single Go binary |
assets/WhySeries/fish-helix-crush.md |
New — Why fish + Helix + crush |
assets/WhySeries/i3-X11.md |
New — Why i3 + Xenocara |
assets/WhySeries/polybar.md |
New — Why Polybar |
README.md |
nmtui section, keybindings, rofi apps table update |
source/go.mod / go.sum |
Added github.com/charmbracelet/bubbles v1.0.0 |
docs/v*.md |
Back-link corrections on all previous release notes |
🗣️ Final Words
v6.0 was beauty. v6.1 was correctness. v6.2 was readability. v6.3 was choice. v6.4 was independence. v6.5 was honesty. v6.6 was precision. v6.7 was proof. v6.8 was clean. v6.9 is connected.
In 1931, Kurt Gödel published his incompleteness theorems and broke mathematics. In 2026, someone named their Wi-Fi network after him and broke our parser. There is a lesson here about the relationship between human creativity and software robustness. The lesson is: people will do unexpected things, and your regex should handle them.
We built a network manager that does not exist for Linux. We built an installer image that installs without internet. We wrote down why we built everything the way we did, so the next person does not have to guess. And we fixed the bug where Gödel showed up as an empty string.
v6.9 is the release where OpenRiot stands on its own networking stack, its own installer, and its own explanations. No dependencies borrowed from other ecosystems. No assumptions that the next OS release will not break your shell script. Just a system that knows how to find a network, join it, and tell you why it works.
— The OpenRiot Crew
“Any sufficiently advanced incompetence is indistinguishable from malice.” — Greyson’s Law, and also why we validate UTF-8 before displaying SSIDs.