-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall
More file actions
executable file
·430 lines (384 loc) · 13.2 KB
/
install
File metadata and controls
executable file
·430 lines (384 loc) · 13.2 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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
#!/bin/bash
if ! command -v rsync >/dev/null; then
echo "Error: This script requires rsync."
exit 1
fi
# --- Argument parsing ---
dev_mode=0
links_mode=0
no_backup=0
restore_backup=""
while [[ $# -gt 0 ]]; do
case "$1" in
--dev)
dev_mode=1
shift
;;
--links)
links_mode=1
shift
;;
--no-backup)
no_backup=1
shift
;;
--verbose)
# Kept for backwards compatibility; output is always verbose
shift
;;
--restore-backup)
if [[ -z "$2" ]]; then
echo "Error: --restore-backup requires a backup directory path"
echo "Usage: $0 --restore-backup <backup_dir>"
echo "Example: $0 --restore-backup dotfiles_backups/backup.1"
exit 1
fi
restore_backup="$2"
shift 2
;;
--help | -h)
cat <<EOF
Usage: $0 [OPTIONS]
Install dotfiles to the home directory.
Options:
(default) Copy files from repo — no symlinks to the repo remain.
Re-run ./install after repo changes to update.
--links Symlink individual files/dirs to the repo instead of copying.
Changes in the repo take effect immediately.
--dev Directory-level symlinks to the repo (e.g. ~/.config/nvim -> repo/nvim).
Easiest when editing files frequently. Skips backups.
--no-backup Skip creating a backup of existing dotfiles before installing.
--restore-backup <dir> Restore dotfiles from a previous backup directory.
Example: $0 --restore-backup dotfiles_backups/backup.1
--help, -h Show this help message.
EOF
exit 0
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 [--dev] [--links] [--no-backup] [--verbose] [--restore-backup <dir>]"
echo "Run '$0 --help' for details."
exit 1
;;
esac
done
# --- Helper functions ---
lns() {
local unsafe=false
local verbose=false
local args=()
while [[ $# -gt 0 ]]; do
case "$1" in
--unsafe)
unsafe=true
shift
;;
--verbose)
verbose=true
shift
;;
*)
args+=("$1")
shift
;;
esac
done
if [[ ${#args[@]} -lt 2 ]]; then
echo "Error: lns requires at least 2 arguments (target and link_name)" >&2
return 1
fi
local link_name="${args[@]: -1}"
if [[ -e "$link_name" || -L "$link_name" ]]; then
if [[ -d "$link_name" && ! -L "$link_name" ]]; then
if [[ "$unsafe" == false ]]; then
echo "Error: '$link_name' exists as a directory and cannot be removed (use --unsafe to override)" >&2
exit 1
else
[[ "$verbose" == true ]] && echo "rm -rf '$link_name'" >&2
if ! rm -rf "$link_name" 2>/dev/null; then
echo "Error: Failed to remove directory '$link_name'" >&2
exit 1
fi
fi
else
[[ "$verbose" == true ]] && echo "rm -f '$link_name'" >&2
if ! rm -f "$link_name" 2>/dev/null; then
echo "Error: Failed to remove '$link_name'" >&2
exit 1
fi
fi
fi
[[ "$verbose" == true ]] && echo "ln -s ${args[*]}" >&2
ln -s "${args[@]}"
}
mkdirn() {
local base_dir="$1"
local target_dir="$base_dir"
local counter=1
if [[ ! -e "$target_dir" ]]; then
mkdir -p "$target_dir"
echo "$target_dir"
return 0
fi
while [[ -e "$target_dir" ]]; do
target_dir="${base_dir}.${counter}"
((counter++))
done
mkdir -p "$target_dir"
echo "$target_dir"
}
# Install a file or directory from the repo.
# --links mode: symlink to repo. Default: copy (no repo reference remains).
# Local convenience symlinks (.bashrc -> .config/bash/bashrc) use lns directly.
inst() {
local src="$1"
local dest="$2"
if [[ "$links_mode" == "1" ]]; then
lns --verbose "$src" "$dest"
else
if [[ -d "$src" ]]; then
rm -rf "$dest"
mkdir -p "$dest"
rsync -a "$src/" "$dest/"
echo " rsync: $src/ -> $dest/"
else
rm -f "$dest"
mkdir -p "$(dirname "$dest")"
cp -p "$src" "$dest"
echo " cp: $src -> $dest"
fi
fi
}
# --- Restore backup ---
if [[ -n "$restore_backup" ]]; then
if [[ ! -d "$restore_backup" ]]; then
echo "Error: Backup directory not found: $restore_backup"
exit 1
fi
echo "Restoring dotfiles from backup: $restore_backup"
echo ""
echo "Removing current dotfiles..."
# Handle .config/bash specially to preserve user layer dirs
if [[ -L ~/.config/bash ]]; then
rm -f ~/.config/bash
elif [[ -d ~/.config/bash ]]; then
rm -fr ~/.config/bash/global
rm -f ~/.config/bash/functions.sh ~/.config/bash/README.md ~/.config/bash/bashrc
fi
for item in ~/.bashrc ~/.profile ~/.vimrc ~/.tmux.conf ~/.editorconfig ~/.tmux ~/.vim \
~/.config/nvim ~/.config/tmux ~/.config/editorconfig ~/.config/vim; do
if [[ -e "$item" || -L "$item" ]]; then
echo " Removing: $item"
if [[ -L "$item" ]]; then rm -f "$item"; else rm -rf "$item"; fi
fi
done
echo ""
echo "Restoring files from backup..."
if ls -A "$restore_backup" 2>/dev/null | grep -q .; then
cd "$restore_backup"
cp -rPp . "$HOME/"
echo ""
echo "Backup restored successfully!"
echo ""
echo "Restored from: $restore_backup"
else
echo "Error: Backup directory is empty"
exit 1
fi
exit 0
fi
# --- Main ---
repo_dir=$(git rev-parse --show-toplevel)
echo "Dotfiles repo base directory: $repo_dir"
echo "Changing directory to ~"
cd ~
# Backup existing dotfiles (skip in dev mode or with --no-backup)
if [[ "$dev_mode" != "1" && "$no_backup" != "1" ]]; then
backup_dir=$(mkdirn "dotfiles_backups/backup")
echo "Making backups in: $backup_dir"
for x in \
.bashrc \
.profile \
.vimrc \
.vim \
.tmux \
.tmux.conf \
.editorconfig \
.config/vim \
.config/nvim \
.config/bash/global \
.config/bash/functions.sh \
.config/bash/README.md \
.config/bash/bashrc \
.config/tmux \
.config/editorconfig; do
if [[ ! -e "$x" && ! -L "$x" ]]; then
continue
fi
if [[ -L "$x" ]]; then
link_target=$(readlink "$x")
if [[ "$link_target" == "$repo_dir"* ]] ||
[[ -e "$x" && "$(realpath "$x" 2>/dev/null)" == "$repo_dir"* ]]; then
echo " Skipping (points to repo): $x"
continue
fi
fi
echo " Backing up: $x"
mkdir -p "$backup_dir/$(dirname "$x")"
cp -rPp "$x" "$backup_dir/$x"
done
echo ""
fi
mkdir -p .config
if [[ "$dev_mode" == "1" ]]; then
for config_dir in \
nvim \
vim \
tmux \
editorconfig; do
rm -fr .config/$config_dir
lns --verbose $repo_dir/$config_dir .config/$config_dir
done
# Bash: only symlink repo-managed files, preserve user layer dirs.
# If .config/bash is an old directory-level symlink, replace it with
# a real directory (copying user layer dirs out first).
if [[ -L .config/bash ]]; then
# Save user layer dirs before removing the symlink
for layer in corp site project user; do
if [[ -d .config/bash/$layer ]]; then
cp -rPp .config/bash/$layer /tmp/dotfiles_bash_${layer}_$$
fi
done
rm -f .config/bash
mkdir -p .config/bash
for layer in corp site project user; do
if [[ -d /tmp/dotfiles_bash_${layer}_$$ ]]; then
mv /tmp/dotfiles_bash_${layer}_$$ .config/bash/$layer
fi
done
else
mkdir -p .config/bash
# Remove only repo-managed items
if [[ -L .config/bash/global ]]; then rm -f .config/bash/global; else rm -fr .config/bash/global; fi
rm -f .config/bash/functions.sh .config/bash/README.md .config/bash/bashrc
fi
lns --verbose $repo_dir/bash/global .config/bash/global
lns --verbose $repo_dir/bash/functions.sh .config/bash/functions.sh
lns --verbose $repo_dir/bash/README.md .config/bash/README.md
lns --verbose $repo_dir/bash/bashrc .config/bash/bashrc
lns --verbose .config/bash/bashrc .profile
lns --verbose .config/bash/bashrc .bashrc
lns --verbose .config/vim/vimrc .vimrc
lns --verbose .config/vim/vim .vim
lns --verbose .config/tmux/tmux.conf .tmux.conf
lns --verbose .config/tmux/tmux .tmux
lns --verbose .config/editorconfig/editorconfig .editorconfig
else
### BASH ###
rm -f .bashrc .profile
# If .config/bash is an old directory-level symlink, replace it with
# a real directory (copying user layer dirs out first).
if [[ -L .config/bash ]]; then
for layer in corp site project user; do
if [[ -d .config/bash/$layer ]]; then
cp -rPp .config/bash/$layer /tmp/dotfiles_bash_${layer}_$$
fi
done
rm -f .config/bash
mkdir -p .config/bash
for layer in corp site project user; do
if [[ -d /tmp/dotfiles_bash_${layer}_$$ ]]; then
mv /tmp/dotfiles_bash_${layer}_$$ .config/bash/$layer
fi
done
else
mkdir -p .config/bash
if [[ -L .config/bash/global ]]; then rm -f .config/bash/global; else rm -fr .config/bash/global; fi
rm -f .config/bash/functions.sh .config/bash/README.md .config/bash/bashrc
fi
inst $repo_dir/bash/global .config/bash/global
inst $repo_dir/bash/functions.sh .config/bash/functions.sh
inst $repo_dir/bash/README.md .config/bash/README.md
inst $repo_dir/bash/bashrc .config/bash/bashrc
lns --verbose .config/bash/bashrc .bashrc
lns --verbose .config/bash/bashrc .profile
### NEOVIM ###
rm -fr .config/nvim
mkdir -p .config/nvim
mkdir -p .config/nvim/after/lsp
mkdir -p .config/nvim/after/ftplugin
mkdir -p .config/nvim/lsp
mkdir -p .config/nvim/lua/custom/plugins
inst $repo_dir/nvim/lua/custom/plugins/init.lua \
.config/nvim/lua/custom/plugins/init.lua
inst $repo_dir/nvim/lua/kickstart .config/nvim/lua/kickstart
inst $repo_dir/nvim/doc .config/nvim/doc
inst $repo_dir/nvim/lazy-lock.json .config/nvim/lazy-lock.json
inst $repo_dir/nvim/README.md .config/nvim/README.md
inst $repo_dir/nvim/LICENSE.md .config/nvim/LICENSE.md
inst $repo_dir/nvim/init.lua .config/nvim/init.lua
for x in $repo_dir/nvim/lsp/*; do
inst $x .config/nvim/lsp/$(basename $x)
done
if compgen -G "$repo_dir/nvim/lua/kickstart/plugins/*" > /dev/null; then
for x in $repo_dir/nvim/lua/kickstart/plugins/*; do
inst $x .config/nvim/lua/kickstart/plugins/$(basename $x)
done
fi
### VIM ###
rm -f .vimrc
rm -fr .vim
rm -fr .config/vim
mkdir -p .config/vim/vim/pack/vendor/start
mkdir -p .config/vim/vim/pack/vendor/opt
for start_or_opt in start opt; do
for plugin_dir in $repo_dir/vim/vim/pack/vendor/$start_or_opt/*; do
inst $plugin_dir \
.config/vim/vim/pack/vendor/$start_or_opt/$(basename $plugin_dir)
done
done
inst $repo_dir/vim/vimrc .config/vim/vimrc
lns --verbose .config/vim/vimrc .vimrc
### TMUX ###
# All tmux plugins are vendored in the repo
rm -f .tmux.conf
rm -fr .tmux
rm -fr .config/tmux
mkdir -p .config/tmux/tmux/plugins
for plugin_dir in $repo_dir/tmux/vendor/plugins/*; do
inst $plugin_dir \
.config/tmux/tmux/plugins/$(basename $plugin_dir)
done
inst $repo_dir/tmux/tmux.conf .config/tmux/tmux.conf
inst $repo_dir/tmux/tmux-3col-layout.sh .config/tmux/tmux-3col-layout.sh
lns --verbose .config/tmux/tmux.conf .tmux.conf
lns --verbose .config/tmux/tmux .tmux
rm -fr .editorconfig
rm -fr .config/editorconfig
mkdir -p .config/editorconfig
inst $repo_dir/editorconfig/editorconfig .config/editorconfig/editorconfig
lns --verbose .config/editorconfig/editorconfig .editorconfig
fi
# Install git hooks
echo "Installing git hooks..."
if [[ -d "$repo_dir/hooks" ]]; then
for hook in "$repo_dir/hooks"/*; do
if [[ -f "$hook" && ! "$hook" =~ README ]]; then
hook_name=$(basename "$hook")
cp "$hook" "$repo_dir/.git/hooks/$hook_name"
chmod +x "$repo_dir/.git/hooks/$hook_name"
echo " Installed: $hook_name"
fi
done
else
echo " No hooks directory found, skipping"
fi
# Allow for extra layers of installation scripting. Use source so that variables from
# this execution environment are available.
for layer in corp site project user; do
install_script="$HOME/.config/bash/$layer/install.sh"
if [[ -r "$install_script" ]]; then
echo "Sourcing '$install_script' ..."
source $install_script
fi
done