Last active
January 30, 2026 04:37
-
-
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 | |
| LOGGING=false | |
| SUDO_CMD="" | |
| # 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 | |
| } | |
| confirm() { | |
| local prompt="$1" | |
| if [[ "$SKIP_CONFIRMATION" == "true" ]]; then | |
| msg "Skipping confirmation due to -y flag\n" "info" | |
| return 0 | |
| fi | |
| if [[ ! -t 0 ]]; then | |
| msg "Non-interactive shell detected. Use -y to skip prompts.\n" "warning" | |
| return 1 | |
| fi | |
| if ! read -p "$prompt" -n 1 -r; then | |
| echo | |
| return 1 | |
| fi | |
| echo | |
| [[ $REPLY =~ ^[Yy]$ ]] | |
| } | |
| # 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 | |
| } | |
| runPrivileged() { | |
| if [[ -n "$SUDO_CMD" ]]; then | |
| "$SUDO_CMD" "$@" | |
| else | |
| "$@" | |
| fi | |
| } | |
| # Check if running with appropriate privileges | |
| checkPrivileges() { | |
| # Skip privilege checks in dry-run mode | |
| if [[ "$DRY_RUN" == "true" ]]; then | |
| msg "Dry run mode: Skipping privilege checks\n" "info" | |
| return 0 | |
| fi | |
| if [[ $EUID -eq 0 ]]; then | |
| SUDO_CMD="" | |
| msg "Warning: Running as root is not recommended\n" "warning" | |
| msg "Consider running as a regular user with sudo privileges\n" "warning" | |
| if ! confirm "Continue anyway? (y/N): "; then | |
| exit 1 | |
| fi | |
| return 0 | |
| fi | |
| # Check sudo access | |
| if commandExists sudo; then | |
| SUDO_CMD="sudo" | |
| if ! sudo -n true 2>/dev/null; then | |
| msg "This script requires sudo privileges. Please enter your password.\n" "info" | |
| sudo -v | |
| fi | |
| else | |
| msg "sudo not found and not running as root. Cannot continue.\n" "danger" | |
| exit 1 | |
| 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) | |
| runPrivileged env DEBIAN_FRONTEND=noninteractive apt-get update || return 1 | |
| runPrivileged env DEBIAN_FRONTEND=noninteractive apt-get -o APT::Get::Always-Include-Phased-Updates=true dist-upgrade -y | |
| runPrivileged env DEBIAN_FRONTEND=noninteractive apt-get autoremove -y | |
| runPrivileged env DEBIAN_FRONTEND=noninteractive apt-get autoclean | |
| ;; | |
| dnf|yum) | |
| runPrivileged "$pm" update -y || return 1 | |
| runPrivileged "$pm" autoremove -y | |
| runPrivileged "$pm" clean all | |
| ;; | |
| zypper) | |
| runPrivileged zypper --non-interactive refresh || return 1 | |
| runPrivileged zypper --non-interactive dup -y | |
| runPrivileged zypper --non-interactive clean --all | |
| ;; | |
| pacman) | |
| runPrivileged pacman -Syy || return 1 | |
| runPrivileged pacman -Syu --noconfirm | |
| # Clean package cache (keep last 3 versions) | |
| runPrivileged paccache -r -k 3 2>/dev/null || true | |
| ;; | |
| pkg) | |
| runPrivileged pkg update || return 1 | |
| runPrivileged pkg upgrade -y | |
| runPrivileged pkg autoremove -y | |
| ;; | |
| apk) | |
| runPrivileged apk update || return 1 | |
| runPrivileged apk upgrade | |
| ;; | |
| emerge) | |
| runPrivileged emerge --sync || return 1 | |
| runPrivileged 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 | |
| runPrivileged 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 2>/dev/null | awk '/disabled/{print $1, $3}' | | |
| while read snapname revision; do | |
| runPrivileged snap remove "$snapname" --revision="$revision" 2>/dev/null || true | |
| done || true | |
| 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" | |
| # Try to list images without sudo to avoid password prompt | |
| $container_cmd images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}' 2>/dev/null || \ | |
| msg "(Listing skipped: requires privileges)\n" "info" | |
| return 0 | |
| fi | |
| # Get list of images (excluding none tags) | |
| local images | |
| if ! images=$(runPrivileged "$container_cmd" images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | awk -F: '$1 != "<none>" && $2 != "<none>" {print}' | sort -u); then | |
| msg "Failed to list $container_cmd images\n" "warning" | |
| return 0 | |
| fi | |
| 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" | |
| runPrivileged "$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=$(runPrivileged "$container_cmd" images -f "dangling=true" -q 2>/dev/null || true) | |
| if [[ -n "$dangling" ]]; then | |
| while IFS= read -r image_id; do | |
| runPrivileged "$container_cmd" rmi "$image_id" 2>/dev/null || true | |
| done <<< "$dangling" | |
| msg "Removed dangling images\n" "success" | |
| fi | |
| # Prune system (with confirmation in non-skip mode) | |
| if [[ "$SKIP_CONFIRMATION" == "true" ]] || [[ "$container_cmd" == "podman" ]]; then | |
| runPrivileged "$container_cmd" system prune -af 2>/dev/null || true | |
| else | |
| runPrivileged "$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 | |
| local outdated_packages="" | |
| outdated_packages=$(pip3 list --outdated --format=json 2>/dev/null | \ | |
| python3 -c "import json, sys; print('\\n'.join([x['name'] for x in json.load(sys.stdin)]))" 2>/dev/null || true) | |
| if [[ -n "$outdated_packages" ]]; then | |
| while IFS= read -r pkg; do | |
| [[ -n "$pkg" ]] || continue | |
| pip3 install -U "$pkg" 2>/dev/null || true | |
| done <<< "$outdated_packages" | |
| fi | |
| 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" | |
| runPrivileged 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" | |
| # Use mtime instead of atime for better reliability on noatime systems | |
| find /tmp -type f -mtime +7 -delete 2>/dev/null || true | |
| fi | |
| # Clean user cache | |
| if [[ -d "$HOME/.cache" ]]; then | |
| msg "Cleaning old cache files...\n" "info" | |
| # Use mtime instead of atime | |
| find "$HOME/.cache" -type f -mtime +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" | |
| if ! confirm "Continue? (y/N): "; 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