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 ieee80211 in ifconfig -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 … then doas 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,wpa2 vs privacy,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--nmtui under “Network & Battery”
  • config/rofi/apps.txtSelect WiFi 󱚹 at the end of the app list
  • source/settings/settings.goSelect WiFi 󱚹 in Polybar Settings menu
  • config/i3/configfor_window [class="^openriot_wifi$"] floating
  • config/window/icons.tomlopenriot_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.txt formatting — The installer parses ls -lT output to discover install sets. We were hand-crafting a fmt.Sprintf line that looked like ls -lT but was not ls -lT. The installer silently ignored site79.tgz. We now run actual ls -lT on the copied file and append the output verbatim. The set appears.

  • sync before umount — The image builder was unmounting before sync, truncating site79.tgz at the FFS boundary. Data loss without error. Fixed: sync is now explicit and blocking before every umount.

  • End-to-end validated — The image boots from USB. The installer discovers site79.tgz. install.site executes. Packages install via bulk pkg_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_update can find it locally during post-install.

  • install.site simplified — User creation, group modification, and shell changes were removed from the install script. These are runtime concerns handled by setup.sh after 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 NewUpdate(), tea.Msg, tea.Cmd generators
source/nmtui/view.go NewView(), lipgloss styles, pagination, bars
source/nmtui/nmtui.go NewRun() 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.

← Back to README