<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Zack Bartel</title><link>https://zackbartel.com/</link><description></description><generator>Gozer</generator><lastBuildDate>Sun, 23 Aug 2026 22:46:34 -0700</lastBuildDate><item><title>Tether: Linux + iPhone</title><link>https://zackbartel.com/blog/2026/08/tether/</link><description>&lt;br&gt;&#xA;&lt;h3&gt;I didn&#39;t miss macOS, but...&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;When I &lt;a href=&#34;/blog/2025/02/return-to-linux/&#34;&gt;went to Linux&lt;/a&gt; full time I was surprised to not miss much from macOS.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;tldr; I made &lt;a href=&#34;https://github.com/zackb/tether&#34;&gt;Tether&lt;/a&gt; to do all the things I did miss.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/img/tether/messages.webp&#34; alt=&#34;tether open showing imessages on linux&#34;&gt;&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;No AirPlay kinda sucked, but the one thing I kept wishing I had was what Apple calls &lt;a href=&#34;https://www.apple.com/macos/continuity/&#34;&gt;&amp;quot;Continuity&amp;quot;&lt;/a&gt;: the ability to send and receive iMessages and SMS, share files, sync clipboard, and see notifications from my iPhone on my computer.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;In particular, that thing where OTP codes sent to Mail or Messages autofill into the login form I&#39;m waiting for on the Mac. It sounds small, but that was the one thing I really missed!&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;It is impossible to fully implement all of Continuity on Linux, but Tether&#39;s goal is to do all of it that is technically possible.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Before you ask, I know &lt;a href=&#34;https://kdeconnect.kde.org/&#34;&gt;KDE Connect&lt;/a&gt; exists. It&#39;s great! If you&#39;re on Android you should definitely use it. The problem is that it didn&#39;t do what I wanted and it&#39;s roadmap indicated it never would.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Origins&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/zackb/tether&#34;&gt;Tether&lt;/a&gt; started its life filling the major holes. Clipboard sync was first, I wanted the OTP flow and I knew this was going to be required, and it was also the easiest. This let me copy / paste between macOS and Linux (Wayland) and forced the project to build out all the foundation I knew it would need later.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I knew the App Store was going to be a hurdle so I shipped the &lt;a href=&#34;https://apps.apple.com/us/app/tether-linux-companion/id6762097135&#34;&gt;iOS app first&lt;/a&gt; with only support for clipboard sync. There was a very basic &lt;code&gt;tetherd&lt;/code&gt; daemon for the linux side but that was it.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h4&gt;Security was a first class citizen from day one.&lt;/h4&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I never skipped out on safety. The networking between iOS and Linux has been mTLS from the start, and both sides must agree before anything can communicate. Since then I&#39;ve done Opus and then Fable bug / security sweeps regularly.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;After that, file transfer was the obvious next step and that went in quick and easy.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;&lt;a href=&#34;https://www.destroyallsoftware.com/talks/wat&#34;&gt;Wat&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;As for mail and browser stuff, I&#39;m not very creative. To be honest I&#39;ve kinda cheated here. I use Zen Browser (Firefox) and Betterbird (Thunderbird) and both support WebExtensions very well. So, I just made a &lt;a href=&#34;https://addons.mozilla.org/en-US/firefox/addon/tether-browser-extension/&#34;&gt;browser&lt;/a&gt; and &lt;a href=&#34;https://addons.thunderbird.net/en-US/thunderbird/addon/tether-mail-extension/&#34;&gt;mail&lt;/a&gt; extension to deal with OTP stuff.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The mail extension looks for OTP codes and sends them to the browser extension which looks for OTP input elements and autofills OTP codes as they arrive from mail.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;It actually works great! The problem is broad support: if you use a different mail client you&#39;re kinda out of luck for now. I&#39;d LOVE if someone wants to help with that stuff, but it&#39;s so far out of my comfort that I&#39;ll stick with the backend side for now.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Bluetooth Breakthrough&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;My understanding to this point was that there was no interface to iMessage / SMS directly. You could always run a &lt;a href=&#34;https://bluebubbles.app/&#34;&gt;proxy on a Mac&lt;/a&gt; and send and receive texts through that, but in my opinion that is not a solution. I tried it. Even with Tailscale it was an unpleasant experience.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Then I ran across &lt;a href=&#34;https://github.com/pzmarzly/ancs4linux&#34;&gt;ancs4linux&lt;/a&gt; and later &lt;a href=&#34;https://github.com/erikwb/blueferry&#34;&gt;BlueFerry&lt;/a&gt; and I was completely shocked. In particular, &lt;a href=&#34;https://github.com/erikwb&#34;&gt;erikwb&lt;/a&gt;&#39;s &lt;a href=&#34;https://github.com/erikwb/blueferry/blob/main/PROTOCOL.md&#34;&gt;protocol documentation&lt;/a&gt;, which finally gave me enough information to understand what was happening.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;There was a licensing issue, these projects are GPL and Tether is and always will be &lt;a href=&#34;https://github.com/zackb/tether/blob/main/LICENSE&#34;&gt;MIT&lt;/a&gt;, but I wanted a clean room implementation in C++ anyway. Personally, I think putting an interoperability protocol implementation behind a copyleft license is a little unfortunate! I&#39;d rather see something like this usable by anyone, regardless of the license of the project using it (&lt;code&gt;libtether&lt;/code&gt;?).&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Anyway, now that the truly hard problem was solved, implementation went about as smooth as you could expect a bluetooth integration would go (it&#39;s still a &lt;a href=&#34;https://hackernoon.com/why-bluetooth-development-sucksand-how-simpleble-fixes-it&#34;&gt;complete shit show&lt;/a&gt; in 2026). Problem after problem with no clear explanation, an &lt;a href=&#34;https://atp.fm/324&#34;&gt;automatic kicking machine&lt;/a&gt;, and a bunch of carefully tracked edge cases compensating for bluetooth&#39;s inadequacies.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h4&gt;But we got there.&lt;/h4&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;And Tether now supports: iMessage, SMS, Notifications, Contact sync on Linux. In addition to its previous: File Transfer, Clipboard Sync, OTP handling.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;And my damn text OTP codes FINALLY autofill in the web browser!&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Why?&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;While I did scratch my own itch with this project, I genuinely want &lt;a href=&#34;https://github.com/zackb/tether&#34;&gt;Tether&lt;/a&gt; to be for everyone. I don&#39;t get anything out of people using it other than the satisfaction of knowing that something I built is useful.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Contributions are welcome! Bugs, feature requests, translations, documentation, really anything would be wonderful.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;If you&#39;re on Linux with an iPhone, I really hope you give it a try.&lt;/p&gt;&#xA;</description><pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate><guid>https://zackbartel.com/blog/2026/08/tether/</guid></item><item><title>fenriz: A fast, stable tiling Wayland compositor</title><link>https://zackbartel.com/blog/2026/07/fenriz/</link><description>&lt;br&gt;&#xA;&lt;h3&gt;I wrote a Wayland compositor.&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Yes, that&#39;s an unhinged thing to do. Hear me out.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I&#39;ve been on &lt;a href=&#34;https://hypr.land/&#34;&gt;Hyprland&lt;/a&gt; for a while and I want to be clear up front: it&#39;s fantastic. If you&#39;re a Linux power user, it&#39;s probably what you should be running. It does everything, it looks incredible, and the community is enormous. I recommend it to people all the time.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I just didn&#39;t want most of it. And it breaks too often.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;The eye-candy tax&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Hyprland&#39;s whole personality is the eye-candy, the blur, the bounce, the wobble, the buttery animation on every window open. It&#39;s genuinely beautiful and I turned all of it off. I don&#39;t want my windows to spring into place. I want them to &lt;em&gt;be&lt;/em&gt; in place.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;What I actually use is the boring part: tiling, floating, fullscreen, pinning, workspaces. The primitives. That&#39;s the whole compositor as far as I&#39;m concerned, and it turns out the primitives are maybe five percent of what a modern compositor ships. I was paying the eye-candy tax, in animation frames and in &lt;a href=&#34;https://github.com/zackb/dots/blob/main/.config/hypr/hyprland.lua&#34;&gt;config bloat&lt;/a&gt;, for features I actively disabled.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The other tax was breakage. The Hyprland ecosystem moves &lt;em&gt;fast&lt;/em&gt;, which is great when you want the newest thing and less great when a routine update changes a config key out from under you and your bar or your &lt;a href=&#34;https://github.com/hyprwm/hypridle/issues/200&#34;&gt;idle daemon stops working for a few days&lt;/a&gt;. I don&#39;t really want the new fancy, I want the cool old thing that works, and I want it to keep working.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;The clamshell thing&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Here&#39;s the one that finally broke me: clamshell mode. Laptop, external monitor, shut the lid. The internal panel should turn off, the windows should stay where they are, and opening the lid should put everything back. That&#39;s it. That&#39;s the whole feature.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I could never get it to work right on Hyprland. Not for lack of trying, I had a &lt;a href=&#34;https://github.com/zackb/dots/blob/main/bin/clamshell.sh&#34;&gt;script&lt;/a&gt; for it that sometimes worked. Hyprland &lt;a href=&#34;https://hypr.land/news/26_lua/&#34;&gt;broke their config (again)&lt;/a&gt; so then I had a hundred-line &lt;a href=&#34;https://github.com/zackb/dots/blob/main/.config/hypr/clamshell.lua&#34;&gt;Lua script&lt;/a&gt; for it, with a debounce token written to a temp file because a single suspend/resume fires enough monitor events to reload my shell three times. It reads &lt;code&gt;/proc&lt;/code&gt; to check the lid. It juggles enable/disable across lid switches and hotplug and docking. And it &lt;em&gt;still&lt;/em&gt; occasionally left me with a black internal screen or windows on the wrong monitor.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I&#39;m not dunking on Hyprland here. Search &amp;quot;hyprland clamshell&amp;quot; and you&#39;ll find a dozen people with a dozen different scripts, all slightly broken in different ways. It&#39;s a genuinely hard problem to bolt on from the outside. But I decided the right place to solve it was &lt;em&gt;inside&lt;/em&gt; the compositor, where you actually own the output state, instead of in a Lua script racing against event storms.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;fenriz&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;So I wrote &lt;a href=&#34;https://github.com/zackb/fenriz&#34;&gt;fenriz&lt;/a&gt;. It&#39;s a small, fast tiling Wayland compositor built on &lt;a href=&#34;https://gitlab.freedesktop.org/wlroots/wlroots&#34;&gt;wlroots&lt;/a&gt; and &lt;a href=&#34;https://github.com/wlrfx/scenefx&#34;&gt;SceneFX&lt;/a&gt;. Dwindle tiling, floating, fullscreen, pinning, per-window rules, ten workspaces, a Hyprland-style config file, and a small IPC socket for status bars. It reads your config, tiles your windows, and otherwise does nothing you didn&#39;t ask for.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/img/fenriz/demo.webp&#34; alt=&#34;fenriz running a tiled session&#34;&gt;&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;That&#39;s it in action, running my &lt;a href=&#34;/blog/2026/07/quickshell/&#34;&gt;Quickshell setup&lt;/a&gt; for the bar, with vim open on fenriz&#39;s own source. Thin borders, no blur, no bounce. The windows are just &lt;em&gt;there&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;And clamshell works. It&#39;s not a script and there&#39;s nothing to configure. Each workspace lives on an output; when a screen goes away - lid, cable, suspend - its workspaces move to a surviving screen with layout and focus intact, and slide back when it returns. The internal panel turns off when the lid shuts with an external connected, and back on otherwise. Suspend-on-lid is left to logind, which already gets it right. I open and close my laptop lid all day and never think about it, which is exactly how much I want to think about it.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Should you use it?&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Probably not, honestly. Use Hyprland. It&#39;s more capable than fenriz in every direction that isn&#39;t &amp;quot;boot up, tile some windows, close the lid, don&#39;t break.&amp;quot; It has a real community and it isn&#39;t maintained by one d00d.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;But in the meantime I have the compositor I always wanted. It does exactly what I want and nothing else, and I have full control. It&#39;s my daily driver. It hasn&#39;t surprised me yet. My config hasn&#39;t broken once :)&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I am way open to feedback if anyone is actually interested in using it. It&#39;s a small codebase and I can make changes quickly. If you want to try it, &lt;a href=&#34;https://github.com/zackb/fenriz&#34;&gt;here&#39;s the repo&lt;/a&gt;.&lt;/p&gt;&#xA;</description><pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate><guid>https://zackbartel.com/blog/2026/07/fenriz/</guid></item><item><title>Quickshell Takes Over, Don&#39;t Be Like Me</title><link>https://zackbartel.com/blog/2026/07/quickshell/</link><description>&lt;br&gt;&#xA;&lt;h3&gt;I deleted my whole desktop and rebuilt it.&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Not the compositor. That one I &lt;a href=&#34;https://github.com/zackb/fenriz&#34;&gt;already built&lt;/a&gt;. I mean everything sitting on top of it: the bar, the launcher, notifications, the wallpaper, the idle timer, the lock screen, the polkit agent, bluetooth, clipboard history. All of it. It&#39;s now one &lt;a href=&#34;https://quickshell.org/&#34;&gt;Quickshell&lt;/a&gt; config, and I regret nothing.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;The pile&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Here&#39;s what a normal, sane Hyprland setup looks like, and what I was running until recently:&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;waybar&lt;/strong&gt; for the bar&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;rofi&lt;/strong&gt; / &lt;strong&gt;wofi&lt;/strong&gt; for the launcher&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;swaync&lt;/strong&gt; for notifications&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;hyprpaper&lt;/strong&gt; for the wallpaper&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;hypridle&lt;/strong&gt; for the idle timer&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;hyprlock&lt;/strong&gt; for the lock screen&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;hyprpolkitagent&lt;/strong&gt; for polkit prompts&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;hyprwat&lt;/strong&gt; for menus (I &lt;a href=&#34;/blog/2025/10/hyprwat/&#34;&gt;wrote that one&lt;/a&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;bluetui&lt;/strong&gt; / &lt;strong&gt;blueman&lt;/strong&gt; for bluetooth&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;cliphist&lt;/strong&gt; for clipboard history&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Every one of these is good software. Most of them are &lt;em&gt;great&lt;/em&gt;. I&#39;d recommend any of them to a friend. The problem was never any single tool, the problem was that I had ten of them, each with its own config format, its own theme file, its own idea of what a rounded corner should look like, and its own update that breaks on a Tuesday.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Themeing that pile so it looks like one coherent thing is a part-time job. You change your accent color and you&#39;re editing a CSS file, a Lua-ish config, three INI files, and a YAML document, and afterwards the notification daemon still looks like it wandered in from a different operating system.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Here&#39;s the launcher, my &lt;code&gt;rofi&lt;/code&gt;/&lt;code&gt;wofi&lt;/code&gt; replacement. Fuzzy app search with frecency ranking, an inline calculator, an address book, clipboard history, all behind one keybind:&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/img/quickshell/launcher-screenshot.webp&#34; alt=&#34;The app launcher&#34;&gt;&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;And the wallpaper picker, because of course I replaced &lt;code&gt;hyprpaper&lt;/code&gt; too:&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/img/quickshell/wallpaper.webp&#34; alt=&#34;The wallpaper picker&#34;&gt;&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;One design language&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;That was the real itch. I didn&#39;t want ten tools that mostly match. I wanted one &lt;code&gt;Theme.qml&lt;/code&gt; where I change a color and the &lt;em&gt;entire&lt;/em&gt; desktop changes with it, the bar, the launcher, the lock screen, the OSD, all of it, because they&#39;re all the same program drawing to the same screen.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Quickshell makes that possible. It&#39;s a QML host for building your desktop shell out of Wayland protocols directly, no scripting a bar to shell out to &lt;code&gt;nmcli&lt;/code&gt; and parse the output with &lt;code&gt;awk&lt;/code&gt;. The battery widget talks to UPower. The network widget talks to NetworkManager over D-Bus. The lock screen speaks &lt;code&gt;ext-session-lock-v1&lt;/code&gt;. The idle timer uses &lt;code&gt;ext-idle-notify-v1&lt;/code&gt; and actually honors inhibitors, so fullscreen video doesn&#39;t dim on me mid-movie. It&#39;s the real protocols, not a pile of scripts pretending to be an integration.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Here&#39;s the bar and the control center. Bar across the top, everything in one place:&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/img/quickshell/control-center.webp&#34; alt=&#34;The bar and control center&#34;&gt;&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;And because it&#39;s all one program, the accent color is derived from the wallpaper. Swap the wallpaper and the whole desktop, bar, launcher, control center, bluetooth menu, follows it. Purple background, purple UI. Change to a green one and the launcher goes green too:&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/img/quickshell/bt-matugen.webp&#34; alt=&#34;Bluetooth menu tinted to match the wallpaper&#34;&gt;&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;The stuff that was annoyingly hard&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Two things did not want to be replaced.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The lock screen was the big one. I wanted my fingerprint and my password to work at the same time, so I could swipe a finger &lt;em&gt;or&lt;/em&gt; just type. Turns out a single stacked PAM service runs its modules one after another, which is exactly what produces that miserable &amp;quot;press Enter to fall through to the fingerprint reader&amp;quot; dance. The fix was two separate PAM services, one per auth method, run concurrently. Obvious in hindsight. Not obvious at 1am.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The other was idle inhibition. Quickshell can&#39;t own the &lt;code&gt;org.freedesktop.ScreenSaver&lt;/code&gt; D-Bus name that windowed browser video uses to say &amp;quot;don&#39;t sleep,&amp;quot; so I wrote a small Go daemon that does. It brokers the inhibits, ignores audio-only ones, captures clipboard selections too, and exits when the shell exits. That&#39;s the one piece of glue I couldn&#39;t avoid, and honestly it was fun to write.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Should you do this?&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;No.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Every tool I replaced is maintained by people who are better at it than I am, and if it breaks, they fix it. Now when &lt;em&gt;my&lt;/em&gt; desktop breaks, the person who fixes it is me, at 1am, with a fingerprint reader that won&#39;t cooperate. I signed up for that maintenance bill on purpose, because I like knowing exactly how my computer works and I like everything looking like it belongs together.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;But if you just want a nice desktop that works, use the pile. It&#39;s a great pile.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Don&#39;t be like me.&lt;/p&gt;&#xA;</description><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://zackbartel.com/blog/2026/07/quickshell/</guid></item><item><title>C++ is Great (again)!</title><link>https://zackbartel.com/blog/2025/11/cpp-is-great/</link><description>&lt;h3&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/C_(programming_language)&#34;&gt;C&lt;/a&gt; is my favorite programming language.&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;It always has been and probably always will be. It allowed me to build cool stuff at a young age, create iOS and Mac software (via Objective-C), contribute to the Linux kernel, and work on embedded systems on airplanes.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;When I first learned C++ in college, it was great. It was like having a super-charged version of C. Classes, inheritance, and polymorphism, it felt modern and powerful.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I last used C++ heavily around 2010. At that time the &lt;a href=&#34;https://learncodethehardway.com/blog/31-c-plus-plus-is-an-absolute-blast#the-c-template-metapocalypse&#34;&gt;Template Metapocalypse&lt;/a&gt; was in full swing. That experience made me not like programming.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I was tired of dealing with the complexities. The syntax was verbose, error messages unusable, build systems a nightmare, and the community divided over best practices. I moved on to other languages: Go, Java/Scala, Swift, Python, Javascript, but I would often miss the power of C++.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Whenever I had to use JNI or some other &lt;a href=&#34;https://en.wikipedia.org/wiki/Foreign_function_interface&#34;&gt;FFI&lt;/a&gt; (Foreign Function Interface), I felt weak. I don&#39;t want to have to wait for some third-party library to bind to my favorite language. I want to write high-performance code directly. But at that time I just didn&#39;t feel like C++ was a reasonable choice.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;C++11 was on the horizon, promising to fix many of the issues, but the complexity of the language had already scared off many developers.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I took a long break from C++.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Necessity is the mother of invention.&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Fast forward to 2024, and I found myself needing to write some high-performance code for a project that couldn&#39;t tolerate garbage collection pauses. Python was too slow, Go&#39;s GC was unpredictable, and Rust&#39;s learning curve felt steep (and frankly I don&#39;t like it - &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;DeezNutz&amp;gt;&amp;gt;&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I reluctantly opened up a C++ project, expecting the same old frustrations. What I found instead was a revelation.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Modern C++ is a different language.&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The C++ I discovered was not the C++ I had left behind. C++11, C++14, C++17, and C++20 had fundamentally transformed the language. The improvements weren&#39;t just incremental, they were transformative.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;strong&gt;Auto type deduction&lt;/strong&gt; means I&#39;m no longer writing &lt;code&gt;std::vector&amp;lt;std::string&amp;gt;::iterator&lt;/code&gt; everywhere. Now it&#39;s just &lt;code&gt;auto&lt;/code&gt;. The compiler figures it out, and my code is cleaner and more maintainable. It feels like I&#39;m writing Python, but with all the performance of C++.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;strong&gt;Lambda functions&lt;/strong&gt; changed everything about how I write callbacks and functional-style code. Before, I&#39;d create a functor class or a separate function. Now I can write inline lambda expressions that capture variables from the surrounding scope. Threading code went from painful to pleasant.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Speaking of which, &lt;strong&gt;the standard threading library&lt;/strong&gt; finally arrived. No more &lt;code&gt;pthread&lt;/code&gt; or platform-specific APIs. &lt;code&gt;std::thread&lt;/code&gt;, &lt;code&gt;std::mutex&lt;/code&gt;, &lt;code&gt;std::atomic&lt;/code&gt;—it&#39;s all there, cross-platform, and actually usable. I can write concurrent code that works on Windows, Linux, and Mac without conditional compilation everywhere.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;But the feature that truly won me back was &lt;strong&gt;smart pointers&lt;/strong&gt;. &lt;code&gt;std::unique_ptr&lt;/code&gt; and &lt;code&gt;std::shared_ptr&lt;/code&gt; eliminated most of my memory management headaches. No more agonizing over when to call &lt;code&gt;delete&lt;/code&gt;. No more memory leaks from forgotten cleanup in error paths. &lt;a href=&#34;https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization&#34;&gt;RAII&lt;/a&gt; (Resource Acquisition Is Initialization) finally felt natural and automatic. I know there&#39;s a lot of RAII hate out there, but for me it&#39;s very natural and makes sense. If it doesn&#39;t for you, that&#39;s okay too.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-cpp&#34;&gt;// old way - error prone&#xA;Widget* w = new Widget();&#xA;// ... do stuff ...&#xA;delete w; // Did I remember? What if there was an exception?&#xA;// new way - automatic cleanup&#xA;auto w = std::make_unique&amp;lt;Widget&amp;gt;();&#xA;// ... do stuff ...&#xA;// automatically cleaned up, exception safe&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;The ecosystem caught up too.&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;It&#39;s not just the language. The tooling improved dramatically. CMake became tolerable. Package managers like Conan and vcpkg emerged. Compiler error messages, while still pretty bad, are lightyears better than the template error novels of 2010.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://microsoft.github.io/language-server-protocol/&#34;&gt;LSPs&lt;/a&gt; made code completion and navigation great. &lt;a href=&#34;https://github.com/zackb/dots/blob/main/.clang-format&#34;&gt;Clang format&lt;/a&gt; standardized code style. Sanitizers made debugging memory issues trivial. The whole development experience modernized. All of these things made editting code in &lt;a href=&#34;https://github.com/zackb/dots/tree/main/.config/nvim&#34;&gt;nvim&lt;/a&gt; a joy.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;AI assistants are a secret weapon.&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Here&#39;s something unsurprising: AI coding assistants have made C++ significantly more approachable. Those cryptic template errors that used to send me down rabbit holes for hours? I paste them into Claude or ChatGPT and get a clear explanation and fix in seconds.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Writing boilerplate for move constructors, copy assignment operators (did i forget the third &lt;code&gt;&amp;amp;&lt;/code&gt;?), or template specializations? The AI generates it correctly while I focus on the actual logic. Debugging subtle lifetime issues with references and pointers? The AI spots the problem I&#39;ve been staring at for twenty minutes.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;C++ has always had a steep learning curve with tons of gotchas: the rule of three, the rule of five, argument-dependent lookup, template SFINAE, undefined behavior landmines. AI assistants act like an expert pair programmer who knows all these pitfalls and helps you avoid them. They catch my mistakes before they cause the old headaches.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;It&#39;s ironic: C++ became easier to write precisely when we got AI tools trained on decades of C++ code, patterns, and best practices. The accumulated wisdom of the C++ community is now available on-demand, making the language&#39;s complexity manageable in a way it never was before.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;They still can&#39;t do the hard code, I recently wrote a &lt;a href=&#34;https://github.com/zackb/hyprwat/tree/main/src/wayland&#34;&gt;Wayland&lt;/a&gt;  + &lt;a href=&#34;https://github.com/zackb/hyprwat/tree/main/src/renderer&#34;&gt;EGL backend&lt;/a&gt; for &lt;a href=&#34;https://github.com/ocornut/imgui&#34;&gt;Dear ImGui&lt;/a&gt; and the AI was totally useless, but they handle the tedious stuff that used to make C++ a chore.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;C++ is great again.&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I&#39;m writing C++ daily now, and I&#39;m enjoying it. I never, ever would have thought I&#39;d say that again. I am having fun, and getting excited about &lt;a href=&#34;https://github.com/zackb/code/blob/master/cpp/mimic/src/main.cpp&#34;&gt;programming things I couldn&#39;t do in other languages&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The language gives me the control and performance I need without the tedious manual memory management and verbose boilerplate that drove me away. It&#39;s the super language and probably always will be.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Modern C++ found the sweet spot: it kept the zero-cost abstractions and raw power, but added the ergonomics that developers expect in 2024. It&#39;s not perfect of course, the language is still complex, and there&#39;s legacy baggage, but it&#39;s legitimately great.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;If you walked away from C++ years ago, give it another look. You might be surprised at what you find.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;If you wish you could learn C++ and harness its power without the frustrations, now is the time, fire up &lt;a href=&#34;https://antigravity.google/&#34;&gt;Antigravity&lt;/a&gt; and get going.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The king has reclaimed its throne.&lt;/p&gt;&#xA;</description><pubDate>Mon, 24 Nov 2025 00:00:00 +0000</pubDate><guid>https://zackbartel.com/blog/2025/11/cpp-is-great/</guid></item><item><title>Hyprwat: A Wayland native menu tool for Hyprland</title><link>https://zackbartel.com/blog/2025/10/hyprwat/</link><description>&lt;br&gt;&#xA;&lt;h3&gt;What is &lt;a href=&#34;https://github.com/zackb/hyprwat/&#34;&gt;Hyprwat&lt;/a&gt;?&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/zackb/hyprwat/&#34;&gt;Hyprwat&lt;/a&gt; is a menu tool designed specifically for Hyprland and Wayland compositor. It allows users to create and manage customizable menus that can be accessed directly from the Hyprland environment.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;It creates a popup menu at your cursor position where you can select from a list of options. It also has built-in support for WiFi network selection, audio input/output device selection, wallpapers, and custom menus. Custom menus can be defined using simple YAML configuration files.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The name is inspired by some &lt;a href=&#34;https://www.destroyallsoftware.com/talks/wat&#34;&gt;javascript bashing video&lt;/a&gt; I saw many years ago. But, also like &amp;quot;What do you need?&amp;quot;... because it&#39;s a menu tool.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Right now you can:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Create a list of key / value pairs that show up in a menu, the selected value is printed to stdout.&lt;/li&gt;&#xA;&lt;li&gt;Create an input menu, where the user can type in a value that is printed to stdout.&lt;/li&gt;&#xA;&lt;li&gt;Create password menu, where the user can type in a masked value that is printed to stdout.&lt;/li&gt;&#xA;&lt;li&gt;Select from available WiFi networks (dbus + NetworkManager).&lt;/li&gt;&#xA;&lt;li&gt;Select audio input/output devices (pipewire).&lt;/li&gt;&#xA;&lt;li&gt;Select wallpaper (hyprpaper).&lt;/li&gt;&#xA;&lt;li&gt;Create custom menus with a bunch of GUI controls using YAML configuration files.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3&gt;What does it look like?&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Integrate with powerprofilesctl to switch power profiles:&#xA;&lt;img src=&#34;/img/hyprwat/powerprofiles.png&#34; alt=&#34;Hyprwat Screenshot&#34;&gt;&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Change wallpapers and lockscreen:&#xA;&lt;img src=&#34;/img/hyprwat/wallpapers.png&#34; alt=&#34;Hyprwat Screenshot&#34;&gt;&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Connect to wifi networks:&#xA;&lt;img src=&#34;/img/hyprwat/wifi.png&#34; alt=&#34;Hyprwat Screenshot&#34;&gt;&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Why?&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I created hyprwat to fill a gap in the Wayland ecosystem for a simple, flexible, and visually appealing menu system that can be easily integrated into various use cases. Particularly, for waybar I needed a menu selection for wifi networks, power profiles, pavcontrol, etc.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Then, I wanted an easier way to switch wallpapers and lockscreen and found the existing solutions too hard, so I just added it into hyprwat. I&#39;ll keep doing this, it&#39;s fun.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/zackb/code/tree/master/cpp/wat&#34;&gt;My first attempt&lt;/a&gt; used SDL2/3 and ImGui, but I didn&#39;t like the menu being a window (there&#39;s no &lt;a href=&#34;https://wayland.app/protocols/wlr-layer-shell-unstable-v1&#34;&gt;layer shell&lt;/a&gt; support in SDL). So I rewrote it using pure Wayland protocols and EGL for rendering.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;If nothing else it&#39;s a pretty &lt;a href=&#34;https://github.com/zackb/hyprwat/tree/main/src/wayland&#34;&gt;cool cpp wrapper for Wayland protocols&lt;/a&gt;.&lt;/p&gt;&#xA;</description><pubDate>Mon, 06 Oct 2025 00:00:00 +0000</pubDate><guid>https://zackbartel.com/blog/2025/10/hyprwat/</guid></item><item><title>Returning to Linux</title><link>https://zackbartel.com/blog/2025/02/return-to-linux/</link><description>&lt;h3&gt;Mac OS X is getting worse&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;/em&gt;: Liquid Glass is awful, and the number of bugs in all of Apple&#39;s platforms is no longer acceptable. My 2017 &lt;em&gt;&lt;strong&gt;iMac Pro&lt;/strong&gt;&lt;/em&gt; was working fine for what I needed. Yes, it&#39;s old but for what I do (edit C code in vim, play chess online) it was great. But after installing Tahoe it&#39;s noticeably slower, buggier, and plaqued with permission popus and &amp;quot;unexpected error&amp;quot; dialogs. My Apple &lt;em&gt;&lt;strong&gt;TV&lt;/strong&gt;&lt;/em&gt; is annoying now, there are too many clicks to do anything, it&#39;s slower, the remote stopped working correctly. My &lt;em&gt;&lt;strong&gt;watch&lt;/strong&gt;&lt;/em&gt; is slower, battery life worse, again with the UI taking more taps to do what I used to be able to do. Hopefully the post-Allan Dye era will be a renaissance for Apple software like the post-Johny Ive era is for hardware.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Apple&#39;s hardware is by far the best on the market, but I feel the software has been deteriorating for some time. Gatekeeper is becoming more and more invasive, and the constant permissions dialogs are getting annoying.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;For average users It&#39;s probably all for the better, but as a developer I no longer want to use a computer I can&#39;t make software (freely) for and share with people.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;In addition, I feel I&#39;ve become too reliant on iCloud and some other overpriced Apple services.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I love the Mac. Some of the &lt;a href=&#34;https://techcrunch.com/2012/12/06/frequency-iphone-app/&#34;&gt;most&lt;/a&gt; &lt;a href=&#34;https://github.com/zackb/iOS&#34;&gt;important&lt;/a&gt; &lt;a href=&#34;https://apps.apple.com/us/developer/zack-bartel/id1069647834&#34;&gt;software&lt;/a&gt; I&#39;ve ever written was for Apple OSs. When Mac OS X first came out it seemed like the perfect OS. I was an Apple and Unix geek so it felt magical.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;I Love Linux&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I was a Linux desktop user for many years before switching to Mac OS X in 2009.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;My &lt;a href=&#34;/dad&#34;&gt;dad&lt;/a&gt; and I installed a Redhat distribution with kernel version 1.1.0 around 1995, and I&#39;ve been using Linux on and off ever since. As a kid, I was absolutely fascinated by the idea of open source software and the ability to modify and share code freely.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;In college I worked at the &lt;a href=&#34;https://osuosl.org/&#34;&gt;OSU Open Source Lab&lt;/a&gt;, where I was introduced to &lt;a href=&#34;https://en.wikipedia.org/wiki/Ion_(window_manager)&#34;&gt;ion3&lt;/a&gt;. I was instantly hooked on tiling window managers and used ion3 for years. This was the ultimate developer experience.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;i use arch btw&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/img/linux/rice.png&#34; alt=&#34;Hyprland Rice&#34; title=&#34;Hyprland Rice&#34;&gt;&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;After a completely unrelated need arose to have a Linux development environment, I decided to dip my toe in the water. I bought a $300 Acer laptop and installed Arch, and Kali (I work in cybersec).&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The funniest thing about the whole process is how awful the pre-loaded Windows experience was. Their beloved OOBE experience is a joke. It took me hours to get the system into a usable state, and it was unusably slow.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Anyway, I missed ion3, tried i3 (it&#39;s not for me), and then found &lt;a href=&#34;https://hyprland.org/&#34;&gt;Hyprland&lt;/a&gt;. It&#39;s amazing. I can&#39;t believe how amazing it is. It is absolutely perfect for me. I can&#39;t impress upon you enough how amazing Hyprland is.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Hyprland did take days to setup, but that&#39;s what I wanted. Although, you can get up and running in a matter of minutes, tweaking everything to be just how I wanted it took a while. And (hopefully) I&#39;ll keep improving it forever. If you don&#39;t want that there are plenty of good alternatives (xfce).&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The other thing is, this setup is &lt;strong&gt;SO FAST&lt;/strong&gt;. Even on a budget laptop, everything is snappy and responsive.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;A non-exhaustive list of things I use&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://hypr.land&#34;&gt;Hyprland&lt;/a&gt; / Wayland (tiling window manager)&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://ghostty.org/&#34;&gt;Ghostty&lt;/a&gt; (terminal)&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://neovim.io/&#34;&gt;Neovim&lt;/a&gt; (for everything)&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.hypr.land/Hypr-Ecosystem/&#34;&gt;hypr*&lt;/a&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;hyprpaper&lt;/li&gt;&#xA;&lt;li&gt;hypridle&lt;/li&gt;&#xA;&lt;li&gt;hyprlock&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/Alexays/Waybar&#34;&gt;Waybar&lt;/a&gt; (status bar)&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/davatorium/rofi&#34;&gt;Rofi&lt;/a&gt; (app launcher)&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.thunderbird.net&#34;&gt;Thunderbird&lt;/a&gt;  (email - I can&#39;t find anything better)&#xA;&lt;ul&gt;&#xA;&lt;li&gt;aerc was pretty cool but there is way too much html email these days&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://zen-browser.app/&#34;&gt;Zen&lt;/a&gt; (browser)&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bitwarden.com/&#34;&gt;Bitwarden&lt;/a&gt; (password manager)&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://thunar.org/&#34;&gt;Thunar&lt;/a&gt; (file manager)&lt;/li&gt;&#xA;&lt;li&gt;A bunch of hacks to get iCloud CardDav and CalDav working&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Anyway,&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I&#39;m back on Linux full time now, and I couldn&#39;t be happier. It&#39;s not just for developers anymore. &lt;code&gt;time.Now().Format(&amp;quot;2006&amp;quot;)&lt;/code&gt; is the year of the Linux desktop.&lt;/p&gt;&#xA;</description><pubDate>Wed, 05 Feb 2025 00:00:00 +0000</pubDate><guid>https://zackbartel.com/blog/2025/02/return-to-linux/</guid></item><item><title>Why I Stopped Trying to Improve at Chess</title><link>https://zackbartel.com/blog/2023/03/chess-for-fun/</link><description>&lt;h3&gt;I&#39;ve played chess off and on since I was a kid&lt;/h3&gt;&#xA;&lt;p&gt;I remember the excitement of learning the rules, beating my dad (he let me win), the thrill of my first rated victory, and the countless hours spent poring over strategies and tactics. Over the years, I reached a respectable level of play, competing in local tournaments and climbing the ranks.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;However, as time went on, I found myself increasingly frustrated. The more I studied and practiced, the more I realized how much there was to learn. Chess is a game of infinite complexity, and no matter how hard I tried, I felt like I was hitting a ceiling. My progress slowed to a crawl, and the joy I once found in the game began to wane. What was really the kicker was when I hit an all-time high rating of 1895 (chess.com) and then promptly dropped back down to 1700 within a few weeks. It was disheartening to see my hard work seemingly unravel and never quite reach the heights I once had.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I was spending nights and weekends reading books, doing tactics puzzles, and analyzing games, but it felt like I was running in place. The more I learned, the more I realized how much I didn&#39;t know. It was a humbling experience, but also a frustrating one. I began to question whether the time and effort I was putting into chess was worth it.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Games weren&#39;t fun anymore; they felt like chores. I found myself dreading the start of a game, which used to be adrenaline filled excitement. The pressure to improve and perform well was taking the joy out of the game. I started to see chess as a source of stress rather than a source of enjoyment.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Then, one night I said &amp;quot;fuck it&amp;quot;&lt;/h3&gt;&#xA;&lt;p&gt;and decided to just blitz out a bunch of games. It made me realize that chess was supposed to be fun, not a source of frustration. I&#39;m never going to be a grandmaster and I don&#39;t have to feel like I should aspire to that. I started playing for the sheer enjoyment of the game, without worrying about my rating or performance. I played casual games with friends, tried tricky and trappy gambits online, and simply enjoyed the process of playing. The thrill of finding a brilliancy, laughing at silly blunders that lost me games. I can&#39;t understate how upset I would typically get if I made a mistake and/or lost a game.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;This realization reinvigorated my play. I once again look forward to starting a game, tough situations in the mid-game, king-and-pawn endgames. The joy for the game returned! I know I will probably not improve my rating much for the remainder of my playing days, but I really don&#39;t care. What I care about is being in a community of people who love the game as much as I do, and enjoying the process of playing. Chess is a beautiful game, and I&#39;m grateful to have it in my life, even if I&#39;m not the best player out there.&lt;/p&gt;&#xA;</description><pubDate>Tue, 14 Mar 2023 00:00:00 +0000</pubDate><guid>https://zackbartel.com/blog/2023/03/chess-for-fun/</guid></item><item><title>I love the English Opening</title><link>https://zackbartel.com/blog/2022/05/the-english-opening/</link><description>&lt;h3&gt;I fell out of love with 1.d4 and the London System.&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I&#39;ll always have a soft spot for it in my heart as it&#39;s responsible for a lot of my early chess success. But I&#39;m not a system player, and the London System felt like playing chess on autopilot.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;On paper, it makes sense. Low theory, solid structure, you can play it against almost anything. Just put your pieces on their natural squares: Bf4, e3, Nf3, Nbd2, Bd3, c3 and then what? London players always fall down here.&lt;/p&gt;&#xA;&lt;p&gt;I&#39;d clawed my way up to around 1600, feeling pretty good about my chess. My London was solid, my Queen&#39;s Gambit sharp, and I could navigate the gambits well enough to stay out of danger. Then something shifted.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Once I reached a certain rating threshold everyone knew their theory. I would get crushed with 1.e4 by opponents who had memorized the best lines.&lt;/p&gt;&#xA;&lt;p&gt;Suddenly, every opponent knew their theory cold. They&#39;d rattle off the first fifteen moves without thinking. They&#39;d steer me into playing their game. And I realized: I was spending more time memorizing opening lines than actually improving at chess.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I needed a change. But I wasn&#39;t about to become &lt;em&gt;that guy&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Except it felt soulless. Every game looked the same. I wasn&#39;t responding to what my opponent was doing; I was just executing a setup regardless of what was happening on the other side of the board. It was chess on autopilot.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;And the players I encountered who played the London? They had this glazed look in their eyes during the opening, like they were going through a checklist. &amp;quot;Bf4, check. E3, check. Nf3, check.&amp;quot; No spark, no creativity, no actual chess decisions until move 15.&lt;/p&gt;&#xA;&lt;p&gt;I distinctly remember sitting across from an opponent who played the London against my King&#39;s Indian setup, and I could see him mentally running through his system. He wasn&#39;t looking at &lt;em&gt;my&lt;/em&gt; position. He was following a recipe. When I threw in an early ...Ne4 to disrupt his flow, he sat there for twenty minutes because he&#39;d never actually learned to think in the opening. He&#39;d only learned to &lt;em&gt;deploy&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I refused to become that player. I&#39;d rather memorize theory than give up on actually playing chess in the opening.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;But there had to be a better way.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;Hello 1.c4&lt;/h2&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;That&#39;s when I discovered the English Opening, and it completely transformed my game.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The English Opening (1.c4) seemed almost too quiet when I first looked at it. After years of fighting for the center with 1.e4 or 1.d4, starting with a flank pawn felt... indirect? But then I started playing it, and I realized: this was everything the London pretended to be, but actually delivered.&lt;/p&gt;&#xA;&lt;p&gt;The English gets you out of theory fast, not because you&#39;re ignoring your opponent&#39;s moves, but because you&#39;re creating unique positions they haven&#39;t memorized. There&#39;s no &amp;quot;English System&amp;quot; where you mindlessly deploy pieces to predetermined squares. Every game demands actual chess thinking from move one.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;Why It Catches Everyone Off Guard&lt;/h2&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Here&#39;s the beautiful thing: most players at the club level have prepared something against 1.e4 and 1.d4. They&#39;ve got their Sicilian or French or King&#39;s Indian or Nimzo-Indian ready to go.&lt;/p&gt;&#xA;&lt;p&gt;But 1.c4? They pause. You can see it on their face. &amp;quot;Wait, what&#39;s he doing?&amp;quot;&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;They&#39;re out of their prep by move two. Not because you&#39;ve done something gimmicky, but because the English is genuinely flexible and can transpose to many different structures. They can&#39;t just follow their memorized lines - they have to actually evaluate the position.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;One of my favorite moments was against a 1900-rated player who clearly had a Najdorf repertoire memorized to move 25. After 1.c4, he thought for fifteen minutes on move one. He ended up playing 1...e5, we reached a Reversed Sicilian, and suddenly &lt;em&gt;I&lt;/em&gt; was the one playing Sicilian structures (from the better side!) while he struggled with positions he&#39;d never studied.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;Aggressive or Positional: Your Choice&lt;/h2&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;What really sold me on the English is its flexibility - and this is where it completely diverges from something like the London.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The London commits you to a specific structure and plan. You&#39;re playing for a slow positional squeeze almost every game. Sure, sometimes you get a kingside attack going, but you&#39;re fundamentally locked into a style.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The English? It can be whatever you need it to be.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Against a King&#39;s Indian setup, I can play the Botvinnik System (my favorite) with g3, Bg2, and Nc3, building for a slow positional grind where I control the center from afar.&lt;/p&gt;&#xA;&lt;p&gt;Against a symmetrical structure (1.c4 c5), I can play aggressively with Nc3, g3, Bg2, e4!, blasting open the center and creating tactical complications.&lt;/p&gt;&#xA;&lt;p&gt;Against the more classical setups with ...e5, I can reverse my beloved Sicilian structures and play for the same types of attacks my opponents used to play against me, except now I have an extra tempo.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The choice is mine. I&#39;m responding to what&#39;s on the board, reading the position, making real chess decisions. You know, actually playing chess.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;Getting Out of Theory, Into Chess&lt;/h2&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Here&#39;s the thing about theory: it&#39;s useful until it isn&#39;t. And at some point, every opening becomes about understanding ideas rather than memorizing moves.&lt;/p&gt;&#xA;&lt;p&gt;The English forced me to understand chess rather than memorize it. Because there&#39;s no set &amp;quot;system,&amp;quot; I had to learn:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;How to control the center from a distance&lt;/li&gt;&#xA;&lt;li&gt;When to break with d4 or e4 vs. when to keep the pawn structure fluid&lt;/li&gt;&#xA;&lt;li&gt;How to evaluate transpositions (Is this Reversed Sicilian better than a normal one? Should I allow this transposition to a Queen&#39;s Gambit Declined?)&lt;/li&gt;&#xA;&lt;li&gt;How to handle space disadvantages when opponents claim the center with ...e5 and ...d5&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;These are real chess skills. Pattern recognition, positional understanding, strategic thinking. Not &amp;quot;if he plays ...Na5, I play b4, and if he takes, I play a3.&amp;quot; And it has made me a much better player overall.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;The Freedom of Flexibility&lt;/h2&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Maybe the best part? No two English Opening games feel the same.&lt;/p&gt;&#xA;&lt;p&gt;With the London, every game felt like déjà vu. Same structure, same pieces on the same squares, same middlegame ideas.&lt;/p&gt;&#xA;&lt;p&gt;With the English, I might get a Reversed Dragon one game, a Hedgehog structure the next, a Symmetrical Variation the game after that, and occasionally it transposes into something completely different like a Catalan or Queen&#39;s Indian.&lt;/p&gt;&#xA;&lt;p&gt;It keeps chess fresh. It keeps me engaged. And it keeps my opponents off-balance.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;The Dirty Little Secret&lt;/h2&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Want to know the truth? The English has plenty of theory. There are whole books on the Symmetrical Variation alone. The Reversed Sicilian lines can get incredibly deep.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;But here&#39;s the difference: your opponents haven&#39;t studied it.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;At the club level, nearly everyone has studied the Open Sicilian or the Najdorf or the French Defense. Almost nobody has studied the Symmetrical English or the Reversed Dragon in depth. So even though theory exists, you&#39;re functionally out of book by move five.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;And unlike the London, where being &amp;quot;out of book&amp;quot; just means you&#39;re playing generic moves, being out of book in the English means you&#39;re in rich, complex positions where understanding matters more than memory.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;My Rating Thanks Me&lt;/h2&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Since switching to the English, my rating has climbed steadily. It didn&#39;t at first, I took a big hit, but in the long run it has gotten me farther than I think the London could have. More importantly, I&#39;ve improved at chess in general.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I&#39;m not winning because I memorized better lines. I&#39;m winning because I&#39;m making better decisions, understanding positions more deeply, and outplaying opponents in the middlegame.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The opening gets me to playable, interesting positions where both sides have chances. Then the game is decided by who understands chess better, not who memorized more theory the night before.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;And I&#39;ve avoided becoming a London player, which frankly might be the biggest victory of all.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;Tired of Theory?&lt;/h2&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;If you&#39;re facing the theory wall, if you&#39;re tired of memorizing, if you want to play chess instead of reciting it, give the English a try.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Just promise me one thing: don&#39;t treat it like the London. Don&#39;t look for a &amp;quot;system&amp;quot; you can play mindlessly. The English demands that you think, that you evaluate, that you respond to what your opponent is doing.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;That&#39;s what makes it beautiful.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Start with 1.c4, follow basic opening principles, and see where the game takes you. You might be surprised at how much chess you&#39;ve been missing while you were memorizing theory.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;And you definitely won&#39;t end up with that glazed London System look in your eyes.&lt;/p&gt;&#xA;</description><pubDate>Thu, 05 May 2022 00:00:00 +0000</pubDate><guid>https://zackbartel.com/blog/2022/05/the-english-opening/</guid></item><item><title>Kubernetes Loves Go</title><link>https://zackbartel.com/blog/2022/01/kubernetes-and-go/</link><description>&lt;p&gt;I&#39;ve been working with Kubernetes and Go for years now. There&#39;s something deeply satisfying about this combination. Kubernetes gives you this incredibly powerful orchestration platform, and Go provides the perfect language to extend it. The two were made for each other, and nowhere is this more apparent than when you need a custom controller to solve a particularly gnarly scaling problem.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;One Pod Per User&lt;/h2&gt;&#xA;&lt;p&gt;Our system had an unusual but deliberate architecture: one pod per user. Each user would &amp;quot;reserve&amp;quot; a pod for their exclusive use during a session. Think of it like a personalized sandbox environment, isolated, secure, and fully theirs.&lt;/p&gt;&#xA;&lt;p&gt;There were a lot of challenges. These pods weren&#39;t instant. Startup time was significant enough that we couldn&#39;t just spin them up on-demand. Users expect immediate access, so we needed a pool of pre-warmed pods sitting ready to go.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;Why Standard Scaling Wasn&#39;t Enough&lt;/h2&gt;&#xA;&lt;p&gt;My first attempt was using Kubernetes&#39; Horizontal Pod Autoscaler with custom metrics from Prometheus. After all, that&#39;s what it&#39;s for, right? But as time went on it became clear that HPA wasn&#39;t sophisticated enough for what we needed.&lt;/p&gt;&#xA;&lt;p&gt;Our scaling logic had to account for:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Time-based patterns&lt;/strong&gt;: We needed different pool sizes at 2 PM versus 2 AM. User demand followed predictable daily rhythms.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Velocity and acceleration&lt;/strong&gt;: A steady increase in reservations required different handling than a sudden spike. If someone tweeted a link to our service and we got a flood of new users, we needed to scale &lt;em&gt;fast&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Smart pool management&lt;/strong&gt;: The pool couldn&#39;t just be &amp;quot;any X pods.&amp;quot; We needed to track which pods were reserved, which were ready, and which were warming up.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Standard metrics-based scaling gave us crude reactive behavior. We needed something predictive and loaded with business logic.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;The Custom Controller&lt;/h2&gt;&#xA;&lt;p&gt;This is where Go and Kubernetes really shine together. I decided to build a custom controller (technically an operator, since it manages a custom resource) using the excellent &lt;code&gt;controller-runtime&lt;/code&gt; library.&lt;/p&gt;&#xA;&lt;p&gt;None of this is the actual code, but here&#39;s a simplified version.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;The Custom Resource Definition&lt;/h3&gt;&#xA;&lt;p&gt;I wanted it to be cute, so I defined a CRD to represent our pod pool:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apiextensions.k8s.io/v1&#xA;kind: CustomResourceDefinition&#xA;metadata:&#xA;  name: podpools.scaling.example.com&#xA;spec:&#xA;  group: scaling.example.com&#xA;  versions:&#xA;    - name: v1&#xA;      served: true&#xA;      storage: true&#xA;      schema:&#xA;        openAPIV3Schema:&#xA;          type: object&#xA;          properties:&#xA;            spec:&#xA;              type: object&#xA;              properties:&#xA;                minPoolSize:&#xA;                  type: integer&#xA;                  minimum: 0&#xA;                maxPoolSize:&#xA;                  type: integer&#xA;                  minimum: 1&#xA;                targetReadyPods:&#xA;                  type: integer&#xA;                  minimum: 0&#xA;                scaleUpVelocityThreshold:&#xA;                  type: number&#xA;                scaleUpAccelerationThreshold:&#xA;                  type: number&#xA;                timeBasedScaling:&#xA;                  type: array&#xA;                  items:&#xA;                    type: object&#xA;                    properties:&#xA;                      hourStart:&#xA;                        type: integer&#xA;                      hourEnd:&#xA;                        type: integer&#xA;                      targetPoolSize:&#xA;                        type: integer&#xA;                podTemplate:&#xA;                  type: object&#xA;                  x-kubernetes-preserve-unknown-fields: true&#xA;            status:&#xA;              type: object&#xA;              properties:&#xA;                readyPods:&#xA;                  type: integer&#xA;                reservedPods:&#xA;                  type: integer&#xA;                warmingPods:&#xA;                  type: integer&#xA;                currentVelocity:&#xA;                  type: number&#xA;                currentAcceleration:&#xA;                  type: number&#xA;                lastScaleTime:&#xA;                  type: string&#xA;                  format: date-time&#xA;  scope: Namespaced&#xA;  names:&#xA;    plural: podpools&#xA;    singular: podpool&#xA;    kind: PodPool&#xA;    shortNames:&#xA;    - pp&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This CRD let us declare a &lt;code&gt;PodPool&lt;/code&gt; resource with all the knobs I needed: time-based scaling windows, velocity thresholds, and the pod template to use.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;The Controller Logic&lt;/h3&gt;&#xA;&lt;p&gt;The controller&#39;s reconciliation loop is the brain of the operation. Here&#39;s the core logic I implemented in Go:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func (r *PodPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {&#xA;&#xA;    // find the PodPool&#xA;    podPool := &amp;amp;scalingv1.PodPool{}&#xA;    if err := r.Get(ctx, req.NamespacedName, podPool); err != nil {&#xA;        return ctrl.Result{}, client.IgnoreNotFound(err)&#xA;    }&#xA;&#xA;    // list all pods managed by this pool&#xA;    pods := &amp;amp;corev1.PodList{}&#xA;    if err := r.List(ctx, pods, &#xA;        client.InNamespace(req.Namespace),&#xA;        client.MatchingLabels{&amp;quot;pool&amp;quot;: podPool.Name}); err != nil {&#xA;        return ctrl.Result{}, err&#xA;    }&#xA;&#xA;    // pods by state&#xA;    ready, reserved, warming := r.categorizePods(pods.Items)&#xA;&#xA;    // calculate reservation velocity and acceleration&#xA;    velocity, acceleration := r.calculateMetrics(podPool, len(reserved))&#xA;&#xA;    // determine target pool size based on time of day&#xA;    targetSize := r.getTimeBasedTarget(podPool)&#xA;&#xA;    // check for spike conditions&#xA;    if velocity &amp;gt; podPool.Spec.ScaleUpVelocityThreshold || &#xA;       acceleration &amp;gt; podPool.Spec.ScaleUpAccelerationThreshold {&#xA;        targetSize = r.calculateSpikeTarget(targetSize, velocity, acceleration)&#xA;    }&#xA;&#xA;    // scale the pool&#xA;    currentTotal := len(ready) + len(warming)&#xA;    if currentTotal &amp;lt; targetSize {&#xA;        r.scaleUp(ctx, podPool, targetSize-currentTotal)&#xA;    } else if currentTotal &amp;gt; targetSize &amp;amp;&amp;amp; len(ready) &amp;gt; podPool.Spec.TargetReadyPods {&#xA;        r.scaleDown(ctx, podPool, ready[podPool.Spec.TargetReadyPods:])&#xA;    }&#xA;&#xA;    // update state&#xA;    podPool.Status.ReadyPods = len(ready)&#xA;    podPool.Status.ReservedPods = len(reserved)&#xA;    podPool.Status.WarmingPods = len(warming)&#xA;    podPool.Status.CurrentVelocity = velocity&#xA;    podPool.Status.CurrentAcceleration = acceleration&#xA;&#xA;    return ctrl.Result{RequeueAfter: 10 * time.Second}, r.Status().Update(ctx, podPool)&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The beauty of this approach is that everything is declarative. I could define different scaling behaviors for different environments just by creating different &lt;code&gt;PodPool&lt;/code&gt; resources.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Measuring Velocity and Acceleration&lt;/h3&gt;&#xA;&lt;p&gt;The most interesting part was tracking reservation velocity and acceleration. I kept a sliding window of reservation timestamps in the controller&#39;s memory (later moved to the PodPool status for persistence):&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func (r *PodPoolReconciler) calculateMetrics(podPool *scalingv1.PodPool, currentReserved int) (float64, float64) {&#xA;    now := time.Now()&#xA;&#xA;    // reservation history from status or cache&#xA;    history := r.getReservationHistory(podPool)&#xA;&#xA;    // add current count&#xA;    history = append(history, ReservationDataPoint{&#xA;        Timestamp: now,&#xA;        Count: currentReserved,&#xA;    })&#xA;&#xA;    // keep only last 5 minutes&#xA;    cutoff := now.Add(-5 * time.Minute)&#xA;    history = filterAfter(history, cutoff)&#xA;&#xA;    // calculate velocity (reservations per minute)&#xA;    velocity := 0.0&#xA;    if len(history) &amp;gt;= 2 {&#xA;        deltaCount := history[len(history)-1].Count - history[0].Count&#xA;        deltaTime := history[len(history)-1].Timestamp.Sub(history[0].Timestamp).Minutes()&#xA;        velocity = float64(deltaCount) / deltaTime&#xA;    }&#xA;&#xA;    // calculate acceleration (change in velocity)&#xA;    acceleration := 0.0&#xA;    if len(history) &amp;gt;= 3 {&#xA;        midpoint := len(history) / 2&#xA;        recentVelocity := calculateVelocityBetween(history[midpoint:])&#xA;        olderVelocity := calculateVelocityBetween(history[:midpoint])&#xA;        acceleration = recentVelocity - olderVelocity&#xA;    }&#xA;&#xA;    return velocity, acceleration&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This gave us real-time awareness of not just &lt;em&gt;how many&lt;/em&gt; reservations were happening, but &lt;em&gt;how fast&lt;/em&gt; the rate was changing. When acceleration spiked, we knew something unusual was happening and could preemptively scale.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;The Results&lt;/h2&gt;&#xA;&lt;p&gt;The controller worked beautifully. During normal hours, the pool would smoothly adjust based on our time-based configuration. During the night, we&#39;d have just enough pods to handle occasional use. During peak hours, the pool would be pre-scaled.&lt;/p&gt;&#xA;&lt;p&gt;But the real magic happened during spikes. When (hypothetically) some CEO accidentally tweeted a direct link to our service, the controller would detect the acceleration in reservations and immediately start scaling up. The pool would stay ahead of demand.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h2&gt;Why I Love This Stack&lt;/h2&gt;&#xA;&lt;p&gt;This project exemplified everything I love about Kubernetes and Go together. Kubernetes provides this amazing extension mechanism through CRDs and controllers. It doesn&#39;t try to solve every problem. Instead, it gives you the tools to solve your own problems in a native, first-class way.&lt;/p&gt;&#xA;&lt;p&gt;Go makes building these controllers almost effortless. The &lt;code&gt;controller-runtime&lt;/code&gt; library handles all the boilerplate of watching resources, managing work queues, and handling retries. The type safety catches bugs at compile time. The standard library has everything you need. And the resulting binary is a single executable that&#39;s trivial to containerize and deploy. This is why I love Go.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I could have built a separate service that called the Kubernetes API to manage pods, but making it a proper operator meant it integrated seamlessly with the rest of our Kubernetes infrastructure. We could use &lt;code&gt;kubectl&lt;/code&gt; to inspect pool status, GitOps to manage pool configurations, and RBAC to control access.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;If you&#39;re working with Kubernetes and facing a scaling problem that doesn&#39;t fit the standard tools, don&#39;t be afraid to build a custom controller. With Go and &lt;code&gt;controller-runtime&lt;/code&gt;, it&#39;s easier than you might think, and the results can be truly powerful.&lt;/p&gt;&#xA;</description><pubDate>Tue, 18 Jan 2022 00:00:00 +0000</pubDate><guid>https://zackbartel.com/blog/2022/01/kubernetes-and-go/</guid></item><item><title>Callout: The Boxing App</title><link>https://zackbartel.com/blog/2020/08/callout/</link><description>&lt;br&gt;&#xA;&lt;img src=&#34;/img/callout/Box.png&#34; width=&#34;250&#34;/&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;I started boxing when I was in high school. My football coach didn&#39;t want some of us to have too much free time on our hands during the winter, and I got my ass kicked in wrestling. So, he took us to the local gym and taught us the basics. I&#39;d go on to compete in Golden Gloves, becoming a long time member of a local gym, and continuing the practice on and off into my 30s. Despite what you&#39;d think, I&#39;ve met some of the most compassionate and empatheic people in my life in boxing gyms. It&#39;s been a big part of my life.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;Since I put the gloves down, I&#39;ve wanted a better way to train at home. So much so that I actually &lt;a href=&#34;https://github.com/zackb/code/blob/master/c/callout/callout.c&#34;&gt;wrote a C program&lt;/a&gt; to help.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;What I really wanted was someone, or something to call out punch combinations during a &amp;quot;round&amp;quot;.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;h3&gt;Callout&lt;/h3&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;So I wrote &lt;a href=&#34;https://apps.apple.com/us/app/callout-the-boxing-app/id1473350118&#34;&gt;Callout - The Boxing App&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;The idea is pretty simple, but nothing like it exists. You select or create punch combinations to be called during a round, and iOS SpeechSythesizer does the rest. You can customize the speed, rest time, and variance of combinations called. You can even create your own combinations.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;&lt;p&gt;It&#39;s available on the &lt;a href=&#34;https://apps.apple.com/us/app/callout-the-boxing-app/id1473350118&#34;&gt;App Store&lt;/a&gt; for free with an in-app purchase to unlock all features.&lt;/p&gt;&#xA;&lt;br&gt;&#xA;</description><pubDate>Mon, 17 Aug 2020 00:00:00 +0000</pubDate><guid>https://zackbartel.com/blog/2020/08/callout/</guid></item></channel></rss>