MacBook M4 2025 Migration Log

Summary

This document chronicles the migration from an Intel x86_64 MacBook to an Apple Silicon M4 MacBook (arm64) using macOS Migration Assistant. The migration revealed several architecture-dependent issues and provided valuable insights for improving the dotfiles bootstrap process.

Key Statistics:

  • Migration time: ~1 hour
  • Architecture: Intel x86_64 Apple Silicon arm64
  • Major issues: 7 (PAM/sudo, bootstrap script, broken symlinks, architecture mismatches, Homebrew dual-install, Docker, cache incompatibility)
  • Final state: 100% native arm64 applications, zero Rosetta 2 dependencies, ~121MB of x86_64 files removed

Phase 1: Pre-Migration & Initial Setup

Migration Assistant Transfer

  • Used macOS Migration Assistant to transfer all data from Intel MacBook
  • Duration: Approximately 1 hour
  • A few non-essential documents failed to transfer
  • Migration Assistant prompted to update the source laptop to avoid issues
    • Problem: 2020 Intel Mac was not supported for macOS 26 update
    • Proceeded anyway without apparent consequences

Initial System Configuration

After first boot on the new MacBook:

Apple Wallet Cards

  • System prompted to add cards from Apple Wallet
  • Issue: None of the cards were able to be verified
  • Status: Unresolved in this session

Application Permissions Required manual permission grants for:

  • BetterTouchTool (Accessibility, Screen Recording)
  • Logi Options++ (Accessibility)
  • Karabiner (Observer/Grader Input permissions)

Account Setup

  • Added Google account as Internet Account for email
  • Bitwarden app and credentials transferred seamlessly
  • ChatGPT extension for Siri required re-enabling and sign-in
  • Quick Look extensions (Windows and Xcode) required manual enabling

Phase 2: Bootstrap Script Issues

Issue 1: PAM Initialization Error

Attempted to run the dotfiles startup script, which started Nix installation.

Error:

sudo: unable to initialize PAM: Undefined error: 0

Root Cause: The Intel Mac had a custom PAM configuration at /etc/pam.d/sudo that included:

auth       include        sudo_local
auth       sufficient     pam_tid.so
auth       sufficient     pam_smartcard.so
auth       sufficient     pam_smartcard.so
auth       required       pam_opendirectory.so
account    required       pam_permit.so
password   required       pam_deny.so
session    required       pam_permit.so

The sudo_local file referenced pam_reattach.so which was compiled for x86_64 and incompatible with arm64.

Context: The PAM modification was needed to enable Touch ID with tmux in the terminal. Tmux persists across user login sessions, which breaks the default PAM integration. The pam_reattach.so module fixes this by reattaching to the user login session scope.

Resolution:

  • Used macOS Finder trick to gain write permissions to /etc/pam.d/sudo:
    • open /etc/pam.d/ in terminal
    • Selected sudo file in Finder
    • Unlocked details and granted read/write access temporarily
  • Removed sudo_local include from /etc/pam.d/sudo
  • Immediately restored read-only permissions for everyone
  • sudo functionality restored

Issue 2: Pre-existing Backup Files

Error:

---- oh no! --------------------------------------------------------------------
I back up shell profile/rc scripts before I add Nix to them.
I need to back up /etc/bash.bashrc to /etc/bash.bashrc.backup-before-nix,
but the latter already exists.

Root Cause: Migration Assistant transferred old Nix backup files from the Intel Mac.

Additional Discovery: The /etc/bash.bashrc file already contained Nix daemon initialization code (also migrated from Intel Mac).

Resolution:

sudo rm /etc/bash.bashrc.backup-before-nix
sudo rm /etc/zshrc.backup-before-nix

Then manually cleaned the Nix daemon shell script from /etc/bash.bashrc and recreated a clean backup file to represent the state before Nix installation.

Error:

/dev/fd/11: line 61: /Users/achhina/.config/nix/nix.conf: No such file or directory

Root Cause: Migration Assistant copied Home Manager symlinks that pointed to Nix store paths from the Intel Mac:

