Last active
November 4, 2025 14:28
-
-
Save bluPhy/e9979ef668d959c40ec70700dfb9fa92 to your computer and use it in GitHub Desktop.
Script to keep Linux updated and basic clean-up
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail # Exit on error, undefined variables, and pipe failures | |
| IFS=$'\n\t' # Set secure Internal Field Separator | |
| # Configuration | |
| readonly SCRIPT_NAME="$(basename "$0")" | |
| readonly SCRIPT_VERSION="2.0.0" | |
| readonly LOG_FILE="${HOME}/.cache/system-update.log" | |
| # Color codes | |
| readonly COLOR_RESET="\033[0m" | |
| readonly COLOR_INFO="\033[0;96m" | |
| readonly COLOR_SUCCESS="\033[0;92m" | |
| readonly COLOR_WARNING="\033[0;93m" | |
| readonly COLOR_DANGER="\033[0;91m" | |
| # Global variables | |
| VERBOSE=false | |
| DRY_RUN=false | |
| SKIP_CONFIRMATION=false | |
| # Function to show usage | |
| usage() { | |
| cat <<EOF | |
| Usage: $SCRIPT_NAME [OPTIONS] | |
| A comprehensive system update script for various package managers and tools. | |
| OPTIONS: | |
| -h, --help Show this help message | |
| -v, --verbose Enable verbose output | |
| -d, --dry-run Show what would be done without executing | |
| -y, --yes Skip confirmation prompts | |
| -l, --log Enable logging to $LOG_FILE | |
| --version Show script version | |
| Examples: | |
| $SCRIPT_NAME # Run with default settings | |
| $SCRIPT_NAME -v # Run with verbose output | |
| $SCRIPT_NAME -d # Dry run mode | |
| $SCRIPT_NAME -y # Skip confirmations | |
| EOF | |
| } | |
| # Enhanced message function with logging support | |
| msg() { | |
| local message="$1" | |
| local type="${2:-}" | |
| local color="" | |
| case "$type" in | |
| info) color="$COLOR_INFO" ;; | |
| success) color="$COLOR_SUCCESS" ;; | |
| warning) color="$COLOR_WARNING" ;; | |
| danger) color="$COLOR_DANGER" ;; | |
| *) color="$COLOR_RESET" ;; | |
| esac | |
| # Print to console | |
| printf "${color}%b${COLOR_RESET}" "$message" >&2 | |
| # Log if enabled | |
| if [[ "${LOGGING:-false}" == "true" ]]; then | |
| echo "$(date '+%Y-%m-%d %H:%M:%S') [$type] $message" | sed 's/\\n//g' >> "$LOG_FILE" | |
| fi | |
| } | |
| # Enhanced command existence check | |
| commandExists() { | |
| local cmd="$1" | |
| if command -v "$cmd" &>/dev/null; then | |
| [[ "$VERBOSE" == "true" ]] && msg "✓ Command $cmd exists\n" "success" | |
| return 0 | |
| else | |
| [[ "$VERBOSE" == "true" ]] && msg "✗ Command $cmd not found\n" "warning" | |
| return 1 | |
| fi | |
| } | |
| # Check if running with appropriate privileges | |
| checkPrivileges() { | |
| if [[ $EUID -eq 0 ]]; then | |
| msg "Warning: Running as root is not recommended\n" "warning" | |
| msg "Consider running as a regular user with sudo privileges\n" "warning" | |
| read -p "Continue anyway? (y/N): " -n 1 -r | |
| echo | |
| if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
| exit 1 | |
| fi | |
| fi | |
| # Check sudo access | |
| if ! sudo -n true 2>/dev/null; then | |
| msg "This script requires sudo privileges. Please enter your password.\n" "info" | |
| sudo -v | |
| fi | |
| } | |
| # Function to detect operating system and CPU architecture | |
| detectOsAndArch() { | |
| local os=$(uname -s) | |
| local arch=$(uname -m) | |
| local os_pretty_name="" | |
| case "$os" in | |
| Linux) | |
| if [[ -f /etc/os-release ]]; then | |
| source /etc/os-release | |
| os_pretty_name="${PRETTY_NAME:-$ID ${VERSION_ID:-}}" | |
| elif [[ -f /etc/redhat-release ]]; then | |
| os_pretty_name=$(< /etc/redhat-release) | |
| elif [[ -f /etc/SuSE-release ]]; then | |
| os_pretty_name=$(< /etc/SuSE-release) | |
| else | |
| os_pretty_name="Unknown Linux Distribution" | |
| fi | |
| ;; | |
| Darwin) | |
| os_pretty_name="$(sw_vers -productName) $(sw_vers -productVersion)" | |
| ;; | |
| *BSD) | |
| os_pretty_name="$os $(uname -r)" | |
| ;; | |
| *) | |
| os_pretty_name="Unknown OS: $os" | |
| ;; | |
| esac | |
| msg "\n=== System Information ===\n" "info" | |
| msg "OS: $os_pretty_name\n" "info" | |
| msg "Architecture: $arch\n" "info" | |
| msg "Hostname: $(hostname -f 2>/dev/null || hostname)\n" "info" | |
| msg "User: $USER\n" "info" | |
| # Export for use in other functions | |
| export DETECTED_OS="$os" | |
| export DETECTED_ARCH="$arch" | |
| } | |
| # Enhanced package manager detection with priority | |
| detectPackageManager() { | |
| local package_managers=( | |
| "apt:apt-get" | |
| "dnf:dnf" | |
| "yum:yum" | |
| "zypper:zypper" | |
| "pacman:pacman" | |
| "pkg:pkg" | |
| "apk:apk" # Added Alpine Linux support | |
| "emerge:emerge" # Added Gentoo support | |
| ) | |
| for pm in "${package_managers[@]}"; do | |
| local name="${pm%%:*}" | |
| local cmd="${pm##*:}" | |
| if commandExists "$cmd"; then | |
| msg "Detected $name package manager\n" "info" | |
| export PACKAGE_MANAGER="$name" | |
| return 0 | |
| fi | |
| done | |
| msg "Warning: No supported package manager detected\n" "warning" | |
| export PACKAGE_MANAGER="" | |
| return 1 | |
| } | |
| # Enhanced package update with error handling | |
| updatePackages() { | |
| local pm="${1:-$PACKAGE_MANAGER}" | |
| [[ -z "$pm" ]] && return 1 | |
| msg "\n=== Updating System Packages ($pm) ===\n" "info" | |
| if [[ "$DRY_RUN" == "true" ]]; then | |
| msg "DRY RUN: Would update packages using $pm\n" "warning" | |
| return 0 | |
| fi | |
| case "$pm" in | |
| apt) | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get update || return 1 | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get -o APT::Get::Always-Include-Phased-Updates=true dist-upgrade -y | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove -y | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get autoclean | |
| ;; | |
| dnf|yum) | |
| sudo $pm update -y || return 1 | |
| sudo $pm autoremove -y | |
| sudo $pm clean all | |
| ;; | |
| zypper) | |
| sudo zypper --non-interactive refresh || return 1 | |
| sudo zypper --non-interactive dup -y | |
| sudo zypper --non-interactive clean --all | |
| ;; | |
| pacman) | |
| sudo pacman -Syy || return 1 | |
| sudo pacman -Syu --noconfirm | |
| # Clean package cache (keep last 3 versions) | |
| sudo paccache -r -k 3 2>/dev/null || true | |
| ;; | |
| pkg) | |
| sudo pkg update || return 1 | |
| sudo pkg upgrade -y | |
| sudo pkg autoremove -y | |
| ;; | |
| apk) | |
| sudo apk update || return 1 | |
| sudo apk upgrade | |
| ;; | |
| emerge) | |
| sudo emerge --sync || return 1 | |
| sudo emerge -uDN @world | |
| ;; | |
| *) | |
| msg "Unsupported package manager: $pm\n" "danger" | |
| return 1 | |
| ;; | |
| esac | |
| msg "System packages updated successfully\n" "success" | |
| } | |
| # Enhanced Flatpak update with better error handling | |
| updateFlatpak() { | |
| if ! commandExists flatpak; then | |
| return 0 | |
| fi | |
| msg "\n=== Updating Flatpak Packages ===\n" "info" | |
| if [[ "$DRY_RUN" == "true" ]]; then | |
| msg "DRY RUN: Would update Flatpak packages\n" "warning" | |
| flatpak remote-ls --updates 2>/dev/null || true | |
| return 0 | |
| fi | |
| # Update both system and user Flatpaks | |
| flatpak update -y --noninteractive 2>/dev/null || true | |
| flatpak update -y --noninteractive --user 2>/dev/null || true | |
| # Remove unused runtimes | |
| flatpak uninstall --unused -y --noninteractive 2>/dev/null || true | |
| msg "Flatpak packages updated\n" "success" | |
| } | |
| # Enhanced Snap update | |
| updateSnap() { | |
| if ! commandExists snap; then | |
| return 0 | |
| fi | |
| msg "\n=== Updating Snap Packages ===\n" "info" | |
| if [[ "$DRY_RUN" == "true" ]]; then | |
| msg "DRY RUN: Would update Snap packages\n" "warning" | |
| snap list --all | tail -n +2 || true | |
| return 0 | |
| fi | |
| sudo snap refresh 2>/dev/null || true | |
| # Remove old snap revisions to save space | |
| if [[ "$VERBOSE" == "true" ]]; then | |
| msg "Removing old snap revisions...\n" "info" | |
| snap list --all | awk '/disabled/{print $1, $3}' | | |
| while read snapname revision; do | |
| sudo snap remove "$snapname" --revision="$revision" 2>/dev/null || true | |
| done | |
| fi | |
| msg "Snap packages updated\n" "success" | |
| } | |
| # Enhanced Homebrew update | |
| updateHomebrew() { | |
| if ! commandExists brew; then | |
| return 0 | |
| fi | |
| msg "\n=== Updating Homebrew ===\n" "info" | |
| if [[ "$DRY_RUN" == "true" ]]; then | |
| msg "DRY RUN: Would update Homebrew packages\n" "warning" | |
| brew outdated || true | |
| return 0 | |
| fi | |
| # Update Homebrew itself | |
| brew update | |
| # Upgrade packages | |
| brew upgrade | |
| # Upgrade casks | |
| brew upgrade --cask | |
| # Cleanup | |
| brew cleanup -s | |
| brew autoremove | |
| # Run diagnostics if verbose | |
| if [[ "$VERBOSE" == "true" ]]; then | |
| brew doctor || true | |
| fi | |
| msg "Homebrew updated successfully\n" "success" | |
| } | |
| # Enhanced container image update with better error handling | |
| updateContainerImages() { | |
| local container_cmd="" | |
| if commandExists docker; then | |
| container_cmd="docker" | |
| elif commandExists podman; then | |
| container_cmd="podman" | |
| else | |
| return 0 | |
| fi | |
| msg "\n=== Updating Container Images ($container_cmd) ===\n" "info" | |
| if [[ "$DRY_RUN" == "true" ]]; then | |
| msg "DRY RUN: Would update $container_cmd images\n" "warning" | |
| sudo $container_cmd images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}' || true | |
| return 0 | |
| fi | |
| # Get list of images (excluding none tags) | |
| local images | |
| images=$(sudo $container_cmd images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>' | sort -u) | |
| if [[ -z "$images" ]]; then | |
| msg "No container images found\n" "info" | |
| return 0 | |
| fi | |
| # Update each image | |
| while IFS= read -r image; do | |
| msg "Updating $image...\n" "info" | |
| sudo $container_cmd pull "$image" 2>/dev/null || msg "Failed to update $image\n" "warning" | |
| done <<< "$images" | |
| # Cleanup | |
| msg "Cleaning up container system...\n" "info" | |
| # Remove dangling images | |
| local dangling | |
| dangling=$(sudo $container_cmd images -f "dangling=true" -q) | |
| if [[ -n "$dangling" ]]; then | |
| echo "$dangling" | xargs sudo $container_cmd rmi 2>/dev/null || true | |
| msg "Removed dangling images\n" "success" | |
| fi | |
| # Prune system (with confirmation in non-skip mode) | |
| if [[ "$SKIP_CONFIRMATION" == "true" ]] || [[ "$container_cmd" == "podman" ]]; then | |
| sudo $container_cmd system prune -af 2>/dev/null || true | |
| else | |
| sudo $container_cmd system prune -a 2>/dev/null || true | |
| fi | |
| msg "Container images updated\n" "success" | |
| } | |
| # Update other tools | |
| updateMiscTools() { | |
| msg "\n=== Updating Miscellaneous Tools ===\n" "info" | |
| # GitHub CLI extensions | |
| if commandExists gh; then | |
| msg "Updating GitHub CLI extensions...\n" "info" | |
| if [[ "$DRY_RUN" == "false" ]]; then | |
| gh extension upgrade --all 2>/dev/null || true | |
| fi | |
| fi | |
| # Update pip packages | |
| if commandExists pip3; then | |
| msg "Updating pip packages...\n" "info" | |
| if [[ "$DRY_RUN" == "false" ]]; then | |
| pip3 list --outdated --format=json | \ | |
| python3 -c "import json, sys; print('\n'.join([x['name'] for x in json.load(sys.stdin)]))" | \ | |
| xargs -n1 pip3 install -U 2>/dev/null || true | |
| fi | |
| fi | |
| # Update npm global packages | |
| if commandExists npm; then | |
| msg "Updating npm global packages...\n" "info" | |
| if [[ "$DRY_RUN" == "false" ]]; then | |
| npm update -g 2>/dev/null || true | |
| fi | |
| fi | |
| msg "Miscellaneous tools updated\n" "success" | |
| } | |
| # System cleanup | |
| systemCleanup() { | |
| msg "\n=== System Cleanup ===\n" "info" | |
| if [[ "$DRY_RUN" == "true" ]]; then | |
| msg "DRY RUN: Would perform system cleanup\n" "warning" | |
| return 0 | |
| fi | |
| # Clean journal logs | |
| if commandExists journalctl; then | |
| msg "Cleaning journal logs...\n" "info" | |
| sudo journalctl --vacuum-time=7d --vacuum-size=1G 2>/dev/null || true | |
| fi | |
| # Clean temp files (be careful!) | |
| if [[ -d /tmp ]]; then | |
| msg "Cleaning old temp files...\n" "info" | |
| find /tmp -type f -atime +7 -delete 2>/dev/null || true | |
| fi | |
| # Clean user cache | |
| if [[ -d "$HOME/.cache" ]]; then | |
| msg "Cleaning old cache files...\n" "info" | |
| find "$HOME/.cache" -type f -atime +30 -delete 2>/dev/null || true | |
| fi | |
| msg "System cleanup completed\n" "success" | |
| } | |
| # Summary report | |
| showSummary() { | |
| msg "\n=== Update Summary ===\n" "success" | |
| msg "✓ All updates completed successfully\n" "success" | |
| # Show disk usage | |
| msg "\nDisk usage:\n" "info" | |
| df -h / | tail -n 1 | |
| # Show if reboot is required (for systems that support it) | |
| if [[ -f /var/run/reboot-required ]]; then | |
| msg "\n⚠ System reboot required\n" "warning" | |
| fi | |
| } | |
| # Parse command line arguments | |
| parseArguments() { | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| -v|--verbose) | |
| VERBOSE=true | |
| shift | |
| ;; | |
| -d|--dry-run) | |
| DRY_RUN=true | |
| msg "DRY RUN MODE ENABLED\n" "warning" | |
| shift | |
| ;; | |
| -y|--yes) | |
| SKIP_CONFIRMATION=true | |
| shift | |
| ;; | |
| -l|--log) | |
| LOGGING=true | |
| mkdir -p "$(dirname "$LOG_FILE")" | |
| msg "Logging enabled: $LOG_FILE\n" "info" | |
| shift | |
| ;; | |
| --version) | |
| echo "$SCRIPT_NAME version $SCRIPT_VERSION" | |
| exit 0 | |
| ;; | |
| *) | |
| msg "Unknown option: $1\n" "danger" | |
| usage | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| } | |
| # Main execution | |
| main() { | |
| # Parse arguments | |
| parseArguments "$@" | |
| # Header | |
| msg "========================================\n" "info" | |
| msg " System Update Script v$SCRIPT_VERSION\n" "info" | |
| msg "========================================\n" "info" | |
| # Check privileges | |
| checkPrivileges | |
| # Detect system | |
| detectOsAndArch | |
| # Confirmation | |
| if [[ "$SKIP_CONFIRMATION" == "false" ]] && [[ "$DRY_RUN" == "false" ]]; then | |
| msg "\nThis script will update all package managers and tools.\n" "warning" | |
| read -p "Continue? (y/N): " -n 1 -r | |
| echo | |
| if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
| msg "Update cancelled\n" "info" | |
| exit 0 | |
| fi | |
| fi | |
| # Run updates | |
| detectPackageManager && updatePackages | |
| updateFlatpak | |
| updateSnap | |
| updateHomebrew | |
| updateContainerImages | |
| updateMiscTools | |
| systemCleanup | |
| # Summary | |
| showSummary | |
| msg "\n✓ All operations completed\n" "success" | |
| } | |
| # Error handling | |
| trap 'msg "\nError occurred on line $LINENO\n" "danger"; exit 1' ERR | |
| # Run main function | |
| main "$@" | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment