#!/usr/bin/env bash # Install managed easyzsh shell layout into a home directory. # Overwrites: ~/.zshenv ~/.zshrc ~/.zprofile ~/.config/zsh/{env,interactive,login} # Never overwrites: ~/.config/zsh/local/** ~/.config/zshrc/** ~/.local/bin/** # # Usage: # ./deploy.sh # ./deploy.sh --home /path/to/home # ./deploy.sh --migrate-zprofile # move existing ~/.zprofile body into login.local 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" 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 } echo "Deploying easyzsh managed shell layout into $TARGET_HOME" mkdir -p \ "${ZSH_CFG}/env" \ "${ZSH_CFG}/interactive" \ "${ZSH_CFG}/login" \ "${ZSH_CFG}/local" \ "${TARGET_HOME}/.config/zshrc" \ "${TARGET_HOME}/.cache/zsh" \ "${TARGET_HOME}/.local/bin" # Migrate legacy early env if present and new path missing. if [[ -f "${TARGET_HOME}/.config/zshenv.local" && ! -f "${ZSH_CFG}/local/env.local.zsh" ]]; then cp -Pp "${TARGET_HOME}/.config/zshenv.local" "${ZSH_CFG}/local/env.local.zsh" echo "Migrated ~/.config/zshenv.local -> ~/.config/zsh/local/env.local.zsh" fi # Optionally migrate existing zprofile body into login.local (strip redundant PATH). if [[ "$MIGRATE_ZPROFILE" -eq 1 ]]; then if [[ -f "${TARGET_HOME}/.zprofile" && ! -f "${ZSH_CFG}/local/login.local.zsh" ]]; then # Drop easyzsh stub lines and pipx ~/.local/bin PATH (now owned by env/00-path.zsh). awk ' /^# easyzsh stub/ { next } /^EASYZSH_HOME=/ { next } /for _easyzsh_f in/ { skip=1 } skip && /unset _easyzsh_f/ { skip=0; next } skip { next } /login\.local\.zsh/ { next } /Created by `pipx`/ { skip_pipx=1; next } skip_pipx && /local\/bin/ { skip_pipx=0; next } skip_pipx { next } { print } ' "${TARGET_HOME}/.zprofile" > "${ZSH_CFG}/local/login.local.zsh" # Trim trailing blank lines noise if file became empty-ish if ! grep -q '[^[:space:]]' "${ZSH_CFG}/local/login.local.zsh"; then rm -f "${ZSH_CFG}/local/login.local.zsh" echo "Existing .zprofile had no login-local content after migration" else echo "Migrated ~/.zprofile body -> ~/.config/zsh/local/login.local.zsh" fi fi fi backup_file "${TARGET_HOME}/.zshenv" backup_file "${TARGET_HOME}/.zshrc" backup_file "${TARGET_HOME}/.zprofile" install -m 644 "${ROOT}/.zshenv" "${TARGET_HOME}/.zshenv" install -m 644 "${ROOT}/.zshrc" "${TARGET_HOME}/.zshrc" install -m 644 "${ROOT}/.zprofile" "${TARGET_HOME}/.zprofile" # Replace managed modules only (never touch local/). rm -f "${ZSH_CFG}/env"/*.zsh rm -f "${ZSH_CFG}/interactive"/*.zsh rm -f "${ZSH_CFG}/login"/*.zsh install -m 644 "${ROOT}/config/env/"*.zsh "${ZSH_CFG}/env/" install -m 644 "${ROOT}/config/interactive/"*.zsh "${ZSH_CFG}/interactive/" # login may be empty of modules if compgen -G "${ROOT}/config/login/"*.zsh > /dev/null; then install -m 644 "${ROOT}/config/login/"*.zsh "${ZSH_CFG}/login/" fi # Seed examples only when missing (never overwrite user locals). for example in env.local.zsh login.local.zsh interactive.local.zsh; do src="${ROOT}/local/${example}.example" dst="${ZSH_CFG}/local/${example}.example" if [[ -f "$src" ]]; then install -m 644 "$src" "$dst" fi done # 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; echo PATH=\$PATH'"