1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 19:46:05 +01:00

targets/darwin: init copyapps

This is basically a better version of `targets.darwin.linkApps` that
copy apps instead of linking (hence the name). While this is a
convoluted approach, it works with Spotlight, where the previous
approach doesn't. This is also the approach adopted by nix-darwin, see
PR: https://github.com/nix-darwin/nix-darwin/pull/1396.

There are a few particularities about this implementation, for one the
flags we use in rsync are different since we are not using root to copy
the apps to `~/Applications`. This may or may not cause some issues with
specific applications so further testing will be needed. Also the check
for App Management permission needs root (via sudo), so this check is
gated behind a flag that can be disabled if needed.

Fix: #1341.
This commit is contained in:
Thiago Kenji Okada 2025-10-20 10:58:40 +01:00 committed by Austin Horstman
parent 44ca573665
commit 4b846fa3aa
3 changed files with 158 additions and 2 deletions

View file

@ -0,0 +1,145 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.targets.darwin.copyApps;
in
{
options.targets.darwin.copyApps = {
enable =
lib.mkEnableOption "copying macOS applications to the user environment (works with Spotlight)"
// {
default =
pkgs.stdenv.hostPlatform.isDarwin && (lib.versionAtLeast config.home.stateVersion "25.11");
defaultText = lib.literalExpression ''pkgs.stdenv.hostPlatform.isDarwin && (lib.versionAtLeast config.home.stateVersion "25.11")'';
};
enableChecks =
lib.mkEnableOption "enable App Management checks (needs sudo; may ask sudo twice with nix-darwin)"
// {
default = true;
};
directory = lib.mkOption {
type = lib.types.str;
default = "Applications/Home Manager Apps";
description = "Path to link apps relative to the home directory.";
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = !config.targets.darwin.linkApps.enable;
message = "This modules conflicts with `targets.darwin.linkApps`.";
}
(lib.hm.assertions.assertPlatform "targets.darwin.copyApps" pkgs lib.platforms.darwin)
];
home.activation = {
checkAppManagementPermission = lib.mkIf cfg.enableChecks (
lib.hm.dag.entryBefore [ "copyApps" ]
# bash
''
ensureAppManagement() {
for appBundle in '${cfg.directory}/'*.app; do
if [[ -d "$appBundle" ]]; then
if ! run /usr/bin/sudo /usr/bin/touch "$appBundle/.DS_Store" &> /dev/null; then
return 1
fi
fi
done
return 0
}
if ! ensureAppManagement; then
if [[ "$(/bin/launchctl managername)" != Aqua ]]; then
# It is possible to grant the App Management permission to `sshd-keygen-wrapper`, however
# there are many pitfalls like requiring the primary user to grant the permission and to
# be logged in when `darwin-rebuild` is run over SSH and it will still fail sometimes...
printf >&2 '\e[1;31merror: permission denied when trying to update apps over SSH, aborting activation\e[0m\n'
printf >&2 'Apps could not be updated as `darwin-rebuild` requires Full Disk Access to work over SSH.\n'
printf >&2 'You can either:\n'
printf >&2 '\n'
printf >&2 ' grant Full Disk Access to all programs run over SSH\n'
printf >&2 '\n'
printf >&2 'or\n'
printf >&2 '\n'
printf >&2 ' run `darwin-rebuild` in a graphical session.\n'
printf >&2 '\n'
printf >&2 'The option "Allow full disk access for remote users" can be found by\n'
printf >&2 'navigating to System Settings > General > Sharing > Remote Login\n'
printf >&2 'and then pressing on the i icon next to the switch.\n'
exit 1
else
# The TCC service required to modify notarised app bundles is `kTCCServiceSystemPolicyAppBundles`
# and we can reset it to ensure the user gets another prompt
run /usr/bin/tccutil reset SystemPolicyAppBundles > /dev/null
if ! ensureAppManagement; then
printf >&2 '\e[1;31merror: permission denied when trying to update apps, aborting activation\e[0m\n'
printf >&2 '`darwin-rebuild` requires permission to update your apps, please accept the notification\n'
printf >&2 'and grant the permission for your terminal emulator in System Settings.\n'
printf >&2 '\n'
printf >&2 'If you did not get a notification, you can navigate to System Settings > Privacy & Security > App Management.\n'
exit 1
fi
fi
fi
''
);
copyApps = lib.hm.dag.entryAfter [ "installPackages" ] (
let
applications = pkgs.buildEnv {
name = "home-manager-applications";
paths = config.home.packages;
pathsToLink = "/Applications";
};
in
# bash
''
targetFolder='${cfg.directory}'
echo "setting up ~/$targetFolder..." >&2
ourLink () {
local link
link=$(readlink "$1")
[ -L "$1" ] && [ "''${link#*-}" = 'home-manager-applications/Applications' ]
}
if [ -e "$targetFolder" ] && ourLink "$targetFolder"; then
run rm "$targetFolder"
fi
run mkdir -p "$targetFolder"
rsyncFlags=(
# mtime is standardized in the nix store, which would leave only file size to distinguish files.
# Thus we need checksums, despite the speed penalty.
--checksum
# Converts all symlinks pointing outside of the copied tree (thus unsafe) into real files and directories.
# This neatly converts all the symlinks pointing to application bundles in the nix store into
# real directories, without breaking any relative symlinks inside of application bundles.
# This is good enough, because the make-symlinks-relative.sh setup hook converts all $out internal
# symlinks to relative ones.
--copy-unsafe-links
--archive
--delete
--chmod=+w
--no-group
--no-owner
)
run ${lib.getExe pkgs.rsync} "''${rsyncFlags[@]}" ${applications}/Applications/ "$targetFolder"
''
);
};
};
}

View file

@ -1,4 +1,9 @@
{ lib, ... }:
{
config,
lib,
pkgs,
...
}:
{
meta.maintainers = with lib.maintainers; [ midchildan ];
@ -7,6 +12,7 @@
./user-defaults
./fonts.nix
./keybindings.nix
./copyapps.nix
./linkapps.nix
./search.nix
];

View file

@ -11,7 +11,8 @@ in
{
options.targets.darwin.linkApps = {
enable = lib.mkEnableOption "linking macOS applications to the user environment" // {
default = pkgs.stdenv.hostPlatform.isDarwin;
default = pkgs.stdenv.hostPlatform.isDarwin && (lib.versionOlder config.home.stateVersion "25.11");
defaultText = lib.literalExpression ''pkgs.stdenv.hostPlatform.isDarwin && (lib.versionOlder config.home.stateVersion "25.11")'';
};
directory = lib.mkOption {
@ -23,6 +24,10 @@ in
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = !config.targets.darwin.copyApps.enable;
message = "This modules conflicts with `targets.darwin.copyApps`.";
}
(lib.hm.assertions.assertPlatform "targets.darwin.linkApps" pkgs lib.platforms.darwin)
];