nix.conf: broken symbolic link to /nix/store/mihd906bd8vlm09q3w6apak7gxq0n9h3-home-manager-files/.config/nix/nix.conf

Resolution:

rm ~/.config/nix/nix.conf

Issue 4: Bootstrap Script Bug

Error:

Checking for conflicts with existing /Users/achhina/.config...
/dev/fd/11: line 112: conflicts[*]: unbound variable

Root Cause: Bootstrap script attempts to expand array variable without checking if it’s set.

Status: Needs fix (see Phase 2 action items)


Phase 3: Manual Dotfiles Installation

At this point, the bootstrap script had too many issues. Proceeded with manual installation.

Repository Setup

Discovery: The .git directory was already present (transferred via Migration Assistant), so no need to reinitialize.

Issue: Lost .git directory at some point during troubleshooting (likely during mv ~/.config ~/.config.backup operations).

Resolution:

git init
git remote add origin https://github.com/achhina/dotfiles.git
git fetch --all
git reset --hard origin/main

Home Manager Installation Attempt

Initial Error:

nix run home-manager/master -- switch --flake ~/.config/nix#$(scutil --get LocalHostName)
# Output: MacBook-Pro
 
error: flake 'path:/Users/achhina/.config/nix' does not provide attribute
'packages.aarch64-darwin.homeConfigurations."MacBook-Pro".activationPackage'

Root Cause: The flake defined homeConfigurations.achhina and homeConfigurations.default, but scutil --get LocalHostName returned "MacBook-Pro", which didn’t match any defined configuration.

Attempted Fix:

nix run home-manager/master -- switch --flake ~/.config/nix#achhina

Issue 5: Architecture Mismatch in Flake

Error:

error: Cannot build '/nix/store/aw8s03lz3ficjlh2cn998a0r8vhdh3rr--.drv'.
       Reason: required system or feature not available
       Required system: 'x86_64-darwin' with features {}
       Current system: 'aarch64-darwin' with features {apple-virt, benchmark, big-parallel, nixos-test}

This error appeared for multiple packages:

  • bordersrc.drv
  • builder.pl.drv
  • detex-70015.drv
  • hm-modules-messages.drv
  • hm-session-vars.sh.drv
  • org.nix-community.home.aerospace.plist.drv
  • activation-script.drv
  • home-manager-generation.drv

Root Cause: The flake was using builtins.currentSystem which doesn’t work correctly in flake context. The configuration was hardcoded for x86_64-darwin.

Resolution: Updated flake.nix to explicitly specify the architecture:

homeConfigurations."MacBook-Pro" = mkHomeConfiguration "aarch64-darwin";

Home Manager Activation

File Collision Error:

Existing file '/Users/achhina/.config/nix/nix.conf' would be clobbered

Resolution:

nix run home-manager/master -- switch --flake ~/.config/nix#$(scutil --get LocalHostName) -b backup

Success: Home Manager activated successfully with backup of existing files.

Post-Activation: Required re-granting Accessibility permissions to:

  • Aerospace
  • Borders (jankyborders)
  • BetterTouchTool
  • Logi Options++

Issue 6: Untrusted User Warnings

Warnings during activation:

warning: ignoring untrusted substituter 'https://nix-community.cachix.org', you are not a trusted user.
warning: ignoring untrusted substituter 'https://cache.garnix.io', you are not a trusted user.
warning: ignoring the client-specified setting 'trusted-public-keys', because it is a restricted setting and you are not a trusted user

Resolution:

# Add to /etc/nix/nix.conf
echo "trusted-users = root @admin" | sudo tee -a /etc/nix/nix.conf
 
# Restart Nix daemon
sudo launchctl stop org.nixos.nix-daemon
sudo launchctl start org.nixos.nix-daemon

Phase 4: Architecture Migration Issues

Issue 7: Cached Binaries Architecture Mismatch

Symptoms:

  • Neovim plugins failing to load (fzf)
  • Pre-commit gitleaks hook failing
  • Various executable incompatibilities

