macOS path_helper runs after .zshenv and can move system Python ahead of pyenv. Add an idempotent login hook and preserve it across later easyzsh migrations.
165 lines
4.6 KiB
Bash
Executable File
165 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Install easyzsh shell stubs into a home directory.
|
|
# Overwrites: ~/.zshenv ~/.zshrc
|
|
# Never overwrites: ~/.config/zsh/local.zsh ~/.config/zshrc/** ~/.local/bin/**
|
|
# Does not manage ~/.zprofile (machine/login hooks belong in local.zsh).
|
|
#
|
|
# Usage:
|
|
# ./deploy.sh
|
|
# ./deploy.sh --home /path/to/home
|
|
# ./deploy.sh --migrate-zprofile # fold real ~/.zprofile into local.zsh, then remove stub
|
|
|
|
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"
|
|
}
|
|
|
|
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"
|
|
}
|
|
|
|
has_pyenv_login_hook() {
|
|
local hook='[[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh" ]] && . "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh"'
|
|
[[ -f "$1" ]] && grep -Fqx "$hook" "$1"
|
|
}
|
|
|
|
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
|
|
|
|
# Optional: fold a real (non-stub) ~/.zprofile into local.zsh.
|
|
if [[ "$MIGRATE_ZPROFILE" -eq 1 && -f "$ZPROFILE" ]]; then
|
|
if has_pyenv_login_hook "$ZPROFILE"; then
|
|
# ponytail: keep the whole profile; split migration only if per-line ownership is needed.
|
|
echo "Keeping ~/.zprofile: contains the pyenv login hook"
|
|
elif is_easyzsh_zprofile_stub "$ZPROFILE"; then
|
|
echo "Removing easyzsh ~/.zprofile stub (hooks live in local.zsh / .zshenv)"
|
|
rm -f "$ZPROFILE"
|
|
elif [[ ! -f "$LOCAL_ZSH" ]]; then
|
|
zprofile_extract=$(mktemp)
|
|
awk '
|
|
/Created by `pipx`/ { skip_pipx=1; next }
|
|
skip_pipx && /local\/bin/ { skip_pipx=0; next }
|
|
skip_pipx { next }
|
|
{ print }
|
|
' "$ZPROFILE" >"$zprofile_extract"
|
|
if file_has_content "$zprofile_extract"; then
|
|
cp "$zprofile_extract" "$LOCAL_ZSH"
|
|
echo "Migrated ~/.zprofile body -> ~/.config/zsh/local.zsh"
|
|
rm -f "$ZPROFILE"
|
|
echo "Removed ~/.zprofile after migration"
|
|
fi
|
|
rm -f "$zprofile_extract"
|
|
elif is_easyzsh_zprofile_stub "$ZPROFILE"; then
|
|
rm -f "$ZPROFILE"
|
|
fi
|
|
fi
|
|
|
|
# Always drop the comment-only easyzsh stub if present (even without --migrate).
|
|
if is_easyzsh_zprofile_stub "$ZPROFILE"; then
|
|
rm -f "$ZPROFILE"
|
|
echo "Removed empty easyzsh ~/.zprofile stub"
|
|
fi
|
|
|
|
backup_file "${TARGET_HOME}/.zshenv"
|
|
backup_file "${TARGET_HOME}/.zshrc"
|
|
|
|
install -m 644 "${ROOT}/.zshenv" "${TARGET_HOME}/.zshenv"
|
|
install -m 644 "${ROOT}/.zshrc" "${TARGET_HOME}/.zshrc"
|
|
|
|
# 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"
|
|
|
|
# Ensure intentional grok shim when the binary tree exists.
|
|
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
|
|
|
|
echo "Deploy complete."
|
|
echo "Verify: env -i HOME=\"$TARGET_HOME\" PATH=/usr/bin:/bin zsh -c '. ~/.zshenv; command -v grok'"
|