-
-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·271 lines (233 loc) · 8.11 KB
/
install.sh
File metadata and controls
executable file
·271 lines (233 loc) · 8.11 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
#!/usr/bin/env bash
# Copyright (c) 2015-2026 Dotfiles. All rights reserved.
# Universal Dotfiles Installer (Zero-Dependency)
# Usage: bash -c "$(curl -fsSL https://raw.githubusercontent.com/sebastienrousseau/dotfiles/master/install.sh)"
# (or ./install.sh locally)
set -euo pipefail
# ANSI Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
# Paths
SOURCE_DIR="${SOURCE_DIR:-$HOME/.dotfiles}"
LEGACY_SOURCE_DIR="$HOME/.local/share/chezmoi"
CHEZMOI_CONFIG_DIR="$HOME/.config/chezmoi"
CHEZMOI_CONFIG_FILE="$CHEZMOI_CONFIG_DIR/chezmoi.toml"
# Utility Functions
step() {
if [[ "${DOTFILES_SILENT:-0}" != "1" ]]; then
printf '%b\n' "${BOLD}${BLUE}==>${NC} ${BOLD}$1${NC}"
fi
}
error() {
printf '%b\n' "${RED}Error:${NC} $1" >&2
exit 1
}
success() {
if [[ "${DOTFILES_SILENT:-0}" != "1" ]]; then
printf '%b\n' "${GREEN}Success!${NC} $1"
fi
}
# Cross-platform sed in-place (BSD vs GNU)
sed_in_place() {
if sed --version >/dev/null 2>&1; then
sed -i "$@" # GNU
else
sed -i '' "$@" # BSD (macOS)
fi
}
show_help() {
cat <<EOF
Usage: install.sh [version] [options]
Arguments:
version The version (tag or branch) to install (default: v0.2.495)
Options:
--help Show this help message
--force Non-interactive mode (sets DOTFILES_NONINTERACTIVE=1)
--silent Quiet mode (sets DOTFILES_SILENT=1)
EOF
}
main() {
local version="${1:-v0.2.495}"
local _cleanup_files=()
trap 'rm -f "${_cleanup_files[@]}" 2>/dev/null' EXIT
case "$version" in
--help)
show_help
exit 0
;;
--silent)
export DOTFILES_SILENT=1
version="v0.2.495"
;;
esac
# Shift arguments to handle mixed flags/version
for arg in "$@"; do
case "$arg" in
--silent) export DOTFILES_SILENT=1 ;;
--force) export DOTFILES_NONINTERACTIVE=1 ;;
esac
done
# 2. Check Prerequisites & Bootstrap Package Managers
step "Checking Prerequisites..."
# Detect Operating System
OS="$(uname -s)"
case "$OS" in
Darwin) target_os="macos" ;;
Linux)
if grep -q Microsoft /proc/version 2>/dev/null; then
target_os="wsl2"
elif [ -f /etc/debian_version ]; then
target_os="debian"
elif [ -f /etc/fedora-release ]; then
target_os="fedora"
elif [ -f /etc/arch-release ]; then
target_os="arch"
else
target_os="linux"
fi
;;
*) target_os="unknown" ;;
esac
# Bootstrap gum for a better UI if available or install it
bootstrap_gum() {
if command -v gum >/dev/null 2>&1; then return 0; fi
if [[ "${DOTFILES_SILENT:-0}" == "1" ]]; then return 0; fi
echo " Bootstrapping UI components (gum)..."
if [[ "$OS" == "Darwin" ]] && command -v brew >/dev/null; then
brew install gum >/dev/null 2>&1
elif [[ "$target_os" == "debian" || "$target_os" == "wsl2" ]]; then
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --batch --yes --dearmor -o /etc/apt/keyrings/charm.gpg
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list
sudo apt-get update && sudo apt-get install gum -y >/dev/null 2>&1
fi
}
# 3. Install Chezmoi (in parallel with other checks where possible)
install_chezmoi() {
if command -v chezmoi >/dev/null; then
echo " chezmoi already installed: $(chezmoi --version)"
return 0
fi
if command -v brew >/dev/null; then
echo " Installing chezmoi via Homebrew..."
brew install chezmoi >/dev/null 2>&1
else
bin_dir="$HOME/.local/bin"
mkdir -p "$bin_dir"
echo " Installing chezmoi via binary download..."
# Use BINDIR env var — get.chezmoi.io passes remaining args to chezmoi
if ! BINDIR="$bin_dir" sh -c "$(curl -fsSL https://get.chezmoi.io)"; then
echo " chezmoi binary installer failed" >&2
return 1
fi
fi
}
# Parallel execution of bootstrapping components
local pid_gum pid_cm
(bootstrap_gum) &
pid_gum=$!
(install_chezmoi) &
pid_cm=$!
# Wait and check exit codes
wait "$pid_gum" || true # gum is optional
wait "$pid_cm" || error "chezmoi installation failed"
# Critical: Add to PATH for the rest of the script to see it
bin_dir="$HOME/.local/bin"
export PATH="$bin_dir:$PATH"
# 4. Prepare source directory
step "Preparing source directory..."
# VERSION pinning for supply-chain security
VERSION="$version"
ensure_chezmoi_source() {
local dir="$1"
mkdir -p "$CHEZMOI_CONFIG_DIR"
# Escape sed metacharacters in replacement string
local escaped_dir
escaped_dir=$(printf '%s\n' "$dir" | sed -e 's/[\/&]/\\&/g')
if [[ -f "$CHEZMOI_CONFIG_FILE" ]] && grep -q '^sourceDir' "$CHEZMOI_CONFIG_FILE"; then
sed_in_place "s,^sourceDir.*$,sourceDir = \"$escaped_dir\"," "$CHEZMOI_CONFIG_FILE"
else
printf 'sourceDir = "%s"\n' "$dir" >"$CHEZMOI_CONFIG_FILE"
fi
}
# 5. Backup existing dotfiles that chezmoi will overwrite
step "Backing up existing dotfiles..."
BACKUP_DIR="$HOME/.dotfiles.bak.$(date +"%Y%m%d_%H%M%S")"
backup_count=0
# Determine the source directory for chezmoi to diff against
if [[ -d "$SOURCE_DIR/.git" ]]; then
ensure_chezmoi_source "$SOURCE_DIR"
elif [[ -d "$LEGACY_SOURCE_DIR/.git" ]]; then
ensure_chezmoi_source "$LEGACY_SOURCE_DIR"
fi
# Back up any existing files that chezmoi would overwrite
if command -v chezmoi >/dev/null && [[ -f "$CHEZMOI_CONFIG_FILE" ]]; then
while IFS= read -r file; do
[[ -z "$file" ]] && continue
if [[ -e "$file" ]]; then
rel="${file#"$HOME"/}"
mkdir -p "$BACKUP_DIR/$(dirname "$rel")"
cp -a "$file" "$BACKUP_DIR/$rel"
backup_count=$((backup_count + 1))
fi
done < <(chezmoi managed --path-style=absolute 2>/dev/null || true)
fi
if [[ "$backup_count" -gt 0 ]]; then
echo " Backed up $backup_count files to $BACKUP_DIR"
else
echo " No existing dotfiles to back up."
rm -rf "$BACKUP_DIR" 2>/dev/null || true
fi
# 6. Initialize & Apply
step "Applying Configuration..."
# If we are running from a local source, just apply
if [[ -d "$SOURCE_DIR/.git" ]]; then
echo " Applying from local source: $SOURCE_DIR"
ensure_chezmoi_source "$SOURCE_DIR"
APPLY_FLAGS=()
if [[ "${DOTFILES_NONINTERACTIVE:-0}" = "1" ]]; then
APPLY_FLAGS=(--force --no-tty)
fi
chezmoi apply "${APPLY_FLAGS[@]}"
elif [[ -d "$LEGACY_SOURCE_DIR/.git" ]]; then
echo " Migrating from legacy source: $LEGACY_SOURCE_DIR"
mv "$LEGACY_SOURCE_DIR" "$SOURCE_DIR"
ensure_chezmoi_source "$SOURCE_DIR"
APPLY_FLAGS=()
if [[ "${DOTFILES_NONINTERACTIVE:-0}" = "1" ]]; then
APPLY_FLAGS=(--force --no-tty)
fi
chezmoi apply "${APPLY_FLAGS[@]}"
else
echo " Initializing from GitHub (Branch/Tag: $VERSION)..."
printf '%b\n' "${CYAN} SECURITY NOTE: Cloning pinned version $VERSION for supply-chain safety${NC}"
# STRICT MODE: We pin to the specific tag to avoid 'main' branch drift
git clone --depth 1 --branch "$VERSION" https://github.com/sebastienrousseau/dotfiles.git "$SOURCE_DIR" 2>/dev/null ||
{ git clone https://github.com/sebastienrousseau/dotfiles.git "$SOURCE_DIR" && (cd "$SOURCE_DIR" && git checkout "$VERSION"); }
# Verify the checkout succeeded and we're on the expected version
ACTUAL_REF=$(
cd "$SOURCE_DIR" || exit 1
if ! git describe --tags --exact-match 2>/dev/null; then
git rev-parse --short HEAD
fi
)
if [[ "$ACTUAL_REF" != "$VERSION" ]] && [[ "${ACTUAL_REF#v}" != "${VERSION#v}" ]]; then
printf '%b\n' "${CYAN} INFO: Checked out ref $ACTUAL_REF (requested: $VERSION)${NC}"
fi
ensure_chezmoi_source "$SOURCE_DIR"
APPLY_FLAGS=()
if [[ "${DOTFILES_NONINTERACTIVE:-0}" = "1" ]]; then
APPLY_FLAGS=(--force --no-tty)
fi
chezmoi apply "${APPLY_FLAGS[@]}"
fi
success "Configuration loaded. Please restart your shell."
}
# Run main if executed directly (or via bash -c where BASH_SOURCE is unset)
if [[ "${BASH_SOURCE[0]:-}" == "${0:-}" ]] || [[ -z "${BASH_SOURCE[0]:-}" ]]; then
main "$@"
fi