<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Learning Nix]]></title><description><![CDATA[Learning Nix]]></description><link>https://blog.deepwatercreature.com</link><generator>RSS for Node</generator><lastBuildDate>Sat, 23 May 2026 19:21:57 GMT</lastBuildDate><atom:link href="https://blog.deepwatercreature.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Mounting /nix automatically on MacOS]]></title><description><![CDATA[I use the Determinate Nix installer to provide nix on my MacMini desktop. It creates a partition for /nix, but does not mount it automatically on boot, because that creates the potential for some problems that are explained well by AI chatbots. This ...]]></description><link>https://blog.deepwatercreature.com/mounting-nix-automatically-on-macos</link><guid isPermaLink="true">https://blog.deepwatercreature.com/mounting-nix-automatically-on-macos</guid><category><![CDATA[Nix]]></category><dc:creator><![CDATA[Anwer Khan]]></dc:creator><pubDate>Fri, 23 May 2025 00:43:04 GMT</pubDate><content:encoded><![CDATA[<p>I use the <a target="_blank" href="https://determinate.systems/nix-installer/">Determinate Nix</a> installer to provide nix on my MacMini desktop. It creates a partition for /nix, but does not mount it automatically on boot, because that creates the potential for some problems that are explained well by AI chatbots. This leaves it up to the user to mount and activate the nix system as needed, while also enhancing reliability and durability against system breakage during macOS updates.</p>
<p>But some of us have configuration that we want activated on boot, for things like shells, and apps installed via nix that we want available immediately. After seeking a good solution with the help of AI I settled on using a LaunchAgent to mount the partition. I keep it on an external drive, as disk space is expensive on Apple hardware.</p>
<p>Here is the LaunchAgent offered by Grok. The specifics of the disk partition are particular to one’s system and must be inserted in place of diskXsY in the code sample.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">plist</span> <span class="hljs-meta-keyword">PUBLIC</span> <span class="hljs-meta-string">"-//Apple//DTD PLIST 1.0//EN"</span> <span class="hljs-meta-string">"http://www.apple.com/DTDs/PropertyList-1.0.dtd"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">plist</span> <span class="hljs-attr">version</span>=<span class="hljs-string">"1.0"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dict</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>Label<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span>com.nix.mount<span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>ProgramArguments<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">array</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span>/bin/sh<span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span>-c<span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span>/usr/sbin/diskutil mount -mountPoint /nix diskXsY<span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">array</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>RunAtLoad<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">true</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>KeepAlive<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">false</span>/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dict</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">plist</span>&gt;</span>
</code></pre>
<p>I realized that this configuration could be added to the declarative system definition used by nix-darwin, which helps with reproducibility. However, the volume identifier diskXsY might change in a new system or even with changes in storage configuration in one’s current system, and recording the UUID in place of diskXsY can be protective against breakage, while also clarifying the information needed when transfering this snippet to a new system. It is one line to find the UUID after installation of nix:</p>
<pre><code class="lang-bash">diskutil info /nix | grep <span class="hljs-string">"Volume UUID"</span>
</code></pre>
<p>In keeping with my goals of modularity and reusability, I keep this bit of configuration in a host-specific module that is imported. The UUID shown in this snippet is for my system, and should be replaced with the results found from diskutil for others using this code.</p>
<pre><code class="lang-nix"><span class="hljs-comment"># hosts/macminim4/nix-store-uuid.nix</span>
{ config, lib, ... }:
{
  custom.nix-mount.<span class="hljs-attr">uuid</span> = <span class="hljs-string">"E1A6C722-457C-4C80-AA6C-098431B3BD0D"</span>;
}
</code></pre>
<p>I use a multi-host configuration repository, and created a module usable by all nix-darwin hosts to import and thus mount /nix.It doesn’t contain any host-specific details as the partition UUID is already separated in the module above.</p>
<pre><code class="lang-nix"><span class="hljs-comment"># modules/nix-darwin/nix-mount.nix</span>
{ config, lib, pkgs, ... }:
<span class="hljs-keyword">let</span>
  <span class="hljs-attr">cfg</span> = config.custom.nix-mount;

  <span class="hljs-comment"># Create an executable script in the Nix store</span>
  <span class="hljs-attr">fixNixMountPlistScript</span> = pkgs.runCommand <span class="hljs-string">"fix-nix-mount-plist.sh"</span> {
    <span class="hljs-attr">src</span> = ./fix-nix-mount-plist.sh;
  } <span class="hljs-string">''
    mkdir -p $out
    cp $src $out/fix-nix-mount-plist.sh
    chmod +x $out/fix-nix-mount-plist.sh
  ''</span>;
