Repository navigation
Expand file tree
/
Copy pathsetup.sh
More file actions
executable file
·220 lines (202 loc) · 8.37 KB
/
Copy pathsetup.sh
File metadata and controls
executable file
·220 lines (202 loc) · 8.37 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
#!/usr/bin/env bash
# Sets up a new Mac from this repo. Safe to re-run: each step checks before it changes anything.
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
step() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
note() { printf ' %s\n' "$*"; }
# Writes through symlinks, so dotfiles managed elsewhere stay linked.
prepend_to_file() { # prepend_to_file <file> <text>
local file="$1" text="$2" tmp
tmp="$(mktemp)"
{ printf '%s\n' "$text"; cat "$file"; } > "$tmp"
cat "$tmp" > "$file"
rm -f "$tmp"
}
find_brew() {
command -v brew 2>/dev/null && return
local candidate
for candidate in /opt/homebrew/bin/brew /usr/local/bin/brew; do
[[ -x "$candidate" ]] && { echo "$candidate"; return; }
done
return 1
}
if [[ "$(uname -s)" != "Darwin" ]]; then
echo "This script is for macOS." >&2
exit 1
fi
step "Xcode Command Line Tools"
if xcode-select -p >/dev/null 2>&1; then
note "already installed"
else
xcode-select --install
echo "Finish the Command Line Tools install in the dialog that opened, then re-run ./setup.sh"
exit 1
fi
step "Homebrew"
if BREW="$(find_brew)"; then
note "already installed"
else
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
BREW="$(find_brew)"
fi
eval "$("$BREW" shellenv)"
touch "$HOME/.zprofile"
if ! grep -q 'brew shellenv' "$HOME/.zprofile"; then
# shellcheck disable=SC2016 # $(...) is meant to be written literally
printf '\neval "$(%s shellenv)"\n' "$BREW" >> "$HOME/.zprofile"
note "added Homebrew to ~/.zprofile"
fi
step "Apps and tools from the Brewfile"
# A failed install doesn't stop the setup: the steps below don't need it.
# The failures are listed at the end.
BUNDLE_FAILED=""
brew bundle --file="$REPO_DIR/Brewfile" || BUNDLE_FAILED=1
# Your own additions, kept out of git so `git pull` never conflicts with them.
BREWFILE_LOCAL="$REPO_DIR/Brewfile.local"
if [[ -f "$BREWFILE_LOCAL" ]]; then
step "Apps and tools from Brewfile.local"
brew bundle --file="$BREWFILE_LOCAL" || BUNDLE_FAILED=1
fi
step "Shell config"
ZSHRC_LINE="source \"$REPO_DIR/dotfiles/zshrc\""
touch "$HOME/.zshrc"
if grep -qxF "$ZSHRC_LINE" "$HOME/.zshrc"; then
note "already sourced from ~/.zshrc"
else
printf '\n%s\n' "$ZSHRC_LINE" >> "$HOME/.zshrc"
note "added to the end of ~/.zshrc"
fi
step "Ghostty config"
GHOSTTY_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/ghostty/config"
GHOSTTY_LINE="config-file = \"$REPO_DIR/dotfiles/ghostty\""
mkdir -p "$(dirname "$GHOSTTY_CONFIG")"
touch "$GHOSTTY_CONFIG"
if grep -qxF "$GHOSTTY_LINE" "$GHOSTTY_CONFIG"; then
note "already loaded from $GHOSTTY_CONFIG"
else
printf '\n%s\n' "$GHOSTTY_LINE" >> "$GHOSTTY_CONFIG"
note "added to the end of $GHOSTTY_CONFIG (applies to new Ghostty windows)"
fi
# Listed after the repo's file, so it loads later and its settings win. The ? makes it
# optional, and the path is relative to $GHOSTTY_CONFIG.
GHOSTTY_LOCAL_LINE="config-file = ?config.local"
if ! grep -qxF "$GHOSTTY_LOCAL_LINE" "$GHOSTTY_CONFIG"; then
printf '%s\n' "$GHOSTTY_LOCAL_LINE" >> "$GHOSTTY_CONFIG"
note "settings in $(dirname "$GHOSTTY_CONFIG")/config.local now override the repo's"
fi
step "herdr config"
# herdr's config.toml has no include directive, so it is copied rather than linked.
HERDR_CONFIG="$HOME/.config/herdr/config.toml"
if [[ ! -f "$HERDR_CONFIG" ]]; then
mkdir -p "$(dirname "$HERDR_CONFIG")"
cp "$REPO_DIR/dotfiles/herdr.toml" "$HERDR_CONFIG"
note "created $HERDR_CONFIG"
elif grep -q '^switch_tab' "$HERDR_CONFIG"; then
note "tab shortcut already set in $HERDR_CONFIG"
elif ! grep -q '^\[keys\]' "$HERDR_CONFIG"; then
printf '\n[keys]\nswitch_tab = "alt+1..9"\n' >> "$HERDR_CONFIG"
note "added Option+1..9 tab switching to $HERDR_CONFIG"
else
note "add switch_tab = \"alt+1..9\" under [keys] in $HERDR_CONFIG"
fi
step "Git config"
GIT_INCLUDE="$REPO_DIR/dotfiles/gitconfig"
touch "$HOME/.gitconfig"
if git config --global --get-all include.path 2>/dev/null | grep -qxF "$GIT_INCLUDE"; then
note "already included from ~/.gitconfig"
else
# At the top, so anything already in ~/.gitconfig takes precedence.
prepend_to_file "$HOME/.gitconfig" "$(printf '[include]\n\tpath = "%s"\n' "$GIT_INCLUDE")"
note "included at the top of ~/.gitconfig"
fi
SSH_KEY="$HOME/.ssh/id_ed25519.pub"
if [[ -n "$(git config --global user.signingkey || true)" ]]; then
note "commit signing already configured"
elif [[ -f "$SSH_KEY" ]]; then
git config --global gpg.format ssh
git config --global user.signingkey "$SSH_KEY"
git config --global commit.gpgsign true
note "commits will be signed with $SSH_KEY"
note "add it to GitHub as a signing key: gh ssh-key add $SSH_KEY --type signing"
else
note "no SSH key yet, so commit signing is off. Create one, then re-run ./setup.sh:"
note " ssh-keygen -t ed25519 -C \"you@example.com\""
fi
# Without an allowed signers file, git log --show-signature shows an error instead of "Good signature".
SIGNING_KEY="$(git config --global user.signingkey || true)"
[[ -f "${SIGNING_KEY/#\~/$HOME}" ]] && SIGNING_KEY="$(cat "${SIGNING_KEY/#\~/$HOME}")"
read -r KEY_TYPE KEY_DATA _ <<< "${SIGNING_KEY#key::}" || true
ALLOWED_SIGNERS="$(git config --global gpg.ssh.allowedSignersFile || true)"
GIT_EMAIL="$(git config --global user.email || true)"
if [[ "$(git config --global gpg.format || true)" != ssh || -z "${KEY_DATA:-}" ]]; then
: # not signing with an SSH key
elif [[ -n "$ALLOWED_SIGNERS" ]]; then
if grep -qF "$KEY_DATA" "${ALLOWED_SIGNERS/#\~/$HOME}" 2>/dev/null; then
note "signature verification already set up"
else
note "to verify your signatures, add your key to $ALLOWED_SIGNERS"
fi
elif [[ -z "$GIT_EMAIL" ]]; then
note "set your git email (see the end), then re-run ./setup.sh so git can verify your signatures"
else
ALLOWED_SIGNERS="$HOME/.ssh/allowed_signers"
if ! grep -qF "$KEY_DATA" "$ALLOWED_SIGNERS" 2>/dev/null; then
printf '%s namespaces="git" %s %s\n' "$GIT_EMAIL" "$KEY_TYPE" "$KEY_DATA" >> "$ALLOWED_SIGNERS"
fi
git config --global gpg.ssh.allowedSignersFile "$ALLOWED_SIGNERS"
note "git can verify your signatures now: git log --show-signature"
fi
step "SSH config"
SSH_CONFIG="$HOME/.ssh/config"
if grep -qi '^[[:space:]]*UseKeychain' "$SSH_CONFIG" 2>/dev/null; then
note "already uses the macOS keychain"
else
[[ -d "$HOME/.ssh" ]] || mkdir -m 700 "$HOME/.ssh"
[[ -f "$SSH_CONFIG" ]] || (umask 077 && touch "$SSH_CONFIG")
{ echo; printf '%s\n' \
'# Added by developer-machine-setup: ask for SSH key passphrases once, then keep them in the keychain' \
'Host *' \
' IgnoreUnknown UseKeychain' \
' UseKeychain yes' \
' AddKeysToAgent yes'; } >> "$SSH_CONFIG"
note "added keychain settings to the end of $SSH_CONFIG"
fi
step "Docker CLI plugins (compose, buildx)"
DOCKER_CONFIG_FILE="$HOME/.docker/config.json"
PLUGIN_DIR="$HOMEBREW_PREFIX/lib/docker/cli-plugins"
if ! command -v docker >/dev/null; then
note "docker CLI not installed, skipping"
elif [[ ! -f "$DOCKER_CONFIG_FILE" ]]; then
mkdir -p "$HOME/.docker"
printf '{\n "cliPluginsExtraDirs": ["%s"]\n}\n' "$PLUGIN_DIR" > "$DOCKER_CONFIG_FILE"
note "created $DOCKER_CONFIG_FILE"
elif grep -qF "$PLUGIN_DIR" "$DOCKER_CONFIG_FILE"; then
note "already configured"
elif command -v jq >/dev/null; then
tmp="$(mktemp)"
jq --arg dir "$PLUGIN_DIR" '.cliPluginsExtraDirs = ((.cliPluginsExtraDirs // []) + [$dir])' \
"$DOCKER_CONFIG_FILE" > "$tmp"
cat "$tmp" > "$DOCKER_CONFIG_FILE"
rm -f "$tmp"
note "added $PLUGIN_DIR to $DOCKER_CONFIG_FILE"
else
note "add \"cliPluginsExtraDirs\": [\"$PLUGIN_DIR\"] to $DOCKER_CONFIG_FILE"
fi
step "Done. Next steps:"
note "- Open a new terminal (Ghostty) to load the shell config"
note "- Log in to GitHub: gh auth login"
note "- Start Docker: colima start (or open OrbStack if you use it instead)"
note "- Optional macOS tweaks: ./macos-defaults.sh"
if [[ -z "$(git config --global user.email || true)" ]]; then
note "- Set your git identity: git config --global user.name \"Your Name\""
note " git config --global user.email \"you@example.com\""
fi
if [[ -n "$BUNDLE_FAILED" ]]; then
step "Some Brewfile entries didn't install"
for brewfile in "$REPO_DIR/Brewfile" "$BREWFILE_LOCAL"; do
[[ -f "$brewfile" ]] || continue
brew bundle check --file="$brewfile" --no-upgrade --verbose 2>&1 | sed 's/^/ /' || true
done
note "Look for \"Error:\" in the brew bundle output above, fix it, then re-run ./setup.sh"
exit 1
fi