Skip to content

Instantly share code, notes, and snippets.

@CRCinAU
Last active April 23, 2026 06:23
Show Gist options
  • Select an option

  • Save CRCinAU/917949155eb29194405352efa3848a1d to your computer and use it in GitHub Desktop.

Select an option

Save CRCinAU/917949155eb29194405352efa3848a1d to your computer and use it in GitHub Desktop.
Alpine Linux automatic updates - place in /etc/periodic/daily/
#!/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
LOCK_FILE="/var/run/alpine-auto-update.lock" # Lock file to prevent concurrent runs
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.example.com" # SMTP server for sending notifications
REBOOT_AFTER_KERNEL_UPDATE=1 # Set to 0 to skip rebooting after kernel updates
BACKUP_RETENTION_DAYS=7 # Number of days to keep old backups
MAX_START_DELAY=1800 # Maximum random delay in seconds before starting (0 to disable)
# Internal state
HOSTNAME=$(hostname)
# Check if any ancestor process is crond (works with busybox on Alpine)
is_cron_child() {
local pid=$$
while [ "$pid" -gt 1 ]; do
pid=$(awk '{print $4}' /proc/$pid/stat 2>/dev/null) || return 1
[ -r "/proc/$pid/comm" ] || return 1
[ "$(cat /proc/$pid/comm 2>/dev/null)" = "crond" ] && return 0
done
return 1
}
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
local tar_output
if ! tar_output=$(tar -czf "$BACKUP_DIR/$today/etc-backup.tar.gz" /etc/ 2>&1); then
log_message "ERROR: Failed to backup /etc: $tar_output"
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
log_message "Backup completed"
}
# Update repository lists
update_repositories() {
log_message "Updating repository lists"
if apk update 2>&1 | tee -a "$LOG_FILE"; 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"
UPDATED_PACKAGES=$(apk list -u 2>/dev/null)
local update_count
update_count=$(echo "$UPDATED_PACKAGES" | grep -c "upgradable" || true)
log_message "Found $update_count available updates"
if [ "$update_count" -eq 0 ]; then
return 1
fi
return 0
}
# Install updates
install_updates() {
log_message "Installing available updates"
echo "$UPDATED_PACKAGES" | tee -a "$LOG_FILE"
if apk upgrade 2>&1 | tee -a "$LOG_FILE"; 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"
if ! apk cache clean 2>&1 | tee -a "$LOG_FILE"; then
log_message "WARNING: Cache cleanup encountered errors"
fi
log_message "Cache cleanup completed"
}
# Remove old backups beyond retention period
cleanup_backups() {
local removed
removed=$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +"$BACKUP_RETENTION_DAYS" -print -exec rm -rf {} \;)
if [ -n "$removed" ]; then
log_message "Removed backups older than $BACKUP_RETENTION_DAYS days"
fi
}
# Send notification email (requires s-nail, no local MTA needed)
send_notification() {
local status="$1"
local message="$2"
if [ -n "$ADMIN_EMAIL" ]; then
if ! apk info -e s-nail >/dev/null 2>&1; then
log_message "s-nail not found, installing"
apk add s-nail
fi
if apk info -e s-nail >/dev/null 2>&1; then
if echo "$message" | mail -S v15-compat \
-S mta="$SMTP_SERVER" -S smtp-auth=none \
-s "Alpine Auto-Update $HOSTNAME: $status" "$ADMIN_EMAIL"; then
log_message "Notification email sent to $ADMIN_EMAIL"
else
log_message "ERROR: Failed to send notification email to $ADMIN_EMAIL"
fi
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 ] && is_cron_child; 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" "Host: $HOSTNAME
Pre-update backup failed, update aborted"
exit 1
fi
# Clean up old backups
cleanup_backups
# Update repositories (also serves as connectivity check)
if ! update_repositories; then
send_notification "FAILED" "Host: $HOSTNAME
Repository update failed (check network connectivity)"
exit 1
fi
# Check for updates
if ! check_updates; then
log_message "=== Automatic update process completed ==="
exit 0
fi
# Install updates
if install_updates; then
cleanup_cache
local email_body="Host: $HOSTNAME
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 "=== Automatic update process completed ==="
if [ "$REBOOT_REQUIRED" -eq 1 ] && [ "$REBOOT_AFTER_KERNEL_UPDATE" -eq 1 ]; then
sleep 30
reboot
fi
else
send_notification "FAILED" "Host: $HOSTNAME
Packages pending:
$UPDATED_PACKAGES
Update installation failed."
log_message "=== Automatic update process completed ==="
exit 1
fi
}
# Acquire lock and run main function
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
echo "Another instance is already running, exiting" | tee -a "$LOG_FILE"
exit 0
fi
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment