From 550fca23831b72dbd2492ab1c796103d67aae3a5 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Fri, 13 Mar 2026 16:44:38 -0400 Subject: [PATCH] cleanup --- .gitignore | 1 - README.md | 55 +++++++++++++++--- config/codex/config.toml | 3 - docs/architecture.md | 65 +++++++++++++++++++++ home/claude.nix | 2 + home/default.nix | 1 + home/migration.nix | 29 ++++++++++ home/tmux.nix | 18 ------ home/zsh.nix | 16 +----- justfile | 3 - modules/homebrew.nix | 1 + modules/macos.nix | 2 + scripts/backup-machine.sh | 118 -------------------------------------- 13 files changed, 151 insertions(+), 163 deletions(-) create mode 100644 docs/architecture.md create mode 100644 home/migration.nix delete mode 100755 scripts/backup-machine.sh diff --git a/.gitignore b/.gitignore index e472383..148ceac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ result .direnv .cache-home -private-backup/ diff --git a/README.md b/README.md index 14e16f4..240f27c 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,16 @@ ## Approach -The repo now owns the active shell/editor/tool config directly: +This repo is the source of truth for the machine's reproducible developer +environment: - `home/` contains the Home Manager modules for user-facing tools - `config/` contains the repo-owned config trees copied from your daily setup -- `modules/homebrew.nix` is intentionally narrow and should eventually disappear -- Homebrew cleanup is still set to `"none"` so the first switch is non-destructive +- `modules/` contains host-level `nix-darwin` policy and package layers +- `modules/homebrew.nix` is intentionally narrow and only exists for GUI apps + that are still easier to keep in Brew on macOS +- `home/migration.nix` contains one-time ownership handoff logic from `~/dots` + into Home Manager so the steady-state modules can stay focused on real config ## Layout @@ -18,11 +22,30 @@ The repo now owns the active shell/editor/tool config directly: - `modules/packages.nix`: system packages and fonts - `modules/homebrew.nix`: the remaining Homebrew-managed GUI apps - `home/`: Home Manager modules for shell, editor, CLI tools, and app config +- `home/migration.nix`: transitional cleanup for old `~/dots` symlinks - `config/`: repo-owned config files consumed by Home Manager +## Ownership Boundaries + +- Nix owns packages, dotfiles, shell/editor config, launchd services, and + selected macOS defaults +- Homebrew is retained only for a narrow GUI cask boundary +- Keychain items, TCC/privacy permissions, browser history, and most + `~/Library/Application Support` state are intentionally outside declarative + Nix ownership + +## Dedicated Inputs + +Most tools come from `nixpkgs`. Fast-moving CLIs that you want to update on +their own cadence are pinned as dedicated flake inputs: + +- `googleworkspace-cli` +- `codex` +- `claudeCode` + ## Commands -Bootstrap the host: +First switch: ```bash nix run github:LnL7/nix-darwin/master#darwin-rebuild -- switch --flake .#hari-macbook-pro @@ -36,13 +59,27 @@ just build just check ``` -## What Still Needs Manual Work +Update everything pinned by the flake: + +```bash +nix flake update +just switch +``` + +Update only Codex or Claude: + +```bash +nix flake lock --update-input codex +nix flake lock --update-input claudeCode +just switch +``` + +## What Still Needs Manual Handling - Secrets and tokens under `~/.secrets`, `~/.npmrc`, `~/.config/gcloud`, `~/.config/gh`, and similar paths -- Launch agents that are currently outside Nix - App state under `~/Library/Application Support` - Anything that depends on local credentials, keychains, or encrypted stores -- Replacing or intentionally dropping the remaining GUI apps still delivered via Homebrew +- Manual cleanup of old non-Nix installs that are no longer wanted ## Current Homebrew Scope @@ -50,5 +87,9 @@ The current Homebrew boundary is only: - `cap` - `raycast` +- `riptide-dev` - `thebrowsercompany-dia` - `wispr-flow` + +Homebrew activation is currently `cleanup = "uninstall"`, so anything outside +that list is treated as drift and removed on `darwin-rebuild switch`. diff --git a/config/codex/config.toml b/config/codex/config.toml index 71acefe..5b1871c 100644 --- a/config/codex/config.toml +++ b/config/codex/config.toml @@ -62,9 +62,6 @@ trust_level = "trusted" [projects."/Users/rathi/Documents/GitHub/clank-artifacts"] trust_level = "trusted" -[projects."/Users/rathi/dots/.git/modules/nvim/.config"] -trust_level = "trusted" - [projects."/Users/rathi/Documents/GitHub/kubasync"] trust_level = "trusted" diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..d6bdd84 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,65 @@ +# Architecture + +## Goal + +This repo should read like a steady-state machine configuration, not a diary of +whatever was necessary to survive the first migration. + +The structure is intentionally split into three layers: + +- `modules/`: host-wide `nix-darwin` policy +- `home/`: user-facing Home Manager config +- `config/`: raw config payloads consumed by Home Manager + +## Host Layer + +- [modules/base.nix](/Users/rathi/Documents/GitHub/nix/modules/base.nix) owns + baseline Nix settings, shells, and common packages +- [modules/packages.nix](/Users/rathi/Documents/GitHub/nix/modules/packages.nix) + owns the heavier developer tooling and fonts +- [modules/homebrew.nix](/Users/rathi/Documents/GitHub/nix/modules/homebrew.nix) + is the explicitly narrow Brew escape hatch for GUI casks +- [modules/macos.nix](/Users/rathi/Documents/GitHub/nix/modules/macos.nix) + owns system defaults and macOS-specific integration + +## Home Layer + +- each app/tool gets its own module under `home/` +- raw config trees live under `config/` and are linked by Home Manager +- [home/migration.nix](/Users/rathi/Documents/GitHub/nix/home/migration.nix) + is the only place where takeover logic for old `~/dots` symlinks lives + +That separation matters. Steady-state modules should describe how the machine +works today. Migration-only ownership cleanup belongs in one place and should be +easy to delete later. + +## Package Sources + +Default rule: + +- use `nixpkgs` for stable everyday tooling + +Exceptions: + +- use dedicated flake inputs for fast-moving product CLIs whose release cadence + matters to the machine owner + +Current dedicated inputs: + +- `googleworkspace-cli` +- `codex` +- `claudeCode` + +## Intentional Pragmatism + +Some pieces are still pragmatic compatibility shims rather than ideal upstream +state: + +- [modules/macos.nix](/Users/rathi/Documents/GitHub/nix/modules/macos.nix) + carries a Karabiner launch-agent override because current nix-darwin still + points at the older Karabiner bundle layout +- [home/claude.nix](/Users/rathi/Documents/GitHub/nix/home/claude.nix) manages + `~/.local/bin/claude` so the Nix package cleanly replaces the old manual path + that was already first in shell PATH + +Those are acceptable as long as they are explicit and documented. diff --git a/home/claude.nix b/home/claude.nix index e42d5bf..e515e46 100644 --- a/home/claude.nix +++ b/home/claude.nix @@ -6,6 +6,8 @@ claudePackage = inputs.claudeCode.packages.${pkgs.stdenv.hostPlatform.system}.default; in { + # Keep the managed Claude binary on the same path the live machine was using + # so the Nix package cleanly replaces the prior manual install. home.file.".local/bin/claude".source = "${claudePackage}/bin/claude"; home.file.".claude/CLAUDE.md".source = ../config/claude/CLAUDE.md; home.file.".claude/commands" = { diff --git a/home/default.nix b/home/default.nix index 390abae..cada377 100644 --- a/home/default.nix +++ b/home/default.nix @@ -10,6 +10,7 @@ ./karabiner.nix ./k9s.nix ./lazygit.nix + ./migration.nix ./nvim.nix ./rectangle.nix ./tmux.nix diff --git a/home/migration.nix b/home/migration.nix new file mode 100644 index 0000000..bcd42ba --- /dev/null +++ b/home/migration.nix @@ -0,0 +1,29 @@ +{lib, ...}: { + # Transitional cleanup for files previously owned by ~/dots. Keeping this + # separate from steady-state modules makes it obvious what can be deleted + # once every managed path has been fully handed over to Home Manager. + home.activation.removeLegacyZshLinks = lib.hm.dag.entryBefore ["checkLinkTargets"] '' + for path in "$HOME/.zshenv" "$HOME/.zshrc"; do + if [ -L "$path" ]; then + target="$(readlink "$path")" + case "$target" in + dots/zsh/*|"$HOME"/dots/zsh/*) + rm -f "$path" + ;; + esac + fi + done + ''; + + home.activation.removeLegacyTmuxLink = lib.hm.dag.entryBefore ["checkLinkTargets"] '' + path="$HOME/.config/tmux/tmux.conf" + if [ -L "$path" ]; then + target="$(readlink "$path")" + case "$target" in + ../../dots/tmux/*|dots/tmux/*|"$HOME"/dots/tmux/*) + rm -f "$path" + ;; + esac + fi + ''; +} diff --git a/home/tmux.nix b/home/tmux.nix index 478689d..21560ab 100644 --- a/home/tmux.nix +++ b/home/tmux.nix @@ -1,16 +1,4 @@ {lib, ...}: { - home.activation.removeLegacyTmuxLink = lib.hm.dag.entryBefore ["checkLinkTargets"] '' - path="$HOME/.config/tmux/tmux.conf" - if [ -L "$path" ]; then - target="$(readlink "$path")" - case "$target" in - ../../dots/tmux/*|dots/tmux/*|"$HOME"/dots/tmux/*) - rm -f "$path" - ;; - esac - fi - ''; - programs.tmux = { enable = true; extraConfig = '' @@ -40,12 +28,6 @@ bind K switch-client -p bind L switch-client -n - # Switch sessions with prefix + H/J/K/L (capital) - bind J switch-client -n - bind K switch-client -p - bind H switch-client -p - bind L switch-client -n - # fzf pane switcher bind f display-popup -w 80% -h 80% -E "\ tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index} #{session_name}/#{window_name} [#{pane_current_command}] #{pane_current_path}' \ diff --git a/home/zsh.nix b/home/zsh.nix index 8f39d6a..7ee364c 100644 --- a/home/zsh.nix +++ b/home/zsh.nix @@ -7,19 +7,6 @@ home.file.".oh-my-zsh/custom/themes/agnoster.zsh-theme".source = ../config/agnoster.zsh-theme; - home.activation.removeLegacyZshLinks = lib.hm.dag.entryBefore ["checkLinkTargets"] '' - for path in "$HOME/.zshenv" "$HOME/.zshrc"; do - if [ -L "$path" ]; then - target="$(readlink "$path")" - case "$target" in - dots/zsh/*|"$HOME"/dots/zsh/*) - rm -f "$path" - ;; - esac - fi - done - ''; - home.activation.ensureOhMyZshCache = lib.hm.dag.entryAfter ["writeBoundary"] '' mkdir -p "${config.xdg.cacheHome}/oh-my-zsh" ''; @@ -56,6 +43,9 @@ envExtra = '' . "$HOME/.cargo/env" + + # Ghostty shell integration expects a resource directory; the Nix app + # bundle lives in the store instead of /Applications. export GHOSTTY_RESOURCES_DIR="${pkgs.ghostty-bin}/Applications/Ghostty.app/Contents/Resources/ghostty" ''; diff --git a/justfile b/justfile index 182b281..72c987a 100644 --- a/justfile +++ b/justfile @@ -10,8 +10,5 @@ build: switch: sudo env PATH="$PATH" nix --extra-experimental-features 'nix-command flakes' run github:LnL7/nix-darwin/master#darwin-rebuild -- switch --flake .#hari-macbook-pro -backup: - ./scripts/backup-machine.sh - fmt: nix --extra-experimental-features 'nix-command flakes' fmt diff --git a/modules/homebrew.nix b/modules/homebrew.nix index f235261..57a04be 100644 --- a/modules/homebrew.nix +++ b/modules/homebrew.nix @@ -9,6 +9,7 @@ }; taps = [ + # riptide-dev is still sourced from this tap. "humanlayer/humanlayer" ]; diff --git a/modules/macos.nix b/modules/macos.nix index ed6be39..e433fb6 100644 --- a/modules/macos.nix +++ b/modules/macos.nix @@ -11,6 +11,8 @@ in { services.karabiner-elements.enable = true; + # Upstream nix-darwin still expects Karabiner's old agent layout. Override it + # here until the built-in module catches up to the 15.7+ bundle structure. # Karabiner-Elements 15.7.0 moved its user launch agents into the # Non-Privileged Agents v2 bundle and renamed karabiner_grabber. # nix-darwin's built-in module still points at the old top-level paths. diff --git a/scripts/backup-machine.sh b/scripts/backup-machine.sh deleted file mode 100755 index 2b9162e..0000000 --- a/scripts/backup-machine.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -umask 077 - -repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -backup_root="${repo_root}/private-backup" -timestamp="$(date +%Y%m%d-%H%M%S)" -backup_dir="${1:-${backup_root}/${timestamp}}" - -mkdir -p "${backup_dir}/archives" "${backup_dir}/manifests" - -snapshot_log="$(tmutil localsnapshot 2>&1 || true)" -printf '%s\n' "${snapshot_log}" > "${backup_dir}/manifests/apfs-localsnapshot.log" -tmutil listlocalsnapshots / > "${backup_dir}/manifests/apfs-localsnapshots.txt" 2>&1 || true - -{ - printf 'created_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" - printf 'hostname=%s\n' "$(scutil --get HostName 2>/dev/null || hostname)" - printf 'local_host_name=%s\n' "$(scutil --get LocalHostName 2>/dev/null || true)" - printf 'computer_name=%s\n' "$(scutil --get ComputerName 2>/dev/null || true)" - printf 'repo_root=%s\n' "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -} > "${backup_dir}/manifests/backup-meta.txt" - -sw_vers > "${backup_dir}/manifests/sw_vers.txt" -uname -a > "${backup_dir}/manifests/uname.txt" -df -h / /System/Volumes/Data /nix > "${backup_dir}/manifests/disk-usage.txt" - -if command -v brew >/dev/null 2>&1; then - brew bundle dump --file=- --force --describe > "${backup_dir}/manifests/Brewfile" 2> "${backup_dir}/manifests/brew-bundle.stderr" || true - brew list --formula --versions > "${backup_dir}/manifests/brew-formulae.txt" 2> "${backup_dir}/manifests/brew-formulae.stderr" || true - brew list --cask --versions > "${backup_dir}/manifests/brew-casks.txt" 2> "${backup_dir}/manifests/brew-casks.stderr" || true - brew services list > "${backup_dir}/manifests/brew-services.txt" 2> "${backup_dir}/manifests/brew-services.stderr" || true -fi - -if command -v nix >/dev/null 2>&1; then - nix --version > "${backup_dir}/manifests/nix-version.txt" 2>&1 || true -fi - -git -C "${repo_root}" status --short > "${backup_dir}/manifests/nix-repo-status.txt" 2>&1 || true -git -C "${repo_root}" rev-parse HEAD > "${backup_dir}/manifests/nix-repo-head.txt" 2>&1 || true - -find /Applications -maxdepth 1 -type d -name "*.app" | sort > "${backup_dir}/manifests/applications-system.txt" -find "${HOME}/Applications" -maxdepth 1 -type d -name "*.app" | sort > "${backup_dir}/manifests/applications-user.txt" 2>/dev/null || true - -{ - printf '%s\n' "${HOME}/.claude" - printf '%s\n' "${HOME}/.codex" - printf '%s\n' "${HOME}/Library/Application Support/Claude" - printf '%s\n' "${HOME}/Library/Application Support/Code" - printf '%s\n' "${HOME}/Library/Application Support/Cursor" - printf '%s\n' "${HOME}/Library/Application Support/Zed" -} > "${backup_dir}/manifests/excluded-paths.txt" - -home_paths=( - ".config" - ".ssh" - ".gnupg" - ".aws" - ".npmrc" - ".gitconfig" - ".gitignore" - ".zshenv" - ".zprofile" - ".zshrc" - ".zlogin" - ".zlogout" - ".bash_profile" - ".profile" - ".secrets" - ".claude.json" - ".claude.json.backup" - "dots" -) - -library_paths=( - "Library/Preferences" - "Library/Fonts" - "Library/Application Support/Codex" -) - -archive_from_home() { - local archive_name="$1" - shift - local source_root="$1" - shift - local -a requested=("$@") - local -a existing=() - - for path in "${requested[@]}"; do - if [[ -e "${source_root}/${path}" || -L "${source_root}/${path}" ]]; then - existing+=("${path}") - fi - done - - printf '%s\n' "${existing[@]}" > "${backup_dir}/manifests/${archive_name%.tar.gz}-contents.txt" - - if ((${#existing[@]} == 0)); then - return - fi - - COPYFILE_DISABLE=1 tar \ - --exclude ".ssh/agent" \ - --exclude ".ssh/controlmasters" \ - -C "${source_root}" \ - -czf "${backup_dir}/archives/${archive_name}" \ - "${existing[@]}" -} - -archive_from_home "home-config.tar.gz" "${HOME}" "${home_paths[@]}" -archive_from_home "library-config.tar.gz" "${HOME}" "${library_paths[@]}" - -( - cd "${backup_dir}" - shasum -a 256 archives/*.tar.gz > manifests/archive-checksums.txt -) - -printf 'Backup written to %s\n' "${backup_dir}"