This week I'm releasing my latest major project, Cackledaemon.
What is Cackledaemon?
Cackledaemon is an installation and runtime manager for Emacs and Windows 10 written as a literate program in PowerShell. Cackledaemon comes with an interactive installer/updates wizard and a tray icon for managing Emacs. Here's a screenshot of the tray icon:
and here's a screenshot of the wizard running updates on my machine:
The tray icon, implemented using the NotifyIcon class from Windows Forms, has functionality for stopping and starting the Emacs daemon in the background, as well as log rotation and handy shortcuts for accessing its configuration and for launching the wizard.
The install wizard will install Cackledaemon, which is packaged as a PowerShell module on the PowerShell Gallery, and Emacs itself. It will also add Emacs to the user's path, set the $Env:HOME
environment variable, and create shortcuts inside the user's start menu for both Emacs and the tray icon.
This is all written as a literate program using org-mode and org-babel. This means that the program itself isn't mere code listings but a document with headings, links and formatting - similar to markdown - and can be read from top to bottom. Cackledaemon is on GitHub, where you can read a nicely rendered version of the source with in-depth instructions on how to install and configure it.
Installing Cackledaemon
Cackledaemon is on the PowerShell Gallery and can be installed with Install-Package. However, I also made it so that you can download and run the wizard without Cackledaemon installed, which will automatically bootstrap installing Cackledaemon for your user. You can do this by copying and pasting this snippet into a PowerShell terminal:
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/jfhbrook/cackledaemon/master/InstallWizard.ps1')
You don't need to run this as Administrator - Cackledaemon will automatically ask for elevated privileges when it needs them.
A full README can be found on GitHub, and the source code for Cackledaemon doubles as full documentation due to its literate nature.
Why I Wrote Cackledaemon
I've been using Emacs as my primary editor for about a year now. I started using it with the spacemacs configuration but I'm now using Doom Emacs. Both of these pretend to be vim via the evil package.
Emacs is cool because it's about as much of a code editor as Node.js is a webserver. Any given configuration for Emacs is less a configuration and more of a complete application assembled from many third party packages. The language Emacs uses is a lisp dialect called Emacs lisp, and configuring Emacs means writing lisp code.
So I really enjoy Emacs. But I also have multiple computers, all running different operating systems, and one of them runs Windows. I do in fact write code on Windows, but I also use Emacs for personal task management via org-mode, with a system that's a mash-up between GTD and bullet journaling. I want to be able to access my system no matter which machine I'm on, and so I run Emacs on Windows.
Emacs is fundamentally designed to run in Unix-like environments. Because of this, setting up Emacs in Linux is relatively straightforward - you can install it with your OS's package manager and then run its daemon with systemd.
Windows, however, requires some assembly. Emacs for Windows is distributed as zip files on an Apache webserver running Trisquel Linux. "Installing" Emacs becomes a matter of unzipping that file into your Program Files directory, followed by making a number of system modifications to integrate Emacs with Windows.
In addition, running the Emacs daemon is challenging because Windows doesn't have a strict analog to systemd. Windows does have the concept of Services, but they're not regular exe's - they're special programs that expose an interface custom tailored for service management. There are alternative approaches - nssm can be used to wrap system services, and Windows can be configured to start apps on user login. Launching the Emacs daemon with the latter approach is viable and is documented in the Emacs wiki, but it also quietly launches it in the background without a sensible way to stop and restart it nor a way to access output.
I wanted to use the Emacs daemon because any non-trivial configuration, like Doom, takes a while to load. The same way that you don't want to use CGI to handle web requests, you don't want to load Emacs every time you want to edit a file either. But I also wanted to be able to interact with it using a nice interface.
Between wanting a nice way to manage the Emacs daemon and wanting tools to manage installing and updating Emacs, writing a module seemed like a natural thing to do.
Postmortem
First of all: I've been pretty impressed with PowerShell. What I like about PowerShell is that, while it syntactically tries to mirror other shells like Bash, it's semantically a general purpose language more akin to Python and Ruby. Invoke-Action -KeyWord value
is equivalent to action(keyword='value')
in Python, { param($Foo) ... }
is similar to Proc.new { |foo| ... }
in Ruby, and the language has full support for .NET classes. Because of these strengths, I think PowerShell has a decent chance of becoming commonly-used for devops use cases in Linux once the experience becomes more polished and it gains traction. I'm actually quite interested in using PowerShell as my main shell in Linux - a future project I'm sure.
I did run into some interesting issues with PowerShell. The biggest is that there's no "sudo but for Windows". The most obvious approach is to spawn an Administrator instance of PowerShell in a new process, but that opens a new window that then has to be managed separately. To address this, I found a package on the PowerShell gallery, reached an agreement with the maintainer where I would take over development and change the name of the package, and hammered on it until it did the things I wanted it to do.
This project is called PSeudo. It works by spawning the Administrator process in a hidden window and then connecting back to the host PowerShell process using a named pipe and sending objects in and out using .NET's binary serialization capabilities. It finally pulls a few dirty tricks with PowerShell aliases to make the experience seamless. The approach is extremely cursed but in practice does what I need it to do.
PowerShell's handling of streams and errors is also interesting - this comes from it trying to emulate shells. Similar to how processes have stdout and stderr, PowerShell functions and cmdlets can write to six streams, representing output, errors, warnings, and other log levels. All of these streams can emit multiple items, including output, which means that in a sense PowerShell functions can return more than once.
Errors that show up on the error stream are what are called non-terminating errors. This means that they don't actually stop your program - they're just emitted, and the program continues. PowerShell also supports terminating errors, which are thrown as in other languages. Both of these error types utilize a class called an ErrorRecord.
Both PowerShell's output and error behavior were surprising to me as someone expecting it to behave more like Python. On reflection, I understand why PowerShell would try to emulate the behavior of Bash and shells more generally, but for a general purpose language these do stand as quirks.
I've also found that literate programming with org-babel is a very pleasing experience. The powershell.el package is, while not perfect, serviceable. I did have some disagreements with the formatting decisions Emacs made as I worked on my code and I found that sometimes it would insist on pushing my cursor to the very bottom of the page, and these were frustrating! But I found that org-mode allowed me to organize my thoughts in a very nice way. In particular, I was able to inline my research and notes on how parts of PowerShell and Emacs worked with my code, and eventually clean them up into professionally-written (I hope) documentation of both my module and of running Emacs on Windows.
You'll notice that the source code is a single 3000 line document, which is about 20 times longer than I like most of my source files to be. I thought this could end up being annoying, but because of org-mode's headers and the ability to collapse and expand them, it didn't bother me in the least. The places where I would normally leverage the filesystem ended up being handled by the natural outline that org-mode documents have.
Finally, writing Cackledaemon took six weeks, which is about five weeks longer than my initial guess. This seems to be a pattern for me. IHydra, a project of mine from earlier this year, also took about six weeks, and korbenware, a project I started in between jobs last year, is incomplete and probably has about five weeks worth of dev time behind it.
For context, I'm currently unemployed (and looking for a staff or lead role - hit me up lmao) so I was able to work on IHydra and Cackledaemon more or less full time.
So on one hand, this makes project estimation a lot easier - any given project that stands as a simple but complete application takes six weeks of almost full time work, and any project that I think will take about a week of hard development will probably take six times that time. This also means that any project I work on consistently for one day a week would take seven times that long - by my math, nearly nine months.
Knowing this, I need to be judicious with the projects that I take on going forward. I have many, many ideas that I would love to spend time on. I'd like to write my own org-mode-centric Emacs config, a business sim video game on top of PostgreSQL, some opinionated Python project management tooling on top of Conda, Airflow-but-actually-good (and probably with Elixir), desktop search for Linux on top of Solr, an AUR helper using buildbot, and more - but if each of those projects will take three quarters to complete I'm looking at nearly five years of development work. I also quickly gain and lose interest in projects, so given the level of commitment required to work on any one of these it's unlikely that I would succeed on anything but the ones I'm the most passionate about.
Ultimately, I'm pretty happy with how Cackledaemon turned out. It definitely was a lot of work, but I really enjoyed working with both PowerShell and org-mode's literate programming facilities, and so far it seems to be scratching the itches that caused me to take this work on in the first place quite well.
Top comments (0)