-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbootstrap.sh
More file actions
280 lines (241 loc) · 7.88 KB
/
Copy pathbootstrap.sh
File metadata and controls
280 lines (241 loc) · 7.88 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
#!/usr/bin/env bash
set -euo pipefail
# --- Config (edit here if you add more stow packages) ---
# Add "ctags" so we can manage ~/.ctags.d via GNU Stow
DEFAULT_STOW_PKGS=("zsh" "nvim" "tmux" "ctags")
# --- Globals ---
SCRIPT_PATH="${BASH_SOURCE[0]}"
SCRIPT_DIR="$(cd -- "${SCRIPT_PATH%/*}" && pwd)"
REPO_ROOT="$SCRIPT_DIR"
TARGET_DIR="$HOME"
ADOPT=false
DRY_RUN=false
UNSTOW=false
NO_INSTALL=false
STOW_PKGS=("${DEFAULT_STOW_PKGS[@]}")
UNSTOW_PKGS=()
# --- Helpers ---
log() { printf '[bootstrap] %s\n' "$*"; }
err() { printf '[bootstrap][error] %s\n' "$*" >&2; }
die() { err "$*"; exit 1; }
usage() {
cat <<'EOF'
Usage: ./bootstrap.sh [options]
Options:
-p "pkg1 pkg2" Stow packages to apply (default: "zsh nvim tmux ctags")
-a Use 'stow --adopt' (take over existing files into repo)
-n Dry-run (show what would happen)
-u "pkg1 pkg2" Unstow only the specified packages (implies -U)
-U Unstow (remove symlinks) instead of stowing
--no-install Skip package installation (git/stow/neovim/tmux/zsh/curl/ctags)
-h, --help Show this help
Examples:
./bootstrap.sh
./bootstrap.sh -p "zsh tmux" -a
./bootstrap.sh -n
./bootstrap.sh -U
./bootstrap.sh -u "tmux"
EOF
}
have() { command -v "$1" >/dev/null 2>&1; }
# Print the first supported package manager found in PATH; return 1 if none.
detect_pm() {
local pm
for pm in dnf apt-get pacman brew; do
have "$pm" && { printf '%s\n' "$pm"; return 0; }
done
return 1
}
# pm_install <pm> <pkg...> — install packages via the given package manager.
pm_install() {
local pm="$1"; shift
case "$pm" in
dnf) sudo dnf install -y "$@";;
apt-get) sudo apt-get update -y
sudo apt-get install -y "$@";;
pacman) sudo pacman -Sy --noconfirm "$@";;
brew) brew install "$@";;
*) return 1;;
esac
}
# pm_install_ctags <pm> — install Universal Ctags with PM-specific fallbacks.
pm_install_ctags() {
case "$1" in
dnf) sudo dnf install -y universal-ctags;;
apt-get) sudo apt-get update -y || true
sudo apt-get install -y universal-ctags \
|| sudo apt-get install -y exuberant-ctags;;
pacman) sudo pacman -Sy --noconfirm universal-ctags \
|| sudo pacman -Sy --noconfirm ctags;;
brew) brew install universal-ctags;;
*) return 1;;
esac
}
detect_pm_and_install_base() {
$NO_INSTALL && { log "Skipping base package install (--no-install)."; return; }
local need=()
for bin in git stow curl; do
have "$bin" || need+=("$bin")
done
# Optional QoL tools (ignore if already installed)
for bin in neovim tmux zsh; do
have "$bin" || need+=("$bin")
done
((${#need[@]}==0)) && { log "All required base tools already installed."; return; }
local pm
if ! pm="$(detect_pm)"; then
die "No supported package manager found (supported: dnf, apt-get, pacman, brew). Install manually: ${need[*]}"
fi
log "Installing with $pm: ${need[*]}"
pm_install "$pm" "${need[@]}"
}
# Install ctags CLI if "ctags" package is selected
install_ctags_cli_if_requested() {
$NO_INSTALL && { log "Skipping ctags install (--no-install)."; return; }
case " ${STOW_PKGS[*]} " in
*" ctags "*) ;;
*) return 0;;
esac
# If any viable ctags is present, we're good.
if have ctags; then
log "ctags CLI already available: $(ctags --version 2>/dev/null | head -n1 || echo present)"
return 0
fi
log "ctags CLI not found. Attempting to install Universal Ctags…"
local pm
if pm="$(detect_pm)" && pm_install_ctags "$pm"; then
log "Installed ctags via $pm."
return 0
fi
err "Failed to install ctags automatically."
err "Please install Universal Ctags manually or ensure 'ctags' is in PATH."
return 1
}
# back up a path if it exists and is NOT a symlink into this repo
backup_if_conflict() {
local path="$1"
if [[ -e "$path" || -L "$path" ]]; then
if [[ -L "$path" ]]; then
local target
target="$(readlink -f -- "$path" || true)"
[[ "${target:-}" == "$REPO_ROOT"* ]] && return 0
fi
local ts bak
ts="$(date +%Y%m%d-%H%M%S)"
bak="${path}.bak.${ts}"
log "Backing up existing: $path -> $bak"
$DRY_RUN || mv -- "$path" "$bak"
fi
}
# scan stow package to pre-backup potential conflicts
prebackup_for_pkg() {
local pkg="$1" rel dst base
base="$REPO_ROOT/$pkg"
[[ -d "$base" ]] || die "Stow package not found: $pkg ($base)"
while IFS= read -r -d '' rel; do
rel="${rel#./}"
dst="$TARGET_DIR/$rel"
backup_if_conflict "$dst"
done < <(cd "$base" && find . -type f -print0)
}
validate_selected_packages() {
local p
for p in "${STOW_PKGS[@]}"; do
[[ -d "$REPO_ROOT/$p" ]] || die "Selected package '$p' not found in repo: $REPO_ROOT/$p"
done
}
run_stow() {
local stow_args=(-v -t "$TARGET_DIR")
$DRY_RUN && stow_args=(-n "${stow_args[@]}")
if $UNSTOW; then
log "Unstowing: ${STOW_PKGS[*]}"
(set -x; stow -D "${stow_args[@]}" "${STOW_PKGS[@]}")
else
if ! $ADOPT; then
for p in "${STOW_PKGS[@]}"; do
prebackup_for_pkg "$p"
done
fi
$ADOPT && stow_args=(--adopt "${stow_args[@]}")
log "Stowing: ${STOW_PKGS[*]} (adopt=${ADOPT}, dry-run=${DRY_RUN})"
(set -x; stow "${stow_args[@]}" "${STOW_PKGS[@]}")
fi
}
install_zinit() {
local zinit_home="${XDG_DATA_HOME:-$HOME/.local/share}/zinit/zinit.git"
if [[ -d "$zinit_home" ]]; then
log "Zinit already installed at $zinit_home"
else
log "Installing Zinit (zsh plugin manager)..."
bash -c "$(curl --fail --show-error --silent --location https://raw.githubusercontent.com/zdharma-continuum/zinit/HEAD/scripts/install.sh)"
fi
}
# Install TPM (tmux plugin manager) and the plugins declared in tmux.conf.
# Plugins live under the data dir (matches TMUX_PLUGIN_MANAGER_PATH in tmux.conf)
# so they stay out of the stow-managed ~/.config/tmux directory.
install_tpm() {
local tpm_home="${XDG_DATA_HOME:-$HOME/.local/share}/tmux/plugins/tpm"
if [[ -d "$tpm_home" ]]; then
log "TPM already installed at $tpm_home"
else
log "Installing TPM (tmux plugin manager)..."
git clone --depth 1 https://github.com/tmux-plugins/tpm "$tpm_home"
fi
# Install/refresh the declared plugins non-interactively.
if have tmux; then
log "Installing tmux plugins via TPM..."
export TMUX_PLUGIN_MANAGER_PATH="${XDG_DATA_HOME:-$HOME/.local/share}/tmux/plugins/"
"$tpm_home/bin/install_plugins" \
|| err "TPM plugin install failed; run '<prefix> + I' inside tmux to retry."
else
log "tmux not found; skipping plugin install (run '<prefix> + I' after installing tmux)."
fi
}
# --- Parse args ---
while (( "$#" )); do
case "${1:-}" in
-p) shift; IFS=' ' read -r -a STOW_PKGS <<< "${1:-}";;
-a) ADOPT=true;;
-n) DRY_RUN=true;;
-u)
shift
[[ "${1:-}" ]] || die "Missing package list for -u."
IFS=' ' read -r -a _tmp_unstow <<< "${1:-}"
UNSTOW=true
UNSTOW_PKGS+=("${_tmp_unstow[@]}")
;;
-U) UNSTOW=true;;
--no-install) NO_INSTALL=true;;
-h|--help) usage; exit 0;;
*) err "Unknown option: $1"; usage; exit 2;;
esac
shift || true
done
# If specific packages were requested for unstow, operate on those
if $UNSTOW && ((${#UNSTOW_PKGS[@]} > 0)); then
STOW_PKGS=("${UNSTOW_PKGS[@]}")
fi
# --- Main ---
log "Repo: $REPO_ROOT"
log "Target: $TARGET_DIR"
log "Packages: ${STOW_PKGS[*]}"
# Ensure selected packages exist before doing anything
validate_selected_packages
# Install base toolchain (git/stow/curl/neovim/tmux/zsh)
$UNSTOW || detect_pm_and_install_base
# If user chose the "ctags" package, ensure ctags CLI exists
$UNSTOW || install_ctags_cli_if_requested || true
# Perform stow/unstow
run_stow
# Post steps
if [[ " ${STOW_PKGS[*]} " == *" zsh "* && $UNSTOW == false ]]; then
install_zinit
fi
if [[ " ${STOW_PKGS[*]} " == *" tmux "* && $UNSTOW == false ]]; then
if $NO_INSTALL; then
log "Skipping TPM install (--no-install)."
else
install_tpm
fi
fi
log "Done."