Automatische Updates von opensuse

Automatische Updates gibt es für opensuse mit dem opensuse-online-Update schon lange. Diese werden automatisiert zu einer vorgegebenen Zeit durchgeführt. Für einen Laptop, der immer zu unterschiedlichen Zeiten verwendet wird, ist das aber eher ungeeignet, da der Laptop meist nicht z.B. morgens um 06:00 Uhr läuft. Hier ist eine Aktualisierung sinnvoll, die beim Herunterfahren des Gerätes die vorhandenen Updates installiert.

Idealerweise sollten die Updates direkt nach dem Start installiert werden. Das bedingt aber eine Wartezeit für den Anwender, bevor der Rechner verwendet werden kann. Und in der Regel schaltet man den Rechner ja auch erst dann an, wenn man ihn dringend benötigt. Da ist so eine Wartezeit eher hinderlich.

Nun wird sicherlich jemand kommen und sagen, dass Patches doch durch den Anwender selbst zu einer ihm genehmen Zeit installiert werden sollten. Das ist aber für Menschen, die mit der zugrunde liegenden Technik eher auf Kriegsfuß stehen, eher eine Last. Da werden die notwendigen Updates dann auch gerne mal ein halbes Jahr ignoriert. Das kann in der heutigen Zeit schnell kritisch werden.

Mit der Anregung von Kegan Edwards Zypper Automatic Update System habe ich daher eine konfigurierbare Lösung erarbeitet. Dabei kann durch Einstellungen festgelegt werden, ob das Update mit „zypper up“ (Patchinstallation bei Leap) oder mit „zypper dup“ (Updates bei Tumbleweed) erfolgen soll. Ebenso kann festgelegt werden, ob Recommended Patches installiert oder übersprungen werden sollen.

Zusätzlich sollen E-Mails versandt werden, wenn eine Adresse angegeben ist, und das Update durchgeführt wurde.

  • Download der verfügbaren Patches
  • Installation der heruntergeladenen Patches beim Shutdown oder Reboot
  • Benachrichtigung der Anwender
  • Konfiguration des Installationsmodus und -umfanges

Hier werden mittels zypper die Paketquellen aktualisiert und die notwendigen Patches heruntergeladen. Dazu wird ein Service erstellt, der nach der Verfügbarkeit des Netzwerkes die notwendigen Aktionen ausführt. Für den Fall, dass der Rechner länger eingeschaltet ist, wiederholt sich dies alle 24 Stunden.

Aktualisierung der Paketquellen und laden der Patches

/usr/local/bin/zypper-refresh-download.sh
#!/bin/bash
 
# update mode for zypper
# dup or up
ZYPPER_OFFLINE_UPDATE_MODE=up
 
# include recommended patches
# yes or no
ZYPPER_OFFLINE_UPDATE_RECOMENDS=no
 
ZYPPER_OFFLINE_UPDATE_STATUS_FILE=/var/run/zypper-update-triggered
 
if [ -e /etc/sysconfig/zypper-offline-update ]; then
        . /etc/sysconfig/zypper-offline-update
fi
 
if [ $ZYPPER_OFFLINE_UPDATE_RECOMENDS = no ]; then
        NORECOMMENDS=--no-recommends
fi
 
function log() {
        LEVEL=$1
        MESSAGE=$2
        echo $MESSAGE 
        echo $MESSAGE | systemd-cat -t zypper-auto-update -p $LEVEL
}
 
# Refresh repositories
/usr/bin/zypper refresh
 
# Download updates (non-interactive) without changing recommendations and other specified options
if /usr/bin/zypper $ZYPPER_OFFLINE_UPDATE_MODE -y $NORECOMMENDS --download-only; then
    # Create a flag file to indicate the update was triggered and completed successfully
    echo 1 > $ZYPPER_OFFLINE_UPDATE_STATUS_FILE
    log "info" "Update download completed successfully"
else
    echo 2 > $ZYPPER_OFFLINE_UPDATE_STATUS_FILE
    log "err" "Update download failed"
    exit 1
fi
 
# Ensure the service exits cleanly
exit 0

Service für die Aktualisierung der Paketquellen und laden der Patches

/etc/systemd/system/zypper-refresh-download.service
[Unit]
Description=Zypper Refresh and Download Updates (Non-interactive)
Wants=network-online.target zypper-offline-update.service
After=network-online.target
 
[Service]
Type=oneshot
RemainAfterExit=yes
TimeoutStartSec=0
ExecStartPre=/bin/sleep 10
ExecStart=/usr/local/bin/zypper-refresh-download.sh
Environment="ZYPP_LOCK_TIMEOUT=600"
 
[Install]
WantedBy=multi-user.target

Timer für die Aktualisierung der Paketquellen und laden der Patches

