LnL7 / nix-darwin

nix modules for darwin
MIT License
2.78k stars 429 forks source link

feat/recommendation: wrap launch agents and daemons in named files #871

Open tmillr opened 7 months ago

tmillr commented 7 months ago

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:

Screenshot 2024-02-16 at 9 00 48 PM

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).

bjeanes commented 1 month ago

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.

gjolund commented 1 week ago

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
emilazy commented 1 week ago

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.

gjolund commented 1 week ago

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 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>
gjolund commented 1 week ago

what I can't seem to figure out is why I have 6 entries in login items and only 5 nixos daemons

Screenshot 2024-09-09 at 10 30 48 PM
emilazy commented 1 week ago

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.

gjolund commented 1 week ago

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>
emilazy commented 1 week ago

I think we would prefer to make a bundle per daemon, so that we can give them human‐readable descriptions.

gjolund commented 1 week ago

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?

emilazy commented 1 week ago

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.

gjolund commented 1 week ago

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.

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.

tmillr commented 1 week ago

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:

Screenshot 2024-09-10 at 4 32 31 AM

But I think it'd be nice if:

  1. The builtin non-binary agents did something like this by default
  2. 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?

gjolund commented 6 days ago

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:

Screenshot 2024-09-10 at 4 32 31 AM

But I think it'd be nice if:

  1. The builtin non-binary agents did something like this by default
  2. 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?

this is great, thanks for sharing

yeah the primary reason to bundle is to sign it and distribute it through the installer

emilazy commented 6 days ago

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.