diff --git a/README.md b/README.md index a7aa707..f9f3d55 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # server-scripts -Useful scripts for managing servers written in Bash. Supported OS includes only Ubuntu/Debian and Alpine. +Useful scripts for managing servers written in Bash. Supported OS includes Debian/Ubuntu, Alpine, Arch, RHEL/Fedora/CentOS, openSUSE, Void, and Gentoo. In the past I was using it more for maintaining my home servers, bare metal with Docker, however when I changed to k3s, I started using it less. Now I'm using this as a collection of useful scripts that I can use when needed. Also `clean.sh` is still useful. @@ -8,6 +8,16 @@ In the past I was using it more for maintaining my home servers, bare metal with This script is used to clean some of the files, docker dangling images, and docker stopped/unused containers. +### `update.sh` + +Detects the running OS (Alpine, Arch, Debian/Ubuntu, RHEL/Fedora/CentOS, openSUSE, Void, Gentoo) and updates the **package list** only — it does not upgrade or install any packages. Useful for keeping package indexes fresh so that `upgrade` commands run faster later. + +Run with `curl | bash`: + +```bash +curl -sSL https://git.ivanch.me/ivanch/server-scripts/raw/branch/main/update.sh | bash +``` + ### `windows-backup.ps1` This PowerShell script is used to backup files from a Windows machine to a remote Samba server. It uses `7zip` to create the zip file, and then sends over the network using SMB protocol. diff --git a/clean.sh b/clean.sh index c052feb..481aba1 100644 --- a/clean.sh +++ b/clean.sh @@ -20,8 +20,9 @@ readonly YELLOW='\033[1;33m' # Cleanup configuration readonly LOG_RETENTION_DAYS=30 readonly JOURNAL_RETENTION_DAYS=7 +readonly CACHE_RETENTION_DAYS=7 readonly TEMP_DIRS=("/tmp" "/var/tmp") -readonly CACHE_DIRS=("/var/cache" "/root/.cache") +readonly CACHE_DIRS=("/var/cache/apt" "/root/.cache") #============================================================================== # UTILITY FUNCTIONS @@ -56,6 +57,7 @@ get_dir_size() { } # Safe directory cleanup with size reporting +# Deletes files older than CACHE_RETENTION_DAYS instead of wiping everything clean_directory() { local dir="$1" local description="$2" @@ -74,16 +76,26 @@ clean_directory() { log_step "$description (was $size_before)..." - # Use find with -delete for safer cleanup - if find "$dir" -mindepth 1 -delete 2>/dev/null; then - log_success "$description: freed $size_before" - else - # Fallback to rm if find fails - if rm -rf "$dir"/* 2>/dev/null; then - log_success "$description: freed $size_before" + # Use find to delete files older than retention period + # -type f -mtime +N: only files older than N days (not directories) + local deleted=false + if find "$dir" -type f -mtime +"${CACHE_RETENTION_DAYS:-7}" -delete 2>/dev/null; then + deleted=true + fi + + # Clean up empty directories left behind (but don't remove the dir itself) + find "$dir" -mindepth 1 -type d -empty -delete 2>/dev/null || true + + if $deleted; then + local size_after + size_after=$(get_dir_size "$dir") + if [[ "$size_after" == "$size_before" ]]; then + log_info "$description: nothing old enough to clean (retention: ${CACHE_RETENTION_DAYS:-7}d)" else - log_warning "$description: partial cleanup completed" + log_success "$description: freed (was $size_before, now $size_after)" fi + else + log_warning "$description: partial cleanup completed" fi } @@ -269,13 +281,19 @@ cleanup_cache_dirs() { fi done - # Clean additional cache locations + # Clean safe cache locations only + # NOTE: We do NOT recursively delete /var/cache or /root/.cache as a whole, + # as that can break package managers and remove important data. local additional_caches=( - "/var/lib/apt/lists" + "/var/cache/apt/archives" + "/var/cache/apt/archives/partial" "/var/cache/debconf" + "/var/lib/apt/lists" "/root/.npm" - "/root/.pip" - "/home/*/.cache" + "/root/.cache/pip" + "/root/.cache/npm" + "/home/*/.cache/pip" + "/home/*/.cache/npm" "/home/*/.npm" "/home/*/.pip" ) @@ -300,12 +318,16 @@ cleanup_logs() { # Find and remove old log files while IFS= read -r -d '' logfile; do - rm -f "$logfile" 2>/dev/null && ((cleaned_count++)) + if rm -f "$logfile" 2>/dev/null; then + ((++cleaned_count)) || true + fi done < <(find /var/log -type f -name "*.log" -mtime +"$LOG_RETENTION_DAYS" -print0 2>/dev/null || true) # Clean compressed logs while IFS= read -r -d '' logfile; do - rm -f "$logfile" 2>/dev/null && ((cleaned_count++)) + if rm -f "$logfile" 2>/dev/null; then + ((++cleaned_count)) || true + fi done < <(find /var/log -type f \( -name "*.log.gz" -o -name "*.log.bz2" -o -name "*.log.xz" \) -mtime +"$LOG_RETENTION_DAYS" -print0 2>/dev/null || true) if [[ $cleaned_count -gt 0 ]]; then diff --git a/update.sh b/update.sh new file mode 100644 index 0000000..be51bee --- /dev/null +++ b/update.sh @@ -0,0 +1,269 @@ +#!/bin/bash + +# Package List Update Script +# Detects the running OS and updates the package index only (no upgrades). + +set -euo pipefail + +#============================================================================== +# CONFIGURATION +#============================================================================== + +readonly NC='\033[0m' +readonly RED='\033[1;31m' +readonly GREEN='\033[1;32m' +readonly LIGHT_GREEN='\033[1;32m' +readonly LIGHT_BLUE='\033[1;34m' +readonly LIGHT_GREY='\033[0;37m' +readonly YELLOW='\033[1;33m' + +readonly SCRIPT_NAME="$(basename "$0")" + +#============================================================================== +# UTILITY FUNCTIONS +#============================================================================== + +log_info() { echo -e "${LIGHT_GREY}[i] $1${NC}"; } +log_success() { echo -e "${LIGHT_GREEN}[✓] $1${NC}"; } +log_warning() { echo -e "${YELLOW}[!] $1${NC}"; } +log_error() { echo -e "${RED}[x] $1${NC}" >&2; } +log_step() { echo -e "${LIGHT_BLUE}[i] $1${NC}"; } + +die() { + log_error "$1" + exit 1 +} + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Detect the running OS and its ID string. +# Prints two values: the distro id (lowercase) and the family id (lowercase). +detect_os() { + local id="" + local family="" + + if [[ -f /etc/os-release ]]; then + # shellcheck disable=SC1091 + . /etc/os-release + id="${ID:-unknown}" + family="${ID_LIKE:-}" + elif [[ -f /etc/alpine-release ]]; then + id="alpine" + family="alpine" + else + # Fall back to package manager detection + if command_exists apk; then + id="alpine"; family="alpine" + elif command_exists pacman; then + id="arch"; family="arch" + elif command_exists apt-get; then + id="debian"; family="debian" + elif command_exists dnf; then + id="fedora"; family="rhel" + elif command_exists yum; then + id="centos"; family="rhel" + elif command_exists zypper; then + id="opensuse"; family="suse" + elif command_exists xbps-install; then + id="void"; family="void" + elif command_exists emerge; then + id="gentoo"; family="gentoo" + else + id="unknown"; family="unknown" + fi + fi + + echo "${id} ${family}" +} + +#============================================================================== +# OS-SPECIFIC UPDATE FUNCTIONS +#============================================================================== + +update_alpine() { + if ! command_exists apk; then + log_warning "apk not found on Alpine-type system" + return 0 + fi + + log_info "Running apk update..." + if apk update 2>&1; then + log_success "Alpine package index updated" + else + log_warning "apk update failed" + fi +} + +update_arch() { + if ! command_exists pacman; then + log_warning "pacman not found on Arch-type system" + return 0 + fi + + log_info "Refreshing pacman database (pacman -Sy)..." + # -S = sync, -y = refresh local database only (no package installation) + if pacman -Sy --noconfirm 2>&1; then + log_success "Arch package database refreshed" + else + log_warning "pacman -Sy failed" + fi +} + +update_debian() { + if ! command_exists apt-get; then + log_warning "apt-get not found on Debian-type system" + return 0 + fi + + log_info "Running apt-get update..." + if apt-get update 2>&1; then + log_success "Debian/Ubuntu package index updated" + else + log_warning "apt-get update failed" + fi +} + +update_rhel() { + if command_exists dnf; then + log_info "Running dnf makecache..." + if dnf makecache 2>&1; then + log_success "RHEL/Fedora metadata cache refreshed (dnf)" + else + log_warning "dnf makecache failed" + fi + elif command_exists yum; then + log_info "Running yum makecache..." + if yum makecache fast 2>&1; then + log_success "CentOS/RHEL metadata cache refreshed (yum)" + else + log_warning "yum makecache failed" + fi + else + log_warning "No DNF or YUM found on RHEL-type system" + fi +} + +update_suse() { + if ! command_exists zypper; then + log_warning "zypper not found on openSUSE-type system" + return 0 + fi + + log_info "Running zypper refresh..." + if zypper --non-interactive refresh 2>&1; then + log_success "openSUSE repositories refreshed" + else + log_warning "zypper refresh failed" + fi +} + +update_void() { + if ! command_exists xbps-install; then + log_warning "xbps-install not found on Void system" + return 0 + fi + + log_info "Running xbps-install -S (sync repository index)..." + if xbps-install -S 2>&1; then + log_success "Void package index synced" + else + log_warning "xbps-install -S failed" + fi +} + +update_gentoo() { + if ! command_exists emerge; then + log_warning "emerge not found on Gentoo system" + return 0 + fi + + log_info "Running emaint sync -a..." + if emaint sync -a 2>&1; then + log_success "Gentoo Portage tree synced" + else + log_warning "emaint sync failed" + fi +} + +#============================================================================== +# MAIN EXECUTION +#============================================================================== + +main() { + log_step "Starting package list update (${SCRIPT_NAME})" + echo + + # Detect the OS + local distro_info distro_id family_id + distro_info="$(detect_os)" + distro_id="${distro_info%% *}" + family_id="${distro_info##* }" + + log_info "Detected OS: ${distro_id}" + if [[ -n "${family_id}" && "${family_id}" != "${distro_id}" ]]; then + log_info "OS family: ${family_id}" + fi + echo + + # Dispatch to the correct handler based on distro family + # Check distro_id first, then fall back to family_id for aliases + case "${distro_id}" in + alpine) + update_alpine + ;; + arch|artix|manjaro|garuda) + update_arch + ;; + debian|ubuntu|linuxmint|kali|raspbian|pop) + update_debian + ;; + fedora|rhel|centos|rocky|almalinux|amazon|oracle|clear-linux-os) + update_rhel + ;; + opensuse*|suse|sles) + update_suse + ;; + void) + update_void + ;; + gentoo|funtoo) + update_gentoo + ;; + *) + # Try matching on family ID as fallback + case "${family_id}" in + *alpine*) + update_alpine + ;; + *arch*) + update_arch + ;; + *debian*|*ubuntu*) + update_debian + ;; + *rhel*|*fedora*|*centos*) + update_rhel + ;; + *suse*|*opensuse*) + update_suse + ;; + *void*) + update_void + ;; + *gentoo*) + update_gentoo + ;; + *) + die "Unsupported OS: ${distro_id} (${family_id}). Cannot update package index." + ;; + esac + ;; + esac + + echo + log_success "Package list update completed!" +} + +main "$@"