-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall
More file actions
executable file
·136 lines (123 loc) · 6.21 KB
/
Copy pathinstall
File metadata and controls
executable file
·136 lines (123 loc) · 6.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env bash
# Self-installing dotfiles. Stows each package into $HOME with GNU stow,
# --no-folding so target directories stay real (nothing folds back into this repo).
# Portable: `git clone … && ./install` works standalone, no esetup required.
set -euo pipefail
DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$DOTFILES_DIR"
TARGET="${HOME}"
# Stow packages (symlinked into $HOME). Non-packages (keyboard/, raycast/) are applied
# by esetup, not stowed, so they are deliberately absent from this list.
PACKAGES=(fish starship wezterm tmux tmux-sessionizer-config bin atuin git htop lazygit nvim swiftbar claude ssh hammerspoon)
if ! command -v stow >/dev/null 2>&1; then
echo "ERROR: GNU stow is required (brew install stow), then re-run ./install." >&2
exit 1
fi
# nvim config + tmux-sessionizer tool are submodules — make sure they are present.
if [ -f .gitmodules ]; then
git submodule update --init --recursive \
|| echo "WARN: submodule update failed (SSH/network); nvim/tmux-sessionizer may be incomplete." >&2
fi
# The tmux-sessionizer tool lives in its own submodule at the repo root; bin/ links to it.
if [ -e tmux-sessionizer/tmux-sessionizer ]; then
ln -sf ../../../tmux-sessionizer/tmux-sessionizer bin/.local/bin/tmux-sessionizer
fi
ts="$(date +%s)"
failed_pkgs=()
# First unstow every package. This removes any pre-existing stow symlinks — crucially
# any FOLDED directory symlink (e.g. ~/.config/fish -> this repo). Without this, the
# backup pass below would follow such a symlink and rename our OWN repo files.
for pkg in "${PACKAGES[@]}"; do
[ -d "$pkg" ] && stow -D --target="$TARGET" "$pkg" 2>/dev/null || true
done
for pkg in "${PACKAGES[@]}"; do
[ -d "$pkg" ] || { echo "skip (missing): $pkg"; continue; }
# A real (non-symlink) file still at the target would make stow abort; move it aside.
# Guard: never touch a path whose real location is inside this repo (a file reached
# through a folded symlink we somehow missed) — that would corrupt the source.
while IFS= read -r -d '' f; do
rel="${f#"$pkg"/}"
tgt="$TARGET/$rel"
# settings.json is copy-managed (see the claude block after the loop), never
# stowed — it's always a real file, so this pass would move it aside on EVERY
# install and litter ~/.claude with identical .bak copies. Leave it alone.
[ "$pkg/$rel" = "claude/.claude/settings.json" ] && continue
[ -e "$tgt" ] && [ ! -L "$tgt" ] || continue
tgtdir="$(cd "$(dirname "$tgt")" 2>/dev/null && pwd -P)" || continue
case "$tgtdir/" in "$DOTFILES_DIR"/*) continue ;; esac
mv "$tgt" "$tgt.bak.$ts"
echo "backed up existing $tgt -> $tgt.bak.$ts"
done < <(find "$pkg" -type f -print0)
# A conflict on ONE package (e.g. a foreign file at its target) must not strand the
# rest. The unstow loop above already removed every symlink, so aborting here under
# `set -e` used to leave all of $HOME with no dotfiles at all. Keep going and collect
# the failures instead.
if stow --no-folding --target="$TARGET" "$pkg"; then
echo "stowed: $pkg"
else
echo "WARN: failed to stow $pkg — continuing with the remaining packages" >&2
failed_pkgs+=("$pkg")
fi
done
if [ "${#failed_pkgs[@]}" -gt 0 ]; then
echo "" >&2
echo "WARNING: these packages did not stow: ${failed_pkgs[*]}" >&2
echo "Fix the conflict shown above (usually a non-symlink file at the target) and re-run ./install." >&2
fi
# Claude Code's settings.json is COPY-managed, not stowed. Claude atomically
# rewrites it (temp + rename) on /model, /config, plugin toggles, etc., which
# replaces a stow symlink with a regular file and detaches it — and symlinked
# settings also trip permission bugs (anthropics/claude-code#3575, #40857). The
# claude package's .stow-local-ignore keeps stow from linking settings.json;
# statusline-pace.py IS stowed normally by the loop above. Repo is the source of
# truth here; `claude-settings-sync` pushes local changes back the other way.
claude_src="claude/.claude/settings.json"
claude_dst="$TARGET/.claude/settings.json"
if [ -f "$claude_src" ]; then
mkdir -p "$TARGET/.claude"
if [ -e "$claude_dst" ] && ! cmp -s "$claude_src" "$claude_dst"; then
mv "$claude_dst" "$claude_dst.bak.$ts"
echo "backed up existing $claude_dst -> $claude_dst.bak.$ts"
fi
cp "$claude_src" "$claude_dst"
echo "copied: $claude_src -> $claude_dst"
fi
# Obsidian: each vault's .obsidian config + pinned community-plugin binaries are
# COPY-managed (not stowed). The vaults live in the iCloud container, where
# symlinks misbehave, and Obsidian rewrites its own JSON at runtime — same reason
# as claude/settings.json. rsync (no --delete) leaves the generator-owned
# types.json and workspace files untouched. One subdir per vault under obsidian/.
# See obsidian/README.md.
obsidian_container="$HOME/Library/Mobile Documents/iCloud~md~obsidian/Documents"
obsidian_copy() { # $1 = package subdir (obsidian/<name>), $2 = target vault path
local src="$DOTFILES_DIR/obsidian/$1/vault-obsidian"
[ -d "$src" ] || return 0
if [ ! -d "$2" ]; then
echo "skip: $1 vault not found at $2 (create it, then re-run ./install)"
return 0
fi
mkdir -p "$2/.obsidian"
# Config + plugin binaries are reasserted every run. Plugin runtime settings
# (plugins/*/data.json — e.g. the spaced-repetition algorithm) are SEEDED ONLY
# IF ABSENT, so a fresh machine gets our defaults but your in-app tweaks are
# never clobbered by a later ./install.
rsync -a --exclude='data.json' "$src/" "$2/.obsidian/"
local rel
while IFS= read -r rel; do
rel="${rel#./}"
if [ ! -f "$2/.obsidian/$rel" ]; then
mkdir -p "$2/.obsidian/$(dirname "$rel")"
cp "$src/$rel" "$2/.obsidian/$rel"
echo "seeded (new): $1/$rel"
fi
done < <(cd "$src" && find . -name data.json -path './plugins/*')
echo "copied: obsidian/$1 -> $2/.obsidian/ (plugin settings preserved)"
}
obsidian_copy habits "${OBSIDIAN_HABITS_VAULT:-$obsidian_container/Habits}"
obsidian_copy lingo "${OBSIDIAN_LINGO_VAULT:-$obsidian_container/Lingo}"
# macOS system defaults (key repeat). Not stowed — cfprefsd owns these plists and
# would detach a symlink on its first write. See macos/defaults.sh.
if [ -x macos/defaults.sh ]; then
./macos/defaults.sh
fi
echo "Done. Dotfiles stowed into $TARGET."