419 lines
20 KiB
Bash
419 lines
20 KiB
Bash
#!/bin/bash
|
||
|
||
# CMtec Enhanced Update System - Core Library
|
||
# Shared functions for logging, UI, health checks, and utilities
|
||
|
||
VERSION="3.0.0"
|
||
LOG_FILE="/tmp/update_$(date +%Y%m%d_%H%M%S).log"
|
||
START_TIME=$(date +%s)
|
||
INTERACTIVE=false
|
||
|
||
# Color definitions and Unicode symbols
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[0;33m'
|
||
BLUE='\033[0;34m'
|
||
MAGENTA='\033[0;35m'
|
||
CYAN='\033[0;36m'
|
||
WHITE='\033[1;37m'
|
||
GRAY='\033[0;37m'
|
||
BOLD='\033[1m'
|
||
RESET='\033[0m'
|
||
|
||
# Unicode symbols
|
||
CHECK="✓"
|
||
CROSS="✗"
|
||
WARNING="⚠"
|
||
INFO="ℹ"
|
||
ROCKET="🚀"
|
||
GEAR="⚙"
|
||
TIMER="⏱"
|
||
PACKAGE="📦"
|
||
|
||
# Initialize core variables
|
||
declare -A ROLES
|
||
declare -A SYSTEM_INFO
|
||
|
||
# Logging function with colored output
|
||
log() {
|
||
local level=$1
|
||
shift
|
||
local message="$*"
|
||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
|
||
|
||
case $level in
|
||
ERROR) echo -e "${RED}${CROSS}${RESET} $message" ;;
|
||
SUCCESS) echo -e "${GREEN}${CHECK}${RESET} $message" ;;
|
||
WARNING) echo -e "${YELLOW}${WARNING}${RESET} $message" ;;
|
||
INFO) echo -e "${BLUE}${INFO}${RESET} $message" ;;
|
||
SECTION) echo -e "\n${CYAN}${GEAR} $message${RESET}" ;;
|
||
*) echo -e "${GRAY}$message${RESET}" ;;
|
||
esac
|
||
}
|
||
|
||
# Fancy spinner function for long operations
|
||
spinner() {
|
||
local pid=$1
|
||
local message="$2"
|
||
local delay=0.08
|
||
local spinstr='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
||
|
||
echo -e "${CYAN}${GEAR} $message${RESET}"
|
||
printf " "
|
||
|
||
while kill -0 $pid 2>/dev/null; do
|
||
local temp=${spinstr#?}
|
||
printf "${BLUE}%c${RESET}" "$spinstr"
|
||
spinstr=$temp${spinstr%"$temp"}
|
||
sleep $delay
|
||
printf "\b"
|
||
done
|
||
|
||
printf "${GREEN}${CHECK}${RESET}\n"
|
||
}
|
||
|
||
# Progress bar function
|
||
progress_bar() {
|
||
local current=$1
|
||
local total=$2
|
||
local message="$3"
|
||
local width=40
|
||
local percentage=$((current * 100 / total))
|
||
local filled=$((current * width / total))
|
||
local empty=$((width - filled))
|
||
|
||
printf "\r${message} ${BLUE}["
|
||
printf "%*s" $filled | tr ' ' '█'
|
||
printf "%*s" $empty | tr ' ' '░'
|
||
printf "] %d%% (%d/%d)${RESET}" $percentage $current $total
|
||
if [ $current -eq $total ]; then
|
||
echo
|
||
fi
|
||
}
|
||
|
||
# Timer function
|
||
show_timer() {
|
||
local start_time=$1
|
||
local end_time=$(date +%s)
|
||
local elapsed=$((end_time - start_time))
|
||
local hours=$((elapsed / 3600))
|
||
local minutes=$(((elapsed % 3600) / 60))
|
||
local seconds=$((elapsed % 60))
|
||
|
||
if [ $hours -gt 0 ]; then
|
||
printf "${CYAN}${TIMER} %02d:%02d:%02d${RESET}" $hours $minutes $seconds
|
||
else
|
||
printf "${CYAN}${TIMER} %02dm %02ds${RESET}" $minutes $seconds
|
||
fi
|
||
}
|
||
|
||
# Confirmation function for interactive mode
|
||
confirm() {
|
||
if [ "$INTERACTIVE" == "true" ]; then
|
||
local prompt="$1"
|
||
local default="${2:-n}"
|
||
echo -ne "${YELLOW}${WARNING} $prompt [y/N]: ${RESET}"
|
||
read -r response
|
||
response=${response:-$default}
|
||
case $response in
|
||
[yY][eE][sS]|[yY]) return 0 ;;
|
||
*) return 1 ;;
|
||
esac
|
||
else
|
||
return 0
|
||
fi
|
||
}
|
||
|
||
# System health check function
|
||
system_health_check() {
|
||
local section_start=$(date +%s)
|
||
log SECTION "Running system health check"
|
||
local issues=0
|
||
|
||
# Check disk space
|
||
local root_usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
|
||
if [ "$root_usage" -gt 85 ]; then
|
||
log WARNING "Root filesystem is ${root_usage}% full"
|
||
((issues++))
|
||
else
|
||
log SUCCESS "Root filesystem usage: ${root_usage}%"
|
||
fi
|
||
|
||
# Check available memory
|
||
local mem_available=$(free -m | awk 'NR==2{printf "%d", $7}')
|
||
if [ "$mem_available" -lt 512 ]; then
|
||
log WARNING "Low memory available: ${mem_available}MB"
|
||
((issues++))
|
||
else
|
||
log SUCCESS "Available memory: ${mem_available}MB"
|
||
fi
|
||
|
||
# Check for failed systemd services
|
||
local failed_services=$(systemctl --failed --quiet --no-legend 2>/dev/null | wc -l)
|
||
if [ "$failed_services" -gt 0 ]; then
|
||
log WARNING "$failed_services failed systemd services detected"
|
||
((issues++))
|
||
else
|
||
log SUCCESS "All systemd services running normally"
|
||
fi
|
||
|
||
# Check package manager locks (distro-specific)
|
||
case "${SYSTEM_INFO[DISTRO]}" in
|
||
"arch")
|
||
if [ -f /var/lib/pacman/db.lck ]; then
|
||
log WARNING "Pacman database is locked"
|
||
((issues++))
|
||
fi
|
||
;;
|
||
"ubuntu"|"debian")
|
||
if sudo fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; then
|
||
log WARNING "APT database is locked"
|
||
((issues++))
|
||
fi
|
||
;;
|
||
esac
|
||
|
||
if [ $issues -eq 0 ]; then
|
||
log SUCCESS "System health check passed $(show_timer $section_start)"
|
||
else
|
||
log WARNING "System health check found $issues issues $(show_timer $section_start)"
|
||
if [ "$INTERACTIVE" == "true" ]; then
|
||
if ! confirm "Continue despite health issues?"; then
|
||
log INFO "Update cancelled by user due to health issues"
|
||
exit 1
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
return $issues
|
||
}
|
||
|
||
# Error handler function
|
||
error_handler() {
|
||
local line_number=$1
|
||
local error_code=$2
|
||
local command="$3"
|
||
log ERROR "Script failed at line $line_number with exit code $error_code"
|
||
log ERROR "Failed command: $command"
|
||
log ERROR "Check log file: $LOG_FILE"
|
||
|
||
echo -e "\n${RED}╔══════════════════════════════════════════════════════════════════════════╗${RESET}"
|
||
echo -e "${RED}║${RESET} ${BOLD}SCRIPT FAILED${RESET} ${RED}║${RESET}"
|
||
echo -e "${RED}╠══════════════════════════════════════════════════════════════════════════╣${RESET}"
|
||
echo -e "${RED}║${RESET} Line: $line_number ${RED}║${RESET}"
|
||
echo -e "${RED}║${RESET} Exit Code: $error_code ${RED}║${RESET}"
|
||
echo -e "${RED}║${RESET} Log File: $LOG_FILE ${RED}║${RESET}"
|
||
echo -e "${RED}╚══════════════════════════════════════════════════════════════════════════╝${RESET}\n"
|
||
exit $error_code
|
||
}
|
||
|
||
# Fancy header display with original ASCII art
|
||
show_header() {
|
||
local hostname=$1
|
||
local distro=$2
|
||
clear
|
||
echo -e "\n${BLUE}╔══════════════════════════════════════════════════════════════════════════╗${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}.${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/ \\${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/ \\${RESET} ${WHITE} # ${BLUE}| *${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/^. \\${RESET} ${WHITE} a##e #%\" a#\"e 6##% ${BLUE}| | |-^-. | | \\ /${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/ .-. \\${RESET} ${WHITE}.oOo# # # # # ${BLUE}| | | | | | X${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/ ( ) _\\${RESET} ${WHITE}%OoO# # %#e\" # # ${BLUE}| | | | ^._.| / \\${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/ _.~ ~._^\\${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/.^ ^\\ ${GRAY}™${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${GREEN}${ROCKET} CMtec Universal Update System v$VERSION${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${GRAY}$hostname | $distro | $(date '+%Y-%m-%d %H:%M:%S')${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════╝${RESET}\n"
|
||
}
|
||
|
||
# Alternative fancy header with original Arch-style ASCII art
|
||
show_arch_header() {
|
||
local hostname=$1
|
||
clear
|
||
echo -e "\n${BLUE}╔══════════════════════════════════════════════════════════════════════════╗${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}.${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/ \\${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/ \\${RESET} ${WHITE} # ${BLUE}| *${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/^. \\${RESET} ${WHITE} a##e #%\" a#\"e 6##% ${BLUE}| | |-^-. | | \\ /${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/ .-. \\${RESET} ${WHITE}.oOo# # # # # ${BLUE}| | | | | | X${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/ ( ) _\\${RESET} ${WHITE}%OoO# # %#e\" # # ${BLUE}| | | | ^._.| / \\${RESET} ${GRAY}™${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/ _.~ ~._^\\${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}/.^ ^\\ ${GRAY}™${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${GREEN}${ROCKET} CMtec '$hostname' Enhanced Update Script${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}║${RESET} ${GRAY}$(date '+%Y-%m-%d %H:%M:%S')${RESET} ${BLUE}║${RESET}"
|
||
echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════╝${RESET}\n"
|
||
}
|
||
|
||
# Ubuntu-style header with Ubuntu ASCII art
|
||
show_ubuntu_header() {
|
||
local hostname=$1
|
||
clear
|
||
echo -e "\n${YELLOW}╔══════════════════════════════════════════════════════════════════════════╗${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${RED} .--.${RESET} ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${RED} .-\"''\`(|||)${RESET} ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${RED} ,\`\\ \\ \`-\`.${RESET} 88 88 ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${RED} / \\ \"\`\`-. \`${RESET} 88 88 ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${RED} .-. , \`___:${RESET} 88 88 88,888, 88 88 ,88888, 88888 ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${RED} (:::) : ___ ${RESET} 88 88 88 88 88 88 88 88 88 ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${RED} \`-\` \` , :${RESET} 88 88 88 88 88 88 88 88 88 ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${RED} \\ / ,..-\` ,${RESET} 88 88 88 88 88 88 88 88 88 ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${RED} \`.\/ / .-.\`${RESET} \"88888\" \"88888\" \"88888\" 88 88 \"8888${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${RED} \`-..-( )${RESET} ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${RED} \`-\`${RESET} ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${GREEN}${ROCKET} CMtec Ubuntu Install/Update Script${RESET} ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}║${RESET} ${GRAY}$hostname | $(date '+%Y-%m-%d %H:%M:%S')${RESET} ${YELLOW}║${RESET}"
|
||
echo -e "${YELLOW}╚══════════════════════════════════════════════════════════════════════════╝${RESET}\n"
|
||
}
|
||
|
||
# Display enabled roles with fancy formatting
|
||
show_roles() {
|
||
local role_count=0
|
||
for role in "${!ROLES[@]}"; do
|
||
if [ "${ROLES[$role]}" == "yes" ]; then
|
||
((role_count++))
|
||
fi
|
||
done
|
||
|
||
if [ $role_count -eq 0 ]; then
|
||
log WARNING "No roles enabled"
|
||
return
|
||
fi
|
||
|
||
log SECTION "System Configuration"
|
||
echo -e "${BOLD}${PACKAGE} Enabled Roles ($role_count total):${RESET}"
|
||
|
||
for role in "${!ROLES[@]}"; do
|
||
if [ "${ROLES[$role]}" == "yes" ]; then
|
||
case $role in
|
||
desktop*|DESKTOP*) icon="🖥️" ;;
|
||
game*|GAME*) icon="🎮" ;;
|
||
music*|MUSIC*) icon="🎵" ;;
|
||
dev*|code*|DEV*|CODE*) icon="💻" ;;
|
||
terminal*|TERMINAL*) icon="📟" ;;
|
||
nvidia*|NVIDIA*) icon="🎯" ;;
|
||
vr*|VR*) icon="🥽" ;;
|
||
lab*|LAB*) icon="🔬" ;;
|
||
bt*|BT*|bluetooth*) icon="📶" ;;
|
||
hypr*|HYPERLAND*) icon="🌊" ;;
|
||
wsl*|WSL*) icon="🐧" ;;
|
||
*) icon="⚙️" ;;
|
||
esac
|
||
echo -e " ${CYAN}$icon $role${RESET}"
|
||
fi
|
||
done
|
||
echo
|
||
}
|
||
|
||
# Final summary with statistics
|
||
show_final_summary() {
|
||
local end_time=$(date +%s)
|
||
local total_time=$((end_time - START_TIME))
|
||
local total_hours=$((total_time / 3600))
|
||
local total_minutes=$(((total_time % 3600) / 60))
|
||
local total_seconds=$((total_time % 60))
|
||
|
||
# Collect system info for summary
|
||
local kernel_version=$(uname -r)
|
||
local role_count=0
|
||
for role in "${!ROLES[@]}"; do
|
||
if [ "${ROLES[$role]}" == "yes" ]; then
|
||
((role_count++))
|
||
fi
|
||
done
|
||
|
||
log SECTION "Update Summary"
|
||
|
||
echo -e "\n${GREEN}╔══════════════════════════════════════════════════════════════════════════╗${RESET}"
|
||
echo -e "${GREEN}║${RESET} ${BOLD}🎉 UPDATE COMPLETE! 🎉${RESET} ${GREEN}║${RESET}"
|
||
echo -e "${GREEN}╠══════════════════════════════════════════════════════════════════════════╣${RESET}"
|
||
echo -e "${GREEN}║${RESET} ${CYAN}📊 Statistics:${RESET} ${GREEN}║${RESET}"
|
||
|
||
if [ $total_hours -gt 0 ]; then
|
||
echo -e "${GREEN}║${RESET} • Total time: ${CYAN}${TIMER} ${total_hours}h ${total_minutes}m ${total_seconds}s${RESET} ${GREEN}║${RESET}"
|
||
else
|
||
echo -e "${GREEN}║${RESET} • Total time: ${CYAN}${TIMER} ${total_minutes}m ${total_seconds}s${RESET} ${GREEN}║${RESET}"
|
||
fi
|
||
|
||
echo -e "${GREEN}║${RESET} • Hostname: ${CYAN}${SYSTEM_INFO[HOSTNAME]}${RESET} ${GREEN}║${RESET}"
|
||
echo -e "${GREEN}║${RESET} • Distro: ${CYAN}${SYSTEM_INFO[DISTRO]} ${SYSTEM_INFO[VERSION]}${RESET} ${GREEN}║${RESET}"
|
||
echo -e "${GREEN}║${RESET} • Kernel: ${CYAN}$kernel_version${RESET} ${GREEN}║${RESET}"
|
||
echo -e "${GREEN}║${RESET} • Active roles: ${CYAN}$role_count enabled${RESET} ${GREEN}║${RESET}"
|
||
echo -e "${GREEN}║${RESET} • Log file: ${GRAY}$LOG_FILE${RESET} ${GREEN}║${RESET}"
|
||
echo -e "${GREEN}╠══════════════════════════════════════════════════════════════════════════╣${RESET}"
|
||
echo -e "${GREEN}║${RESET} ${YELLOW}⚠️ SYSTEM REBOOT RECOMMENDED ⚠️${RESET} ${GREEN}║${RESET}"
|
||
echo -e "${GREEN}║${RESET} ${GREEN}║${RESET}"
|
||
echo -e "${GREEN}║${RESET} ${WHITE}Some updates may require a reboot to take effect${RESET} ${GREEN}║${RESET}"
|
||
echo -e "${GREEN}╚══════════════════════════════════════════════════════════════════════════╝${RESET}\n"
|
||
|
||
log SUCCESS "Update script completed successfully"
|
||
log INFO "Total execution time: $(show_timer $START_TIME)"
|
||
log INFO "System: ${SYSTEM_INFO[DISTRO]} ${SYSTEM_INFO[VERSION]} on ${SYSTEM_INFO[HOSTNAME]}"
|
||
log INFO "Active roles: $role_count"
|
||
|
||
# Interactive reboot prompt
|
||
if [ "$INTERACTIVE" == "true" ]; then
|
||
echo -ne "${YELLOW}${WARNING} Reboot system now? [y/N]: ${RESET}"
|
||
read -r -t 10 response || response="n"
|
||
case $response in
|
||
[yY][eE][sS]|[yY])
|
||
log INFO "Rebooting system as requested by user"
|
||
echo -e "${GREEN}${CHECK} Rebooting in 3 seconds...${RESET}"
|
||
sleep 1
|
||
echo -e "${GREEN}${CHECK} Rebooting in 2 seconds...${RESET}"
|
||
sleep 1
|
||
echo -e "${GREEN}${CHECK} Rebooting in 1 second...${RESET}"
|
||
sleep 1
|
||
sudo reboot
|
||
;;
|
||
*)
|
||
log INFO "Reboot cancelled by user"
|
||
echo -e "${BLUE}${INFO} Remember to reboot when convenient!${RESET}"
|
||
;;
|
||
esac
|
||
else
|
||
echo -e "${BLUE}${INFO} Please reboot your system when convenient${RESET}"
|
||
fi
|
||
|
||
echo -e "${GREEN}${CHECK} Thank you for using CMtec Universal Update System!${RESET}\n"
|
||
}
|
||
|
||
# Utility function to check if command exists
|
||
command_exists() {
|
||
command -v "$1" >/dev/null 2>&1
|
||
}
|
||
|
||
# Utility function to check if package is installed (distro-agnostic)
|
||
package_installed() {
|
||
local package="$1"
|
||
case "${SYSTEM_INFO[DISTRO]}" in
|
||
"arch")
|
||
pacman -Qs "$package" >/dev/null 2>&1
|
||
;;
|
||
"ubuntu"|"debian")
|
||
dpkg -l "$package" >/dev/null 2>&1
|
||
;;
|
||
*)
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# Initialize logging
|
||
init_core() {
|
||
# Set error trap
|
||
trap 'error_handler ${LINENO} $? "$BASH_COMMAND"' ERR
|
||
|
||
# Initialize log
|
||
log INFO "Starting CMtec Universal Update System v$VERSION"
|
||
log INFO "Log file: $LOG_FILE"
|
||
log INFO "Interactive mode: $INTERACTIVE"
|
||
}
|
||
|
||
# Source guard to prevent double-sourcing
|
||
if [ -z "$CORE_LIB_LOADED" ]; then
|
||
CORE_LIB_LOADED=true
|
||
fi |