diff --git a/ZSH StianNOR install.png b/ZSH StianNOR install.png new file mode 100644 index 0000000..c9d7b33 Binary files /dev/null and b/ZSH StianNOR install.png differ diff --git a/uninstall.sh b/uninstall.sh new file mode 100644 index 0000000..f15dac6 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,459 @@ +#!/bin/bash +# ============================================================================= +# đŸ—‘ī¸ StianNOR — COMPLETE UNINSTALLER v1.0 +# Uninstalls everything installed by: +# - setup_zsh_font.sh (Zsh, Oh My Zsh, p10k, plugins, colorls, fastfetch, Hack Nerd Font) +# - portainerup.sh (Portainer CE, Docker, Docker Compose) +# ============================================================================= +set -uo pipefail + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' +BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' +LINE="────────────────────────────────────────────────────────────────────────────" + +info() { echo -e "${CYAN}ℹ $*${RESET}"; } +success() { echo -e "${GREEN}✅ $*${RESET}"; } +warn() { echo -e "${YELLOW}⚠ $*${RESET}"; } +error() { echo -e "${RED}❌ $*${RESET}"; } +step() { echo -e "\n${BOLD}${BLUE}➤ $*${RESET}"; echo -e "${BLUE}${LINE}${RESET}"; } +skip() { echo -e " ${BLUE}↷ $* — not found, skipping${RESET}"; } + +# ============================================================================= +# DETECT PACKAGE MANAGER +# ============================================================================= +detect_pm() { + if command -v pacman >/dev/null 2>&1; then PM="pacman" + elif command -v apt >/dev/null 2>&1; then PM="apt" + elif command -v dnf >/dev/null 2>&1; then PM="dnf" + elif command -v zypper >/dev/null 2>&1; then PM="zypper" + elif command -v apk >/dev/null 2>&1; then PM="apk" + else PM="unknown"; fi +} +detect_pm + +pkg_remove() { + case "$PM" in + pacman) sudo pacman -Rns --noconfirm "$@" 2>/dev/null || true ;; + apt) sudo apt remove -y --purge "$@" 2>/dev/null || true; sudo apt autoremove -y 2>/dev/null || true ;; + dnf) sudo dnf remove -y "$@" 2>/dev/null || true ;; + zypper) sudo zypper remove -y "$@" 2>/dev/null || true ;; + apk) sudo apk del "$@" 2>/dev/null || true ;; + esac +} + +# ============================================================================= +# CONFIRM — show full plan before doing anything +# ============================================================================= +clear +echo -e "${LINE}" +echo -e "${BOLD}${RED} đŸ—‘ī¸ StianNOR COMPLETE UNINSTALLER${RESET}" +echo -e "${LINE}" +echo "" +echo -e " This will remove the following — ${BOLD}select what to uninstall:${RESET}" +echo "" +echo -e " ${BOLD}${CYAN}[A] ZSH + FONT SETUP${RESET}" +echo -e " â€ĸ Hack Nerd Font (system-wide)" +echo -e " â€ĸ fontconfig monospace alias (/etc/fonts/local.conf)" +echo -e " â€ĸ Oh My Zsh + Powerlevel10k + all plugins" +echo -e " â€ĸ colorls Ruby gem" +echo -e " â€ĸ fastfetch" +echo -e " â€ĸ ~/.zshrc and ~/.p10k.zsh" +echo -e " â€ĸ Default shell reverted to bash" +echo "" +echo -e " ${BOLD}${CYAN}[B] PORTAINER + DOCKER${RESET}" +echo -e " â€ĸ Portainer CE container + data volume" +echo -e " â€ĸ Docker CE + Docker Compose" +echo -e " â€ĸ docker group membership for $USER" +echo -e " â€ĸ Docker config dir (~/.docker)" +echo "" +echo -e " ${BOLD}${CYAN}[C] BOTH (full wipe)${RESET}" +echo "" +echo -e "${LINE}" +echo -e " ${YELLOW}[q]${RESET} cancel and exit" +echo -e "${LINE}" +echo "" +printf " Choice: " +read -r CHOICE +echo "" + +case "${CHOICE,,}" in + a) DO_ZSH=true; DO_DOCKER=false ;; + b) DO_ZSH=false; DO_DOCKER=true ;; + c) DO_ZSH=true; DO_DOCKER=true ;; + q|"") echo -e " ${YELLOW}Cancelled.${RESET}"; exit 0 ;; + *) echo -e " ${RED}Invalid choice. Exiting.${RESET}"; exit 1 ;; +esac + +# Final confirm +echo -e "${LINE}" +echo -e " ${BOLD}${RED}This is destructive and cannot be undone.${RESET}" +printf " Type ${BOLD}YES${RESET} to confirm: " +read -r CONFIRM +echo "" +[[ "$CONFIRM" != "YES" ]] && echo -e " ${YELLOW}Aborted.${RESET}" && exit 0 + +# ============================================================================= +# LOG FILE +# ============================================================================= +LOG_DIR="$HOME/Homelab/logs" +mkdir -p "$LOG_DIR" 2>/dev/null || LOG_DIR="/tmp" +LOG_FILE="$LOG_DIR/uninstall-$(date +%Y%m%d-%H%M%S).log" +exec > >(tee -a "$LOG_FILE") 2>&1 +info "Logging to $LOG_FILE" + +# ============================================================================= +# A — UNINSTALL ZSH + FONT SETUP +# ============================================================================= +uninstall_zsh_font() { + + # ── 1. Revert default shell to bash ────────────────────────────────────── + step "Reverting default shell to bash" + BASH_PATH=$(command -v bash 2>/dev/null || echo "/bin/bash") + CURRENT_SHELL=$(getent passwd "$USER" | cut -d: -f7) + if [[ "$CURRENT_SHELL" == *zsh* ]]; then + grep -qxF "$BASH_PATH" /etc/shells || echo "$BASH_PATH" | sudo tee -a /etc/shells + if chsh -s "$BASH_PATH" 2>/dev/null; then + success "Default shell reverted to $BASH_PATH" + else + warn "Could not change shell automatically — run: chsh -s $BASH_PATH" + fi + else + skip "Shell was not zsh ($CURRENT_SHELL)" + fi + + # ── 2. Remove Oh My Zsh ────────────────────────────────────────────────── + step "Removing Oh My Zsh" + if [[ -d "$HOME/.oh-my-zsh" ]]; then + rm -rf "$HOME/.oh-my-zsh" + success "Oh My Zsh removed" + else + skip "~/.oh-my-zsh" + fi + + # ── 3. Remove Powerlevel10k (standalone clone, if outside OMZ) ─────────── + step "Removing Powerlevel10k" + for p10k_path in \ + "$HOME/.oh-my-zsh/custom/themes/powerlevel10k" \ + "$HOME/powerlevel10k" \ + "${ZSH_CUSTOM:-}/themes/powerlevel10k"; do + if [[ -d "$p10k_path" ]]; then + rm -rf "$p10k_path" + success "Removed $p10k_path" + fi + done + + # ── 4. Remove Zsh plugin dirs ───────────────────────────────────────────── + step "Removing Zsh plugins" + for plugin in zsh-autosuggestions zsh-syntax-highlighting zsh-completions; do + PLUGIN_DIR="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/$plugin" + if [[ -d "$PLUGIN_DIR" ]]; then + rm -rf "$PLUGIN_DIR" + success "Removed plugin: $plugin" + else + skip "Plugin $plugin" + fi + done + + # ── 5. Remove colorls gem ───────────────────────────────────────────────── + step "Removing colorls Ruby gem" + if gem list -i colorls >/dev/null 2>&1; then + gem uninstall colorls -a -x 2>/dev/null && success "colorls removed" \ + || warn "colorls uninstall had warnings" + else + skip "colorls gem" + fi + # Clean up gem home if empty + GEM_HOME_DIR="$HOME/.gem" + if [[ -d "$GEM_HOME_DIR" ]]; then + REMAINING=$(find "$GEM_HOME_DIR" -name "*.gemspec" 2>/dev/null | wc -l) + if [[ "$REMAINING" -eq 0 ]]; then + rm -rf "$GEM_HOME_DIR" + success "Removed empty ~/.gem" + else + info "$REMAINING other gems remain in ~/.gem — leaving it" + fi + fi + + # ── 6. Remove fastfetch ─────────────────────────────────────────────────── + step "Removing fastfetch" + if command -v fastfetch >/dev/null 2>&1; then + FF_PATH=$(command -v fastfetch) + # If installed via package manager + removed=false + case "$PM" in + pacman) sudo pacman -Rns --noconfirm fastfetch 2>/dev/null && removed=true ;; + apt) sudo apt remove -y --purge fastfetch 2>/dev/null && removed=true ;; + dnf) sudo dnf remove -y fastfetch 2>/dev/null && removed=true ;; + zypper) sudo zypper remove -y fastfetch 2>/dev/null && removed=true ;; + apk) sudo apk del fastfetch 2>/dev/null && removed=true ;; + esac + # If it was a manually installed binary + if [[ "$removed" == false ]] || command -v fastfetch >/dev/null 2>&1; then + sudo rm -f /usr/local/bin/fastfetch /usr/bin/fastfetch 2>/dev/null || true + fi + command -v fastfetch >/dev/null 2>&1 \ + && warn "fastfetch still found at $(command -v fastfetch) — remove manually" \ + || success "fastfetch removed" + else + skip "fastfetch" + fi + + # ── 7. Remove Zsh itself ────────────────────────────────────────────────── + step "Removing Zsh" + if command -v zsh >/dev/null 2>&1; then + printf " Remove zsh package itself? (leaves system zsh on some distros) [y/N]: " + read -r yn + if [[ "${yn,,}" == "y" ]]; then + pkg_remove zsh + command -v zsh >/dev/null 2>&1 \ + && warn "zsh still present (may be a system dependency)" \ + || success "zsh removed" + else + info "Zsh package kept (shell reverted to bash above)" + fi + else + skip "zsh (not installed)" + fi + + # ── 8. Remove dot files ─────────────────────────────────────────────────── + step "Removing ~/.zshrc and ~/.p10k.zsh" + for f in "$HOME/.zshrc" "$HOME/.p10k.zsh" "$HOME/.zsh_history"; do + if [[ -f "$f" ]]; then + # Back up before deleting + cp "$f" "${f}.uninstall-backup-$(date +%Y%m%d)" 2>/dev/null || true + rm -f "$f" + success "Removed $f (backup saved as ${f}.uninstall-backup-*)" + else + skip "$f" + fi + done + + # ── 9. Remove Hack Nerd Font ────────────────────────────────────────────── + step "Removing Hack Nerd Font" + FONT_DIR="/usr/local/share/fonts/nerd-fonts" + if [[ -d "$FONT_DIR" ]]; then + sudo rm -rf "$FONT_DIR" + sudo fc-cache -f 2>/dev/null || true + success "Hack Nerd Font removed from $FONT_DIR" + else + skip "$FONT_DIR" + fi + # Also check user font dir + USER_FONT_DIR="$HOME/.local/share/fonts/nerd-fonts" + if [[ -d "$USER_FONT_DIR" ]]; then + rm -rf "$USER_FONT_DIR" + fc-cache -f "$HOME/.local/share/fonts" 2>/dev/null || true + success "Removed user font dir $USER_FONT_DIR" + fi + + # ── 10. Remove fontconfig override ─────────────────────────────────────── + step "Removing fontconfig monospace alias" + if [[ -f /etc/fonts/local.conf ]]; then + if grep -q "Hack Nerd Font" /etc/fonts/local.conf 2>/dev/null; then + sudo rm -f /etc/fonts/local.conf + sudo fc-cache -f 2>/dev/null || true + success "Removed /etc/fonts/local.conf" + else + info "/etc/fonts/local.conf exists but doesn't contain Hack Nerd Font — leaving it" + fi + else + skip "/etc/fonts/local.conf" + fi + + # ── 11. Clean up PATH entries added to shell rc files ──────────────────── + step "Cleaning gem PATH entries from shell rc files" + for rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.bash_profile" "$HOME/.profile"; do + if [[ -f "$rc" ]]; then + if grep -q "\.gem" "$rc" 2>/dev/null; then + sed -i '/\.gem\/ruby/d' "$rc" 2>/dev/null || true + sed -i '/GEM_HOME/d' "$rc" 2>/dev/null || true + success "Cleaned gem PATH from $rc" + fi + fi + done +} + +# ============================================================================= +# B — UNINSTALL PORTAINER + DOCKER +# ============================================================================= +uninstall_docker_portainer() { + + # ── 1. Stop and remove Portainer ───────────────────────────────────────── + step "Removing Portainer CE" + if command -v docker >/dev/null 2>&1; then + DOCKER_CMD="docker" + docker info >/dev/null 2>&1 || DOCKER_CMD="sudo docker" + + if $DOCKER_CMD ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^portainer$"; then + $DOCKER_CMD stop portainer 2>/dev/null || true + $DOCKER_CMD rm portainer 2>/dev/null || true + success "Portainer container stopped and removed" + else + skip "Portainer container" + fi + + # Remove portainer data volume + if $DOCKER_CMD volume ls --format '{{.Name}}' 2>/dev/null | grep -q "^portainer_data$"; then + printf " Remove portainer_data volume? This deletes all Portainer settings [y/N]: " + read -r yn + if [[ "${yn,,}" == "y" ]]; then + $DOCKER_CMD volume rm portainer_data 2>/dev/null || true + success "portainer_data volume removed" + else + info "portainer_data volume kept" + fi + else + skip "portainer_data volume" + fi + + # Remove the image + if $DOCKER_CMD image ls --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q "portainer/portainer-ce"; then + printf " Remove Portainer CE docker image? [y/N]: " + read -r yn + if [[ "${yn,,}" == "y" ]]; then + $DOCKER_CMD rmi portainer/portainer-ce:latest 2>/dev/null || true + success "Portainer CE image removed" + else + info "Portainer CE image kept" + fi + fi + else + skip "Docker not installed — Portainer check skipped" + fi + + # ── 2. Remove Docker ────────────────────────────────────────────────────── + step "Removing Docker" + if command -v docker >/dev/null 2>&1; then + printf " ${BOLD}${RED}Remove Docker CE completely?${RESET} This removes ALL containers, images and volumes [y/N]: " + read -r yn + if [[ "${yn,,}" != "y" ]]; then + info "Docker kept — only Portainer was removed" + return + fi + + # Stop daemon first + sudo systemctl stop docker docker.socket 2>/dev/null || sudo service docker stop 2>/dev/null || true + sudo systemctl disable docker docker.socket 2>/dev/null || true + + case "$PM" in + pacman) + sudo pacman -Rns --noconfirm docker docker-compose 2>/dev/null || true + ;; + apt) + sudo apt remove -y --purge \ + docker-ce docker-ce-cli containerd.io \ + docker-buildx-plugin docker-compose-plugin \ + docker-compose 2>/dev/null || true + sudo apt autoremove -y 2>/dev/null || true + # Remove Docker apt repo + sudo rm -f /etc/apt/sources.list.d/docker.list + sudo rm -f /etc/apt/keyrings/docker.gpg + sudo apt update -qq 2>/dev/null || true + ;; + dnf) + sudo dnf remove -y \ + docker-ce docker-ce-cli containerd.io \ + docker-buildx-plugin docker-compose-plugin 2>/dev/null || true + sudo rm -f /etc/yum.repos.d/docker-ce.repo 2>/dev/null || true + ;; + zypper) + sudo zypper remove -y docker docker-compose 2>/dev/null || true + ;; + apk) + sudo apk del docker docker-compose 2>/dev/null || true + sudo rc-update del docker boot 2>/dev/null || true + ;; + esac + + # Remove standalone docker-compose binary if present + [[ -f /usr/local/bin/docker-compose ]] && sudo rm -f /usr/local/bin/docker-compose \ + && success "Removed standalone docker-compose" + + success "Docker CE removed" + else + skip "Docker (not installed)" + fi + + # ── 3. Remove Docker data directories ──────────────────────────────────── + step "Removing Docker data directories" + for dir in /var/lib/docker /var/lib/containerd /etc/docker; do + if [[ -d "$dir" ]]; then + printf " Remove %s? [y/N]: " "$dir" + read -r yn + if [[ "${yn,,}" == "y" ]]; then + sudo rm -rf "$dir" + success "Removed $dir" + else + info "Kept $dir" + fi + else + skip "$dir" + fi + done + + # Remove user docker config + if [[ -d "$HOME/.docker" ]]; then + rm -rf "$HOME/.docker" + success "Removed ~/.docker" + fi + + # ── 4. Remove user from docker group ───────────────────────────────────── + step "Removing $USER from docker group" + if getent group docker >/dev/null 2>&1; then + if groups "$USER" | grep -qw docker; then + sudo gpasswd -d "$USER" docker 2>/dev/null \ + && success "$USER removed from docker group" \ + || warn "Could not remove $USER from docker group" + else + skip "$USER was not in docker group" + fi + printf " Delete the docker group itself? [y/N]: " + read -r yn + if [[ "${yn,,}" == "y" ]]; then + sudo groupdel docker 2>/dev/null && success "docker group deleted" \ + || warn "Could not delete docker group" + fi + else + skip "docker group (does not exist)" + fi + + # ── 5. Remove Docker socket and service files (if any remain) ──────────── + step "Cleaning up Docker service files" + for f in \ + /lib/systemd/system/docker.service \ + /lib/systemd/system/docker.socket \ + /etc/systemd/system/docker.service \ + /etc/systemd/system/docker.socket; do + if [[ -f "$f" ]]; then + sudo rm -f "$f" + success "Removed $f" + fi + done + sudo systemctl daemon-reload 2>/dev/null || true +} + +# ============================================================================= +# RUN SELECTED SECTIONS +# ============================================================================= +[[ "$DO_ZSH" == true ]] && uninstall_zsh_font +[[ "$DO_DOCKER" == true ]] && uninstall_docker_portainer + +# ============================================================================= +# DONE +# ============================================================================= +echo "" +echo -e "${LINE}" +echo -e "${BOLD}${GREEN} ✅ Uninstall complete!${RESET}" +echo -e "${LINE}" +[[ "$DO_ZSH" == true ]] && echo -e " ${CYAN}Zsh/Font:${RESET} removed — default shell reverted to bash" +[[ "$DO_DOCKER" == true ]] && echo -e " ${CYAN}Docker:${RESET} removed — log out and back in to clear group session" +echo -e " ${YELLOW}Log:${RESET} $LOG_FILE" +echo -e "${LINE}" +echo "" +if [[ "$DO_ZSH" == true ]]; then + echo -e " ${YELLOW}Note:${RESET} Log out and back in for shell change to take full effect." +fi +if [[ "$DO_DOCKER" == true ]]; then + echo -e " ${YELLOW}Note:${RESET} Log out and back in to clear docker group from your session." +fi +echo "" diff --git a/uninstall_zsh_setup.sh b/uninstall_zsh_setup.sh new file mode 100644 index 0000000..dd143c7 --- /dev/null +++ b/uninstall_zsh_setup.sh @@ -0,0 +1,94 @@ +#!/bin/bash +set -e # Exit immediately if a command exits with a non-zero status + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BOLD='\033[1m' +NC='\033[0m' # No Color + +info() { echo -e "${YELLOW}â„šī¸ $*${NC}"; } +success() { echo -e "${GREEN}✅ $*${NC}"; } +warn() { echo -e "${YELLOW}âš ī¸ $*${NC}"; } +error() { echo -e "${RED}❌ $*${NC}"; } +step() { echo -e "${BOLD}${YELLOW}--- $* ---${NC}"; } + +step "Uninstalling Zsh/Powerlevel10k setup and related tools" + +# 1. Remove Oh My Zsh directory +info "Removing Oh My Zsh directory..." +if [ -d "$HOME/.oh-my-zsh" ]; then + rm -rf "$HOME/.oh-my-zsh" + success "Oh My Zsh directory removed." +else + warn "Oh My Zsh directory not found." +fi + +# 2. Remove Zsh configuration files +info "Removing Zsh configuration files..." +ZSH_CONFIG_FILES=("$HOME/.zshrc" "$HOME/.zshenv" "$HOME/.zprofile" "$HOME/.zlogin" "$HOME/.p10k.zsh") +for file in "${ZSH_CONFIG_FILES[@]}"; do + if [ -f "$file" ]; then + rm -f "$file" + success "Removed $file" + else + warn "$file not found." + fi +done + +# 3. Remove up.sh maintenance script +info "Removing up.sh maintenance script..." +if [ -f "$HOME/Documents/up.sh" ]; then + rm -f "$HOME/Documents/up.sh" + success "up.sh maintenance script removed." +else + warn "up.sh maintenance script not found." +fi + +# 4. Uninstall colorls (user gem) +info "Uninstalling colorls Ruby gem..." +# Ensure GEM_HOME and PATH are set correctly for user gems +export GEM_HOME="$HOME/.gem" +export PATH="$PATH:$GEM_HOME/bin" +if gem list -i colorls >/dev/null 2>&1; then + if gem uninstall colorls; then # Removed -x and sudo + success "colorls uninstalled." + else + error "Failed to uninstall colorls." + fi +else + warn "colorls gem not found." +fi + +# 5. Uninstall fastfetch +info "Uninstalling fastfetch..." +if command -v fastfetch >/dev/null 2>&1; then + # Assume it was installed via the curl installer which puts files in /usr/local + if sudo rm -f /usr/local/bin/fastfetch && sudo rm -rf /usr/local/share/fastfetch; then + success "fastfetch uninstalled." + else + error "Failed to remove fastfetch binaries. Manual removal may be needed." + fi +else + warn "fastfetch not found." +fi + + +# 6. Optionally restore default shell to bash +# Check if zsh is the current default shell before attempting to change +CURRENT_DEFAULT_SHELL=$(getent passwd "$USER" | awk -F: '{print $NF}') +if [ "$CURRENT_DEFAULT_SHELL" != "/bin/bash" ] && [ -f "/bin/bash" ]; then + info "Restoring default shell to /bin/bash..." + if chsh -s /bin/bash "$USER"; then # Specify user for chsh + success "Default shell changed to /bin/bash." + echo -e "${GREEN}Please log out and log in again for shell changes to take effect.${NC}" + else + error "Failed to change default shell to /bin/bash. You may need to do it manually." + fi +else + success "Default shell is already /bin/bash or /bin/bash not found." +fi + +echo -e "${BOLD}${GREEN}✅ Uninstallation complete.${NC}" +echo -e "${BOLD}${GREEN}Note: Log out and log back in for shell changes to fully apply.${NC}" diff --git a/up.sh b/up.sh new file mode 100644 index 0000000..06d7405 Binary files /dev/null and b/up.sh differ diff --git a/update_zshrc.sh b/update_zshrc.sh new file mode 100644 index 0000000..eb562f6 --- /dev/null +++ b/update_zshrc.sh @@ -0,0 +1,186 @@ +#!/bin/bash +# ============================================================================= +# 🔧 StianNOR-ZSHkit — .zshrc Merge Script +# Merges managed sections from ZSHkit/.zshrc into the live ~/.zshrc +# without overwriting personal aliases or custom configuration. +# +# How it works: +# - Managed sections in ~/.zshrc are wrapped with markers: +# # --- ZSHKIT:section_name --- +# ... content ... +# # --- /ZSHKIT:section_name --- +# - On first run, markers are injected into your existing ~/.zshrc +# - On subsequent runs, content between markers is updated from ZSHkit +# - Everything outside the markers is never touched +# +# Usage: +# ./update_zshrc.sh — merge and apply +# ./update_zshrc.sh --dry-run — show what would change, no writes +# ./update_zshrc.sh --status — show which sections are present +# ============================================================================= +set -euo pipefail + +GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m' +RED='\033[0;31m'; BOLD='\033[1m'; RESET='\033[0m' +LINE="────────────────────────────────────────────────────────────────────────────" + +info() { echo -e "${CYAN}ℹ $*${RESET}"; } +success() { echo -e "${GREEN}✅ $*${RESET}"; } +warn() { echo -e "${YELLOW}⚠ $*${RESET}"; } +error() { echo -e "${RED}❌ $*${RESET}"; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SOURCE="$SCRIPT_DIR/.zshrc" +TARGET="$HOME/.zshrc" +BACKUP="$HOME/.zshrc.bak.$(date +%Y%m%d-%H%M%S)" +DRY_RUN=false +STATUS_ONLY=false + +case "${1:-}" in + --dry-run) DRY_RUN=true ;; + --status) STATUS_ONLY=true ;; +esac + +if [[ ! -f "$SOURCE" ]]; then + error "Source not found: $SOURCE" + exit 1 +fi + +if [[ ! -f "$TARGET" ]]; then + warn "No ~/.zshrc found — copying source directly" + cp "$SOURCE" "$TARGET" + success "Created $TARGET from ZSHkit source" + exit 0 +fi + +declare -A SECTION_START=( + [autosuggestions]="# ─── Autosuggestions" + [aliases]="# ─── User Aliases and Functions" +) +declare -A SECTION_END=( + [autosuggestions]="source \$ZSH/oh-my-zsh.sh" + [aliases]="alias rsy=" +) + +# ============================================================================= +# STATUS MODE +# ============================================================================= +if [[ "$STATUS_ONLY" = true ]]; then + echo -e "\n${BOLD}${CYAN}🔧 .zshrc Managed Sections Status${RESET}" + echo -e "${LINE}" + for section in "${!SECTION_START[@]}"; do + marker="# --- ZSHKIT:${section} ---" + if grep -qF "$marker" "$TARGET" 2>/dev/null; then + success "Section present: $section" + else + warn "Section missing (will be injected on next run): $section" + fi + done + echo -e "${LINE}\n" + exit 0 +fi + +# ============================================================================= +# BACKUP +# ============================================================================= +cp "$TARGET" "$BACKUP" +info "Backup saved: $BACKUP" + +echo -e "\n${BOLD}${CYAN}🔧 Merging .zshrc sections${RESET}" +echo -e "${LINE}" + +CHANGED=0 + +merge_section() { + local name="$1" + local start_pat="$2" + local end_pat="$3" + + local begin_marker="# --- ZSHKIT:${name} ---" + local end_marker="# --- /ZSHKIT:${name} ---" + + local new_content + new_content=$(awk "/$start_pat/{found=1} found{print} /$end_pat/{if(found) {found=0}}" "$SOURCE") + + if [[ -z "$new_content" ]]; then + warn "Could not extract section '$name' from source — skipping" + return + fi + + local block + block="$begin_marker +$new_content +$end_marker" + + if grep -qF "$begin_marker" "$TARGET"; then + local existing + existing=$(awk "/$begin_marker/{found=1; next} /$end_marker/{found=0; next} found{print}" "$TARGET") + if [[ "$existing" == "$new_content" ]]; then + success "Up to date: $name" + return + fi + if [[ "$DRY_RUN" = true ]]; then + warn "[DRY RUN] Would update section: $name" + diff <(echo "$existing") <(echo "$new_content") || true + else + python3 - "$TARGET" "$begin_marker" "$end_marker" "$new_content" <<'PYEOF' +import sys, re +target = sys.argv[1] +begin = sys.argv[2] +end = sys.argv[3] +new_body = sys.argv[4] +with open(target, 'r') as f: + content = f.read() +pattern = re.escape(begin) + r'.*?' + re.escape(end) +replacement = begin + '\n' + new_body + '\n' + end +new_content = re.sub(pattern, replacement, content, flags=re.DOTALL) +with open(target, 'w') as f: + f.write(new_content) +PYEOF + success "Updated section: $name" + (( CHANGED++ )) || true + fi + else + if [[ "$DRY_RUN" = true ]]; then + warn "[DRY RUN] Would inject new section: $name" + else + if grep -q "# End of .zshrc" "$TARGET"; then + python3 - "$TARGET" "# End of .zshrc" "$block" <<'PYEOF' +import sys +target = sys.argv[1] +marker = sys.argv[2] +new_block = sys.argv[3] +with open(target, 'r') as f: + content = f.read() +content = content.replace(marker, new_block + '\n\n' + marker) +with open(target, 'w') as f: + f.write(content) +PYEOF + else + echo "" >> "$TARGET" + echo "$block" >> "$TARGET" + fi + success "Injected new section: $name" + (( CHANGED++ )) || true + fi + fi +} + +merge_section "autosuggestions" \ + "# ─── Autosuggestions" \ + "source \\\$ZSH/oh-my-zsh.sh" + +merge_section "aliases" \ + "# ─── User Aliases and Functions" \ + "alias rsy=" + +echo -e "${LINE}" +if [[ "$DRY_RUN" = true ]]; then + warn "Dry run complete — no files were modified" +elif [[ $CHANGED -eq 0 ]]; then + success "Everything already up to date — no changes needed" +else + success "$CHANGED section(s) updated in $TARGET" + echo -e "\n ${YELLOW}Run ${CYAN}fresh${RESET}${YELLOW} or open a new terminal to apply changes.${RESET}" +fi +echo -e "${LINE}\n"