Files
dotfiles/bin/deploy-zshtheme-systemwide.sh
2025-12-13 23:52:12 -05:00

597 lines
17 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ============================================================================
# ADLee Theme System-wide Deployment Script (Enhanced)
# ============================================================================
# This script deploys the adlee.zsh-theme system-wide via symlinks
# Usage:
# sudo ./deploy-theme-systemwide.sh # Interactive mode
# sudo ./deploy-theme-systemwide.sh --all # All users with TTY
# sudo ./deploy-theme-systemwide.sh --current # Current user + root only
# sudo ./deploy-theme-systemwide.sh --force # Force replace all links
set -euo pipefail
# Configuration
MASTER_THEME_DIR="/usr/local/share/zsh/themes"
MASTER_THEME_PATH="${MASTER_THEME_DIR}/adlee.zsh-theme"
THEME_NAME="adlee.zsh-theme"
SOURCE_THEME="${HOME}/.dotfiles/zsh/themes/${THEME_NAME}"
FORCE_REPLACE=false
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# ============================================================================
# Helper Functions
# ============================================================================
print_success() {
echo -e "${GREEN}${NC} $1"
}
print_warning() {
echo -e "${YELLOW}${NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1"
}
print_info() {
echo -e "${CYAN}${NC} $1"
}
print_step() {
echo -e "\n${GREEN}==>${NC} $1"
}
print_header() {
echo -e "\n${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}${NC} ADLee Theme System-wide Deployment ${BLUE}${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}\n"
}
# Check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
print_error "This script must be run as root (use sudo)"
echo "Usage: sudo $0 [--all|--current]"
exit 1
fi
}
# Ask yes/no question
ask_yes_no() {
local prompt="$1"
local default="${2:-y}"
if [[ "$default" == "y" ]]; then
prompt="$prompt [Y/n]: "
else
prompt="$prompt [y/N]: "
fi
read -p "$prompt" response
response=${response:-$default}
[[ "$response" =~ ^[Yy]$ ]]
}
# Check if a path is a symlink pointing to master theme
is_system_symlink() {
local path="$1"
[[ -L "$path" ]] && [[ "$(readlink -f "$path")" == "$MASTER_THEME_PATH" ]]
}
# Check if a path is a local symlink (from dotfiles)
is_local_symlink() {
local path="$1"
if [[ -L "$path" ]]; then
local target=$(readlink -f "$path")
[[ "$target" == *"/.dotfiles/"* ]]
else
return 1
fi
}
# Check if a path is a regular file
is_regular_file() {
local path="$1"
[[ -f "$path" ]] && [[ ! -L "$path" ]]
}
# Get link status description
get_link_status() {
local path="$1"
if [[ ! -e "$path" ]] && [[ ! -L "$path" ]]; then
echo "not_present"
elif is_system_symlink "$path"; then
echo "system_link"
elif is_local_symlink "$path"; then
echo "local_link"
elif is_regular_file "$path"; then
echo "regular_file"
elif [[ -L "$path" ]]; then
echo "other_link"
else
echo "unknown"
fi
}
# ============================================================================
# Master Theme Setup
# ============================================================================
setup_master_location() {
print_step "Setting up master theme location"
# Create directory if it doesn't exist
if [[ ! -d "$MASTER_THEME_DIR" ]]; then
mkdir -p "$MASTER_THEME_DIR"
print_success "Created directory: $MASTER_THEME_DIR"
fi
# Determine source file
local source_file=""
if [[ -f "$SOURCE_THEME" ]]; then
source_file="$SOURCE_THEME"
elif [[ -f "$THEME_NAME" ]]; then
source_file="$THEME_NAME"
elif [[ -f "$HOME/.oh-my-zsh/themes/$THEME_NAME" ]]; then
source_file="$HOME/.oh-my-zsh/themes/$THEME_NAME"
fi
# Copy or verify master theme
if [[ -f "$MASTER_THEME_PATH" ]]; then
print_info "Master theme already exists: $MASTER_THEME_PATH"
if [[ -n "$source_file" ]]; then
# Check if update is needed
if ! diff -q "$source_file" "$MASTER_THEME_PATH" &>/dev/null; then
print_warning "Source theme differs from master"
if ask_yes_no "Update master theme from source?"; then
cp "$source_file" "$MASTER_THEME_PATH"
chmod 644 "$MASTER_THEME_PATH"
print_success "Master theme updated"
fi
else
print_success "Master theme is up to date"
fi
fi
else
if [[ -z "$source_file" ]]; then
print_error "Theme file not found. Please ensure $THEME_NAME exists in:"
echo " - $SOURCE_THEME"
echo " - ./$THEME_NAME"
echo " - ~/.oh-my-zsh/themes/$THEME_NAME"
exit 1
fi
cp "$source_file" "$MASTER_THEME_PATH"
chmod 644 "$MASTER_THEME_PATH"
print_success "Copied theme to master location"
fi
}
# ============================================================================
# User Detection and Selection
# ============================================================================
get_users_with_tty() {
local users=()
# Add root if oh-my-zsh is installed
if [[ -d "/root/.oh-my-zsh" ]]; then
users+=("root")
fi
# Find regular users with oh-my-zsh
while IFS=: read -r username _ uid _ _ home_dir shell; do
# Skip system users (UID < 1000)
[[ $uid -lt 1000 ]] && continue
# Check if user has oh-my-zsh
if [[ -d "$home_dir/.oh-my-zsh" ]]; then
users+=("$username")
fi
done < /etc/passwd
echo "${users[@]}"
}
get_current_user() {
# Get the user who invoked sudo
if [[ -n "${SUDO_USER:-}" ]]; then
echo "$SUDO_USER"
else
# Fallback to first non-root user with oh-my-zsh
while IFS=: read -r username _ uid _ _ home_dir _; do
if [[ $uid -ge 1000 ]] && [[ -d "$home_dir/.oh-my-zsh" ]]; then
echo "$username"
return
fi
done < /etc/passwd
fi
}
select_users() {
local mode="$1"
local users=()
case "$mode" in
all)
users=($(get_users_with_tty))
;;
current)
local current_user=$(get_current_user)
if [[ -n "$current_user" ]]; then
users=("$current_user")
fi
if [[ -d "/root/.oh-my-zsh" ]]; then
users+=("root")
fi
;;
interactive)
local all_users=($(get_users_with_tty))
if [[ ${#all_users[@]} -eq 0 ]]; then
print_error "No users with oh-my-zsh found"
exit 1
fi
echo "Users with oh-my-zsh detected:"
for i in "${!all_users[@]}"; do
echo " $((i+1)). ${all_users[$i]}"
done
echo
if ask_yes_no "Deploy to all users?"; then
users=("${all_users[@]}")
else
local current_user=$(get_current_user)
if [[ -n "$current_user" ]]; then
users=("$current_user")
print_info "Selected: $current_user"
fi
if [[ -d "/root/.oh-my-zsh" ]]; then
if ask_yes_no "Include root user?"; then
users+=("root")
fi
fi
fi
;;
esac
echo "${users[@]}"
}
# ============================================================================
# Deployment Functions
# ============================================================================
analyze_user_theme() {
local username="$1"
local home_dir
if [[ "$username" == "root" ]]; then
home_dir="/root"
else
home_dir=$(eval echo "~$username")
fi
local theme_path="$home_dir/.oh-my-zsh/themes/$THEME_NAME"
local status=$(get_link_status "$theme_path")
echo "$status"
}
deploy_for_user() {
local username="$1"
local home_dir
# Get home directory
if [[ "$username" == "root" ]]; then
home_dir="/root"
else
home_dir=$(eval echo "~$username")
fi
# Check if user's home directory exists
if [[ ! -d "$home_dir" ]]; then
print_warning "Home directory not found for user: $username"
return 1
fi
local oh_my_zsh_themes="$home_dir/.oh-my-zsh/themes"
local theme_link="$oh_my_zsh_themes/$THEME_NAME"
# Check if oh-my-zsh is installed
if [[ ! -d "$home_dir/.oh-my-zsh" ]]; then
print_warning "oh-my-zsh not installed for user: $username"
return 1
fi
# Analyze current status
local status=$(get_link_status "$theme_link")
case "$status" in
system_link)
if [[ "$FORCE_REPLACE" == true ]]; then
print_info "Recreating system-wide link for: $username"
else
print_success "✓ Already using system-wide theme: $username"
return 0
fi
;;
local_link)
print_info "Converting local symlink → system-wide for: $username"
;;
regular_file)
print_warning "Replacing regular file → system-wide for: $username"
;;
other_link)
print_warning "Replacing unknown symlink → system-wide for: $username"
;;
not_present)
print_info "Creating new system-wide link for: $username"
;;
esac
# Create themes directory if it doesn't exist
if [[ ! -d "$oh_my_zsh_themes" ]]; then
mkdir -p "$oh_my_zsh_themes"
if [[ "$username" != "root" ]]; then
chown "$username:$(id -gn "$username" 2>/dev/null || echo "$username")" "$oh_my_zsh_themes"
fi
fi
# Remove existing file/link (this is the key fix!)
if [[ -e "$theme_link" ]] || [[ -L "$theme_link" ]]; then
rm -f "$theme_link"
fi
# Create symlink to master theme
ln -sf "$MASTER_THEME_PATH" "$theme_link"
# Fix ownership
if [[ "$username" != "root" ]]; then
chown -h "$username:$(id -gn "$username" 2>/dev/null || echo "$username")" "$theme_link" 2>/dev/null || true
fi
# Verify the symlink was created correctly
if is_system_symlink "$theme_link"; then
print_success "✓ Deployed system-wide theme for: $username"
else
print_error "✗ Failed to create system-wide link for: $username"
return 1
fi
}
# ============================================================================
# Status and Reporting
# ============================================================================
show_deployment_status() {
print_step "Current deployment status"
local users=($(get_users_with_tty))
if [[ ${#users[@]} -eq 0 ]]; then
print_warning "No users with oh-my-zsh found"
return
fi
echo
printf "%-20s %-20s %s\n" "User" "Status" "Details"
printf "%-20s %-20s %s\n" "----" "------" "-------"
for user in "${users[@]}"; do
local status=$(analyze_user_theme "$user")
local home_dir
if [[ "$user" == "root" ]]; then
home_dir="/root"
else
home_dir=$(eval echo "~$user")
fi
local theme_path="$home_dir/.oh-my-zsh/themes/$THEME_NAME"
case "$status" in
system_link)
printf "%-20s ${GREEN}%-20s${NC} %s\n" "$user" "System-wide ✓" "$MASTER_THEME_PATH"
;;
local_link)
local target=$(readlink "$theme_path")
printf "%-20s ${YELLOW}%-20s${NC} %s\n" "$user" "Local symlink" "$target"
;;
regular_file)
printf "%-20s ${CYAN}%-20s${NC} %s\n" "$user" "Regular file" "(standalone copy)"
;;
other_link)
local target=$(readlink "$theme_path" 2>/dev/null || echo "broken")
printf "%-20s ${YELLOW}%-20s${NC} %s\n" "$user" "Other symlink" "$target"
;;
not_present)
printf "%-20s ${RED}%-20s${NC} %s\n" "$user" "Not installed" ""
;;
*)
printf "%-20s ${RED}%-20s${NC} %s\n" "$user" "Unknown" ""
;;
esac
done
echo
}
# ============================================================================
# Main Execution
# ============================================================================
show_summary() {
echo
echo "============================================================================"
echo "Deployment Summary"
echo "============================================================================"
echo "Master theme location: $MASTER_THEME_PATH"
echo
echo "To update the theme in the future:"
echo " 1. Edit: $MASTER_THEME_PATH"
echo " 2. All users will get updates automatically (via symlinks)"
echo
echo "Alternatively, update from your dotfiles:"
echo " sudo cp ~/.dotfiles/zsh/themes/$THEME_NAME $MASTER_THEME_PATH"
echo
echo "To check deployment status:"
echo " sudo $0 --status"
echo "============================================================================"
}
main() {
local mode="interactive"
local status_only=false
# Parse arguments
for arg in "$@"; do
case "$arg" in
--all)
mode="all"
;;
--current)
mode="current"
;;
--force)
FORCE_REPLACE=true
;;
--status|-s)
status_only=true
;;
--help|-h)
echo "Usage: sudo $0 [OPTIONS]"
echo
echo "Options:"
echo " --all Deploy to all users with oh-my-zsh"
echo " --current Deploy to current user and root only"
echo " --force Force replacement even if system-wide link exists"
echo " --status Show current deployment status"
echo " --help Show this help message"
echo
echo "Examples:"
echo " sudo $0 # Interactive mode"
echo " sudo $0 --all # Deploy to all users"
echo " sudo $0 --current --force # Force update for current user + root"
echo
exit 0
;;
*)
print_error "Unknown option: $arg"
echo "Use --help for usage information"
exit 1
;;
esac
done
print_header
check_root
# Status only mode
if [[ "$status_only" == true ]]; then
show_deployment_status
exit 0
fi
# Show current status first
show_deployment_status
# Setup master location
setup_master_location
# Select users
local users=($(select_users "$mode"))
if [[ ${#users[@]} -eq 0 ]]; then
print_error "No users selected for deployment"
exit 1
fi
# Print info about selected mode
case "$mode" in
all)
print_info "Deploying to all users with oh-my-zsh"
;;
current)
print_info "Deploying to current user and root"
;;
esac
echo
print_step "Deploying to ${#users[@]} user(s): ${users[*]}"
if [[ "$FORCE_REPLACE" == true ]]; then
print_warning "Force mode: will replace ALL existing links"
fi
echo
# Interactive confirmation for converting local links
if [[ "$mode" == "interactive" ]]; then
local has_local_links=false
for user in "${users[@]}"; do
local status=$(analyze_user_theme "$user")
if [[ "$status" == "local_link" ]]; then
has_local_links=true
break
fi
done
if [[ "$has_local_links" == true ]]; then
echo "⚠ Some users have local dotfiles symlinks."
echo "This will replace them with system-wide symlinks pointing to:"
echo " $MASTER_THEME_PATH"
echo
if ! ask_yes_no "Convert local symlinks to system-wide?"; then
print_warning "Deployment cancelled"
exit 0
fi
fi
fi
if ! ask_yes_no "Proceed with deployment?"; then
print_warning "Deployment cancelled"
exit 0
fi
# Deploy to each user
echo
local deployed_count=0
local failed_count=0
for user in "${users[@]}"; do
if deploy_for_user "$user"; then
((deployed_count++))
else
((failed_count++))
fi
done
echo
if [[ $failed_count -eq 0 ]]; then
print_success "Deployment complete! ($deployed_count/${#users[@]} users)"
else
print_warning "Deployment finished with errors ($deployed_count succeeded, $failed_count failed)"
fi
# Show updated status
echo
show_deployment_status
show_summary
}
main "$@"