#!/bin/bash set -o pipefail # Alpine Linux Automatic Update Script # ==================================== # Configuration LOG_FILE="/var/log/alpine-auto-update.log" # Path to the update log file BACKUP_DIR="/var/backups/pre-update" # Directory for pre-update backups ADMIN_EMAIL="admin@example.com" # Email address for update notifications SMTP_SERVER="smtp://mail.server.com" # SMTP server for sending notifications REBOOT_AFTER_KERNEL_UPDATE=1 # Set to 0 to skip rebooting after kernel updates MAX_START_DELAY=1800 # Maximum random delay in seconds before starting (0 to disable) # Internal state UPDATED_PACKAGES="" REBOOT_REQUIRED=0 # Logging function log_message() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" } # Create backup before update create_backup() { local today today=$(date +%Y%m%d) log_message "Creating system backup before update" mkdir -p "$BACKUP_DIR/$today" # Backup critical configurations if ! tar -czf "$BACKUP_DIR/$today/etc-backup.tar.gz" /etc/ 2>/dev/null; then log_message "ERROR: Failed to backup /etc" return 1 fi # Backup package list if ! apk info -v > "$BACKUP_DIR/$today/installed-packages.txt"; then log_message "ERROR: Failed to backup package list" return 1 fi # Remove backups older than 30 days find "$BACKUP_DIR" -mindepth 1 -type d -mtime +30 -exec rm -rf {} \; 2>/dev/null log_message "Removed backups older than 30 days" log_message "Backup completed" } # Update repository lists update_repositories() { log_message "Updating repository lists" if apk update; then log_message "Repository update successful" return 0 else log_message "ERROR: Repository update failed" return 1 fi } # Check for available updates check_updates() { log_message "Checking for available updates" UPDATES=$(apk list -u 2>/dev/null | grep -c "upgradable") log_message "Found $UPDATES available updates" if [ "$UPDATES" -eq 0 ]; then log_message "No updates available" return 1 fi return 0 } # Install updates install_updates() { log_message "Installing available updates" # Capture the list of packages to be updated UPDATED_PACKAGES=$(apk list -u 2>/dev/null) echo "$UPDATED_PACKAGES" | tee -a "$LOG_FILE" if apk upgrade; then log_message "Updates installed successfully" # Check if reboot is required (kernel update) if echo "$UPDATED_PACKAGES" | grep -qE "linux-(virt|lts|edge|hardened)"; then log_message "Kernel update detected - reboot required" REBOOT_REQUIRED=1 fi return 0 else log_message "ERROR: Update installation failed" return 1 fi } # Clean up old packages cleanup_cache() { log_message "Cleaning up package cache" apk cache clean 2>/dev/null # Remove old backups (keep last 7 days) find "$BACKUP_DIR" -mindepth 1 -type d -mtime +7 -exec rm -rf {} \; 2>/dev/null log_message "Cleanup completed" } # Send notification email (requires s-nail, no local MTA needed) send_notification() { local status="$1" local message="$2" if [ -n "$ADMIN_EMAIL" ]; then if ! command -v s-nail >/dev/null 2>&1; then log_message "s-nail not found, installing" apk add s-nail fi if command -v s-nail >/dev/null 2>&1; then echo "$message" | s-nail -S smtp="$SMTP_SERVER" \ -S smtp-use-starttls \ -s "Alpine Auto-Update: $status" "$ADMIN_EMAIL" else log_message "ERROR: Failed to install s-nail, cannot send notification" fi fi } # Main update process main() { # Random delay to stagger updates across multiple systems (only when run from cron) if [ "$MAX_START_DELAY" -gt 0 ] && ps -o comm= -p $PPID 2>/dev/null | grep -q crond; then local delay=$((RANDOM % MAX_START_DELAY)) log_message "Delaying start by ${delay} seconds (max ${MAX_START_DELAY}s)" sleep "$delay" fi log_message "=== Starting automatic update process ===" # Create backup if ! create_backup; then log_message "ERROR: Backup failed, aborting update" send_notification "FAILED" "Pre-update backup failed, update aborted" exit 1 fi # Update repositories (also serves as connectivity check) if ! update_repositories; then send_notification "FAILED" "Repository update failed (check network connectivity)" exit 1 fi # Check for updates if ! check_updates; then log_message "No updates to install" exit 0 fi # Install updates if install_updates; then cleanup_cache local email_body="Packages updated: $UPDATED_PACKAGES Updates installed successfully." # Reboot if kernel was updated and rebooting is enabled if [ "$REBOOT_REQUIRED" -eq 1 ]; then if [ "$REBOOT_AFTER_KERNEL_UPDATE" -eq 1 ]; then log_message "Rebooting system due to kernel update" email_body="$email_body NOTE: System is rebooting due to kernel update." else log_message "Kernel update detected but REBOOT_AFTER_KERNEL_UPDATE is disabled, skipping reboot" email_body="$email_body NOTE: Kernel was updated but automatic reboot is disabled. A manual reboot is required." fi fi send_notification "SUCCESS" "$email_body" log_message "=== Update process completed successfully ===" if [ "$REBOOT_REQUIRED" -eq 1 ] && [ "$REBOOT_AFTER_KERNEL_UPDATE" -eq 1 ]; then sleep 30 reboot fi else send_notification "FAILED" "Packages pending: $UPDATED_PACKAGES Update installation failed." log_message "=== Update process failed ===" exit 1 fi } # Run main function main "$@"