Skip to content

home-manager: replace mkalias with copyApps for nix GUI app exposure - #6

Merged
kriswill merged 3 commits into
mainfrom
feat/home-manager-copyapps
Apr 26, 2026
Merged

home-manager: replace mkalias with copyApps for nix GUI app exposure#6
kriswill merged 3 commits into
mainfrom
feat/home-manager-copyapps

Conversation

@kriswill

Copy link
Copy Markdown
Owner

Summary

  • Replaces modules/home-manager/mkalias.nix with modules/home-manager/copyapps.nix, switching from Finder alias stubs to home-manager's targets.darwin.copyApps. Each nix-installed GUI app is now materialised as a real .app directory at ~/Applications/Home Manager Apps/ via rsync --copy-unsafe-links.
  • Adds modules/home-manager/neovide/default.nix — Neovide as a proper home-manager module pointing NEOVIDE_NEOVIM_BIN at the wrapped nvim so it inherits programs.neovim.extraPackages.
  • Adds a transitional cleanup activation that removes the bare ~/Applications/<App>.app symlinks and $XDG_STATE_HOME/home-manager-app-bundles.list manifest left by an earlier symlink-based attempt. Self-deletes after one activation.

Why

mkalias produces Finder alias stubs at ~/Applications/Home Manager Apps/<App>.app — these work for Spotlight/Launchpad click-to-launch but are not real bundle directories. Tools that enumerate installed apps by walking ~/Applications/ (Claude Code's computer-use MCP scanner, third-party automation harnesses, generic bundle walkers) report nix-installed apps as not installed.

A first attempt added directory symlinks at ~/Applications/<App>.app pointing into /nix/store — parked on attempt/mkalias-realbundle-symlinks for reference. Verified that LaunchServices, PlistBuddy, and mdls all recognised those symlinks, but the MCP scanner still returned not_installed for every nix-symlinked app (Neovide, kitty, LocalSend, KeyCastr). The scanner's enumeration source excludes either symlinks under ~/Applications/ or paths under /nix/store.

copyApps is the upstream-supported approach, default-on for home.stateVersion >= "25.11". We pin "24.11" so we opt in explicitly. Same mechanism nix-darwin uses for environment.systemPackages under /Applications/Nix Apps/.

Trade-offs

  • Disk: real copies, not store-shared. ~hundreds of MB across the current GUI app set.
  • First activation may prompt for App Management TCC permission to overwrite bundles in ~/Applications/Home Manager Apps/.
  • macOS 26.3 mtime regression — home-manager's copyApps already works around this; we are on 25.x.
  • copyApps and linkApps are mutually exclusivecopyapps.nix sets targets.darwin.linkApps.enable = false to satisfy the assertion.

Test plan

  • nix build .#darwinConfigurations.k.system succeeds
  • Resulting activation script contains the upstream copyApps rsync loop
  • The legacy aliasApplications activation is gone
  • After darwin-rebuild switch: 5 real .app directories at ~/Applications/Home Manager Apps/ (KeyCastr, LocalSend, Neovide, Podman Desktop, kitty) — drwxr-xr-x, not symlinks
  • Legacy ~/Applications/<App>.app symlinks cleaned up; manifest at ~/.local/state/home-manager-app-bundles.list self-deleted
  • PlistBuddy reads CFBundleIdentifier = com.neovide.neovide through the new path
  • In a fresh Claude Code session: request_access(["Neovide"]) returns granted; open_application com.neovide.neovide + screenshot confirms MCP-driven control works end-to-end

Follow-ups (not in this PR)

  • Once every host has activated the new module at least once, the transitional cleanupLegacyAppBundleSymlinks activation block in copyapps.nix can be deleted.
  • Unrelated startup error observed in Neovide's first-run screenshot — snacks.nvim/dashboard.lua:1202: Invalid 'group': 43 during a UIEnter autocmd. Tracking separately.

mkalias.nix produced Finder alias stubs at ~/Applications/Home Manager
Apps/<App>.app — fine for click-to-launch via Spotlight/Launchpad but
not real bundle directories, so any tool that enumerates installed
apps by walking ~/Applications (Claude Code computer-use MCP scanner,
third-party automation harnesses, generic bundle walkers) reported
nix-installed apps as not installed. A previous attempt — adding
directory symlinks at ~/Applications/<App>.app pointing into the nix
store — is parked on attempt/mkalias-realbundle-symlinks; verified
that LaunchServices recognised those, but the MCP scanner did not.

Switch to home-manager's targets.darwin.copyApps, which rsyncs the
buildEnv /Applications tree with --copy-unsafe-links so each .app
becomes a real materialised directory at ~/Applications/Home Manager
Apps/. This is the upstream-supported approach (default-on for
home.stateVersion >= "25.11"; we pin "24.11" so opt in explicitly)
and is the same mechanism nix-darwin uses for environment.systemPackages
under /Applications/Nix Apps/.

Also adds modules/home-manager/neovide/default.nix — Neovide as a
proper home-manager module pointing NEOVIDE_NEOVIM_BIN at the wrapped
nvim so it inherits programs.neovim.extraPackages on PATH.

Trade-offs:
  - Disk: real copies, not store-shared. ~hundreds of MB across the
    current GUI app set (kitty, Neovide, LocalSend, KeyCastr,
    Podman Desktop). Acceptable.
  - First activation may prompt for App Management TCC permission to
    overwrite existing bundles in ~/Applications/Home Manager Apps/.
  - copyApps and linkApps are mutually exclusive; copyapps.nix sets
    targets.darwin.linkApps.enable = false to satisfy the assertion.
  - macOS 26.3 has a known signature-verification regression with
    mtime=1 that home-manager works around (--no-times in copyapps.nix
    upstream); we are on 25.x so this is a forward-looking note.

Transitional cleanup activation removes the symlinks and manifest
left by the parked attempt on first activation per host. Once the
manifest is gone, the cleanup is a no-op and the block can be deleted.

Validated via `nix build .#darwinConfigurations.k.system`. The
activation script materialises the rsync loop and the cleanup; the
old aliasApplications activation is gone.

Plan + verification log:
  /Users/k/.claude/plans/sprightly-finding-starfish.md
home-manager defaults at stateVersion 26.05 already make
targets.darwin.copyApps.enable = true and
targets.darwin.linkApps.enable = false, so the explicit toggles in
the previous commit's copyapps.nix are redundant. Bump stateVersion
and drop the module.

Researched the 24.11 → 26.05 delta for Darwin-affecting default
changes: every other gated option this repo touches is already
explicitly set to the post-bump value:
  - programs.git.signing.format = null  (git.nix:31)
  - programs.yazi.shellWrapperName = "y"  (yazi/default.nix:12)
  - programs.neovim.withRuby = false; withPython3 = false  (neovim/default.nix:16-17)
And the Darwin-relevant changes that AREN'T pre-set are wins:
  - copyApps default-on (the whole point of this PR)
  - programs.man.package = null on Darwin (fixes broken GNU-apropos)

No options used in this repo were renamed or removed between 24.11
and 26.05.

The transitional cleanup activation that removed bare
~/Applications/<App>.app symlinks and the
$XDG_STATE_HOME/home-manager-app-bundles.list manifest moves from
copyapps.nix into modules/home-manager/default.nix as an inline
home.activation block. Manifest-gated, so a no-op once cleanup has
run on a host. Marked for deletion in a future cleanup pass.

One non-stateVersion-gated heads-up: at home-manager master, when
used as a nix-darwin module the legacy "per-user shadow profile" is
gone unless home-manager.enableLegacyProfileManagement = true. We
do not opt into the legacy mode, so first switch may regenerate
profile state — non-blocking.

Validated via `nix build .#darwinConfigurations.k.system`:
  - copyApps activation present (rsync --copy-unsafe-links into
    ~/Applications/Home Manager Apps/)
  - linkApps activation absent
  - cleanupLegacyAppBundleSymlinks present, gated on manifest

Files:
  lib/default.nix              — stateVersion 24.11 → 26.05
  modules/home-manager/default.nix — inline cleanup activation,
                                     drop kriswill.copyApps.enable
  modules/home-manager/copyapps.nix — deleted
@kriswill
kriswill merged commit dd1d3c7 into main Apr 26, 2026
kriswill added a commit that referenced this pull request Jun 29, 2026
nix-darwin has no programs.* for most of these tools, so port them the way
the rest of the repo already does (cf. tmux/zsh/yazi): static config lives in
the stow tree (home/) and the /nix/store-derived bits are generated and linked
during activation. The shell integrations (fzf/zoxide/direnv/hstr) already live
in the stow zshrc, so only the FZF_* env vars needed re-adding there.

Moved out of modules/home-manager/core.nix (now deleted) into system-level
modules/darwin/<feature>.nix + home/<pkg> stow packages:

  git/gh      modules/darwin/git.nix         + home/git, home/gh (bare-name
                                                helpers, no stale store paths)
  ssh         modules/darwin/ssh.nix         + home/ssh
  zk          modules/darwin/zk.nix
  diffnav     modules/darwin/diffnav.nix     installs a delta wrapped with the
                                              kanagawa theme (matches HM's
                                              finalPackage; diffnav bundles its
                                              own delta and is unaffected)
  kitty       modules/darwin/kitty.nix       + home/kitty
  neovide     modules/darwin/neovide.nix     toggle only; pkg is per-host
  direnv      modules/darwin/direnv.nix      + home/direnv; nix-direnv stdlib
                                              linked into ~/.config/direnv/lib
  direnv-nom  modules/darwin/direnv-nom.nix  nom wrapper generated + linked
  htop        modules/darwin/htop.nix        immutable htoprc generated + linked
  qmd-sqlite  modules/darwin/qmd-sqlite.nix  extension-enabled sqlite (hiPrio so
                                              it wins the sqlite3 collision with
                                              neovim's plain sqlite) + qmd link
  bat jq nix-index lazygit rmpc fzf go nodejs_24 yamlfmt -> user-packages.nix
  FZF_* env vars -> home/zsh/.config/zsh/.zshrc

The home-manager master `kriswill.enable` toggle and core.nix were removed;
home-manager stays wired only for the GUI/per-host modules (brave, firefox,
vscode, podman-desktop, claude-account-selector). All three host configs build
(nix flake check) and the HM->stow config handoff is clean (the old HM configs
were store symlinks HM removes on switch, then stow/activation redeploys).

flake.lock: `nix flake update` bumps nixpkgs, home-manager and yazi-plugins.
This is a closure no-op for host k -- the new lock evaluates to the identical
darwin-system derivation (verified: same .drv d15di5x4..., same output
axiznxg2...). It is included so future builds and the other hosts track the
newer inputs.

nvd diff -- previous generation (system-184, pre-migration) -> this commit's
closure (== the running gen-185 system). The Selection-state / Added / Removed
sections are this port (note the hm_* generated configs removed and the
delta-wrapped/delta-config -> delta-kanagawa swap); the Version changes are the
already-committed af761b8 nixpkgs bump that the pre-port generation had not yet
realized.

<<< /nix/store/7kx6ii60i6fwp4gsn4l987r9klgwd909-darwin-system-26.11.a1fa429
>>> /nix/store/axiznxg2fgby564shlpxjkjp208wa6p8-darwin-system-26.11.a1fa429
Version changes:
[U*]  #1  buf                           1.70.0 -> 1.71.0
[U.]  #2  expat                         2.8.0 -> 2.8.1
[U*]  #3  fastfetch                     2.64.2, 2.64.2-man -> 2.65.1, 2.65.1-man
[U.]  #4  ffmpeg-headless               8.1-bin, 8.1-data, 8.1-lib -> 8.1.1-bin, 8.1.1-data, 8.1.1-lib
[U.]  #5  fftw-double                   3.3.10 -> 3.3.11
[U*]  #6  fish                          4.7.1, 4.7.1-doc -> 4.8.0, 4.8.0-doc
[U.]  #7  freetype                      2.14.2 -> 2.14.3
[U+]  #8  gh                            2.94.0 -> 2.95.0
[U*]  #9  ghostscript                   10.07.0, 10.07.0-fonts, 10.07.0-man -> 10.07.1, 10.07.1-fonts, 10.07.1-man
[U*]  #10  go                            1.26.3 -> 1.26.4
[U.]  #11  icu4c                         76.1, 76.1-dev -> 78.3, 78.3-dev
[U.]  #12  ijs                           10.07.0 -> 10.07.1
[U*]  #13  imagemagick                   7.1.2-23 -> 7.1.2-24
[U.]  #14  just                          1.51.0, 1.51.0-man -> 1.54.0, 1.54.0-man
[U.]  #15  krb5                          1.22.1-lib -> 1.22.2-lib
[U.]  #16  libde265                      1.0.18 -> 1.1.1
[U.]  #17  libgcrypt                     1.11.2-lib -> 1.12.2-lib
[U.]  #18  libheif                       1.21.2-lib -> 1.23.0-lib
[U.]  #19  libpng-apng                   1.6.56 -> 1.6.58
[U*]  #20  libxml2                       2.15.2, 2.15.2-bin -> 2.15.3, 2.15.3-bin
[U*]  #21  lua-language-server           3.18.1 -> 3.18.2
[D.]  #22  nix                           2.34.7+1 x2 -> 2.34.7 x2
[D.]  #23  nix-cmd                       2.34.7+1 -> 2.34.7
[D.]  #24  nix-expr                      2.34.7+1 -> 2.34.7
[D.]  #25  nix-fetchers                  2.34.7+1 -> 2.34.7
[D.]  #26  nix-flake                     2.34.7+1 -> 2.34.7
[D.]  #27  nix-main                      2.34.7+1 -> 2.34.7
[D.]  #28  nix-store                     2.34.7+1 -> 2.34.7
[D.]  #29  nix-util                      2.34.7+1 -> 2.34.7
[U*]  #30  nodejs                        24.15.0 -> 24.16.0
[C.]  #31  nodejs-slim                   22.22.3, 24.15.0, 24.15.0-corepack, 24.15.0-npm -> 22.23.1, 24.16.0, 24.16.0-corepack, 24.16.0-npm
[U.]  #32  openapv                       0.2.1.2 -> 0.2.1.3
[U.]  #33  openexr                       3.4.10 -> 3.4.11
[U.]  #34  podman                        5.8.2, 5.8.2-man -> 5.8.3, 5.8.3-man
[U.]  #35  publicsuffix-list             0-unstable-2026-03-26 -> 0-unstable-2026-05-13
[U.]  #36  rsync                         3.4.1 -> 3.4.4
[U*]  #37  rumdl                         0.2.16 -> 0.2.21
[U.]  #38  simdjson                      4.6.0 -> 4.6.4
[C*]  #39  sqlite                        3.51.2 x2, 3.51.2-bin x2, 3.51.2-dev, 3.51.2-man x2 -> 3.51.2 x2, 3.51.2-bin x2, 3.51.2-dev, 3.51.2-man
[U.]  #40  unbound                       1.25.0-lib -> 1.25.1-lib
[U.]  #41  uv                            0.11.19 -> 0.11.22
[D*]  #42  vscode-langservers-extracted  4.10.0 -> 1.121.03429
Selection state changes:
[C+]  #1  direnv   2.37.1
[C+]  #2  git-lfs  3.7.1
[C+]  #3  htop     3.5.1, 3.5.1-man
[C+]  #4  kitty    0.47.4, 0.47.4-terminfo
Added packages:
[A+]  #1  delta-kanagawa                                   <none>
[A.]  #2  delta-kanagawa.gitconfig                         <none>
[A.]  #3  fastfetch-unwrapped                              2.65.1, 2.65.1-man
[A.]  #4  home-manager-agent-domains                       <none>
[A.]  #5  org.nix-community.home.claude-config-dir.domain  <none>
[A.]  #6  zz-nom-wrapper.sh                                <none>
Removed packages:
[R.]  #1  delta-config                 <none>
[R.]  #2  delta-wrapped                <none>
[R.]  #3  direnv-config                <none>
[R.]  #4  empty-directory              <none>
[R.]  #5  gh-config.yml                <none>
[R.]  #6  hm_.sshconfig                <none>
[R.]  #7  hm_direnvlibzznomwrapper.sh  <none>
[R.]  #8  hm_gitallowed_signers        <none>
[R.]  #9  hm_gitconfig                 <none>
[R.]  #10  hm_gitignore                 <none>
[R.]  #11  hm_kanagawa.conf             <none>
[R.]  #12  hm_kittydiff.conf            <none>
[R.]  #13  hm_kittykitty.conf           <none>
[R.]  #14  yyjson                       0.12.0
Closure size: 624 -> 616 (611 paths added, 619 paths removed, delta -8, disk usage -53.1MiB).
kriswill added a commit that referenced this pull request Jun 29, 2026
Completes the migration. The home-manager bridge, flake input, and the last
runtime hook are gone; the repo is now pure nix-darwin + the GNU Stow tree under
home/.

- flake.nix: drop the home-manager input (flake.lock: removed the home-manager
  and home-manager/nixpkgs nodes).
- modules/home.nix deleted -- the darwin <- home-manager bridge.
- modules/darwin/core.nix: drop pkgs.home-manager from environment.systemPackages.
- home/zsh/.config/zsh/.zshrc: drop the hm-session-vars.sh source line.
- AGENTS.md / CLAUDE.md updated to describe a darwin-only repo (no home.nix, no
  modules/home-manager/, no mkOutOfStoreSymlink mechanism).

Terminfo is unaffected -- the one real risk. nix-darwin's own set-environment
already exports the full TERMINFO_DIRS (the Ghostty.app bundle, the per-user and
system nix profiles, and /usr/share/terminfo), so xterm-kitty (in the system
profile's share/terminfo) and xterm-ghostty (Ghostty's bundle) still resolve
without the home-manager session-vars line. Verified with infocmp.

nix flake check passes for k, mini, SOC.

nvd diff -- previous generation (system-186, home-manager present) -> this commit
(system-187, now active). Removes the entire home-manager apparatus plus the deps
nothing else in the closure used (man-db, groff, diffutils, inetutils,
libpipeline).

<<< /nix/store/gf8hnm6w88ng2kajiy4x5n29k55nq0wz-darwin-system-26.11.a1fa429
>>> /run/current-system
Removed packages:
[R.]  #1  activation-k                             <none>
[R.]  #2  check-link-targets.sh                    <none>
[R.]  #3  cleanup                                  <none>
[R.]  #4  diffutils                                3.12
[R.]  #5  groff                                    1.24.1
[R.]  #6  hm-modules-messages                      <none>
[R.]  #7  hm-session-vars.sh                       <none>
[R.]  #8  hm_LibraryFonts.homemanagerfontsversion  <none>
[R.]  #9  hm_Usersk.cache.keep                     <none>
[R.]  #10  hm_Usersk.localstate.keep                <none>
[R-]  #11  home-manager                             0-unstable-2026-04-24
[R.]  #12  home-manager-agent-domains               <none>
[R.]  #13  home-manager-agents                      <none>
[R.]  #14  home-manager-applications                <none>
[R.]  #15  home-manager-files                       <none>
[R.]  #16  home-manager-fonts                       <none>
[R.]  #17  home-manager-generation                  <none>
[R.]  #18  home-manager-path                        <none>
[R.]  #19  home-manager-source                      <none>
[R.]  #20  home-manager.sh                          <none>
[R.]  #21  inetutils                                2.7
[R.]  #22  libpipeline                              1.5.8
[R.]  #23  link                                     <none>
[R.]  #24  man-db                                   2.13.1
[R.]  #25  nixos-option                             <none>
[R.]  #26  nixos-option.nix                         <none>
Closure size: 613 -> 587 (6 paths added, 32 paths removed, delta -26, disk usage -20.7MiB).
kriswill added a commit that referenced this pull request Jul 1, 2026
nixpkgs' podman derivation set meta.platforms = lib.platforms.linux, so it
refuses to evaluate on aarch64-darwin and breaks every rebuild that pulls it
into a profile. Rather than override the meta, package the official upstream
release binary.

pkgs/podman.nix fetches podman-remote-release-darwin_arm64.zip as a fixed-
output derivation (fetchzip) and installs the adhoc-signed Mach-O binaries
verbatim (dontFixup keeps the signature valid; deps are all system libs).
overlays/podman.nix replaces pkgs.podman with it, so the host package lists
need no changes.

Note: the upstream zip is the remote client only -- it does not bundle the
gvproxy/krunkit/libkrun helpers nixpkgs' podman propagated on darwin (see the
Removed packages below). `podman machine` user-mode networking may need
gvproxy provided separately.

nvd diff, previous generation -> current (195 -> 196). This generation switch
also carried the concurrent flake-lock bump and the codebase-memory-mcp rename,
committed separately:

    <<< /nix/var/nix/profiles/system-195-link
    >>> /nix/var/nix/profiles/system-196-link
    Version changes:
    [U*]  #1  cbm-tools            0.8.1 -> 0.8.1-nix
    [U*]  #2  codebase-memory-mcp  0.8.1 -> 0.8.1-nix
    [U.]  #3  podman               5.8.3, 5.8.3-man -> 6.0.0
    [U*]  #4  tmux                 3.6a, 3.6a-man -> 3.7, 3.7-man
    [U.]  #5  uv                   0.11.22 -> 0.11.25
    Removed packages:
    [R.]  #1  gvproxy                       0.8.9
    [R.]  #2  krunkit                       1.2.1
    [R.]  #3  libepoxy                      1.5.10
    [R.]  #4  libkrun-efi                   1.19.0
    [R.]  #5  podman-helper-binary-wrapper  <none>
    [R.]  #6  virglrenderer                 0.10.4d-krunkit
    Closure size: 590 -> 583 (14 paths added, 21 paths removed, delta -7, disk usage -14.1MiB).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant