nix-darwin
nix-darwin copied to clipboard
recommendation: wrap launch agents and daemons in named files
This isn't a bug nor anything super important, but it would be nicer if agents and daemons were wrapped into appropriately named files/executables. With the current setup (i.e. just using sh in ProgramArguments), you end up with something like this in the System Settings > Login Items pane:
and furthermore, clicking on the info icon takes you to the sh executable (which isn't very useful: this tells me nothing about which executables/commands are actually invoked and it's hard to determine which is which).
To remedy this, it would probably be necessary to wrap most of the existing agents with perhaps the exception of executables which are already self-explanatory (e.g. gpg-connect-agent). It seems macOS just uses the name/path of the executable here (ignoring the name of the plist, the label defined within the plist, as well as any shebangs the executable file might have).
Agreed. I especially tried to use writeShellScriptBin as the launchd.user.agents.<name>.command instead of just using launchd.user.agents.<name>.script hoping that it would use the script directly, but alas I only see sh.
Bump
This is pretty critical imo, having a bunch of anon launch items is not cool and makes me want to immediately revert this installation and wipe out LaunchDaemons
~ ❯ ls /Library/LaunchAgents
org.gpgtools.Libmacgpg.xpc.plist org.gpgtools.macgpg2.shutdown-gpg-agent.plist
org.gpgtools.macgpg2.fix.plist org.gpgtools.updater.plist
~ ❯ ls /Library/LaunchDaemons
com.docker.socket.plist
com.docker.vmnetd.plist
com.nordvpn.macos.helper.plist
org.nixos.activate-system.plist
org.nixos.darwin-store.plist
org.nixos.nix-daemon.plist
org.nixos.nix-gc.plist
systems.determinate.nix-installer.nix-hook.plist
I understand that the sh display can be solved by wrapping stuff in a bundle, but it seems tricky to make that work without adding a dependency on the Nix store. We would need logic to manually manage those files. This would also want solving upstream in the Nix installers too.
If you want to work on it we’d review PRs to handle this.
I understand that the
shdisplay can be solved by wrapping stuff in a bundle, but it seems tricky to make that work without adding a dependency on the Nix store. We would need logic to manually manage those files. This would also want solving upstream in the Nix installers too.If you want to work on it we’d review PRs to handle this.
I can take a look, seems like the core issue is like you described
the .plist is running an arbitrary command instead of a binary
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>Label</key>
<string>org.nixos.activate-system</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>exec /nix/store/sc025m1df2knrv1b0hagzmg42lcsjs0b-activate-system-start</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
what I can't seem to figure out is why I have 6 entries in login items and only 5 nixos daemons
the .plist is running an arbitrary command instead of a binary
Yes, we can’t really fix that. But we can wrap the shell script inside a bundle that will at least show up with a useful description. It’s just that it means managing files outside the Nix store, which is always a pain.
I’m guessing the sixth entry is one of the non‐Nix daemons, but I don’t know.
the .plist is running an arbitrary command instead of a binary
Yes, we can’t really fix that. But we can wrap the shell script inside a bundle that will at least show up with a useful description. It’s just that it means managing files outside the Nix store, which is always a pain.
I’m guessing the sixth entry is one of the non‐Nix daemons, but I don’t know.
Sixth entry might come from https://github.com/dustinlyons/nixos-config which I based my config off of.
Something along these lines?
#!/bin/sh
ID="$1"
BASE_PATH="/nix/store"
# Create the full path by appending the ID to the base path
ACTIVATION_COMMAND="${BASE_PATH}/${ID}-activate-system-start"
exec "$ACTIVATION_COMMAND"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>Label</key>
<string>org.nixos.activate-system</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/nixos-store-activate-system-start</string>
<string>sc025m1df2knrv1b0hagzmg42lcsjs0b</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
I think we would prefer to make a bundle per daemon, so that we can give them human‐readable descriptions.
I can test this out locally, but personally I wouldn't trust myself to push any of this upstream, still pretty new to nixos
from a maintainers perspective what is the biggest technical hurdle preventing this?
managing these bundles outside of nix?
Someone has to do the work and someone else has to check that it’ll work correctly and not break things :)
There’s no inherent obstacle, as far as I know, we just have limited maintainer resources. Generally any time we have to manage a file that isn’t just a symlink into /nix/store it’s a bit of a pain. Probably what we’d want to do is make a /Library/Application Support/Nix or something to contain bundles containing the shell scripts, use rsync during activation to synchronize that with the system generation being activated, and make the LaunchDaemons and LaunchAgents point to there. But this hasn’t been a priority for me and I haven’t had time to look into how using bundles for this works, so it just hasn’t happened so far.
Someone has to do the work and someone else has to check that it’ll work correctly and not break things :)
There’s no inherent obstacle, as far as I know, we just have limited maintainer resources. Generally any time we have to manage a file that isn’t just a symlink into
/nix/storeit’s a bit of a pain. Probably what we’d want to do is make a/Library/Application Support/Nixor something to contain bundles containing the shell scripts, usersyncduring activation to synchronize that with the system generation being activated, and make theLaunchDaemonsandLaunchAgentspoint to there. But this hasn’t been a priority for me and I haven’t had time to look into how using bundles for this works, so it just hasn’t happened so far.
Ok, I'll take a crack at the bundling, I've worked on that kind of thing before.
Once I get something local running well I'll update this issue and we can figure out how to get it upstream.
I'm not sure how to keep it in sync with nix, which is what you mentioned, but I should be able to get something working for my current generation.
Here's a little workaround I used for one of my own/custom agents:
{
launchd = {
user.agents =
{}
// (let
name = "clean-nvim-cache";
in {
${name} = {
serviceConfig = {
Program = "${writeShellApplication {
inherit name;
runtimeInputs = [findutils neovim];
text = ''
cache_path="$(
command nvim -es -i NONE 2>&1 <<< '1verbose lua print(vim.loader.path)'
)"
command find \
"$cache_path" \
-mindepth 1 \
-atime +49 \
-delete
'';
}}/bin/${name}";
RunAtLoad = false;
LowPriorityIO = true;
ProcessType = "Background";
StartCalendarInterval = [
{
Day = 1;
Hour = 1;
Minute = 22;
}
];
};
};
});
};
}
which results in:
But I think it'd be nice if:
- The builtin non-binary agents did something like this by default
- It did it for custom/user-added agents as well (or there was a utility function? idk)
It does say unidentified developer, but this is better than before. And now when I click the info icon it takes me to the shell script/wrapper (which I can directly open via Finder and view the commands being run), as opposed to getting the sh binary every time, which is another improvement.
What would be the point of using an app bundle? So that the script is signed and/or becomes identified developer?
Here's a little workaround I used for one of my own/custom agents:
{ launchd = { user.agents = {} // (let name = "clean-nvim-cache"; in { ${name} = { serviceConfig = { Program = "${writeShellApplication { inherit name; runtimeInputs = [findutils neovim]; text = '' cache_path="$( command nvim -es -i NONE 2>&1 <<< '1verbose lua print(vim.loader.path)' )" command find \ "$cache_path" \ -mindepth 1 \ -atime +49 \ -delete ''; }}/bin/${name}"; RunAtLoad = false; LowPriorityIO = true; ProcessType = "Background"; StartCalendarInterval = [ { Day = 1; Hour = 1; Minute = 22; } ]; }; }; }); }; }which results in:
![]()
But I think it'd be nice if:
- The builtin non-binary agents did something like this by default
- It did it for custom/user-added agents as well (or there was a utility function? idk)
It does say
unidentified developer, but this is better than before. And now when I click the info icon it takes me to the shell script/wrapper (which I can directly open viaFinderand view the commands being run), as opposed to getting theshbinary every time, which is another improvement.What would be the point of using an app bundle? So that the script is signed and/or becomes
identified developer?
this is great, thanks for sharing
yeah the primary reason to bundle is to sign it and distribute it through the installer
I don’t know if signing is on the cards but associated bundles mean we could do things like icons etc. rather than just jamming everything into the name of a script file that shows up with a Terminal icon.
The writeShellApplication solution doesn’t work because it races the mount of the Nix store when encryption is enabled; it would prevent us from using wait4path etc.
The
writeShellApplicationsolution doesn’t work because it races the mount of the Nix store when encryption is enabled; it would prevent us from usingwait4pathetc.
Can you say more about this? I don't understand what race is at play here.
When you have encryption enabled, your Nix Store won’t get mounted until you log in and as launchd doesn’t support unit dependencies, any launchd daemons/agents that run executables in the Nix Store will fail if they run before the Nix Store gets decrypted. The problem is even more annoying because even if you have the agent set to restart on failure if it fails due to the file/executable not existing, launchd will not restart it and try again.
One workaround for this is by using /bin/wait4path /nix/store as part of the launchd commands which has an open PR to automatically add this #1052
OK that makes sense (also hello fellow Melbourner). However, the example above doing this has RunAtLoad = false and uses StartCalendarInterval to run on a timer, so that doesn't seem as relevant for this case (nor mine)
The
writeShellApplicationsolution doesn’t work because it races the mount of the Nix store when encryption is enabled; it would prevent us from usingwait4pathetc.
I would think that the only thing that needs to use wait4path is the services.activate-system daemon? Because doesn't that reload all nix-darwin managed agents/daemons when it runs? So everything should work out ok as long as that isn't disabled in your config?
Currently activate-system only runs the etc, etcChecks and keyboard activation scripts:
https://github.com/LnL7/nix-darwin/blob/bd7d1e3912d40f799c5c0f7e5820ec950f1e0b3d/modules/services/activate-system/default.nix#L37-L39
By the way, we do have a path forward for this: https://github.com/LnL7/nix-darwin/issues/1219#issuecomment-2573095530