/etc/systemd/system/zypper-refresh-download.timer
[Unit]
Description=Run Zypper Refresh and Download Updates daily
 
[Timer]
OnBootSec=10m
OnUnitActiveSec=24h
RandomizedDelaySec=2h
 
[Install]
WantedBy=timers.target

Der Offline Update Service installiert Patches, wenn das System in einem sauberen Zustand ist und der Anwender nicht mehr durch Abbrüche von Anwendungen gestört werden kann. Es werden nur dann Patches installiert, wenn das Herunterladen fehlerfrei erfolgt ist.

Installation der Patches

/usr/local/bin/zypper-offline-update.sh
#!/bin/bash
 
# update mode for zypper
# dup or up
ZYPPER_OFFLINE_UPDATE_MODE=up
 
# include recommended patches
# yes or no
ZYPPER_OFFLINE_UPDATE_RECOMENDS=no
 
ZYPPER_OFFLINE_UPDATE_STATUS_FILE=/var/run/zypper-update-triggered
 
INSTALL_LOG=/tmp/zypper-offline-update.log
 
#pid file
PIDFILE=/var/run/zypper-offline-update.pid
 
if [ -e /etc/sysconfig/zypper-offline-update ]; then
        . /etc/sysconfig/zypper-offline-update
fi
 
if [ $ZYPPER_OFFLINE_UPDATE_RECOMENDS = no ]; then
        NORECOMMENDS=--no-recommends
fi
 
function usage() {
        echo "usage:"
        echo "$0 start Start service for waiting to shutdown"
        echo "$0 stop  Run updates before shutdown"
        echo "$0 drystop Run without installing anything - for testing purposes only"
        exit 1
}
function handle_term_signal() {
    exit 0
}
function log() {
        LEVEL=$1
        MESSAGE=$2
        echo $MESSAGE 
        echo $MESSAGE | systemd-cat -t zypper-auto-update -p $LEVEL
        if [ ! -z $ZYPPER_OFFLINE_UPDATE_MAIL_TO ]; then
                HOST=$(hostname)
                RUNDATE=$(date '+%d.%m.%Y %H:%M:%S')
                LOGDATA="no log output"
                if [ -e $INSTALL_LOG ]; then
                        LOGDATA=$(cat $INSTALL_LOG)
                fi
                mailx -s "$HOST $LEVEL: $MESSAGE" $ZYPPER_OFFLINE_UPDATE_MAIL_TO <<EOF
This mail is for information only.
The zypper-offline-update process on $HOST was run on $RUNDATE.
Result of offline update is:
$MESSAGE
 
LOG from zypper:
================
$LOGDATA
EOF
                echo "sent email!"
        fi
}
trap handle_term_signal SIGTERM
 
if [ X$1 = "X" ]; then
        usage
else
        action=$1
fi
 
if [ $action = "start" ]; then
        nohup $0 run &
fi
if [ $action = "run" ]; then
        echo $$ > $PIDFILE
        while true
        do
            sleep 1
        done
fi
DOWNLOADSTATE=0
if [ $action = "stop" -o $action = "drystop" ]; then
    # Check if updates were downloaded successfully
    if [ -f $ZYPPER_OFFLINE_UPDATE_STATUS_FILE ]; then
        DOWNLOADSTATE=$(cat $ZYPPER_OFFLINE_UPDATE_STATUS_FILE)
    fi
 
    if [ $DOWNLOADSTATE -eq 1 ]; then
        # Apply updates
        if [ $action = "drystop" ]; then
            echo "dry run has no output from zypper" > $INSTALL_LOG
            log "info" "Dry run of offline updates"
        else
            /usr/bin/zypper $ZYPPER_OFFLINE_UPDATE_MODE -y $NORECOMMENDS > $INSTALL_LOG
            ZYPPERSTATE=$?
            cat $INSTALL_LOG
            if [ $ZYPPERSTATE -eq 103 ]; then
              log "info" "The Zypper package was patched, rerunning update to apply remaining patches."
              rm $INSTALL_LOG
              /usr/bin/zypper $ZYPPER_OFFLINE_UPDATE_MODE -y $NORECOMMENDS > $INSTALL_LOG
              ZYPPERSTATE=$?
              cat $INSTALL_LOG
            fi
            if [ $ZYPPERSTATE -eq 0 ]; then
                log "info" "Offline update applied successfully"
            else
                log "err" "Offline update failed"
            fi
            # Remove the trigger file
            rm $ZYPPER_OFFLINE_UPDATE_STATUS_FILE
        fi
    fi
    if [ $DOWNLOADSTATE -eq 2 ]; then
        log "info" "Download of updates failed or download was incomplete"
    fi
    if [ $DOWNLOADSTATE -eq 0 ]; then
        log "info" "Update service not running properly"
    fi
    if [ $action = "stop" ]; then
        # clean up
        rm -f $ZYPPER_OFFLINE_UPDATE_STATUS_FILE
        # stop service process and cleanup
        pid=$(cat $PIDFILE)
        rm $PIDFILE
        kill $pid
    fi
    if [ -e $INSTALL_LOG ]; then
        rm $INSTALL_LOG
    fi
    # wait for sending e-mail
    sleep 1
    # Ensure the service exits cleanly
    exit 0