Root Cause: Migration Assistant transferred the ~/.cache directory containing x86_64 compiled binaries.

Resolution:

rm -rf ~/.cache

Cache directories are typically regenerated on demand.

Neovim Plugin Reinstallation

After removing cache, had to reinstall all Neovim plugins:

Issue with Lazy.nvim:

  • Visual highlighting all packages and pressing X didn’t work
  • Had to visual highlight, press x, then I to install all

Single Plugin Failure:

markdown-preview.nvim  markdown  md  MarkdownPreviewToggle  MarkdownPreview  MarkdownPreviewStop
  Vim:E117: Unknown function: mkdp#util#install

Status: Plugin partially broken, needs investigation

System Settings

Keyboard Remapping:

  • Manually mapped Caps Lock Control
  • Location: System Settings Keyboard Keyboard Shortcuts

Phase 5: Docker & Container Setup

Docker Desktop Issues

Problem 1: Docker Desktop (GUI) had CPU architecture mismatch (x86_64)

Resolution:

  • Removed Intel Docker Desktop
  • Installed Docker engine via Nix
  • Installed Docker Desktop (arm64) from official website
    • Note: Could also be installed via Homebrew

Problem 2: Container images showed amd64 emulation warnings about potential slowdowns

Resolution:

# Pull arm64 images
docker pull --platform linux/arm64 [image-name]
 
# Set default builder to arm64
# Note: This required Docker Desktop restart

Problem 3: Default builder communication error

Resolution:

  • Enabled default socket communication in Docker Desktop Advanced settings
  • Verified /var/run/docker.sock symlink points to default builder

Phase 6: PAM/Touch ID Final Fix

Complete PAM Resolution

Background: Earlier fix temporarily disabled pam_reattach to restore sudo. Now needed to restore Touch ID functionality with arm64-compatible module.

Problem: Original pam_reattach.so was x86_64 (Intel Homebrew at /usr/local).

Resolution:

  1. Install Apple Silicon Homebrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Installed to /opt/homebrew (arm64 location)

  1. Install arm64 pam-reattach:
/opt/homebrew/bin/brew install pam-reattach
  1. Update PAM configuration: Updated /etc/pam.d/sudo_local to point to:
/opt/homebrew/lib/pam/pam_reattach.so
  1. Configured Homebrew PATH in Nix: Added to ~/.config/nix/home-manager/modules/shell.nix:
programs.zsh.profileExtra = ''
  eval "$(/opt/homebrew/bin/brew shellenv)"
'';
  1. Applied changes:
hm switch

Result: Touch ID working with sudo in tmux sessions


Phase 7: Complete Intel Apple Silicon Migration

Homebrew Dual Installation Cleanup

Discovered State:

  • Intel Homebrew at /usr/local/bin/brew (x86_64)
  • Apple Silicon Homebrew at /opt/homebrew/bin/brew (arm64)

Package Migration (5 packages)

Migrated from Intel to Apple Silicon Homebrew:

  1. pam-reattach (formula)
  2. firefoxpwa (formula)
  3. blackhole-2ch (cask)
  4. logi-options+ (cask)
  5. sublime-text (cask)

Intel Homebrew Complete Removal

Cleanup process:

# Remove all packages from Intel Homebrew
/usr/local/bin/brew uninstall --force --ignore-dependencies [package]
 
# Remove Intel Homebrew core
sudo rm -rf /usr/local/Homebrew
 
# Clean up orphaned configs and data (~25MB)
sudo rm -rf /usr/local/etc
sudo rm -rf /usr/local/share
sudo rm -rf /usr/local/var
 
# Remove Intel-only drivers (3 copies of chromedriver, 48MB)
sudo rm -rf /usr/local/Caskroom
 
# Remove osxfuse and Lua modules (x86_64)

Final Verification:

find /usr/local -name "*x86_64*" 2>/dev/null | wc -l
# Output: 0

Result: Zero Intel Homebrew remnants

