2025-03-07
TL;DR: I spent a good portion of the winter making improvements to my lab and office space. The major improvements were expanding my storage capacity, optimizing my desk area, getting rid of more stuff I don’t need, and switching to NixOS on my primary machines.
By the way, even though I mention and link to a lot of products in this post, I don’t get money or anything else from any of the sellers. I have no relationships with them whatsoever.
At the end of my last project, I’d accumulated piles of stuff. Much of it was useful but had no particular home in my lab. I recognized this as similar to the condition that prompted me to do my first lab cleanup, so I thought it would be a good idea to repeat and expand that effort now. My rough goal was to increase my available Gridfinity spaces by 10x, but in the end I only managed about 4–5x. However, it ended up being much more valuable for me to increase my non-Gridfinity shelf and bin space for larger items, such as tools, test equipment, and cables.
I started by learning about what other people have done to increase their Gridfinity storage. A popular option is to buy a particular model of IKEA drawer whose internal width happens to be exactly seven Gridfinity units wide. I was happy with this idea until I tried to order it and found that the shipping cost would be greater than the cost of the drawers themselves. I live a few hours from the nearest IKEA store, so I guess this isn’t that surprising, but in any case it was a disappointing non-starter.
Instead, I bought some ordinary metal shelves on eBay. They came as a kit and seem to be exactly as advertised. The metal mesh doesn’t present a flat surface for the Gridfinity baseplates, so I cut some spare cardboard—including the box that the shelves came in—and laid them on top of the mesh. I was concerned at first that the static shelves would make it harder to see the contents of my bins, and in a sense this is true. However, the fact that they’re open on all sides makes it easier to scan through the whole stack more quickly, rather than opening a drawer, scanning, and moving on to the next one.
As for the bins themselves, I thought for a while that I’d try making a paper pulp version of the common Gridfinity bin design, but I decided against it because it seemed like that approach would be difficult to scale up. Later I tried bins that use paper towel rolls as walls, but this had similar problems due to requiring a smaller nozzle than my preferred 0.6 mm. Finally I decided to use a modified bin set that reduces the amount of plastic needed for each model, which increases speed and lowers cost.1
Some things are worth spending for, and this is one of them. I didn’t always believe that, so I was a bit dismissive some months ago when a coworker told me about his refurbished office chair and all of its features and adjustments. I’d been sitting on the same beaten up hand-me-down chair for years, and it wasn’t until it started shedding its fake leather that I seriously considered upgrading. This was an excellent decision that I delayed far too long.
I bought a remanufactured Steelcase Leap V2 from Crandall Office Furniture. It’s pricey, but the quality is high and the comfort is surprising.2 I was lucky to get my company to pay for most of it, so the cost to me was much lower. Crandall also provides a 12-year warranty—which is so long that I can’t really see how they manage it—and a 30-day return window. Assembly took less than 15 minutes, and their video documentation is lovely. I had the whole thing dialed in how I wanted it within half an hour and never want to go back. I’ve sat in my old chair once since then and can’t believe I ever thought it was acceptable. Maybe it was okay when I was 20, but today my body is different and is starting to need help.
Sitting in this chair is one of those things that can’t really be expressed or explained. All I can say to approach the true experience is:
If you’re interested, I found this video to be helpful during my selection process, although the Leap V2 appears to be the go-to choice when in doubt anyway. I paid a little extra for the roller blade wheels, which I think were worth the money.
For years I’d gotten by with the default internal microphones in webcams and laptop lids. Before COVID, this was acceptable because I didn’t do a lot of video calling, and especially in college when I was moving twice a year it didn’t make sense to have yet another set of items to pack and unpack. Now that I take video calls at least half of the days in any given week, it makes sense to at least try to have a nice setup. It makes for better first impressions and is a bit more comfortable for whoever I’m talking with regularly.
I already had an AudioBox USB, which is a basic two-input XLR-to-USB converter. I also had a “shotgun” condenser microphone and desk stand laying around too, so it was really a matter of setting them up and dialing in all the settings to give decent quality. Fortunately this was pretty easy because my new NixOS installation (see below) has relatively sane defaults and behavior with respect to sound settings. The hardest part was finding a decent place for the microphone to sit on the desk such that it was out of my way but still close enough to pick me up well. In the future I may switch to a different microphone and an adjustable arm.
I bought a Logitech 930e during a previous project and wasn’t using it for anything else, so I plugged that in and found it even easier to set up than the audio equipment. I also printed this privacy cover, which has a clever pop-in hinge design. The designer seems to have intended it for use with the 920, but it works for my 930e just as well. The only problem I have with it is that the cover is a little unstable in the open position and can fall closed if I bump the desk. I might be able to modify the design slighly to include a weak magnet that holds the cover out of the way.
I set up a KVM switch so I can use my work laptop with the same I/O peripherals as my personal machine. This was a big improvement compared to hunching over a laptop like I’d been doing for years. To get my speakers working, I bought an inexpensive 3.5 mm USB adapter. For the most part, switching between the two hosts works as expected, which was a pleasant surprise. I found that the switch can’t supply enough current to run all of my peripherals at once (particularly the webcam and audio interface), so I bought a powered USB hub to supplement. In hindsight it makes sense that this would be necessary because the KVM switch can only get a few watts from the single USB port it’s connected to on the selected host,4 and connecting all of my peripherals to a single port with a passive hub is clearly a non-starter.
After hooking everything up and convincing myself that it was mostly going to behave, I used some velcro cable ties to neaten everything up. This had a similarly positive effect on my mental clarity as cleaning up a desk or table. Something about high-frequency disorganization in an area makes it harder to parse and inherently more frustrating.
I bought a new keyboard. I’d never been picky about keyboards and used whatever I could get away with. Most recently I was using the no-name membrane keyboard that came with my desktop. I decided to get a major upgrade and invest in a high-quality mechanical keyboard that I expect to last me many years. A friend recommended Keychron, so I bought their Q6 Max along with Kalih Pro Purple switches and these keycaps. This is what the switches sound like on a different board.
This board is excellent. It’s heavy, which makes it feel premium due to my caveman brain. The lights are a nice touch but aren’t really my thing. Full-size layout was a requirement for me, and I don’t regret that. The rotary encoder has also proven unexpectedly useful and natural. (By default, the encoder is mapped to volume control.) I like that I can customize the layout and firmware, which is normal for keyboards these days, but so far I’ve been happy with the defaults. Wireless is cool, but I’ve left it plugged in so far. The cable doesn’t bug me, and it’s not like I’m going to move this keyboard around. Between this and my chair, the overall ergonomics of my daily work have improved substantially.
I upgraded the RAM capacity on my main machine from 16 to 64 GB. I found that I was often running low on memory and wanted to rid myself of that. So far I haven’t come close to it. I was a little surprised when I saw that my machine could handle that much memory. It’s only an Optiplex 5050—an old Dell box that I bought off eBay for $300—but it’s capable enough for what I need and has decent room for expansion.5 I also bought a 1 TB SSD to put in its spare slot, which I’ll get around to switching to eventually. The 256 GB HDD has been fine in the meantime.
I bought a filing cabinet. Assembly took an hour or two and required a bit of troubleshooting on the drawer slides, but after all that this quickly became a big win. I didn’t realize how many important papers I had been keeping track of throughout the house. Now they’re all nicely tucked away in drawers and are mostly easy to find when I need them. I guess it shouldn’t surprise me that getting organized feels good, but somehow it does anyway.
I printed a headphone stand after modifying the original model to include an empty internal space for a chunk of spare steel I had laying around. The extra mass in the base makes it somewhat more stable. Some stick-on silicone feet on the bottom help too.
I don’t want to just repeat the sales pitch for NixOS here because others have done it better and more completely than I can. For the curious, consider starting with this video by No Boilerplate, which got me going on this path in the first place.
Still, it’s worth mentioning that my initial reasons for switching away from Debian were the declarative and repeatable configuration system, the up-to-date package repository, and a significant reduction in pain associated with using my machines in general. This reasoning has been more or less completely proven out in my experience, which has led me to the conclusion that NixOS is the Linux community’s best answer to macOS.
Along the way, I learned about Impermanence, which is a NixOS module that allows the end user to erase their machine on every boot, with the exception of a configured subset of files and directories. While this is a radical approach, I’ve found it to be the most freeing personal computing paradigm since Linux itself. (See below.)
Parts of this section won’t make sense unless you already understand some of how NixOS configuration works. I’ve left out a lot of details. I certainly don’t have a complete understanding, but I know enough to meet my needs and (usually) figure out how to do something new. Gathering this knowledge wasn’t easy because Nix/NixOS is an unusually difficult ecosystem to learn, and I can’t compress all that into a few hundred words. In any case, I hope this gives you some inspiration if you’re trying to set up something similar for yourself.
I keep my configuration in a private git
repository.6 Each machine that I configure using
this repo has a copy of it at linked to ~/.system
. The repo
looks like this (with hostnames replaced):
.
├── backup.sh
├── flake.lock
├── flake.nix
├── hosts
│ ├── foo
│ │ ├── config.nix
│ │ ├── hardware.nix
│ │ └── machine-id
│ └── bar
│ ├── config.nix
│ ├── hardware.nix
│ └── machine-id
├── modules
│ ├── common.nix
│ ├── dconf.nix
│ ├── desktop.nix
│ └── ham-radio.nix
├── set-up-zfs.sh
└── snapshot.sh
The repo is a Nix
flake, which means nothing to the newcomer except to say that it’s
the de facto standard way to package Nix code. You can tell
it’s a flake because it has a flake.nix
and
flake.lock
. In my experience, it’s not necessary to
understand flakes deeply to get stuff done with them.
flake.nix
calls out to the hosts/
and
modules/
directories and maps hosts to the modules that
they need. The hosts foo
and bar
in the code
above don’t need to have the same configuration, but I’ve found it
helpful to group tools and settings by general category. For example,
common.nix
specifies all the command line tools that I’d
like to have wherever I go, as well as basic networking configuration
and stuff like that. desktop.nix
covers everything that I’d
expect to have in a desktop environment. As my network changes, I can
update these modules as needed. git
is great for this
because it’s decentralized by design, so I can make a change on one
machine, test it, and then commit it and pull it down on my other
machines over SSH.
The three shell scripts, backup.sh
,
snapshot.sh
, and set-up-zfs.sh
, are small
helper scripts for doing more or less what their names suggest. My
systems use ZFS as described in the next section, and
common.nix
includes a systemd
service and
timer that runs snapshot.sh
and backup.sh
every day. This automatically creates a new ZFS snapshot and sends it to
my NAS. At some point I’ll need to
include a garbage collection step for old snapshots.
set-up-zfs.sh
contains the commands I need to run in
order to set up the disk on a new machine. There are a few steps that
are easier to do manually, which I described in code comments in the
file. I could have used disko
or
similar to fully automate this, but it didn’t seem valuable for me
because I don’t set up new machines that often. I don’t need maximum
purity, just reliable systems that generally cooperate.
Making a change is easy enough. Say I forgot to include
rsync
in my config. If I think I’ll only need it right now
and not in the future, I can create a temporary shell environment that
has rsync
with nix-shell -p rsync
. In the more
likely case that I want it to be a part of my system, I open
common.nix
and add it to the list of packages. Then I run
update
, which is a shell alias for the proper
nix
incantation, and I’m on my way. Every once in a while I
run nix flake update
which updates the lock file and pulls
in any package updates (think apt update
).
At some point during my first week or two using NixOS, I found this blog post by Graham Christensen about the Impermanence module. Since those links explain how and why you’d want to use it, I’ll skip to my own configuration, which is a variation on the arrangement that Graham describes.
The whole thing rests on ZFS and its useful snapshot and rollback features. I have three encrypted datasets:
/data
is for stuff that “belongs to me” such as photos,
documents, code, browser data and my machine configuration repo. This is
everything that I consider to be personal data, and it’s the stuff that
I would be sad to lose./cache
is for stuff that I don’t need but
would prefer to keep around, such as game files and Rust toolchains. I
don’t want to back these files up because they’re large and can be
redownloaded easily, but for convenience I’d like to keep them around on
the machine across reboots. Deleting everything in this dataset wouldn’t
affect my computing experience except making it initially slower./local
is for everything else that makes the system go.
Within this dataset, there are two child datasets:
/local/nix
, which contains the Nix store, and
/local/root
, which contains the logical /
directory for the running system.7In practice, I do back up /cache
, but in principle I
could omit it and might do that in the future. My reasoning for
separating /cache
from /local
is a little
questionable, but I was thinking that if I ever want to switch to a new
machine or disk, it would be convenient to be able to move
/data
and /cache
, rather than just moving
/data
and having to rebuild /cache
from
scratch.
On boot, I’ve configured NixOS to run these commands:
zfs snapshot zpool/local/root@$(date -Iseconds)
zfs rollback -r zpool/local/root@blank
The snapshot
command creates a ZFS snapshot of
/local/root
with the current timestamp. Then, the
rollback
command reverts the dataset to a snapshot I took
just after I created it and it was completely empty. This removes the
previous boot’s root directory from the running system but keeps it
around in a snapshot in case I need something from it later. As the boot
sequence continues, NixOS builds up the root directory based on my
configuration.
The snapshot and rollback doesn’t affect the contents of
/data
or /cache
, but so far they’re also not
accessible to the running system. That’s where the Impermanence module
comes in. I’ve given it a list of files and directories that I want to
make available in locations that would otherwise be deleted, such as
/home/bradley/.config/...
. During boot, it creates links
between the the real files in /data
and /cache
and their desired locations in /local/root
. This relies on
filesystem trickery that I don’t understand. After the links are
created, the system operates as if it had been in that state when it
last powered off, even though it was in some other state with extra
unwanted data.
I feel much more secure when trying out new software or accumulating downloaded data because I know that I can clean it all up by rebooting. I also know that my backups contain exactly the data I want and nothing that I don’t, which I didn’t think was practically possible on a Linux system. One problem I’ve noticed is that some programs expect to be able to overwrite a config file by writing to a separate temporary file and then moving the new file onto the existing version to overwrite it. This doesn’t work with impermanence because of the way individual files are linked. (At least, that’s my understanding.) I haven’t found a good general workaround for this yet—and it seems like the devs haven’t either—but maybe I’m missing something. As far as I can tell, symlinks have this problem as well.
Switching to NixOS has made several things easier:
nixpkgs
. I just run
nix-shell -p <package>
, wait a few seconds, and then
use the tool. When I leave the shell, it’s gone. If the tool left any
residual data, it’s gone after a reboot and I’m truly back to zero.However, some things are harder or just weird:
nixpkgs
—which happens often with
unpopular projects—it’s necessary to package the project for NixOS even
if you’re only using it locally. I haven’t tried this seriously, and it
seems like there are tools that make it easier, but for a newcomer
leaving the city limits of nixpkgs
gets rough fast.Last year I started using CadQuery and had a lot
of fun doing it. CadQuery isn’t in NixOS 24.11 as of writing, so I
needed a way to access it in an idiomatic way. Since CadQuery is a
Python library, it may seem like I only need to run
pip install cadquery
, but this doesn’t work because
CadQuery relies on some native libraries for its CAD kernel operations.
One way to solve this under NixOS is to create a .nix
file
that declares the requirements for the environment and then run that
shell with nix-shell
. I got a head start when I found this
gist by Technicus, which creates a shell for build123d. This seems to
be a fork (or some other relation) of CadQuery, so a configuration that
works for one should be pretty close or even sufficient for the other.
Here’s the minimal Nix file I came up with:
{ pkgs ? import <nixpkgs> { } }:
let
libInputs = with pkgs; [
expat
libGL
stdenv.cc.cc
stdenv.cc.cc.lib
xorg.libX11];
lib-path = with pkgs; lib.makeLibraryPath libInputs;
shell = pkgs.mkShell {
shellHook = ''
SOURCE_DATE_EPOCH=$(date +%s)
export "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${lib-path}"
'';
};
in
shell
The file contains a single Nix function that takes some package set
pkgs
or defaults to nixpkgs
. The entire body
of the function is the last line, which returns the variable
shell
. Everything else is preamble in the let
block. libInputs
is a list of libraries that are needed in
order to install and use CadQuery. I started with the list in the above
gist and used trial and error to figure out which ones were necessary.
Then those are made available to dynamic executables in the shell
environment by creating lib-path
—which I think is just a
colon-separated list of each library’s individual path?—and then
including that in LD_LIBRARY_PATH
for all child processes.
I don’t really understand much more than that, if I’m honest. However,
if you write this to a file and then run
nix-shell <the-file>
, you get a shell where you can
create a Python virtual environment, run
pip install cadquery
, and then use the library as
normal.
This is a representative example of what it’s like to deal with NixOS as a newcomer. Most things are refreshingly easy, but sometimes you’re in a small enough minority that nobody has publicly solved your exact problem yet and you have to roll up your sleeves. Once you’ve solved it, though, it remains quite stable due to the guarantees of the Nix ecosystem. As the saying goes, Linux is only free if you don’t value your time. I still prefer it to all known alternatives.
Writing this felt a little repetitive. It’s more or less the same story every time: it turns out that when you put more effort into your workspace and tools, it makes it feel nicer to use them. It’s obvious, but for a long time I’ve ignored this kind of thing anyway. I don’t really know why, but I guess it has something to do with a desire to do more with less. Efficiency is important, but the 80/20 rule still applies. Maybe it’s possible to still get a lot done with something approaching the bare minimum, but there’s a practical balance between austerity and extravagance. It’s okay to spend some time and money finding it.
I considered not writing anything for this project, since in a sense it doesn’t have anything of novel technical interest. But I think the aggregate effect on my feelings towards my workspace has value, even if I can’t point to a single part of the project that caused it. I look forward to working at my desk or in my lab more than I did before, not because my work is more interesting now but because those places are simply more inviting.
During this project, I’d sometimes think about the potential gains in productivity that these changes could produce. I think it’s likely that I will spend less time fighting my tools now that they’re somewhat more reliable, ergonomic, and organized. But I’ve tried to put that thought aside and focus on the intrinsic value of making my environment better. Even if I never see a payoff in increased project efficiency, making my spaces better was fun in its own right.
Just for fun, I pulled out a very old roll of PLA (Hatchbox silver circa 2014, when I bought my first printer kit) and tried to print some bins with it. I expected it to be so brittle and waterlogged that it wouldn’t print, but actually the bins came out beautifully. The climate here is so dry in the winter that moisture probably isn’t a big problem right now. I guess this is a demonstration of the durability of plastic, for better and worse.↩︎
Not to mention that the original manufacturer charges more than double↩︎
I also considered buying a sit-stand desk with an anti-fatigue mat, but it seems that the science is inconclusive on its health benefits, and they’re quite costly, so I didn’t bother. Getting up regularly, sitting properly, and classic diet/exercise are probably good enough anyway.↩︎
I’m guessing at the internal details, of course. I guess in principle it could bond the outputs from each port on the connected hosts or even draw current from the HDMI ports as well, but that’s probably a flaky design and adds a lot of complexity to a relatively cheap product.↩︎
Maybe someday I’ll build a proper machine for myself. I do get some joy out of using older machines to do all my normal computing. Plus it marginally reduces ewaste. But for someone who claims to be interested in computers, I sure don’t have a lot of nice ones.↩︎
Early on I explored keeping secrets in the
git
repo behind some kind of encryption, but I found it
easier to just declare that the repo would remain private. I lose out on
some nerd cred, but I sleep a little better. It also doesn’t affect my
security model, since if someone I don’t trust gets a copy of the repo,
they probably have access to the rest of my data too.↩︎
I’m certainly no expert on NixOS internals, but my understanding is that NixOS relies on the Nix package manager’s ability to build a shell using symlinks to actual data in the Nix store. This allows Nix to do magical things like build a temporary shell with a particular tool installed. Careful management by the underlying tooling decouples the running system from the space of possibly running systems. Linux’s “everything is a file” abstraction makes this possible in the first place.↩︎