fi

Service zur Installation der Patches

/etc/systemd/system/zypper-offline-update.service
[Unit]
Description=Zypper Offline Update
Wants=network-online.target postfix.service
After=network-online.target postfix.service
Requires=network-online.target postfix.service
 
 
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/zypper-offline-update.sh start
ExecStop=/usr/local/bin/zypper-offline-update.sh stop
TimeoutStopSec=900
 
[Install]
WantedBy=multi-user.target

Benachrichtigt Anwender über den Updatestatus beim Login.

Benachrichtigung der Anwender über Installation der Patches

/usr/local/bin/zypper-update-notify.sh
#!/bin/bash
 
# Get the current user
user=$USER
 
# Ensure we have a user
if [ -z "$user" ]; then
    echo "No user specified" | systemd-cat -t zypper-auto-update -p err
    exit 1
fi
 
# Wait a few seconds for the session to be fully initialized
sleep 5
 
# Check if either service failed
if systemctl is-failed --quiet zypper-refresh-download.service || \
   systemctl is-failed --quiet zypper-offline-update.service; then
    sudo -u $user DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $user)/bus \
    notify-send -a "Zypper Auto Update" -u critical "Zypper Update Failed" "One of the Zypper services has failed. Please check the logs."
else
    # Optionally notify of success
    sudo -u $user DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u $user)/bus \
    notify-send -a "Zypper Auto Update" "Zypper Update Status" "System updates are working normally."
fi
 
exit 0

Autostart Konfiguration für Benachrichtigung der Anwender über den Status der Auto Update Prozesse

/etc/xdg/autostart/zypper-update-notification.desktop
[Desktop Entry]
Name=Zypper Update Notification
Name[de]=Zypper Update Benachrichtigung
Exec=/usr/local/bin/zypper-update-notify.sh
Icon=system-software-update
Type=Application
NoDisplay=true
X-KDE-autostart-phase=1
OnlyShowIn=KDE

Gegebenenfalls ist noch das Paket libnotify-tools zu installieren. Fehlt dieses Paket, so schlägt die Benachrichtigung fehl, da das Programm notify-send nicht vorhanden ist.

zypper install libnotify-tools

Konfigurationsdatei für die Installation der Patches

/etc/sysconfig/zypper-offline-update
# update mode for zypper
# dup or up
ZYPPER_OFFLINE_UPDATE_MODE=up
 
# include recommended patches
# yes or no
ZYPPER_OFFLINE_UPDATE_RECOMENDS=yes
 
# status file for downloads of offline updates
ZYPPER_OFFLINE_UPDATE_STATUS_FILE=/var/run/zypper-update-triggered
 
# send update status as e-mail
# leave blank for no email
ZYPPER_OFFLINE_UPDATE_MAIL_TO=

Alle Dateien werden wie oben dargestellt angelegt. Anschließend müssen noch die Ausführungsrechte gesetzt werden:

chmod +x /usr/local/bin/zypper-refresh-download.sh
chmod +x /usr/local/bin/zypper-offline-update.sh
chmod +x /usr/local/bin/zypper-update-notify.sh

Nun sind die Dienste noch zu aktivieren:

systemctl daemon-reload
systemctl enable zypper-refresh-download.timer
systemctl enable zypper-refresh-download.service
systemctl enable zypper-offline-update.service
systemctl enable zypper-update-notify.service
systemctl start zypper-refresh-download.timer

Gegebenenfalls ist noch die Konfigurationsdatei /etc/sysconfig/zypper-offline-update an die persönlichen Bedürfnisse anzupassen.

Nach der Installation sollten noch die Status der einzelnen Dienste geprüft werden. Siehe dazu unter Fehlerbehebung.

Die Status der Dienste können wie folgt geprüft werden:

# Check timer status
systemctl status zypper-refresh-download.timer
 
# Check download service status
systemctl status zypper-refresh-download.service
 
# Check offline update service status
systemctl status zypper-offline-update.service
 
# Check notification service status
systemctl status zypper-update-notify.service

Die Protokolle können wie folgt untersucht werden:

# View all related logs
journalctl -t zypper-auto-update
 
# View specific service logs
journalctl -u zypper-refresh-download.service
journalctl -u zypper-offline-update.service
journalctl -u zypper-update-notify.service
  • Zuletzt geändert: vor 12 Tagen