-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbootstrap.sh
More file actions
executable file
·252 lines (227 loc) · 8.92 KB
/
Copy pathbootstrap.sh
File metadata and controls
executable file
·252 lines (227 loc) · 8.92 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
#!/bin/bash
set -e
DRY_RUN=0
AUTO_YES=0
VERBOSE=0
PROFILE="all"
# Double-Interrupt Handler
LAST_INT=0
handle_interrupt() {
local now=$(date +%s)
if (( now - LAST_INT <= 2 )); then
echo -e "\n[Abort] Double interrupt detected. Exiting script."
exit 130
fi
if command -v gum &> /dev/null; then
gum style --foreground 220 "[Warning] Interrupt received! Press Ctrl+C again within 2 seconds to abort everything."
else
echo -e "\n[Warning] Interrupt received! Press Ctrl+C again within 2 seconds to abort everything."
fi
LAST_INT=$now
}
trap 'handle_interrupt' SIGINT
while [[ "$#" -gt 0 ]]; do
case $1 in
--dry-run) DRY_RUN=1 ;;
-y|--yes) AUTO_YES=1 ;;
-v|--verbose) VERBOSE=1 ;;
--profile)
PROFILE="$2"
shift
;;
*) echo "Unknown parameter: $1"; exit 1 ;;
esac
shift
done
export AUTO_YES
export DOTFILES_PROFILE="$PROFILE"
# Sudo wrapper (satisfies AGENTS.md rule to use pkexec on Linux)
if [[ "$OSTYPE" == "darwin"* ]]; then
export SUDO_ASKPASS="$(mktemp)"
echo '#!/bin/bash' > "$SUDO_ASKPASS"
echo 'osascript -e "return text returned of (display dialog \"Sudo Password:\" default answer \"\" with hidden answer)"' >> "$SUDO_ASKPASS"
chmod +x "$SUDO_ASKPASS"
export SUI_SUDO="sudo -A"
trap 'rm -f "$SUDO_ASKPASS"' EXIT
else
if command -v pkexec &> /dev/null; then
export SUI_SUDO="pkexec"
else
export SUI_SUDO="sudo"
fi
fi
# Ensure basic prerequisites are installed on Fedora/Linux
if [[ "$OSTYPE" != "darwin"* ]] && command -v dnf &> /dev/null; then
MISSING_PREREQS=()
for cmd in curl tar unzip git pkexec; do
if ! command -v "$cmd" &>/dev/null; then
pkg="$cmd"
if [[ "$cmd" == "pkexec" ]]; then pkg="polkit"; fi
MISSING_PREREQS+=("$pkg")
fi
done
if [ ${#MISSING_PREREQS[@]} -ne 0 ]; then
echo "Installing missing prerequisites: ${MISSING_PREREQS[*]}"
if [ "$EUID" -eq 0 ]; then
dnf install -y "${MISSING_PREREQS[@]}"
else
sudo dnf install -y "${MISSING_PREREQS[@]}"
fi
fi
fi
# Terminal Candy: install gum
if ! command -v gum &> /dev/null; then
echo "Installing gum for terminal UI..."
GUM_VERSION="0.14.3"
ARCH=$(uname -m)
if [[ "$OSTYPE" == "darwin"* ]]; then
GUM_ARCH="Darwin_x86_64"
if [[ "$ARCH" == "arm64" ]]; then
GUM_ARCH="Darwin_arm64"
fi
curl -sL "https://github.com/charmbracelet/gum/releases/download/v${GUM_VERSION}/gum_${GUM_VERSION}_${GUM_ARCH}.tar.gz" | tar xz -C /tmp
mkdir -p "$HOME/.local/bin"
mv "/tmp/gum_${GUM_VERSION}_${GUM_ARCH}/gum" "$HOME/.local/bin/gum"
else
GUM_ARCH="linux_x86_64"
if [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then
GUM_ARCH="linux_arm64"
fi
curl -sL "https://github.com/charmbracelet/gum/releases/download/v${GUM_VERSION}/gum_${GUM_VERSION}_${GUM_ARCH}.tar.gz" | tar xz -C /tmp
mkdir -p "$HOME/.local/bin"
mv "/tmp/gum_${GUM_VERSION}_${GUM_ARCH}/gum" "$HOME/.local/bin/gum"
fi
export PATH="$HOME/.local/bin:$PATH"
fi
run_step() {
local desc="$1"
shift
if [[ $DRY_RUN -eq 1 ]]; then
echo "[DRY-RUN] $desc"
return 0
fi
if [[ $VERBOSE -eq 1 ]]; then
echo "=> $desc"
"$@"
else
if gum spin --spinner dot --title "$desc..." -- "$@"; then
gum style --foreground 46 "✔ $desc"
else
gum style --foreground 196 "✖ $desc failed"
return 1
fi
fi
}
run_step_sudo() {
local desc="$1"
shift
if [[ $DRY_RUN -eq 1 ]]; then
echo "[DRY-RUN] $desc"
return 0
fi
# Notify user before potential GUI sudo prompts
if command -v gum &> /dev/null; then
gum style --foreground 220 "⚠️ Elevating privileges (sudo) for: $desc"
else
echo "⚠️ Elevating privileges (sudo) for: $desc"
fi
if [[ $VERBOSE -eq 1 ]]; then
echo "=> $desc"
$SUI_SUDO "$@"
else
if gum spin --spinner dot --title "$desc..." -- $SUI_SUDO "$@"; then
gum style --foreground 46 "✔ $desc"
else
gum style --foreground 196 "✖ $desc failed"
return 1
fi
fi
}
echo ""
gum style --border normal --margin "1" --padding "1" --border-foreground 212 "Starting Dotfiles Bootstrap (${PROFILE} profile)"
# Shell History Migration (Bash -> Zsh)
if [[ -f "$HOME/.bash_history" ]]; then
run_step "Migrating Bash history to Zsh format" bash -c 'cat "$HOME/.bash_history" | while read -r line; do echo ": $(date +%s):0;$line"; done >> "$HOME/.zsh_history" && mv "$HOME/.bash_history" "$HOME/.bash_history.migrated"'
fi
# 0. Update System Packages
if [[ $AUTO_YES -eq 1 ]] || gum confirm "Update system packages?"; then
if command -v dnf &> /dev/null; then
run_step_sudo "Updating system packages" dnf upgrade --refresh -y
fi
fi
# 0.5 YubiKey Passwordless Sudo Setup
if [[ $AUTO_YES -eq 1 ]] || gum confirm "Setup YubiKey for passwordless sudo now (saves typing password)?"; then
if command -v dnf &> /dev/null; then
run_step_sudo "Installing YubiKey dependencies" dnf install -y pam-u2f pamu2fcfg pcsc-lite ccid
elif command -v apt-get &> /dev/null; then
run_step_sudo "Installing YubiKey dependencies" bash -c "apt-get update && apt-get install -y libpam-u2f pamu2fcfg pcscd libccid"
fi
run_step_sudo "Enabling Smartcard Daemon (pcscd)" systemctl enable --now pcscd.socket 2>/dev/null || true
if [ ! -f "$HOME/.config/Yubico/u2f_keys" ] && command -v pamu2fcfg &> /dev/null; then
if [[ $DRY_RUN -eq 1 ]]; then
echo "[DRY-RUN] Touch YubiKey and write key map to $HOME/.config/Yubico/u2f_keys"
else
echo ""
gum style --foreground 220 "🔑 Please enter your YubiKey PIN (if prompted) and then touch your YubiKey..."
mkdir -p "$HOME/.config/Yubico"
pamu2fcfg > "$HOME/.config/Yubico/u2f_keys" || true
fi
fi
run_step_sudo "Configuring PAM for YubiKey" bash -c "
if command -v authselect &> /dev/null; then
authselect enable-feature with-pam-u2f || true
authselect apply-changes || true
else
if [ -f /etc/pam.d/sudo ]; then grep -q 'pam_u2f.so' /etc/pam.d/sudo || sed -i '1s/^/auth sufficient pam_u2f.so cue\n/' /etc/pam.d/sudo; fi
if [ -f /etc/pam.d/polkit-1 ]; then grep -q 'pam_u2f.so' /etc/pam.d/polkit-1 || sed -i '1s/^/auth sufficient pam_u2f.so cue\n/' /etc/pam.d/polkit-1; fi
fi
"
run_step_sudo "Configuring YubiKey Auto-Lock on Removal" bash -c "cat << 'EOF' > /etc/udev/rules.d/85-yubikey.rules
ACTION==\"remove\", ENV{ID_VENDOR_ID}==\"1050\", RUN+=\"/usr/bin/loginctl lock-session\"
EOF
udevadm control --reload-rules"
fi
# 1. Install Chezmoi
if ! command -v chezmoi &> /dev/null; then
if command -v brew &> /dev/null; then
run_step "Installing Chezmoi via Homebrew" brew install chezmoi
elif command -v dnf &> /dev/null; then
run_step_sudo "Installing Chezmoi via DNF" dnf install -y chezmoi
elif command -v apt-get &> /dev/null; then
run_step_sudo "Installing Chezmoi via APT" bash -c "apt-get update && apt-get install -y chezmoi"
else
curl -fsSL https://get.chezmoi.io -o /tmp/install-chezmoi.sh
run_step "Installing Chezmoi via install script" bash /tmp/install-chezmoi.sh -b "$HOME/.local/bin"
export PATH="$HOME/.local/bin:$PATH"
fi
fi
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
run_step "Linking Chezmoi to $REPO_DIR" bash -c "
mkdir -p '\$HOME/.config/chezmoi'
if [ -f '\$HOME/.config/chezmoi/chezmoi.toml' ]; then
if ! grep -q 'sourceDir' '\$HOME/.config/chezmoi/chezmoi.toml'; then
echo 'sourceDir = \"$REPO_DIR\"' >> '\$HOME/.config/chezmoi/chezmoi.toml'
fi
else
echo 'sourceDir = \"$REPO_DIR\"' > '\$HOME/.config/chezmoi/chezmoi.toml'
fi"
# Apply dotfiles (this automatically triggers run_once_setup scripts)
if [[ $AUTO_YES -eq 1 ]] || gum confirm "Apply dotfiles and overwrite local configurations?"; then
echo ""
gum style --foreground 212 "Applying dotfiles and running OS-specific setups..."
# We do not use gum spin here because OS setups (dnf/brew) take a long time
# and provide their own interactive output/progress bars.
if [[ $DRY_RUN -eq 1 ]]; then
chezmoi apply --force --dry-run
else
chezmoi apply --force
fi
gum style --foreground 46 "✔ Dotfiles applied successfully!"
fi
echo ""
gum style --border double --margin "1" --padding "1" --border-foreground 46 "✨ Bootstrap Completed Successfully! ✨"
echo "What's next?"
echo " 1. Restart your terminal or log out/in to apply all OS and font changes."
echo " 2. Run 'scripts/sync_projects.sh' to fetch or clone your git repositories."
echo " 3. Run 'scripts/maintenance.sh' periodically to keep your system clean."
echo ""