-
-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
Β·279 lines (241 loc) Β· 10.1 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
Β·279 lines (241 loc) Β· 10.1 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
#!/usr/bin/env bash
#
# install.sh β unified dotfiles installer
#
# Usage:
# ./install.sh <subcommand> [flags]
#
# Subcommands:
# bootstrap Full machine provision (Xcode CLT, Homebrew, packages,
# Stow, duti, macOS prefs, git config, fish as default)
# link Symlink dotfiles only (GNU Stow, idempotent)
# help Show this help (default when no subcommand given)
#
# Flags (bootstrap):
# -y, --yes Skip confirmation prompts (non-interactive)
# --no-packages Skip Homebrew bundle
# --no-macos Skip macOS system preferences
# -h, --help Show help and exit
#
# Remote one-liner:
# bash -c "$(curl -fsSL https://raw.githubusercontent.com/edheltzel/dotfiles/master/install.sh)" -- bootstrap
set -euo pipefail
# macOS only β fail fast on other platforms
if [ "$(uname -s)" != "Darwin" ]; then
echo "Error: this installer targets macOS (Darwin) only. Detected: $(uname -s)" >&2
exit 1
fi
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Config
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
readonly SOURCE_REPO="https://github.com/edheltzel/dotfiles"
readonly TARBALL_URL="${SOURCE_REPO}/tarball/master"
readonly DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}"
readonly PROJECTS_DIR="${PROJECTS_DIR:-$HOME/Developer}"
# Keep in sync with justfile:stow_packages
readonly STOW_PACKAGES=(dots git fish zsh config neovim local)
# Flag defaults
SKIP_CONFIRM=0
SKIP_PACKAGES=0
SKIP_MACOS=0
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Self-detection + remote bootstrap
# Runs BEFORE sourcing functions.sh β must be self-contained.
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || echo "")"
if [ -z "$SCRIPT_DIR" ] || [ ! -f "$SCRIPT_DIR/justfile" ] || [ ! -f "$SCRIPT_DIR/packages/packages.sh" ]; then
# Remote mode: script is being piped from curl or running from a temp dir.
echo "Running install.sh in remote mode β will clone repo to $DOTFILES_DIR"
if [ -d "$DOTFILES_DIR/.git" ]; then
echo "Existing repo at $DOTFILES_DIR β skipping clone"
elif [ -d "$DOTFILES_DIR" ] && [ -n "$(ls -A "$DOTFILES_DIR" 2>/dev/null)" ]; then
echo "Error: $DOTFILES_DIR exists and is not empty, but is not a git repo." >&2
echo "Remove or move it, then re-run this installer." >&2
exit 1
else
mkdir -p "$DOTFILES_DIR"
if command -v git >/dev/null 2>&1; then
git clone "$SOURCE_REPO" "$DOTFILES_DIR"
elif command -v curl >/dev/null 2>&1; then
curl -#fL "$TARBALL_URL" | tar -xz -C "$DOTFILES_DIR" --strip-components=1
elif command -v wget >/dev/null 2>&1; then
wget --no-check-certificate -qO- "$TARBALL_URL" | tar -xz -C "$DOTFILES_DIR" --strip-components=1
else
echo "Error: git, curl, or wget is required to fetch the dotfiles." >&2
exit 1
fi
fi
# Verify the clone/extract produced a usable script before re-exec
if [ ! -f "$DOTFILES_DIR/install.sh" ]; then
echo "Error: $DOTFILES_DIR/install.sh missing after fetch. Partial or corrupt download?" >&2
exit 1
fi
echo "Re-executing install.sh from $DOTFILES_DIR"
exec "$DOTFILES_DIR/install.sh" "$@"
fi
cd "$SCRIPT_DIR"
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Load shared helpers (error/success/info/warning, install_xcode,
# install_homebrew, sudo_keep_alive, symlink, etc.)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# shellcheck source=scripts/functions.sh
. "$SCRIPT_DIR/scripts/functions.sh"
DOTFILES_FUNCTIONS_LOADED=1
# One-line fatal for CLI usage problems; runtime failures use error() banner + exit.
die() {
echo "Error: $1" >&2
exit "${2:-1}"
}
# Stow a list of packages. With mode=strict, stow conflicts abort (set -e);
# with mode=lenient, conflicts are reported and the loop continues.
_run_stow() {
local mode="$1"
shift
info "Linking dotfiles with GNU Stow..."
for pkg in "$@"; do
if [ ! -d "$SCRIPT_DIR/$pkg" ]; then
substep_error "Stow package '$pkg' not found β skipping"
continue
fi
substep_info "Stowing $pkg"
if [ "$mode" = "lenient" ]; then
stow --restow "$pkg" || substep_error "Stow conflict for '$pkg' β resolve manually then re-run './install.sh link'"
else
stow --restow "$pkg"
fi
done
}
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Subcommand implementations
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
cmd_help() {
cat <<'EOF'
Usage: ./install.sh <subcommand> [flags]
Subcommands:
bootstrap Full machine provision:
- Xcode Command Line Tools
- Homebrew + Brewfile bundle
- Language package managers (FNM, pipx, rbenv, rustup, Bun)
- GNU Stow symlinks for all dotfiles packages
- macOS system preferences
- File type associations (duti)
- Machine-specific git config
- Fish as default shell (optional)
link Stow-symlink dotfiles only β idempotent, safe to re-run.
Does NOT install any software. Use this when you already
have a bootstrapped machine and just want to (re)link files.
help Show this help (default when no subcommand is given).
Flags (apply to bootstrap):
-y, --yes Skip confirmation prompts (non-interactive mode)
--no-packages Skip Homebrew bundle and language package managers
--no-macos Skip macOS system preferences script
-h, --help Show this help and exit
Notes:
- Re-running bootstrap will run `brew upgrade` on all installed packages.
- `link` is always safe to re-run; it uses `stow --restow`.
Examples:
./install.sh bootstrap # full provision, interactive
./install.sh bootstrap --yes # full provision, no prompts
./install.sh bootstrap --no-macos # skip macOS sysprefs
./install.sh link # (re)symlink dotfiles only
EOF
}
cmd_link() {
check_required_commands
_run_stow lenient "${STOW_PACKAGES[@]}"
success "Dotfiles linked."
}
cmd_bootstrap() {
# stow is installed by Homebrew later, so don't require it up front.
for cmd in curl git; do
command -v "$cmd" >/dev/null 2>&1 || die "$cmd is required to bootstrap but is not installed."
done
if [ "$SKIP_CONFIRM" -eq 0 ]; then
warning "This will install & configure dotfiles on your system. It may overwrite existing files."
read -r -p "Are you sure you want to proceed? (y/N) " confirm
[[ "$confirm" == [yY] ]] || {
error "Installation aborted."
exit 0
}
fi
# -v is portable back to older macOS (--validate is Sudo 1.9+)
if sudo -v; then
sudo_keep_alive &
SUDO_PID=$!
trap 'kill "$SUDO_PID" 2>/dev/null || true' EXIT
substep_info "Sudo validated. Continuing."
else
die "Incorrect sudo password. Exiting."
fi
install_xcode
install_homebrew
git submodule update --init --recursive
mkdir -p "$PROJECTS_DIR"
if [ "$SKIP_PACKAGES" -eq 0 ]; then
info "Installing language/package managers..."
# packages.sh uses relative paths internally; run it from its own dir
(cd "$SCRIPT_DIR/packages" && . ./packages.sh)
else
info "Skipping packages (--no-packages)"
fi
_run_stow strict "${STOW_PACKAGES[@]}"
(cd "$SCRIPT_DIR/duti" && . ./duti.sh)
if [ "$SKIP_MACOS" -eq 0 ]; then
(cd "$SCRIPT_DIR/macos" && . ./macos.sh)
else
info "Skipping macOS sysprefs (--no-macos)"
fi
(cd "$SCRIPT_DIR/git" && . ./git.sh)
# Fish as default shell β irreversible per-user preference, own prompt
if command -v fish >/dev/null 2>&1; then
fish_path="$(command -v fish)"
if ! grep -q "$fish_path" /etc/shells; then
substep_info "Adding Fish to /etc/shells..."
echo "$fish_path" | sudo tee -a /etc/shells >/dev/null
fi
if [ "$SKIP_CONFIRM" -eq 0 ]; then
read -r -p "Set Fish as your default shell? (y/N) " set_fish
[[ "$set_fish" == [yY] ]] && chsh -s "$fish_path"
else
info "Non-interactive mode: Fish NOT set as default shell. Run manually: chsh -s $fish_path"
fi
fi
success "Dotfile setup complete."
}
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Argument parsing
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if [ "$#" -eq 0 ]; then
cmd_help
exit 0
fi
SUBCOMMAND=""
case "${1:-}" in
bootstrap | link | help)
SUBCOMMAND="$1"
shift
;;
-h | --help)
cmd_help
exit 0
;;
*) die "Unknown subcommand: '$1'. Run './install.sh help' for usage." ;;
esac
while [ "$#" -gt 0 ]; do
case "$1" in
-y | --yes) SKIP_CONFIRM=1 ;;
--no-packages) SKIP_PACKAGES=1 ;;
--no-macos) SKIP_MACOS=1 ;;
-h | --help)
cmd_help
exit 0
;;
*) die "Unknown flag: '$1'. Run './install.sh help' for usage." ;;
esac
shift
done
case "$SUBCOMMAND" in
bootstrap) cmd_bootstrap ;;
link) cmd_link ;;
help) cmd_help ;;
esac