System-Wide Application Updates (9 apps)

Migrated applications from Intel to arm64 native:

  1. Spotify - Intel arm64 via Homebrew
  2. VLC - Intel arm64 v3.0.21 via Homebrew
  3. Inkscape - Intel arm64 v1.4 via Homebrew
  4. OBS Studio - Intel arm64 v32.0.1 via Homebrew
  5. Anki - Intel Universal binary via Homebrew
  6. Google Drive - Intel arm64 v116.0.4 via Homebrew
  7. JupyterLab Desktop - Intel arm64 v4.2.5 via Homebrew
  8. Streamlabs Desktop - Intel arm64 v1.19.6 via Homebrew
  9. Obsidian - Migrated from Homebrew to Nix (cross-platform)

Applications Removed

No arm64 support or outdated:

  • VirtualBox - No Apple Silicon support (alternatives: UTM, Parallels)
  • LimeChat - Old Intel IRC client (alternatives: WeeChat, Srain, web IRC)
  • Fliqlo screensaver - Intel-only
  • Old Python 3.8 modules - ~1,450 x86_64 files

Nix Configuration Improvements

packages.nix reorganization:

Before:

darwinPackages = [ aerospace iterm2 jankyborders keycastr obsidian ];

After:

# Cross-platform GUI applications
guiApps = [ obsidian ];
 
# macOS-specific packages
darwinPackages = [ aerospace iterm2 jankyborders keycastr ];

Key Finding: OBS Studio is NOT available in nixpkgs for macOS (Linux-only in Nix), so it correctly remains in Homebrew.

Files Modified:

  1. /Users/achhina/.config/nix/home-manager/modules/packages.nix - Package reorganization
  2. /Users/achhina/.config/nix/home-manager/modules/shell.nix - Homebrew PATH initialization
  3. /etc/pam.d/sudo_local - arm64 pam_reattach path

Final State & Statistics

Before Migration

  • 10 Intel-only apps running via Rosetta 2
  • ~2,700 x86_64 binaries across system
  • Dual Homebrew installation (Intel + Apple Silicon)
  • sudo broken due to PAM architecture mismatch

After Migration

  • 0 Intel-only apps
  • 100% native arm64 applications
  • Single Apple Silicon Homebrew installation
  • sudo working perfectly with Touch ID
  • ~121MB of x86_64 files removed

Package Management Strategy

Declarative (Nix): CLI tools, dev tools, cross-platform apps (Obsidian)

Homebrew: macOS-specific apps not available or broken in Nix (OBS, JupyterLab Desktop, firefoxpwa, blackhole-2ch audio driver, pam-reattach, Spotify, etc.)

Performance Impact

  • No more Rosetta 2 translation overhead
  • Better battery life (native execution uses less power)
  • Faster app launch times
  • Lower memory usage
  • Improved thermal efficiency

Known Issues & Action Items

Bootstrap Script Needs Fixes

  1. [FIXED] Unbound variable error at line 112 (conflicts[*])
  2. [FIXED] No broken symlink detection or cleanup
  3. [FIXED] No architecture detection/warning
  4. [FIXED] No pre-existing Nix installation backup handling

Documentation Gaps

  1. [FIXED] No pre-migration checklist (what to remove/clean before migration)
  2. [FIXED] No architecture migration guide
  3. [FIXED] Hostname vs username configuration strategy not documented
  4. [FIXED] Trusted-users Nix requirement not documented

Configuration Cleanup

  1. [NOT NEEDED] Legacy “default” configuration still in flake.nix (already clean)
  2. [DOCUMENTED] markdown-preview.nvim plugin partially broken
    • Edge case during architecture migration when VimScript autoload not ready
    • Workaround documented in docs/troubleshooting.md
    • Cannot be systematically prevented (race condition during lazy.nvim rebuild)

Unresolved Issues

  1. [UNRESOLVED] Apple Wallet cards unable to be verified

Follow-up 2026-08-08: the “100% native” claim was scoped too narrowly

