OpenRiot v7.9.3 — The One Where We Mapped the Home Directory to Oblivion
“The only truly portable home directory accessor is the one you write yourself, because
os.UserHomeDir()is a trap. A honey pot. A function that looks innocent until you rungo testand realize it cached the wrong home directory like a raccoon with a shiny thing it refuses to let go.” — The OpenRiot Crew, at 4 AM, aftergo testfailed for the fourteenth time
Release Overview
v7.9.3 is the release where we realized that os.UserHomeDir() is the
Go equivalent of a static global variable in a C program from 1987. It
seems fine. It seems reasonable. It is the standard library. The
standard library would not lie to us. But it does. It lies by
caching. It lies by being called thirty times across twenty files. It
lies by making every path construction look like a first-year computer
science homework assignment where the professor said, “Just use
getenv("HOME").”
This release is about abstraction. About building a paths package that
knows where your home directory is, constructs paths under it without
consulting the environment every time, and does not pretend that
filepath.Join(homeDir, ".local", "share", "openriot") is an
acceptable thing to type in the year 2026. It is not. It was never
acceptable. We were young. We were foolish. We have since been to
counseling.
The Great os.UserHomeDir() Extinction Event — or, 28 Calls Die So
That Millions May Live — We replaced twenty-eight calls to
os.UserHomeDir() with paths.HomeDir(), paths.Join(),
paths.OpenRiotDir(), paths.ConfigDir(), and paths.IconDir(). The
victims — and we use that word deliberately, because these functions
did not go quietly — include installer/cmd.go,
installer/configs.go, installer/crush.go, installer/games.go,
installer/tag.go, installer/release_notes.go, notify/cooldown.go,
notify/notify.go, weather/weather.go, wireguard/wireguard.go,
screenrec/screenrec.go, nightlight/nightlight.go,
resolution/backend.go, windowicon/windowicon.go,
screenshot/screenshot.go, update/versions.go, backgrounds/backgrounds.go,
crypto/crypto.go, fonts/fonts.go, gurk/gurk.go, assets/assets.go,
lock/cache.go, lock/lock.go, network/network.go, polybar/polybar.go,
rofi/rofi.go, window/switch.go, settings/settings.go,
commands/commands.go, commands/helpers.go, main.go, and
config/loader.go.
That is thirty-three files. We counted. We also counted the number of
times we typed filepath.Join(homeDir, ".local", "share", "openriot")
and discovered it was approaching a statistically significant portion of
the codebase. If you printed all those lines and stacked them, they
would reach the moon. Not really. But they would reach the top of a
desk, which is tall enough to be embarrassing.
The paths package is now the single source of truth for all path
construction. paths.HomeDir() caches the result once per process.
paths.OpenRiotDir("install", "packages.yaml") returns exactly what
you think it returns. paths.Join(".config", "openriot", "games.cfg")
does the same. No more filepath.Join(homeDir, ...). No more error
handling for a home directory lookup that cannot realistically fail. No
more twenty-line boilerplate blocks that exist only to join strings.
paths/paths.go — The Cache War — The original paths.HomeDir()
used sync.Once to cache the home directory after the first call. This
seemed smart. It is, in fact, the canonical Go pattern for lazy
initialization. But sync.Once never forgets. It is the elephant of
Go concurrency primitives. Once it executes, it remembers. Forever.
The problem: tests. Tests set HOME to a temporary directory. Then
they call paths.HomeDir(). Then sync.Once says, “I already did this.
The home directory is /tmp/TestSomething12345. That is the truth now.
That is the law.” And every subsequent test that changes HOME gets
the old temporary directory back, because sync.Once does not care
about your test isolation. It cares about doing something exactly once.
That is its entire personality.
We removed the cache. HomeDir() now calls os.UserHomeDir() every
time. This is not a performance problem. It is a string lookup. It
takes nanoseconds. The sync.Once cache was optimizing a non-problem
and creating a real one. This is a classic case of premature
optimization causing actual bugs. We are not sorry. We are wiser.
New Wallpapers — 22 Backgrounds, 18 Lock Screens, and the Re-Encoding
of Everything That Came Before — We added backgrounds/23.webp
through backgrounds/32.webp, and Locked/25.webp through
Locked/42.webp. Existing wallpapers were re-encoded at higher quality
settings because our previous batch was compressed with the enthusiasm
of a JPEG from 1998. The new backgrounds include landscapes that make
you want to quit your job, cityscapes that make you want to move to
Tokyo, and abstract compositions that make you wonder if the color
temperature algorithm in cwebp is sentient.
The lock screens are even more aggressive. They are designed to make you pause before unlocking your laptop. Not because they are distracting, but because they are beautiful enough that you want to look at them for an extra second. This is the OpenRiot security model: make the lock screen so appealing that shoulder surfers get distracted and forget what they were doing.
Discord and SolveSpace 3D — The Social Network and the CAD Software
Walk Into a Bar — Two new .desktop files:
config/applications/discord.desktop (abaddon, ) and
config/applications/solvespace.desktop (). The rofi app menu
(Super+D) now includes both. The workspace icon table
(config/window/icons.toml) maps abaddon and solvespace to their
respective nerd font icons. The README App Launcher table now lists them
alongside the existing entries, because a desktop that does not
acknowledge Discord and CAD software is a desktop that does not
acknowledge reality.
The 3D Printing Section — or, README.md Gets a Manufacturing Arm —
We added a new ## 🖨️ 3D Printing section to the README, complete
with a SolveSpace documentation link and a 7-printer compatibility table:
Prusa CORE One+, Bambu Lab P2S, Elegoo Centauri Carbon, Prusa SL1S SPEED,
Anycubic Photon Mono M7 Max, Elegoo Saturn 4 Ultra, and Bambu Lab A1
Mini. The table lists printer type, build volume, and compatibility
status. The README TOC was updated to include [🖨️ 3D Printing](#3d-printing).
This exists because OpenRiot users do not just code. They also design
parts in SolveSpace, slice them in Cura, and watch their printers
create physical objects from nothing while make release runs in the
background. We respect this. We document this. We do not judge the
person who has a Prusa and a ThinkPad running OpenBSD. We celebrate
them.
NZBGet Polybar Module — The SabNZBD Replacement That Does Not Eat
Thirty Percent of Your CPU — The previous approach used SabNZBD, a
Python-based usenet downloader that launches multiple processes, spawns
threads like a mushroom cloud, and generally behaves like a cryptocurrency
miner that happens to download Linux ISOs. We replaced it with NZBGet, a
C binary that uses pgrep -x for process detection instead of
pgrep -f (which self-matched the polybar exec command).
source/polybar/polybar.go now has RunNzbget() with icons
(running) and (installed, not running). source/commands/commands.go
registers --polybar-nzbget and --nzbget-open.
The config/polybar/config.ini has a new [module/nzbget] between crypto
and proton-drive.
source/settings/settings.go adds a conditional “NZBGet” stop entry in
the settings rofi menu, visible only when NZBGet is running.
--nzbget-open logic: if running, opens http://127.0.0.1:6789 via
Firefox with a notification. If installed but not running, launches
/usr/local/bin/nzbget -D (daemon mode, capital D) with a notification
and a fixNzbgetPerms() helper that chowns /etc/nzbget.conf and
/var/nzbget to the current user if they exist and are not already
owned. Otherwise, warns that NZBGet is not installed.
The settings rofi menu also got a violet border (border-color:
#997de1) because a settings dialog without a border color is like a
terminal without a font: technically functional, spiritually empty.
rmpc Workspace Icon Fix — The Music Player That Was Invisible —
config/window/icons.toml now maps rmpc = "". The music player
was using the rmpc window class (set by the i3 keybinding that
launches the terminal with --class rmpc) but the icon table only had
ncmpcpp, mpd, and lxmusic. rmpc got the fallback icon,
which is the polybar equivalent of being sent to the penalty box. It is
now properly labeled. It is now visible. It now has dignity.
Miscellaneous Cleanups — The Things You Notice at 3 AM —
source/commands/helpers.go now uses pre-compiled regexes for
bind-address-ipv4 and bind-address-ipv6 replacement in Transmission
settings, because strings.Replace in a loop is a war crime.
strconv.Atoi replaces manual string-to-int parsing where appropriate.
syscall imports were added for signal handling in process detection.
source/polybar/polybar.go uses pgrep -x instead of pgrep -f to
avoid self-matching polybar exec commands. source/lock/lock.go gained
filterStealth() and isProcessRunning() helpers to deduplicate
process detection logic.
🧾 Files Changed
| File | Nature of Change |
|---|---|
source/paths/paths.go |
Removed sync.Once; HomeDir() uncached |
source/installer/cmd.go |
paths refactor |
source/installer/configs.go |
paths refactor |
source/installer/crush.go |
paths refactor |
source/installer/games.go |
paths refactor |
source/installer/tag.go |
paths refactor |
source/installer/release_notes.go |
paths refactor |
source/notify/cooldown.go |
paths refactor |
source/notify/notify.go |
paths refactor |
source/weather/weather.go |
paths refactor |
source/wireguard/wireguard.go |
paths refactor |
source/screenrec/screenrec.go |
paths refactor |
source/nightlight/nightlight.go |
paths refactor |
source/resolution/backend.go |
paths refactor |
source/windowicon/windowicon.go |
paths refactor |
source/screenshot/screenshot.go |
paths refactor |
source/update/versions.go |
paths refactor |
source/backgrounds/backgrounds.go |
paths refactor |
source/crypto/crypto.go |
paths refactor |
source/fonts/fonts.go |
paths refactor |
source/gurk/gurk.go |
paths refactor |
source/assets/assets.go |
paths refactor |
source/lock/cache.go |
paths refactor |
source/lock/lock.go |
paths refactor; filterStealth() helper |
source/network/network.go |
paths refactor |
source/polybar/polybar.go |
paths refactor; pgrep -x fix |
source/rofi/rofi.go |
paths refactor; strconv.Atoi |
source/window/switch.go |
paths refactor; strconv.Atoi |
source/settings/settings.go |
paths refactor; NZBGet stop entry |
source/commands/commands.go |
paths refactor; NZBGet commands |
source/commands/helpers.go |
paths refactor; pre-compiled regexes |
source/main.go |
paths refactor |
source/config/loader.go |
paths refactor |
config/window/icons.toml |
Added rmpc, abaddon, solvespace icons |
config/polybar/config.ini |
New [module/nzbget]; modules-right updated |
config/rofi/apps.txt |
Added Discord and SolveSpace entries |
config/applications/discord.desktop |
New file |
config/applications/solvespace.desktop |
New file |
backgrounds/*.webp |
22 new backgrounds + re-encoded existing |
Locked/*.webp |
18 new lock screens + re-encoded existing |
install/packages.yaml |
Added nzbget package |
README.md |
Discord/SolveSpace apps, 3D Printing section, TOC |
install/openriot |
Binary rebuilt |
🎵 What We’re Listening To
Same playlist. Same questionable Theremin solo. But now your codebase
does not contain twenty-eight copies of filepath.Join(homeDir,
".local", "share", "openriot"). Your paths package is a single point
of truth. Your tests do not fail because sync.Once decided that the
first temporary directory it saw is the One True Home. Your wallpapers
are numerous enough that you could use a different one every day for a
month and still have leftovers. Your lock screens are so beautiful that
you sometimes forget your password because you were staring at the
gradient. Your app menu has Discord and a 3D CAD program. Your polybar
has an NZBGet module that does not consume an entire core just to check
if a process exists.
The paths refactor is the kind of change that makes you wonder why you
did not do it six months ago. It is also the kind of change that makes
you realize how many times you copy-pasted the same five lines of path
construction. Twenty-eight times. That is not a number. That is a
cry for help.
We answered the cry. We built the abstraction. We eliminated the duplication. We made the code shorter, cleaner, and less likely to break when someone moves their home directory to a ZFS pool on a network-attached toaster. This is what optimization looks like when you optimize for maintainability instead of microseconds.
🗣️ Final Words
“The best home directory accessor is the one you only write once.” — The OpenRiot Crew, after deleting the fourteenth
filepath.Join(homeDir, ".local", "share", "openriot")and feeling a spiritual release usually reserved for finishing a four-hour ambient album
v7.9.3 is the release where we looked at os.UserHomeDir() and said,
“You are not wrong. You are just everywhere. And being everywhere is
worse than being wrong, because wrong can be fixed. Everywhere requires
a systematic extinction event.”
Then we looked at sync.Once and said, “You are a brilliant primitive
that solves a real problem. But you are not the right primitive for a
home directory lookup. That problem does not need solving. It needs
letting go.”
Then we looked at the wallpapers and said, “There should be more of these.”
Then we looked at the lock screens and said, “These should also be more numerous.”
Then we looked at the app menu and said, “Discord and CAD software belong here.”
Then we looked at the polybar and said, “NZBGet should be here, and it should not be written in Python.”
Then we looked at the README and said, “3D printing is a thing that happens. We should document it.”
Then we looked at rmpc and said, “You deserve an icon. Not a
question mark. An icon.”
None of these are revolutionary. All of them are correct. The paths are abstracted. The cache is gone. The wallpapers are many. The lock screens are beautiful. The app menu is complete. The polybar is streamlined. The README has a manufacturing section. The music player has an identity. The code is shorter. The tests pass. The build is green.
Your go test does not fail because sync.Once cached the wrong
home. Your openriot --polybar-nzbget does not match itself. Your
Super+D menu has Discord. Your Super+Shift+K music player has an
icon. Your desktop knows what 3D printing is.
This is maintenance. This is care. This is what it looks like when
people who build the installer also have to look at their own code and
admit that filepath.Join(homeDir, ".local", "share", "openriot") was
never a good idea, no matter how many times we typed it.
— The OpenRiot Crew
“Your home directory should be a constant, not a variable. Your paths should be constructed by functions that know what they are doing. Your cache should solve a real problem, not create one. Your wallpapers should be numerous. Your lock screens should be art. Your app menu should be complete. Your polybar should not self-match. And your music player should never be reduced to a question mark.”