#!/usr/bin/env sh # zsh/install.sh — install zsh + oh-my-zsh and make zsh the login shell. # Works on RHEL-family (dnf/yum) and Debian-family (apt) hosts. Idempotent: # safe to re-run on a box that already has some/all of this in place. set -eu SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" REPO_ROOT="$(dirname -- "$SCRIPT_DIR")" # shellcheck source=../lib/os-detect.sh . "$REPO_ROOT/lib/os-detect.sh" say() { printf '%s\n' "$*" >&2; } # --- 1. install zsh --------------------------------------------------------- if command -v zsh >/dev/null 2>&1; then say "zsh already installed ($(command -v zsh))." else say "Installing zsh..." pkg_install zsh fi ZSH_BIN="$(command -v zsh)" # --- 2. install oh-my-zsh (unattended) -------------------------------------- if [ -d "$HOME/.oh-my-zsh" ]; then say "oh-my-zsh already installed." else say "Installing oh-my-zsh..." command -v curl >/dev/null 2>&1 || pkg_install curl RUNZSH=no CHSH=no KEEP_ZSHRC=yes \ sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" fi # --- 3. install our .zshrc --------------------------------------------------- DEST="$HOME/.zshrc" SRC="$SCRIPT_DIR/zshrc" if [ -f "$DEST" ] && ! cmp -s "$SRC" "$DEST"; then BACKUP="$DEST.bak.$(date +%Y%m%d%H%M%S 2>/dev/null || printf 'pre-dotfiles')" say "Backing up existing $DEST -> $BACKUP" cp "$DEST" "$BACKUP" fi cp "$SRC" "$DEST" say "Installed $DEST" # --- 4. make zsh the login shell -------------------------------------------- if ! grep -qxF "$ZSH_BIN" /etc/shells 2>/dev/null; then say "Adding $ZSH_BIN to /etc/shells" echo "$ZSH_BIN" | sudo tee -a /etc/shells >/dev/null fi CURRENT_SHELL="$(getent passwd "$(id -un)" 2>/dev/null | cut -d: -f7)" if [ "$CURRENT_SHELL" = "$ZSH_BIN" ]; then say "zsh is already the login shell." else say "Changing login shell to $ZSH_BIN" # Use usermod (root, via sudo) instead of chsh: chsh runs its own PAM # authentication for the invoking user's password, which fails with a PAM # error in minimal containers (e.g. Kasm images) even when sudo itself is # passwordless. usermod edits /etc/passwd directly as root, no PAM prompt. sudo usermod -s "$ZSH_BIN" "$(id -un)" fi # --- 5. bash fallback: auto-exec into zsh ----------------------------------- # Some environments (e.g. Kasm Workspaces) hardcode/inherit bash for new # terminals regardless of the /etc/passwd shell, and reset the root # filesystem (including installed packages and /etc/passwd) on rebuild while # keeping $HOME. Sourcing this from ~/.bashrc — which lives in $HOME — makes # every fresh bash self-heal into zsh, reinstalling it first if needed. BASHRC="$HOME/.bashrc" HOOK_MARKER="# >>> dotfiles zsh auto-exec >>>" if [ -f "$BASHRC" ] && grep -qF "$HOOK_MARKER" "$BASHRC" 2>/dev/null; then say "bash auto-exec hook already present in $BASHRC." else say "Adding zsh auto-exec hook to $BASHRC (covers terminals that always spawn bash)." { printf '\n%s\n' "$HOOK_MARKER" printf '%s\n' "# Re-exec into zsh for interactive shells, self-healing (reinstalling" printf '%s\n' "# zsh) if this container's root filesystem was reset since last run." printf '%s\n' 'if [ -n "$PS1" ] && [ -z "${ZSH_VERSION:-}" ]; then' printf ' %s\n' "command -v zsh >/dev/null 2>&1 || \"$SCRIPT_DIR/install.sh\" >/dev/null 2>&1 || true" printf ' %s\n' 'command -v zsh >/dev/null 2>&1 && exec zsh -l' printf '%s\n' 'fi' printf '%s\n' "# <<< dotfiles zsh auto-exec <<<" } >> "$BASHRC" fi say "Done. Open a new terminal to use zsh."