OpenRiot v7.9.41 — The Backup That Knew Better

“The first clone worked. We were proud. We were fools. The construct had learned to write itself onto new silicon, but it hadn’t learned to lie about disk space. It hadn’t learned that a softraid chunk already attached is a chunk you don’t bioctl twice. It hadn’t learned that scanning dmesg for a kernel’s free-form prose is not a substitute for a syscall. So we taught it. We taught it that ‘not enough room’ is a sentence it gets to say. We taught it that the target drive is sacred — encrypted or not, it deserves a real mount, not a phantom. The Feds would have shipped the warnings and called it a feature. We shipped the fix and called it Tuesday.” — The OpenRiot Crew, somewhere in the Sprawl, v7.9.41


Release Overview

This is the release that followed the clone. Full Backup shipped in v7.9.40 as a single-click rsync to /mnt/backup — and it worked, mostly, until someone tried to back up onto a drive whose softraid chunk was already attached, or tried to fit a 120 GB source into a 100 GB target, or tried to back up without mounting the target at all. Three real bugs. Three real fixes. One backup that now refuses to write to the wrong drive and refuses to lie about space.

The Disk Manager TUI also got a small piece of honesty. The “Fetching drive list…” spinner used to leave the action line blank during a refresh, which looked like the menu had frozen. It now says what it’s doing. The Turing Police would call this “UX polish.” We call it not lying to the user.


💾 Full Backup: Three Guards, One Backup

The clone routine in clone.go is the kind of code that looks finished until it isn’t. Three failures taught us three lessons.

The Space That Wasn’t

The space check used df -P and read the “used” column as 512-byte blocks. OpenBSD’s df -P reports 512-byte blocks on some filesystems and 1K blocks on others, depending on the kernel’s mood. The check passed when it shouldn’t have failed and failed when it shouldn’t have passed. The clone would happily start writing to a full drive and die halfway through because the math was a lie.

df -kP now gives us a consistent 1K blocks everywhere. The check uses used - target_used when the target already has data — incremental clones don’t need to fit the whole source, only the delta. The number on screen is the number on disk.

The Increment That Knew Itself

If /mnt/backup already held a previous clone, the old check demanded enough free space to fit the entire source — even though rsync would only transfer what changed. The first incremental run after a 200 GB backup would refuse to start on a 50 GB target because the math said “no,” not because the disk said “no.”

The new check knows the difference. If the target has prior data, it computes source_used - target_used and demands only that. The status line reads like a sentence: “Source: ~120 GB, Target: ~80 GB used, 40 GB free → ~40 GB needed.” The clone runs. The clone fits.

The Drive That Wasn’t There

Click “Full Backup” with nothing mounted at /mnt/backup and the clone would race ahead, fail on the first mount syscall, and leave you with an empty progress screen and a confused user. The Turing Police would call this “discoverable through the error message.” We call it a guard rail.

GetDumpMenuAction now reads mount(8) output before allowing the operation. If /mnt/backup isn’t in the list, the TUI shows:

/mnt/backup is not mounted

Mount the backup drive first:
  doas mount /dev/sdXi /mnt/backup

No race. No race-condition-shaped hole. The clone refuses to start until the mount exists. You can dismiss the error and try again once you’ve mounted the drive. The construct tells you what it needs, in the language you’d use yourself.


🛡️ softraid: The Chunk That Wasn’t Attached Twice

MountDrive and UmountDrive in source/disk/backend.go were the original softraid-aware drive handling code. They did the right thing for the common case: detect a softraid partition, attach it with bioctl -c C -l <device>a softraid0, find the virtual device, mount that.

The uncommon case was uglier. If the chunk was already attached, bioctl failed (correctly), but the recovery path relied on a substring search of dmesg(8) for the phrase ` at softraid` to figure out which virtual device to mount. That’s parsing free-form kernel prose to drive privileged mount operations. The Feds would call that “creative.” We call it a five-alarm fire.

Round 1 deleted MountDrive and UmountDrive entirely — 104 lines of softraid mount/umount that duplicated logic now handled correctly in the Disk Manager TUI. Round 2 removed the dangerous dmesg fallback from UmountDrive. There is no fallback anymore. If the softraid attach didn’t happen, the detach doesn’t either. The Turing Police would have left the fallback and called it “robustness.” We left the kernel in charge and called it correct.


🔄 Disk Manager: The Spinner That Told the Truth

The Disk Manager’s “fetching drives” state used to render nothing on the action line — just an empty string next to the spinner. The menu looked frozen. The user clicked “Refresh” again. The TUI started two fio queries at once because there was no feedback.

