16 May 2024
TLDR - Having experimented with pure nix ways of handling
dmg images, I finally had to settle on an outside-sandbox
approach using the MacOS's built-in
hdiutil
tool. Skip to the Final section to see
how it all ties together.
Having learned about the superb zed editor from the creators of Atom, I wanted to install it on my mac machine.
My mac runs a nix-darwin setup with home-manager on top. A lovely combo, albeit a bit more difficult to update compared to competing solutions.
My whole setup is relatively small and simple. And so normally,
adding a new package is just extending my
home.packages
list.
Looking for zed
in the
nixos packages search tool
yielded some unrelated stuff initially. No editor package in
sight.
But I quickly realized the problem was I looked in the stable
channel (23.11 atm). Upon switching to Unstable, I found what I
was looking for. The name of the package is
zed-editor
in nix world.
So I just added it to my home-manager packages:
home.packages = [
... # other packages
pkgs.zed-editor
];
Sadly, upon running home-manager switch
I learned
it won't do because it's an unstable channel and I haven't
enabled unstable packages.
But to at least try, I tried to install zed-editor in the current session:
nix-shell -p zed-editor
This failed because the
zed-editor build process on darwin is broken
as of the time of writing. Looking up
the underlying issue
I found out it was due a darwin-specific dependency on
LiveKitBridge
(Swift) that fails to build.
So, inspired by the helpful error message suggesting I may try to enable broken packages, just in case it turns out to be working, I tried again:
export NIXPKGS_ALLOW_BROKEN=1
nix-shell -p zed-editor
This took a lot more time but eventually, the build failed due to a similar error as described in the github issue.
This made me think there must be a way to install this somehow as I checked that the pre-built distribution of the editor works fine on Macs.
There are pre-build .dmg
packages (images)
available on zed's github releases page. So I started looking
into installing those. This turned out to be a spiral of doom as
the continued efforts of the community over the last almost 10
years in reverse-engineering Apple's Volume formats were
relentlessly shut down by Apple's ever-changing volume spec,
starting with HFS then over to HFS+ and now finally at APFS.
I tried using libdmg-hfsplus
, undmg
,
xpwn
, apfs-fuse
and
p7zip
. All of these turned out to either not work
with the current format of the APFS images or me not being able
to shake them (here, eg, xpwn) into doing the thing. This took
me several iterations over a config that essentially looks
something like this in my darwin.nix
:
environment.systemPackages = with pkgs; [
# ... other packages
(pkgs.stdenv.mkDerivation {
name = "zed";
src = pkgs.fetchurl {
url = "https://github.com/zed-industries/zed/releases/download/v0.135.2/Zed-x86_64.dmg";
sha256 = "01e6ede9d5ab7e21e54e448e0a13f9d123b1106ce768a22483a8bac9285f083d";
};
buildInputs = [ pkgs.undmg pkgs.p7zip ];
phases = [ "unpackPhase" "installPhase" ];
unpackPhase = ''
undmg $src
'';
installPhase = ''
mkdir -p $out/Applications
cp -r $out/Zed.app $out/Applications/Zed.app
'';
})
];
The most promising candidate was p7zip
, but as of
the time of writing, the required update to extract APFS is
merged into master, albeit not yet released.
Having spent hours tweaking and wrangling the config, I wanted to find out if there perhaps is a way to reach out into the system's native tools outside the sandbox, leave the pure functional world, and just install the package.
Turns out there is a relatively easy way to install this, you can execute arbitrary scripts in the post-setup stage, so as a last resort I just 'bash'ed this into a temporary form.
I wrote a little install_zed.sh
script that
attempts to download, mount, and install the Zed release
package:
#!/usr/bin/env bash
set -e
# Debugging: Print each command being executed
set -x
LOG_FILE="/tmp/install_zed.log"
{
ZED_VERSION = "0.135.2"
if [ -f "/Applications/Zed.app/Contents/MacOS/cli" ]; then
INSTALLED_VERSION=$(/Applications/Zed.app/Contents/MacOS/cli --version)
if [ "$INSTALLED_VERSION" == "Zed $ZED_VERSION - /Applications/Zed.app" ]; then
echo "Chosen Zed version is already installed in /Applications"
exit 0
fi
fi
echo "Installing Zed v$ZED_VERSION..."
URL="https://github.com/zed-industries/zed/releases/download/v$ZED_VERSION/Zed-x86_64.dmg"
MOUNT_POINT="/Volumes/Zed"
DEST_DIR="/Applications"
DMG_FILE=$(mktemp)
echo "Downloading DMG to $DMG_FILE"
curl -L $URL -o $DMG_FILE
echo "Verifying DMG..."
hdiutil verify $DMG_FILE
echo "Mounting DMG..."
hdiutil attach $DMG_FILE -mountpoint $MOUNT_POINT
echo "Copying Zed to $DEST_DIR"
cp -r $MOUNT_POINT/Zed.app $DEST_DIR/Zed.app
echo "Unmounting DMG..."
hdiutil detach $MOUNT_POINT
echo "Cleaning up..."
rm $DMG_FILE
if [ -f /usr/local/bin/zed ]; then
sudo unlink /usr/local/bin/zed
fi
sudo ln -s /Applications/Zed.app/Contents/MacOS/cli /usr/local/bin/zed
echo "Zed installation complete!"
} 2>&1 | tee $LOG_FILE
Here we use hdiutil
, the native and pre-installed
tool for mounting the MacOS DMG application images. I added a
log file output redirection, trivial version control, and simple
binary linking.
This script lives in the same directory as all the rest of my
nix config, and in my darwin.nix
(so, nix-darwin
config) I added this new
system.activationScripts
instance:
{ config, pkgs, lib, ... }:
{
# ... other config
system.activationScripts.installZed.text = builtins.readFile /Users/tomaszsobota/h/install_zed.sh;
}
Then, I ran darwin-rebuild switch
, and it worked
like a charm. Zed is now available in both my Spotlight search
(Cmd+Space) and from cli with just zed
.
Thanks, see you in the next one.