path_helper runs after ~/.zshenv in login shells and puts Homebrew ahead of fnm, pyenv, and ~/.local/bin. Re-source ~/.zshenv from ~/.zprofile and make prepends idempotent so login and zsh -c resolve the same binaries.
206 lines
5.8 KiB
Bash
Executable File
206 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Install easyzsh shell stubs into a home directory.
|
|
# Overwrites: ~/.zshenv ~/.zshrc
|
|
# Installs: ~/.zprofile (re-applies ~/.zshenv after macOS path_helper)
|
|
# Never overwrites: ~/.config/zsh/local.zsh ~/.config/zshrc/** ~/.local/bin/**
|
|
# Extra ~/.zprofile lines stay as login-only hooks unless --migrate-zprofile
|
|
# folds them into local.zsh.
|
|
#
|
|
# Usage:
|
|
# ./deploy.sh
|
|
# ./deploy.sh --home /path/to/home
|
|
# ./deploy.sh --migrate-zprofile # fold extra ~/.zprofile lines into local.zsh
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
TARGET_HOME="${HOME}"
|
|
MIGRATE_ZPROFILE=0
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--home)
|
|
TARGET_HOME="$2"
|
|
shift 2
|
|
;;
|
|
--migrate-zprofile)
|
|
MIGRATE_ZPROFILE=1
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
sed -n '2,12p' "$0"
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $1" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ ! -d "$TARGET_HOME" ]]; then
|
|
echo "Home does not exist: $TARGET_HOME" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ZSH_CFG="${TARGET_HOME}/.config/zsh"
|
|
LOCAL_ZSH="${ZSH_CFG}/local.zsh"
|
|
ZPROFILE="${TARGET_HOME}/.zprofile"
|
|
ts=$(date +%Y%m%d-%H%M%S)
|
|
|
|
backup_file() {
|
|
local f="$1"
|
|
if [[ -e "$f" || -L "$f" ]]; then
|
|
cp -Pp "$f" "${f}.backup.${ts}"
|
|
echo "Backed up $f -> ${f}.backup.${ts}"
|
|
fi
|
|
}
|
|
|
|
file_has_content() {
|
|
[[ -f "$1" ]] && grep -q '[^[:space:]]' "$1"
|
|
}
|
|
|
|
ZPROFILE_MARKER='# easyzsh: reapply always-on PATH after macOS path_helper'
|
|
PYENV_LOGIN_HOOK='[[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh" ]] && . "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh"'
|
|
|
|
is_easyzsh_zprofile_stub() {
|
|
local f="$1"
|
|
[[ -f "$f" ]] || return 1
|
|
# Comment-only placeholder from earlier easyzsh deploys.
|
|
if grep -Eqv '^[[:space:]]*(#|$)' "$f"; then
|
|
return 1
|
|
fi
|
|
grep -Eq 'easyzsh|PATH lives in|Leave this file empty|prefer.*local\.zsh' "$f"
|
|
}
|
|
|
|
is_pyenv_only_zprofile() {
|
|
local f="$1"
|
|
local body
|
|
[[ -f "$f" ]] || return 1
|
|
body=$(grep -Ev '^[[:space:]]*(#|$)' "$f" || true)
|
|
[[ "$body" == "$PYENV_LOGIN_HOOK" ]]
|
|
}
|
|
|
|
# Lines that are not the managed path_helper block or the old pyenv login hook.
|
|
zprofile_extras() {
|
|
local f="$1"
|
|
[[ -f "$f" ]] || return 0
|
|
awk -v marker="$ZPROFILE_MARKER" -v hook="$PYENV_LOGIN_HOOK" '
|
|
$0 == marker { in_block=1; next }
|
|
in_block && $0 == "fi" { in_block=0; next }
|
|
in_block { next }
|
|
$0 == hook { next }
|
|
{ print }
|
|
' "$f"
|
|
}
|
|
|
|
echo "Deploying easyzsh shell stubs into $TARGET_HOME"
|
|
|
|
mkdir -p \
|
|
"${ZSH_CFG}" \
|
|
"${TARGET_HOME}/.config/zshrc" \
|
|
"${TARGET_HOME}/.cache/zsh" \
|
|
"${TARGET_HOME}/.local/bin"
|
|
|
|
# Migrate legacy fragments into local.zsh once.
|
|
if [[ ! -f "$LOCAL_ZSH" ]]; then
|
|
tmp=$(mktemp)
|
|
: >"$tmp"
|
|
if file_has_content "${TARGET_HOME}/.config/zshenv.local"; then
|
|
cat "${TARGET_HOME}/.config/zshenv.local" >>"$tmp"
|
|
printf '\n' >>"$tmp"
|
|
fi
|
|
if [[ -d "${ZSH_CFG}/local" ]]; then
|
|
for part in env.local.zsh login.local.zsh interactive.local.zsh; do
|
|
if file_has_content "${ZSH_CFG}/local/${part}"; then
|
|
cat "${ZSH_CFG}/local/${part}" >>"$tmp"
|
|
printf '\n' >>"$tmp"
|
|
fi
|
|
done
|
|
fi
|
|
if file_has_content "$tmp"; then
|
|
cp "$tmp" "$LOCAL_ZSH"
|
|
echo "Migrated machine-local fragments -> ~/.config/zsh/local.zsh"
|
|
fi
|
|
rm -f "$tmp"
|
|
fi
|
|
|
|
# ~/.zprofile re-applies ~/.zshenv after macOS path_helper.
|
|
# Replace comment-only stubs and the old pyenv-only hook. Keep other login
|
|
# lines, or fold them into local.zsh when --migrate-zprofile is set.
|
|
zprofile_extras_file=$(mktemp)
|
|
: >"$zprofile_extras_file"
|
|
if [[ -f "$ZPROFILE" ]] && ! is_easyzsh_zprofile_stub "$ZPROFILE" && ! is_pyenv_only_zprofile "$ZPROFILE"; then
|
|
zprofile_extras "$ZPROFILE" | awk '
|
|
/Created by `pipx`/ { skip_pipx=1; next }
|
|
skip_pipx && /local\/bin/ { skip_pipx=0; next }
|
|
skip_pipx { next }
|
|
{ print }
|
|
' >"$zprofile_extras_file"
|
|
fi
|
|
|
|
if [[ "$MIGRATE_ZPROFILE" -eq 1 ]] && file_has_content "$zprofile_extras_file"; then
|
|
if [[ -f "$LOCAL_ZSH" ]]; then
|
|
printf '\n' >>"$LOCAL_ZSH"
|
|
cat "$zprofile_extras_file" >>"$LOCAL_ZSH"
|
|
else
|
|
cp "$zprofile_extras_file" "$LOCAL_ZSH"
|
|
fi
|
|
echo "Migrated extra ~/.zprofile lines -> ~/.config/zsh/local.zsh"
|
|
: >"$zprofile_extras_file"
|
|
fi
|
|
|
|
backup_file "${TARGET_HOME}/.zshenv"
|
|
backup_file "${TARGET_HOME}/.zshrc"
|
|
backup_file "$ZPROFILE"
|
|
|
|
install -m 644 "${ROOT}/.zshenv" "${TARGET_HOME}/.zshenv"
|
|
install -m 644 "${ROOT}/.zshrc" "${TARGET_HOME}/.zshrc"
|
|
if file_has_content "$zprofile_extras_file"; then
|
|
{
|
|
cat "${ROOT}/.zprofile"
|
|
printf '\n'
|
|
cat "$zprofile_extras_file"
|
|
} >"$ZPROFILE"
|
|
echo "Installed ~/.zprofile (kept extra login lines)"
|
|
else
|
|
install -m 644 "${ROOT}/.zprofile" "$ZPROFILE"
|
|
echo "Installed ~/.zprofile"
|
|
fi
|
|
rm -f "$zprofile_extras_file"
|
|
|
|
# Remove obsolete managed-tree layout and examples from earlier deploys.
|
|
/bin/rm -rf \
|
|
"${ZSH_CFG}/env" \
|
|
"${ZSH_CFG}/interactive" \
|
|
"${ZSH_CFG}/login" \
|
|
"${ZSH_CFG}/local"
|
|
/bin/rm -f \
|
|
"${ZSH_CFG}/local.zsh.example" \
|
|
"${TARGET_HOME}/.config/zshenv.local"
|
|
|
|
# Grok is `grok`. Cursor owns `agent` when its CLI is present.
|
|
if [[ -e "${TARGET_HOME}/.grok/bin/grok" ]]; then
|
|
ln -sfn "${TARGET_HOME}/.grok/bin/grok" "${TARGET_HOME}/.local/bin/grok"
|
|
echo "Ensured ~/.local/bin/grok -> ~/.grok/bin/grok"
|
|
fi
|
|
|
|
agent_link="${TARGET_HOME}/.local/bin/agent"
|
|
cursor_agent="${TARGET_HOME}/.local/bin/cursor-agent"
|
|
agent_target=""
|
|
if [[ -L "$agent_link" ]]; then
|
|
agent_target=$(readlink "$agent_link")
|
|
fi
|
|
if [[ -x "$cursor_agent" ]]; then
|
|
if [[ ! -e "$agent_link" || "$agent_target" == *'/.grok/'* ]]; then
|
|
ln -sfn "$cursor_agent" "$agent_link"
|
|
echo "Ensured ~/.local/bin/agent -> cursor-agent"
|
|
fi
|
|
elif [[ -L "$agent_link" && "$agent_target" == *'/.grok/'* ]]; then
|
|
rm -f "$agent_link"
|
|
echo "Removed ~/.local/bin/agent (was Grok; Cursor is not installed)"
|
|
fi
|
|
|
|
echo "Deploy complete."
|
|
echo "Verify: env -i HOME=\"$TARGET_HOME\" PATH=/usr/bin:/bin zsh -c '. ~/.zshenv; command -v grok; command -v agent'"
|