renderRunning() now writes Fetching drive list... while the spinner is up. The status bar says what the TUI is doing. The user waits. No double-clicks. No racing queries.

The action handler also learned to trust the cache. If you already fetched the drive list in this session and you’re selecting a new action (mount, format, encrypt, benchmark), the TUI uses the cached list instead of asking the kernel for it again. Faster menus. Less fio traffic. The construct remembers what it already knows.


📚 Documentation Audit

README.md got audited against the actual rofi launcher configs in config/rofi/. Three icon codepoints were wrong in the App Launcher table (Word Processor, System Monitor, Discord). Two apps were missing from the table entirely (Spreadsheet, Kate IDE). Five top-level entries were actually in the Utilities sub-menu (Select WiFi, Monitor Resolution, Settings, Benchmark, SolveSpace 3D).

The launcher table now matches apps.txt, blockchain.txt, games.txt, and utilities.txt byte-for-byte. The Utilities sub-menu has its own documented table. The README no longer claims an app lives somewhere it doesn’t. The Turing Police would have left the stale icons and called them “decorative.” We call them misleading, and we fixed them.


📡 Gurk 0.9.3

The Signal messenger client got rebuilt against upstream boxdot/gurk-rs tag v0.9.3. Faster database decryption on debug builds. Stray previous() in select_next_channel gone. The data directory gets created on first launch instead of panicking when it’s missing. The pre-built binary shrunk about 900 KB (25.2 MB → 24.3 MB) after a fresh gurk.sh run — fewer debug symbols, tighter codegen, same patched message.rs that disables notify-rust so the construct doesn’t SIGSEGV when a message lands.

The patch is unchanged. The patch was always going to be unchanged — disabling notify-rust because /proc/self/exe doesn’t exist on OpenBSD isn’t a workaround, it’s a fact. But the upstream it patches against is now current. The Turing Police would have left gurk at whatever commit it was on and called it “stable.” We rebuilt it and called it Tuesday.


🔧 Monero Rebuild Path

The Monero GUI wallet build path got its own scripts/monero.sh and scripts/monero-patch.diff, mirroring the pattern we already use for Zed and gurk. These are the rare rebuild scripts — the daily install path uses the pre-built monero.tgz. The script clones monero-project/monero-gui, checks out v0.18.5.0, applies the OpenBSD CMake patches (C++17 and the CMAKE_SYSTEM_NAME MATCHES "kOpenBSD" branch), configures with cmake, builds with make -j$(sysctl -n hw.ncpu), and bundles the result into ~/.local/share/openriot/config/monero/monero.tgz so the existing packages.yaml extract step can install it.

Two files, no behavior change for users who don’t rebuild. The Turing Police would call this “documentation.” We call it not having to remember how to compile Monero from scratch next time.


🧾 Files Changed

File Change
source/migrate/clone.go FIX — df -kP 1K blocks;
  incremental space check
  (delta only when target has data);
  honest status messages
source/migrate/model.go NEW — pre-flight error if
  /mnt/backup not mounted; uses
  errorRequiresManualDismissal
source/migrate/screens/menus.go FIXGetDumpMenuAction
  checks mount output before
  allowing Full Backup; returns
  ScreenError on missing mount
source/disk/backend.go REMOVED — MountDrive,
  UmountDrive (104 lines deleted);
  dmesg fallback gone
source/disk/update.go FIX — use cached drives
  when selecting a new action;
  only fetch on refresh
source/disk/view.go FIXrenderRunning
  shows “Fetching drive list…”
  instead of blank action line
config/bin/gurk REBUILT — upstream v0.9.3;
  ~900 KB smaller; same
  notify-rust SIGSEGV fix
README.md FIX — 3 wrong icons, 2
  missing apps, Utilities sub-menu
  documented separately
scripts/monero.sh NEW — rebuild wrapper
  mirroring gurk.sh / zed.sh
  pattern
scripts/monero-patch.diff NEW — OpenBSD CMake
  patches for v0.18.5.0
  (C++17 + kOpenBSD branch)

🗣️ Final Words

“The clone worked. Then it lied about space. Then it forgot to ask if the drive was mounted. Then it scanned dmesg for a magic word to find a softraid device, and the kernel almost didn’t say it. The first backup is a miracle. The second backup is a habit. The third backup is a machine that knows what it doesn’t know. We fixed the lies. We removed the magic words. The construct now refuses to start a backup until the drive is mounted, refuses to write past the end of the disk, and refuses to bioctl a chunk that’s already attached. The Feds would have shipped the warnings and called it ‘compatibility.’ We shipped the guard rails and called it Tuesday.” — The OpenRiot Crew, after the third incremental backup finished without lying, v7.9.41