hraban/mac-app-util

Fix .app programs installed by Nix on Mac

Common Lisp

454

88 commits

updated Jul 4, 2026

See the code

README

* (Nix) Utilities for Mac App launchers

This is a [[https://nixos.org][Nix]] module for Macintosh computers (“darwin”) which fixes a few common problems encountered by users of Nix on Macs:

- Launching .app programs from Spotlight
- Pinning a .app in the Dock, even across Nix updates

Now you can launch Nix-installed apps using only your keyboard, using @@html:<kbd>@@⌘ space@@html:</kbd>@@.

You can also use it to create .app wrappers for non-.app programs, aka stand-alone binary programs, aka non-bundle programs.

** How to use

It works in [[https://github.com/nix-community/home-manager][home-manager]] and [[https://github.com/LnL7/nix-darwin][nix-darwin]] by providing a module for both.  You just need to load the module, nothing else, and it will automatically run every time you =switch= your config.

Home-manager and nix-darwin aren’t either-or: you can have both, and you can install .app bundles on your mac through both (some user specific using home-manager, some system wide using nix-darwin).  In that case you’d want to load both the nix-darwin module, and the home-manager module at the same time in their respective configurations.

*** Flakes, just home-manager

Only using home-manager, no nix-darwin (yet)? You can import the home-manager module directly:

#+begin_src nix
{
  description = "Minimal Flake for macOS with home-manager and mac-app-util";

  inputs = {
    mac-app-util.url = "github:hraban/mac-app-util";
  };

  outputs = { self, nixpkgs, home-manager, mac-app-util, ... }: let
    username = "jdoe";
    system = "aarch64-darwin";
  in {
    homeConfigurations.${username} = home-manager.lib.homeManagerConfiguration {
      pkgs = import nixpkgs { inherit system; config.allowUnfree = true; };
      modules = [
        mac-app-util.homeManagerModules.default
        ({ pkgs, ... }: {
          home = {
            inherit username;
            homeDirectory = "/Users/${username}";
            # This is where you would install any programs as usual:
            packages = with pkgs; [
              ripgrep
              vim

              # What mac-app-util does for you, is that you can also just
              # install derivations here which have a `/Applications/`
              # directory, and it will be available in Spotlight and in your App
              # Launcher, no further configuration needed:
              vscode
              iterm2
            ];
            stateVersion = "24.05";
          };
        })
      ];
    };
  };
}
#+end_src

*** Flakes, nix-darwin (with or without home-manager)

#+begin_src nix
{
  inputs = {
    #...
    mac-app-util.url = "github:hraban/mac-app-util";
    #...
  };

  outputs = {
    nix-darwin
    , home-manager
    , mac-app-util
    , ...
  }: {
    darwinConfigurations = {
      MyHost = nix-darwin.lib.darwinSystem {
        # ...

        modules = [
          mac-app-util.darwinModules.default

          # And if you also use home manager:
          home-manager.darwinModules.home-manager
          (
            { pkgs, config, inputs, ... }:
            {
              # To enable it for all users:
              home-manager.sharedModules = [
                mac-app-util.homeManagerModules.default
              ];

              # Or to enable it for a single user only:
              home-manager.users.foobar.imports = [
                #...
                mac-app-util.homeManagerModules.default
              ];
            }
          )

        ];
      };
    };
  };
}
#+end_src

*** Non-flakes, aka “channels”

This is similar to the above. What will be different is the “plumbing”, i.e. how to get a reference to this app’s derivation. Here’s how:

#+begin_src nix
let
  mac-app-util-src = builtins.fetchTarball "https://github.com/hraban/mac-app-util/archive/master.tar.gz";
  # I advise using the long form with a pinned hash instead
  mac-app-util-src = builtins.fetchTarball {
    url = "https://github.com/hraban/mac-app-util/archive/abcdef123456abcdef123456.tar.gz";
    # Run it once, lift the hash from the error, paste it here and run again
    sha256 = "";
  };
  mac-app-util = import mac-app-util-src {};
in

# Now you have either the program as a derivation itself:
mac-app-util.default

# Or the home manager module:
mac-app-util.homeManagerModules.default

# Or darwin:
mac-app-util.darwinModules.default
#+end_src

Example:

#+begin_src nix
{ config, pkgs, ... }:

let
  mac-app-util-src = builtins.fetchTarball "https://github.com/hraban/mac-app-util/archive/master.tar.gz";
  mac-app-util = import mac-app-util-src {};
in

{
  home = {
    username = "jdoe";
    homeDirectory = "/Users/jdoe";
    stateVersion = "24.05";
    packages = with pkgs; [
      iterm2
    ];
  };
  programs.home-manager.enable = true;
  imports = [
    mac-app-util.homeManagerModules.default
  ];
}
#+end_src

** Commands

At the core of this project is a (Nix-agnostic) program that can:

- =mktrampoline= :: Create a “trampoline” launcher app
- =sync-dock= :: Update persistent apps in the Dock
- =sync-trampolines= :: Create a directory with trampolines to all your apps

** mktrampoline

This creates a “trampoline” launcher app which is a simple wrapper application that just launches your actual application.

#+begin_src shell
$ nix run github:hraban/mac-app-util -- mktrampoline /path/to/MyApp.app /Applications/MyApp.app
#+end_src

Intuitively, you would either fully copy & paste the original .app, or create a symlink or “alias”; all of those solutions have different problems and they don’t get indexed by Spotlight properly.

This trampoline script is indexed by Spotlight and by Launchpad, so you can keep launching your apps using =⌘ SPC <appname> ⏎=

You can also wrap non-app stand-alone binaries with this. For example:

#+begin_src shell
$ nix run github:hraban/mac-app-util -- mktrampoline "$(which darktable)" ~/Applications/Darktable.app
#+end_src

Darktable is a photo editor available on Mac but without a .app bundle in the derivation. It’s just a stand-alone binary. Using mktrampoline, you can make it launchable from Spotlight.

See https://github.com/nix-community/home-manager/issues/1341

** sync-dock

When you have an app in your Dock which doesn’t live in =/Applications/..=, it can get stale: e.g. your app at =/foo/v1/Foo.app= gets replaced by =/foo/v2/Foo.app=. To automatically update the Dock to the new location of Foo, execute:

#+begin_src shell
$ nix run github:hraban/mac-app-util -- sync-dock Foo.app
#+end_src

It will find an old persistent item by the name of "Foo" and update it to the new location.

N.B.: This is currently limited only to Nix apps, but actually it could work for anything. I’ve just kept it conservative to be on the safe side.

** sync-trampolines

Combines =mktrampoline= and =sync-dock= to create a fresh directory with a fresh trampoline for every source app. E.g.:

#+begin_src shell
$ nix run github:hraban/mac-app-util -- sync-trampolines ~/special/apps/ ~/Applications/Special/
#+end_src

Will create a fresh directory (=~/Applications/Special=), deleting if it already existed. In that directory it will create a trampoline app for every single =*.app= file it finds in =~/special/apps/=.

This helps register apps from outside of your =~/Applications= directory with Spotlight and the Launchpad.

* License

mac-app-util - Manage Mac App launchers
Copyright © 2023–2025  Hraban Luyat

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3 of the License.


This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

apple
common-lisp
flakes
macos
nix
plist
plist-files
plutil

Contributors

hraban

87 commits

hraban/mac-app-util

Fix .app programs installed by Nix on Mac

Common Lisp

454

88 commits

updated Jul 4, 2026

See the code

README

* (Nix) Utilities for Mac App launchers

This is a [[https://nixos.org][Nix]] module for Macintosh computers (“darwin”) which fixes a few common problems encountered by users of Nix on Macs:

- Launching .app programs from Spotlight
- Pinning a .app in the Dock, even across Nix updates

Now you can launch Nix-installed apps using only your keyboard, using @@html:<kbd>@@⌘ space@@html:</kbd>@@.

You can also use it to create .app wrappers for non-.app programs, aka stand-alone binary programs, aka non-bundle programs.

** How to use

It works in [[https://github.com/nix-community/home-manager][home-manager]] and [[https://github.com/LnL7/nix-darwin][nix-darwin]] by providing a module for both.  You just need to load the module, nothing else, and it will automatically run every time you =switch= your config.

Home-manager and nix-darwin aren’t either-or: you can have both, and you can install .app bundles on your mac through both (some user specific using home-manager, some system wide using nix-darwin).  In that case you’d want to load both the nix-darwin module, and the home-manager module at the same time in their respective configurations.

*** Flakes, just home-manager

Only using home-manager, no nix-darwin (yet)? You can import the home-manager module directly:

#+begin_src nix
{
  description = "Minimal Flake for macOS with home-manager and mac-app-util";

  inputs = {
    mac-app-util.url = "github:hraban/mac-app-util";
  };

  outputs = { self, nixpkgs, home-manager, mac-app-util, ... }: let
    username = "jdoe";
    system = "aarch64-darwin";
  in {
    homeConfigurations.${username} = home-manager.lib.homeManagerConfiguration {
      pkgs = import nixpkgs { inherit system; config.allowUnfree = true; };
      modules = [
        mac-app-util.homeManagerModules.default
        ({ pkgs, ... }: {
          home = {
            inherit username;
            homeDirectory = "/Users/${username}";
            # This is where you would install any programs as usual:
            packages = with pkgs; [
              ripgrep
              vim

              # What mac-app-util does for you, is that you can also just
              # install derivations here which have a `/Applications/`
              # directory, and it will be available in Spotlight and in your App
              # Launcher, no further configuration needed:
              vscode
              iterm2
            ];
            stateVersion = "24.05";
          };
        })
      ];
    };
  };
}
#+end_src

*** Flakes, nix-darwin (with or without home-manager)

#+begin_src nix
{
  inputs = {
    #...
    mac-app-util.url = "github:hraban/mac-app-util";
    #...
  };

  outputs = {
    nix-darwin
    , home-manager
    , mac-app-util
    , ...
  }: {
    darwinConfigurations = {
      MyHost = nix-darwin.lib.darwinSystem {
        # ...

        modules = [
          mac-app-util.darwinModules.default

          # And if you also use home manager:
          home-manager.darwinModules.home-manager
          (
            { pkgs, config, inputs, ... }:
            {
              # To enable it for all users:
              home-manager.sharedModules = [
                mac-app-util.homeManagerModules.default
              ];

              # Or to enable it for a single user only:
              home-manager.users.foobar.imports = [
                #...
                mac-app-util.homeManagerModules.default
              ];
            }
          )

        ];
      };
    };
  };
}
#+end_src

*** Non-flakes, aka “channels”

This is similar to the above. What will be different is the “plumbing”, i.e. how to get a reference to this app’s derivation. Here’s how:

#+begin_src nix
let
  mac-app-util-src = builtins.fetchTarball "https://github.com/hraban/mac-app-util/archive/master.tar.gz";
  # I advise using the long form with a pinned hash instead
  mac-app-util-src = builtins.fetchTarball {
    url = "https://github.com/hraban/mac-app-util/archive/abcdef123456abcdef123456.tar.gz";
    # Run it once, lift the hash from the error, paste it here and run again
    sha256 = "";
  };
  mac-app-util = import mac-app-util-src {};
in

# Now you have either the program as a derivation itself:
mac-app-util.default

# Or the home manager module:
mac-app-util.homeManagerModules.default

# Or darwin:
mac-app-util.darwinModules.default
#+end_src

Example:

#+begin_src nix
{ config, pkgs, ... }:

let
  mac-app-util-src = builtins.fetchTarball "https://github.com/hraban/mac-app-util/archive/master.tar.gz";
  mac-app-util = import mac-app-util-src {};
in

{
  home = {
    username = "jdoe";
    homeDirectory = "/Users/jdoe";
    stateVersion = "24.05";
    packages = with pkgs; [
      iterm2
    ];
  };
  programs.home-manager.enable = true;
  imports = [
    mac-app-util.homeManagerModules.default
  ];
}
#+end_src

** Commands

At the core of this project is a (Nix-agnostic) program that can:

- =mktrampoline= :: Create a “trampoline” launcher app
- =sync-dock= :: Update persistent apps in the Dock
- =sync-trampolines= :: Create a directory with trampolines to all your apps

** mktrampoline

This creates a “trampoline” launcher app which is a simple wrapper application that just launches your actual application.

#+begin_src shell
$ nix run github:hraban/mac-app-util -- mktrampoline /path/to/MyApp.app /Applications/MyApp.app
#+end_src

Intuitively, you would either fully copy & paste the original .app, or create a symlink or “alias”; all of those solutions have different problems and they don’t get indexed by Spotlight properly.

This trampoline script is indexed by Spotlight and by Launchpad, so you can keep launching your apps using =⌘ SPC <appname> ⏎=

You can also wrap non-app stand-alone binaries with this. For example:

#+begin_src shell
$ nix run github:hraban/mac-app-util -- mktrampoline "$(which darktable)" ~/Applications/Darktable.app
#+end_src

Darktable is a photo editor available on Mac but without a .app bundle in the derivation. It’s just a stand-alone binary. Using mktrampoline, you can make it launchable from Spotlight.

See https://github.com/nix-community/home-manager/issues/1341

** sync-dock

When you have an app in your Dock which doesn’t live in =/Applications/..=, it can get stale: e.g. your app at =/foo/v1/Foo.app= gets replaced by =/foo/v2/Foo.app=. To automatically update the Dock to the new location of Foo, execute:

#+begin_src shell
$ nix run github:hraban/mac-app-util -- sync-dock Foo.app
#+end_src

It will find an old persistent item by the name of "Foo" and update it to the new location.

N.B.: This is currently limited only to Nix apps, but actually it could work for anything. I’ve just kept it conservative to be on the safe side.

** sync-trampolines

Combines =mktrampoline= and =sync-dock= to create a fresh directory with a fresh trampoline for every source app. E.g.:

#+begin_src shell
$ nix run github:hraban/mac-app-util -- sync-trampolines ~/special/apps/ ~/Applications/Special/
#+end_src

Will create a fresh directory (=~/Applications/Special=), deleting if it already existed. In that directory it will create a trampoline app for every single =*.app= file it finds in =~/special/apps/=.

This helps register apps from outside of your =~/Applications= directory with Spotlight and the Launchpad.

* License

mac-app-util - Manage Mac App launchers
Copyright © 2023–2025  Hraban Luyat

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3 of the License.


This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

apple
common-lisp
flakes
macos
nix
plist
plist-files
plutil

Contributors

hraban

87 commits

Languages

Common Lisp

69.0%

Nix

30.9%