2025 State of the Tools
Goodbye 2025! This has not been an easy year for fairly obvious reasons, and I’m not feeling as reflective at the end of the year as I sometimes am, but I’ll try to provide at least some of my usual year-end reporting.
The core of my toolset is pretty stable from 2023 and 2024: still in the Apple ecosystem for endpoints, and a lot of the major infrastructural pieces and small utilities are still in play. I’ll therefore focus this review on the interesting changes.
Programming
The only particularly interesting change in my programming toolset
this year has been leaning more on ZSH shell scripts for various
automation things and even nontrivial logic. I wrote dumptruck(8), an encrypted
backup management tool I use for backing up some of my servers, in Z
Shell. I also replaced several simpler TypeScript programs and with ZSH
scripts, and wrote myself a small [standard library][stdlib.zsh] to
share code across the various scripts and repositories I use.
Shell script is notoriously difficult to write correctly, but both Bash and ZSH provide quite a few features to make it easier if you’re willing to depend on them. I target Bash for most scripts in repositories others need to work on but fully rely on ZSH for things that are mostly me. It’s my standard login shell and I have it installed on all my machines, so the dependency isn’t a problem.
I also moved most of my TypeScript/JavaScript code from Deno back to Node. There are many things I like about Deno as a platform, including the first-class TypeScript support, but deployment and building is a pain (they really only support their own binaries, and keeping the Conda packages working was rather a lot of work; last I touched them they had some outstanding build failures that I couldn’t find workarounds for). They’re also a VC-funded product, which brings various forms of misalignment between community and funder needs. I was also extremely annoyed by their deployment of an AI bot on their support forums to barf incorrect information as a first reply to user questions. I’ve been toying with the idea of using Haskell + Hakyll to build my website again.
Git Hosting
Last year, I was using [soft-serve][] for hosting my
private Git repositories (esp. with many LFS files), and early this year
I set up Laminar to
run CI jobs to rebuild my website. They’re both nice pieces of software,
and fine to use if you don’t need anything more than command-line Git
hosting and CI jobs.
However, I started needing more than that. This month I migrated to a
self-hosted Forgejo instance for my
local Git hosting along with Woodpecker for CI. Now that I have
the Woodpecker runners stable and the relevant permissions and caching
to reliably and efficiently rebuild my website, I’m pretty happy with
this setup. Forgejo also provides package repository support for
probably a dozen different package systems, including Debian
apt, RPM/DNF, Alpine Linux, and Docker, so it serves as a
private container image registry as well as Git and Git-LFS hosting.
This was my primary impetus for the switch, as I am relying on quite a
bit of containerization in my home network setup.
I’ve also started moving some of my public projects from GitHub to Codeberg. GitHub is still super useful, and its performance and community reach are unrivaled, but I’m concerned about the reorganization to put it under the Microsoft AI division. I still keep things that I hope to build community around on GitHub, particularly LensKit, but my little personal things are usually going to Codeberg instead now.
Tasks and Dependencies
Early this year, I discovered mise-en-place, a package that can
install software (from multiple different sources, including a registry
I hadn’t heard of before called Aqua), manage project
environments, and handle task running. I’ve adopted it for managing my
home directory software and across many of my projects. It has largely
replaced direnv, just, and several uses of
pixi or conda in my workflows. Simple task
workflows are easier to write in just, but as tasks get
more complex, mise’s support for writing tasks as
standalone shell scripts provides a better complexity scale-up story.
For several of my projects now, the interesting project-local
development and management scripts are ZSH scripts written to be run by
mise.
Mise provides help, dependencies, and command-line parsing (through
usage, which can also
be used on its own), so it’s pretty easy to write interesting and
reasonably well-documented automations with Mise.
Mise also introduced me to sops, which turns out to be a great way to manage secrets in a project repository. I’ve now been using it, both with Mise and on its own, to manage secrets for several projects (including this website).
As both uv and published Python packages have improved in quality, I’ve stopped using Conda or Pixi for most projects. I’ll still use Pixi for projects with significant non-Python dependencies I want version-locked, but installing scientific Python packages from PyPI works a lot better now than it did a few years ago, and I have slightly fewer difficulties with it than I do with Pixi/Conda. Standard Python installations with uv’s dependency locking work great for a lot of things. For solo projects, I then use Mise or Aqua to pull in other dependencies if needed (or put them in a container).
Home Computing Platform
Concurrently with moving to Forgejo, I also reworked our home networking environment. I plan to write a separate post about this once a few more pieces are finished, but I moved both our home and cloud servers from Debian to AlmaLinux to take advantage of bootc. Some years ago I used NixOS, and wanted for some time a way to declaratively manage my system configurations with a more normal Linux and without the weirdness of the Nix expression language. I’ve experimented with things like Ansible and PyInfra to automate management, but true declarative management they are not.
Turns out bootc scratches that itch! It lets me manage
system state as bootable containers, so I write a
Containerfile (and associated scripts, configuration, etc.)
to assemble my system as an OCI (Docker) image, and bootc
installs and upgrades the system with that image. Mutable local state
lives in /var (and /etc by default, but I’m
working towards the recommended configuration of making
/etc fully immutable/ephemeral as well).
When this migration is finished, I think I’ll have achieved my home admin goal: a git repository defining the software load and configurations for the various servers on our network, making changes through configuration edits + deploys. Forgejo’s container registry support plays a vital role here, as the container-based systems install and update from it.
I also replaced Tailscale with Nebula. Tailscale is great, I highly recommend it and have few complaints. I wanted slightly different firewall / access control capabilities than it provides, however; I also have a small amount of nervousness about two single-points-of-failure (Tailscale and GitHub authentication) for my network security, so managing my overlay network in a self-hosted Git repository of machine definitions and certificates is a nice benefit. Nebula also provides front-line security for sensitive network services: anything I don’t want chatted at by unsecured devices is only on the Nebula network, even within our LAN, so it’s secured from the various IoT devices and other things that might be on the network. I’ll be using WireGuard to provide myself a roaming VPN for travel and off-site access (accessing Nebula from outside our home network is no problem, but it doesn’t support exit nodes like Tailscale, so a full exit to protect from dodgy Wi-Fi needs another solution).
With Windows 10 end-of-life this year, I switched our desktop over to Fedora. It runs (older) Steam games fine, and we don’t have a burning need to upgrade the machine.
Document Preparation
This year I also started exploring Typst, and love it. My use of LaTeX has increasingly been begrudging over the last few years, and Typst is the most promising entrant in a while to try to deliver strong typesetting capabilities with a programming language that isn’t screamingly old in its design principles. My CV is now in Typst, and I’m using it for other documents that I don’t need to collaborate on. I plan to use it for a lot of my course materials next term (and possibly also my lecture slides, we will see — I like PowerPoint, but PowerPoint’s lack of style support makes it annoyingly difficult to consistently typeset code snippets).
It isn’t ready to replace LaTeX for writing papers for ACM conferences and journals, but I wouldn’t be surprised if it gets there in the next five years (whether ACM’s backend will support it is another question, whose positive resolution is less likely).
I have always found it far more painful than worthwhile to attempt to customize LaTeX appearance beyond changing fonts and the page/layout customization supported by the Memoir class. With Typst, making a fully-custom layout and theme for my CV, and another one to implement Drexel letterhead, was pleasant work.
Web Serving
I was not happy with my web server situation this year:
- Apache is reliable for many things, and its interesting
mod_spelingmodule allowed me to self-host my website while maintaining URL compatibility with my past hosting on Netlify, which has the interesting and questionable behavior of forcing all URLs to lowercase. However, its “managed domain” support for automatic HTTPS is glitchy and annoying, requiring multiple server reboots to bring a new domain online. - Caddy has outstanding automatic HTTPS support, and supports 95% of
my web serving needs, but it doesn’t have an equivalent to
mod_speling, and I don’t know Go so writing a plugin to add that support was going to require learning both a new programming language and the Caddy API. And while I’m rarely one to shy away from a new programming language, I don’t find Go to be a very interesting language (although it’s used for a lot of very useful software!), so learning it isn’t a priority for me.
So I did the obvious thing, and wrote a (small) web server. The Opinionated Page
Server (opserve) serves static files using a
pre-generated manifest of files (with their types and hashes), with
support for case-folding. It probably isn’t very useful to others, and
is currently undocumented, but it does what I need it to and happily
serves up my website from a container. It also gave me an excuse to play
with some more async Rust. The whole thing took maybe a couple of days
to write (using Trillium). It only
supports plain HTTP, so I have Caddy in front of it as an
SSL-terminating reverse proxy (and Bunny CDN in front of that).
Music
I continue my vinyl music collection and listening hobby, and this year upgraded my stereo receiver: replaced the Sony STR-DH180 with a used Marantz NR1200. The Marantz has noticeably better sound quality, especially for anything digital (even though the Sony supported hi-fi audio over Bluetooth, its quality was pretty poor). I’m still looking to upgrade my speakers, or at least the subwoofer, but I’m pretty happy with most of the sound now.
I replaced the Shure open-back headphones I was using at home with another pair of Sennheiser HD-600s (I already had a pair in my Drexel office), for both weight and sound reasons.
Coming Attractions
I’m always tweaking and improving (or breaking) things, as regular
readers may have noticed 🙃. In the next months, I hope to finish the
network upgrade (particularly adding the router to the
declaratively-managed set and implementing SSO, which is the last major
piece needed to switch to ephemeral /etc on managed
machines), and I’ll probably write some more random scripts and Rust
programs.