|
#!/bin/zsh |
|
set -eu |
|
setopt PIPE_FAIL |
|
|
|
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${HOME}/.fzf/bin:${PATH:-}" |
|
|
|
DOTFILES_REPO="https://github.com/telnet2/dotfiles.git" |
|
DOTFILES_DIR="${HOME}/dotfiles" |
|
BACKUP_ROOT="${HOME}/.local-setup-backups/$(date +%Y%m%d-%H%M%S)" |
|
GHOSTTY_CONFIG="${HOME}/Library/Application Support/com.mitchellh.ghostty/config.ghostty" |
|
if [[ -f "${HOME}/Library/Application Support/com.mitchellh.ghostty/config" ]]; then |
|
GHOSTTY_CONFIG="${HOME}/Library/Application Support/com.mitchellh.ghostty/config" |
|
fi |
|
|
|
note() { |
|
print -r -- "==> $*" |
|
} |
|
|
|
fail() { |
|
print -ru2 -- "error: $*" |
|
exit 1 |
|
} |
|
|
|
exists() { |
|
[[ -e "$1" || -L "$1" ]] |
|
} |
|
|
|
backup_target() { |
|
local target="$1" |
|
if exists "$target"; then |
|
mkdir -p "$BACKUP_ROOT" |
|
mv "$target" "$BACKUP_ROOT/${target:t}" |
|
note "Backed up $target" |
|
fi |
|
} |
|
|
|
link_target() { |
|
local source="$1" |
|
local target="$2" |
|
[[ -e "$source" ]] || fail "Missing link source: $source" |
|
if [[ -L "$target" && "${target:A}" == "${source:A}" ]]; then |
|
return |
|
fi |
|
backup_target "$target" |
|
ln -s "$source" "$target" |
|
note "Linked $target -> $source" |
|
} |
|
|
|
check_install() { |
|
local missing=0 |
|
local required_path |
|
for required_path in \ |
|
"$DOTFILES_DIR" \ |
|
"$HOME/.zprezto" \ |
|
"$HOME/.fzf" \ |
|
"$HOME/.vim/bundle/Vundle.vim" \ |
|
"$HOME/.vimrc" \ |
|
"$HOME/.zpreztorc" \ |
|
"$HOME/.ssh" \ |
|
"$HOME/.gitconfig" \ |
|
"$HOME/.git-credentials"; do |
|
if exists "$required_path"; then |
|
print -r -- "OK $required_path" |
|
else |
|
print -r -- "MISSING $required_path" |
|
missing=1 |
|
fi |
|
done |
|
|
|
local utility |
|
for utility in brew gh mosh fzf vim; do |
|
if [[ "$utility" == "fzf" && -x "$HOME/.fzf/bin/fzf" ]]; then |
|
print -r -- "OK fzf -> $HOME/.fzf/bin/fzf" |
|
elif command -v "$utility" >/dev/null 2>&1; then |
|
print -r -- "OK $utility -> $(command -v "$utility")" |
|
else |
|
print -r -- "MISSING $utility" |
|
missing=1 |
|
fi |
|
done |
|
|
|
if [[ "$(uname -s)" == "Darwin" ]]; then |
|
[[ -f "$GHOSTTY_CONFIG" ]] || { |
|
print -r -- "MISSING $GHOSTTY_CONFIG" |
|
missing=1 |
|
} |
|
fi |
|
return "$missing" |
|
} |
|
|
|
if [[ "${1:-}" == "--check" ]]; then |
|
check_install |
|
exit $? |
|
fi |
|
|
|
[[ "$(uname -s)" == "Darwin" ]] || |
|
fail "This bootstrap currently supports macOS only." |
|
[[ "$SHELL" == */zsh ]] || |
|
fail "Set zsh as the default shell first: chsh -s $(command -v zsh)" |
|
|
|
if ! command -v brew >/dev/null 2>&1; then |
|
note "Installing Homebrew" |
|
/bin/bash -c \ |
|
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
|
if [[ -x /opt/homebrew/bin/brew ]]; then |
|
eval "$(/opt/homebrew/bin/brew shellenv)" |
|
elif [[ -x /usr/local/bin/brew ]]; then |
|
eval "$(/usr/local/bin/brew shellenv)" |
|
else |
|
fail "Homebrew installation did not produce a brew executable." |
|
fi |
|
fi |
|
|
|
note "Installing command-line utilities" |
|
brew install gh mosh |
|
|
|
note "Installing Ghostty and terminal fonts" |
|
brew install --cask \ |
|
ghostty \ |
|
font-d2coding \ |
|
font-meslo-lg-nerd-font \ |
|
font-jetbrains-mono-nerd-font \ |
|
font-fira-code-nerd-font \ |
|
font-hack-nerd-font |
|
|
|
if ! gh auth status >/dev/null 2>&1; then |
|
fail "Authenticate GitHub first with: gh auth login" |
|
fi |
|
visibility="$(gh repo view telnet2/dotfiles --json visibility --jq .visibility)" |
|
[[ "$visibility" == "PRIVATE" ]] || |
|
fail "Refusing to clone dotfiles because repository visibility is $visibility." |
|
|
|
if [[ -d "$DOTFILES_DIR/.git" ]]; then |
|
if [[ -n "$(git -C "$DOTFILES_DIR" status --porcelain)" ]]; then |
|
note "Dotfiles has local changes; skipping pull" |
|
else |
|
git -C "$DOTFILES_DIR" pull --ff-only |
|
fi |
|
elif exists "$DOTFILES_DIR"; then |
|
fail "$DOTFILES_DIR exists but is not a Git checkout." |
|
else |
|
gh repo clone telnet2/dotfiles "$DOTFILES_DIR" |
|
fi |
|
|
|
if [[ ! -d "$HOME/.zprezto" ]]; then |
|
note "Installing Prezto" |
|
git clone --recursive https://github.com/sorin-ionescu/prezto.git "$HOME/.zprezto" |
|
fi |
|
|
|
if [[ ! -d "$HOME/.zprezto/modules/fast-syntax-highlighting" ]]; then |
|
git clone https://github.com/zdharma/fast-syntax-highlighting \ |
|
"$HOME/.zprezto/modules/fast-syntax-highlighting" |
|
fi |
|
|
|
for rcfile in "$HOME"/.zprezto/runcoms/^README.md(.N); do |
|
[[ "${rcfile:t}" == "zpreztorc" ]] && continue |
|
link_target "$rcfile" "$HOME/.${rcfile:t}" |
|
done |
|
|
|
backup_target "$HOME/.zpreztorc" |
|
cat > "$HOME/.zpreztorc" <<'EOF' |
|
zstyle ':prezto:*:*' color 'yes' |
|
zstyle ':prezto:load' pmodule \ |
|
'fasd' \ |
|
'environment' \ |
|
'terminal' \ |
|
'editor' \ |
|
'history' \ |
|
'directory' \ |
|
'spectrum' \ |
|
'utility' \ |
|
'autosuggestions' \ |
|
'completion' \ |
|
'prompt' \ |
|
'git' \ |
|
'node' \ |
|
'fast-syntax-highlighting' \ |
|
'history-substring-search' |
|
zstyle ':prezto:module:editor' key-bindings 'emacs' |
|
zstyle ':prezto:module:prompt' theme 'sorinx' |
|
EOF |
|
|
|
curl -fsSL \ |
|
"https://gist.githubusercontent.com/telnet2/affe231f01922b1b0ec28a2702fff0f9/raw/prompt_sorinx_setup" \ |
|
-o "$HOME/.zprezto/modules/prompt/functions/prompt_sorinx_setup" |
|
|
|
if [[ ! -d "$HOME/.fzf" ]]; then |
|
note "Installing fzf" |
|
git clone --depth 1 https://github.com/junegunn/fzf.git "$HOME/.fzf" |
|
"$HOME/.fzf/install" --all |
|
fi |
|
|
|
python3 - "$HOME/.zshrc" <<'PY' |
|
import pathlib |
|
import sys |
|
|
|
path = pathlib.Path(sys.argv[1]) |
|
start = "# >>> local-setup dotfiles >>>" |
|
end = "# <<< local-setup dotfiles <<<" |
|
text = path.read_text() |
|
before, marker, rest = text.partition(start) |
|
if marker: |
|
_, closing, after = rest.partition(end) |
|
if not closing: |
|
raise SystemExit("zshrc managed dotfiles block is missing its closing marker") |
|
text = before.rstrip() + "\n" + after.lstrip() |
|
block = """# >>> local-setup dotfiles >>> |
|
source "$HOME/dotfiles/tt/alias.zsh" |
|
source "$HOME/dotfiles/tt/funcs.zsh" |
|
# <<< local-setup dotfiles <<< |
|
""" |
|
path.write_text(text.rstrip() + "\n\n" + block) |
|
PY |
|
|
|
if [[ ! -d "$HOME/.vim/bundle/Vundle.vim" ]]; then |
|
git clone https://github.com/VundleVim/Vundle.vim.git \ |
|
"$HOME/.vim/bundle/Vundle.vim" |
|
fi |
|
|
|
if [[ ! -f "$HOME/.vimrc" ]]; then |
|
curl -fsSL \ |
|
"https://gist.githubusercontent.com/telnet2/048a582b5a3354ad70cab9c6f787390c/raw/870f6742fa15bf6c30a826dcfb863821db8e1aa9/.vimrc" \ |
|
-o "$HOME/.vimrc" |
|
fi |
|
|
|
note "Installing Vim plugins" |
|
if ! vim -E -s -u "$HOME/.vimrc" +PluginInstall +qall; then |
|
vim +PluginInstall +qall </dev/null >/dev/null 2>&1 |
|
fi |
|
|
|
mkdir -p "${GHOSTTY_CONFIG:h}" |
|
python3 - "$GHOSTTY_CONFIG" <<'PY' |
|
import pathlib |
|
import sys |
|
|
|
path = pathlib.Path(sys.argv[1]) |
|
start = "# >>> local-setup fonts >>>" |
|
end = "# <<< local-setup fonts <<<" |
|
text = path.read_text() if path.exists() else "" |
|
before, marker, rest = text.partition(start) |
|
if marker: |
|
_, closing, after = rest.partition(end) |
|
if not closing: |
|
raise SystemExit("Ghostty managed font block is missing its closing marker") |
|
text = before.rstrip() + "\n" + after.lstrip() |
|
block = """# >>> local-setup fonts >>> |
|
font-family = "" |
|
font-family = D2Coding |
|
font-family = JetBrainsMono Nerd Font Mono |
|
font-family = MesloLGM Nerd Font Mono |
|
# <<< local-setup fonts <<< |
|
""" |
|
path.write_text(text.rstrip() + "\n\n" + block if text.strip() else block) |
|
PY |
|
|
|
chmod 700 "$DOTFILES_DIR/tt/tt.ssh" |
|
find "$DOTFILES_DIR/tt/tt.ssh" -type f -exec chmod 600 {} + |
|
chmod 600 \ |
|
"$DOTFILES_DIR/tt/tt.gitconfig" \ |
|
"$DOTFILES_DIR/tt/tt.git-credentials" |
|
link_target "$DOTFILES_DIR/tt/tt.ssh" "$HOME/.ssh" |
|
link_target "$DOTFILES_DIR/tt/tt.gitconfig" "$HOME/.gitconfig" |
|
link_target "$DOTFILES_DIR/tt/tt.git-credentials" "$HOME/.git-credentials" |
|
|
|
note "Verifying installation" |
|
check_install |
|
note "Setup complete. Fully quit and reopen Ghostty, or run: exec zsh" |