Intel leftovers were still on the machine more than three months after this project closed. Found and removed during an unrelated cleanup (2026-08-08 Dead Intel Kexts and Flash Remnants).

What survived:

PathDetail
/Library/Extensions/intelhaxm.kext (+ .zip)x86_64, unloaded
/Library/Extensions/HighPointRR.kextx86_64, unloaded
/Library/Extensions/HighPointIOP.kextx86_64, unloaded
/Library/Extensions/SoftRAID.kextx86_64, unloaded
/Library/LaunchDaemons/com.intel.haxm.plistpointed into the HAXM kext
/Library/LaunchDaemons/com.adobe.fpsaud.plistAdobe Flash updater, EOL 2020-12-31
/Library/Application Support/Adobe, .../MacromediaFlash remnants

All four kexts were Mach-O 64-bit x86_64 kext bundle on an arm64 machine. /Library/Extensions is now empty.

Why the original verification missed them. The completion check was find /usr/local -name "*x86_64*", which only covers the Intel Homebrew prefix. /Library/Extensions and /Library/LaunchDaemons were never in scope. Nothing surfaced them at runtime either, because a wrong-architecture kext does not fail loudly, it simply never loads. So “0 Intel-only apps” was true of applications and Homebrew, and silently untrue of system components. The 2026-07-13 The Intel Audit That Could Not See Shims pass missed them for a different reason: it was hunting runtime shims, not unloadable system extensions.

Correction to Final State above: read “100% native arm64 applications” as scoped to applications and Homebrew packages as of 2026-04-27. System-level kexts and LaunchDaemons were not audited until 2026-08-08.

Generalizable lesson: an architecture migration needs a verification that enumerates by architecture across the filesystem, not by absence within one package manager’s prefix. The useful probe is file on the actual binaries in /Library/Extensions, /Library/LaunchDaemons targets, /Library/Application Support, and login items via sfltool dumpbtm, rather than a name-pattern search under a single prefix.

Backup of everything removed: ~/Downloads/dead-intel-leftovers-2026-08-08.tar.gz.

Second batch, same day. Widening the audit past kexts found six more, about 63M:

PathSizeArch
/Library/Internet Plug-Ins/PepperFlashPlayer50MFlash, EOL 2020
/Library/Audio/Plug-Ins/HAL/LogiGamingAudio.driver11Mx86_64
/Library/PreferencePanes/Flash Player.prefPane912Kx86_64
/Library/Frameworks/OSXFUSE.framework456Kppc7400, ppc64, i386, x86_64
/Library/Audio/Plug-Ins/HAL/MSTeamsAudioDevice.driver380Kx86_64, Teams absent
/Library/PrivilegedHelperTools/com.microsoft.office.licensingV2.helpersmallno Office installed

OSXFUSE.framework still carried PowerPC slices, so it predated even the Intel machine. Phase 7 above records removing “osxfuse and Lua modules (x86_64)”, but only the Lua side and the kext went; the framework survived. /Library/Internet Plug-Ins and /Library/PreferencePanes are now empty. Backup: ~/Downloads/dead-leftovers-batch2-2026-08-08.tar.gz.

Verified clean and left alone: all 45 bundles in /Applications are arm64 (that half of the original claim holds), ParrotAudioPlugin.driver is arm64e, and the Docker, Zoom, and Microsoft AutoUpdate privileged helpers all have live owners.

Method note for any future migration. Both batches were found by enumerating architecture, not by searching for names. lipo -archs on the real binary under /Library/Extensions, /Library/Internet Plug-Ins, /Library/PreferencePanes, /Library/Frameworks, /Library/Audio/Plug-Ins/HAL, and each LaunchDaemon’s program path, cross-checked against whether the owning application still exists. Name-pattern searches under a single prefix cannot find any of this.

Confirmed still working from this project: the Phase 6 PAM fix. /etc/pam.d/sudo_local still points at /opt/homebrew/lib/pam/pam_reattach.so, and TouchID sudo authenticated the removal.