linuxbox/update/lib/core.sh

419 lines
20 KiB
Bash
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.

#!/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