diff --git a/modules/targets/darwin/copyapps.nix b/modules/targets/darwin/copyapps.nix new file mode 100644 index 00000000..d0539668 --- /dev/null +++ b/modules/targets/darwin/copyapps.nix @@ -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" + '' + ); + }; + }; +} diff --git a/modules/targets/darwin/default.nix b/modules/targets/darwin/default.nix index 360a3f3d..6f551e04 100644 --- a/modules/targets/darwin/default.nix +++ b/modules/targets/darwin/default.nix @@ -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 ]; diff --git a/modules/targets/darwin/linkapps.nix b/modules/targets/darwin/linkapps.nix index d5352134..bcc9f5b8 100644 --- a/modules/targets/darwin/linkapps.nix +++ b/modules/targets/darwin/linkapps.nix @@ -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) ];