<span class="hljs-keyword">in</span>
{
  options.custom.<span class="hljs-attr">nix-mount</span> = {
    <span class="hljs-attr">uuid</span> = lib.mkOption {
      <span class="hljs-attr">type</span> = lib.types.str;
      <span class="hljs-attr">description</span> = <span class="hljs-string">"UUID of the /nix volume to mount"</span>;
      <span class="hljs-attr">example</span> = <span class="hljs-string">"12345678-1234-1234-1234-1234567890AB"</span>;
    };
  };

  <span class="hljs-attr">config</span> = {
    <span class="hljs-attr">assertions</span> = [
      {
        <span class="hljs-attr">assertion</span> = cfg.uuid != <span class="hljs-string">""</span>;
        <span class="hljs-attr">message</span> = <span class="hljs-string">"custom.nix-mount.uuid must be set for nix-mount daemon"</span>;
      }
    ];

    launchd.daemons.<span class="hljs-attr">nix-mount</span> = {
      <span class="hljs-attr">serviceConfig</span> = {
        <span class="hljs-attr">ProgramArguments</span> = [
          <span class="hljs-string">"/bin/sh"</span>
          <span class="hljs-string">"-c"</span>
          <span class="hljs-comment"># Initial 5-second delay, retry every 5 seconds for 60 seconds</span>
          <span class="hljs-string">''/bin/sleep 5; for i in {1..12}; do /usr/sbin/diskutil mount -mountPoint /nix <span class="hljs-subst">${cfg.uuid}</span> &amp;&amp; exit 0; /bin/sleep 5; done; exit 1''</span>
        ];
        <span class="hljs-attr">Label</span> = <span class="hljs-string">"com.nix.mount"</span>;
        <span class="hljs-attr">RunAtLoad</span> = <span class="hljs-literal">true</span>;
        <span class="hljs-attr">KeepAlive</span> = <span class="hljs-literal">false</span>;
        <span class="hljs-attr">StandardErrorPath</span> = <span class="hljs-string">"/tmp/nix-mount-error.log"</span>;
        <span class="hljs-attr">StandardOutPath</span> = <span class="hljs-string">"/tmp/nix-mount-out.log"</span>;
      };
    };

    environment.etc.<span class="hljs-string">"fix-nix-mount-plist.sh"</span>.<span class="hljs-attr">source</span> = <span class="hljs-string">"<span class="hljs-subst">${fixNixMountPlistScript}</span>/fix-nix-mount-plist.sh"</span>;

    system.activationScripts.fixLaunchDaemonPermissions.<span class="hljs-attr">text</span> = <span class="hljs-string">''
      /bin/sh /run/current-system/sw/etc/fix-nix-mount-plist.sh
    ''</span>;
  };
}
</code></pre>
<p>There is code added for robustness, since my Nix Store is on a USB drive, which takes some time during system boot before partitions on it become available for mounting. Thus there is a retry loop that makes repeated attempts to mount, within a 60-second time limit. There is also logging code useful for debugging.</p>
<p>LaunchDaemons on macOS must have 644 permissions to work, and setting permissions on the plist file created by the system definition files was difficult for me. Standard nix methods for setting permissions, suggested by AI, did not work for reasons that were difficult to debug. The ultimate solution was to create an imperative script to change the permissions on the newly-created plist file, on system activation. It is a messy solution but it seems to work. I have placed the script in the same directory as the module that mounts /nix, but this isn’t necessary. Other locations can be used, with the code that loads it adjusted accordingly. The script that changes permissions must be executable. This is handled in the configuration file - as can be seen above - for easy reproducibility. One benefit of keeping the script in its own file, and not simply defining it within the module, is that it can be viewed or edited with syntax highlighting that matches the file format (shell script).</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/sh</span>
LOG=<span class="hljs-string">"/tmp/nix-darwin-activation.log"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-subst">$(date)</span>: Running fix-nix-mount-plist.sh"</span> &gt;&gt; <span class="hljs-string">"<span class="hljs-variable">$LOG</span>"</span>
PLIST=<span class="hljs-string">"/Library/LaunchDaemons/com.nix.mount.plist"</span>
<span class="hljs-comment"># Wait up to 5 seconds for plist to appear</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> {1..5}; <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">if</span> [ -f <span class="hljs-string">"<span class="hljs-variable">$PLIST</span>"</span> ]; <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-subst">$(date)</span>: Found <span class="hljs-variable">$PLIST</span>, setting permissions"</span> &gt;&gt; <span class="hljs-string">"<span class="hljs-variable">$LOG</span>"</span>
    /bin/chmod 644 <span class="hljs-string">"<span class="hljs-variable">$PLIST</span>"</span> 2&gt;&gt; <span class="hljs-string">"<span class="hljs-variable">$LOG</span>"</span> || {
      <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-subst">$(date)</span>: Failed to chmod 644 <span class="hljs-variable">$PLIST</span>"</span> &gt;&gt; <span class="hljs-string">"<span class="hljs-variable">$LOG</span>"</span>
      <span class="hljs-built_in">exit</span> 1
    }
    /bin/chown root:wheel <span class="hljs-string">"<span class="hljs-variable">$PLIST</span>"</span> 2&gt;&gt; <span class="hljs-string">"<span class="hljs-variable">$LOG</span>"</span> || {
      <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-subst">$(date)</span>: Failed to chown <span class="hljs-variable">$PLIST</span>"</span> &gt;&gt; <span class="hljs-string">"<span class="hljs-variable">$LOG</span>"</span>
      <span class="hljs-built_in">exit</span> 1
    }
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-subst">$(date)</span>: Successfully set permissions on <span class="hljs-variable">$PLIST</span>"</span> &gt;&gt; <span class="hljs-string">"<span class="hljs-variable">$LOG</span>"</span>
    <span class="hljs-built_in">exit</span> 0
  <span class="hljs-keyword">fi</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-subst">$(date)</span>: Waiting for <span class="hljs-variable">$PLIST</span> (<span class="hljs-variable">$i</span>/5)"</span> &gt;&gt; <span class="hljs-string">"<span class="hljs-variable">$LOG</span>"</span>
  sleep 1
<span class="hljs-keyword">done</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-subst">$(date)</span>: Error: <span class="hljs-variable">$PLIST</span> not found after 5 seconds"</span> &gt;&gt; <span class="hljs-string">"<span class="hljs-variable">$LOG</span>"</span>
<span class="hljs-built_in">exit</span> 1
</code></pre>
<p>This use of a synthetic mount for /nix requires that the contents of /etc/synthetic.conf include a specification for this partition. The Determinate Nix installer should have taken care of this already, and it’s difficult to manage the file using nix-darwin itself. But it’s worth checking for the presence of nix in the file, for the sake of troubleshooting if problems arise. Here are the contents in my system:</p>
<pre><code class="lang-plaintext">run     private/var/run
nix
</code></pre>
<p>After a successful darwin-rebuild one can verify that the LaunchAgent has been created in the right place, and contents of the file can be examined with a command like <code>cat</code></p>
<pre><code class="lang-bash">ls -l /Library/LaunchDaemons/com.nix.mount.plist
</code></pre>
<p>.<code>r--r--r-- root wheel 719 B Fri May 23 13:40:18 2025 /Library/LaunchDaemons/com.nix.mount.plist</code></p>
]]></content:encoded></item></channel></rss>