#!/usr/bin/env bash # # setup-auto-updates-security-only.sh # # Debian/Ubuntu universal unattended-upgrades setup (SECURITY ONLY): # - fixes locale (en_US.UTF-8) to avoid perl/locale warnings # - installs & enables unattended-upgrades # - installs ONLY security updates # - smart reboot at 04:00 (only when needed, and no logged-in users) # - no email configuration # - optional self-deletion at end set -Eeuo pipefail trap 'echo "[ERROR] Line $LINENO failed" >&2' ERR REBOOT_TIME="04:00" TARGET_LOCALE="en_US.UTF-8" require_root() { if [[ $EUID -ne 0 ]]; then echo "[ERROR] Run as root (sudo)" >&2 exit 1 fi } detect_os() { if [[ ! -f /etc/os-release ]]; then echo "[ERROR] /etc/os-release not found" >&2 exit 1 fi . /etc/os-release case "$ID" in ubuntu) OS="ubuntu" ;; debian) OS="debian" ;; *) echo "[ERROR] Unsupported OS: $ID" >&2; exit 1 ;; esac CODENAME="${VERSION_CODENAME:-$UBUNTU_CODENAME}" echo "[INFO] Detected: $PRETTY_NAME ($OS / $CODENAME)" } fix_locale() { echo "[INFO] Ensuring locale ${TARGET_LOCALE} is generated…" apt-get update -y >/dev/null 2>&1 || true DEBIAN_FRONTEND=noninteractive apt-get install -y locales >/dev/null 2>&1 || true if [[ -f /etc/locale.gen ]]; then if grep -qE "^\s*#\s*${TARGET_LOCALE}\s+UTF-8" /etc/locale.gen; then sed -ri "s/^\s*#\s*(${TARGET_LOCALE}\s+UTF-8)/\1/" /etc/locale.gen elif ! grep -qE "^\s*${TARGET_LOCALE}\s+UTF-8" /etc/locale.gen; then echo "${TARGET_LOCALE} UTF-8" >> /etc/locale.gen fi else echo "${TARGET_LOCALE} UTF-8" > /etc/locale.gen fi locale-gen update-locale LANG=${TARGET_LOCALE} LC_ALL=${TARGET_LOCALE} export LANG=${TARGET_LOCALE} export LC_ALL=${TARGET_LOCALE} } install_unattended() { echo "[INFO] Updating apt cache…" apt update echo "[INFO] Installing unattended-upgrades…" DEBIAN_FRONTEND=noninteractive apt install -y unattended-upgrades } enable_unattended() { echo "[INFO] Enabling unattended-upgrades via debconf…" echo 'unattended-upgrades unattended-upgrades/enable_auto_updates boolean true' | debconf-set-selections DEBIAN_FRONTEND=noninteractive dpkg-reconfigure -f noninteractive unattended-upgrades } write_50_conf_ubuntu() { cat > /etc/apt/apt.conf.d/50unattended-upgrades < /etc/apt/apt.conf.d/50unattended-upgrades < /etc/apt/apt.conf.d/20auto-upgrades <<'EOF' APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Download-Upgradeable-Packages "1"; APT::Periodic::AutocleanInterval "7"; APT::Periodic::Unattended-Upgrade "1"; EOF } dry_run_test() { echo "[INFO] Dry-run unattended-upgrades (debug)…" unattended-upgrades --dry-run --debug > /tmp/unattended-upgrades-test.log 2>&1 || true echo "[INFO] Tail of /tmp/unattended-upgrades-test.log:" tail -n 30 /tmp/unattended-upgrades-test.log || true } show_status() { echo "[INFO] apt timers:" systemctl list-timers --all | grep -E 'apt-(daily|daily-upgrade)\.timer' || true echo echo "[INFO] Config files written:" ls -l /etc/apt/apt.conf.d/20auto-upgrades /etc/apt/apt.conf.d/50unattended-upgrades } prompt_self_delete() { echo read -r -p "Remove this script? [Y/n] " confirm case "$confirm" in [nN]*) echo "[INFO] Script not removed." ;; *) echo "[INFO] Removing script: $0"; rm -- "$0" ;; esac } main() { require_root detect_os fix_locale install_unattended enable_unattended echo "[INFO] Writing /etc/apt/apt.conf.d/50unattended-upgrades…" if [[ "$OS" == "ubuntu" ]]; then write_50_conf_ubuntu else write_50_conf_debian fi echo "[INFO] Writing /etc/apt/apt.conf.d/20auto-upgrades…" write_20_conf dry_run_test show_status echo "[OK] Security-only unattended updates configured. Reboot @ ${REBOOT_TIME} if needed." prompt_self_delete } main "$@"