#!/bin/bash # ============================================================================= # wp-auto-update.sh · WordPress automatisch bijwerken met backup, controle en herstel # Versie 1.0 · Invoker BV · https://www.invoker.nl/kennisbank/wordpress-automatisch-updaten-script # Licentie: MIT. Vrij te gebruiken, aan te passen en te delen; gebruik op eigen risico. # # Wat het doet, in volgorde: # 1. controleert of de site nu werkt (anders: niets doen, melden) # 2. maakt een backup (snapbak-user als dat er is, anders database-export + archief van wp-content) # 3. werkt WordPress, plugins, thema's en vertalingen bij # 4. controleert de site opnieuw (twee pogingen, cache geleegd) # 5. kapot? zet de bijgewerkte onderdelen terug naar hun oude versie # 6. nog steeds kapot? zet de bestanden uit de backup terug (en de database, alleen als RESTORE_DB=yes) # 7. stuurt een rapport per e-mail # # Gebruik: bash wp-auto-update.sh [--dry-run] [--test-mail] # Cron (elke maandag 06:00): 0 6 * * 1 /bin/bash $HOME/maintenance/jouwsite.nl/wp-auto-update.sh >/dev/null 2>&1 # # Exitcodes: 0 ok · 1 gestopt vóór het bijwerken · 2 hersteld (oude versies) · 3 hersteld uit backup · 4 handmatig ingrijpen nodig # ============================================================================= # ---------------------------- instellingen ----------------------------------- SITE_URL="https://jouwsite.nl" # zonder slash aan het eind WP_PATH="$HOME/domains/jouwsite.nl/public_html" # map met wp-config.php REPORT_TO="jij@jouwsite.nl" # hier komt het rapport MAIL_FROM="wordpress@jouwsite.nl" # afzender, liefst op je eigen domein EXTRA_URLS="" # extra pagina's om te controleren, spatie-gescheiden, bv. "/contact/ /winkel/" MAIL_ON_SUCCESS="yes" # yes = ook een mail als alles goed ging; no = alleen bij problemen # Database automatisch terugzetten als de site na herstel nog steeds kapot is? # yes: prima voor een brochure-site of blog zonder invoer van bezoekers # no: webshop, boekingen, formulieren, leden. Een database-restore gooit bestellingen en inzendingen van # na de backup weg. Dan worden alleen bestanden teruggezet en krijg jij een melding. RESTORE_DB="no" DATA_DIR="$HOME/maintenance/$(echo "$SITE_URL" | sed -E 's#^https?://##; s#/.*##')" # buiten de website! KEEP_LOG_DAYS=60 PHP_BIN="" # leeg = automatisch; of bv. /usr/local/php84/bin/php # ------------------------------------------------------------------------------ set -u export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:$PATH" umask 077 DRY=0; TESTMAIL=0 for a in "$@"; do case "$a" in --dry-run) DRY=1;; --test-mail) TESTMAIL=1;; *) echo "onbekende optie: $a"; exit 1;; esac; done PHP="${PHP_BIN:-$(command -v php)}" WPCLI="$(command -v wp)" WP="$PHP $WPCLI --path=$WP_PATH --skip-plugins --skip-themes" # zonder plugins: geen ruis, werkt ook op een kapotte site WPFULL="$PHP $WPCLI --path=$WP_PATH" # met plugins: alleen voor cache legen mkdir -p "$DATA_DIR/logs" && chmod 700 "$DATA_DIR" STAMP="$(date +%Y-%m-%d_%H%M)" LOG="$DATA_DIR/logs/update-$STAMP.log" LOCK="$DATA_DIR/.lock" log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG"; } send_report() { # $1 = status, $2 = altijd (1) of alleen bij problemen (0) if [ "${2:-1}" = 0 ] && [ "$MAIL_ON_SUCCESS" != "yes" ]; then return; fi { echo "Rapport van de automatische update van $SITE_URL" echo "Datum: $(date '+%d-%m-%Y %H:%M')" echo "Resultaat: $1"; echo; echo "---- logboek ----"; cat "$LOG" } | mail -r "$MAIL_FROM" -s "[$(echo "$SITE_URL" | sed -E 's#^https?://##')] WordPress-update: $1" "$REPORT_TO" } finish() { rm -f "$LOCK" find "$DATA_DIR/logs" -name 'update-*.log' -mtime +"$KEEP_LOG_DAYS" -delete 2>/dev/null exit "$1" } if [ "$TESTMAIL" = 1 ]; then echo "Dit is een testmail van wp-auto-update.sh voor $SITE_URL." | mail -r "$MAIL_FROM" -s "Testmail wp-auto-update" "$REPORT_TO" \ && echo "testmail verstuurd naar $REPORT_TO" || echo "mail versturen mislukt" exit 0 fi [ -f "$WP_PATH/wp-config.php" ] || { echo "geen WordPress gevonden in $WP_PATH"; exit 1; } if [ -e "$LOCK" ] && kill -0 "$(cat "$LOCK")" 2>/dev/null; then log "Er draait al een update, gestopt."; exit 1; fi echo $$ > "$LOCK" trap 'rm -f "$LOCK"' EXIT purge_cache() { $WPFULL litespeed-purge all >/dev/null 2>&1 $WP cache flush >/dev/null 2>&1 } check_site() { # 0 = alles werkt local ok=0 url code body newest newest="$($WP post list --post_type=post --post_status=publish --posts_per_page=1 --orderby=date --order=DESC --field=url 2>/dev/null | head -1)" for url in "$SITE_URL/" $(for p in $EXTRA_URLS; do echo "$SITE_URL$p"; done) "$newest" "$SITE_URL/wp-login.php"; do [ -z "$url" ] && continue body="$(mktemp)" code="$(curl -s -o "$body" -w '%{http_code}' --max-time 30 -A 'wp-auto-update-check' "${url}?autocheck=$(date +%s)")" if [ "$code" != "200" ]; then log " FOUT: $url geeft code $code"; ok=1 elif grep -qiE 'id="error-page"|class="wp-die-message"|(Fatal|Parse) error:' "$body"; then log " FOUT: $url toont een foutmelding"; ok=1 elif ! grep -qi '' "$body"; then log " FOUT: $url laadt maar half"; ok=1 else log " OK: $url"; fi rm -f "$body" done return $ok } check_site_twice() { check_site && return 0 log " Nog een keer proberen over 30 seconden..."; sleep 30; purge_cache; check_site } versions() { # soort,naam,versie echo "core,wordpress,$($WP core version 2>/dev/null)" $WP plugin list --fields=name,version,status --format=csv 2>/dev/null | grep -E '^[^,]+,[^,]+,(active|inactive|active-network)$' | awk -F, '{print "plugin,"$1","$2}' $WP theme list --fields=name,version --format=csv 2>/dev/null | grep -vE '^name,' | grep -E '^[^,]+,[^,]+$' | awk -F, '{print "theme,"$1","$2}' } log "=== Start automatische update van $SITE_URL ===" # 1. werkt de site nu? log "Stap 1: site controleren vóór de update" if ! check_site_twice; then log "De site werkte al niet vóór de update. Er is niets bijgewerkt en niets teruggezet." send_report "GESTOPT: site werkte al niet"; finish 1 fi if [ "$DRY" = 1 ]; then log "Proefdraai: beschikbare updates" $WP core check-update 2>&1 | tee -a "$LOG" $WP plugin list --update=available --fields=name,version,update_version 2>&1 | tee -a "$LOG" $WP theme list --update=available --fields=name,version,update_version 2>&1 | tee -a "$LOG" log "Proefdraai klaar: niets veranderd, geen mail verstuurd."; finish 0 fi # 2. backup log "Stap 2: backup maken" BACKUP_ID="" if command -v snapbak-user >/dev/null 2>&1 && [ -d "$HOME/.snapbak" ]; then snapbak-user backup --wait >>"$LOG" 2>&1 BACKUP_ID="$(snapbak-user list --json 2>/dev/null | $PHP -r '$l=json_decode(stream_get_contents(STDIN),true); if($l && time()-$l[0]["ts"]<7200) echo $l[0]["id"];')" [ -n "$BACKUP_ID" ] && log " Hostingbackup $BACKUP_ID klaar" fi LOCAL_BK="$DATA_DIR/backup-$STAMP" mkdir -p "$LOCAL_BK" if ! $WP db export "$LOCAL_BK/database.sql" >>"$LOG" 2>&1; then log "Database-export mislukt. Update afgebroken voor de veiligheid."; send_report "GESTOPT: geen database-backup"; finish 1 fi if [ -z "$BACKUP_ID" ]; then if ! tar -czf "$LOCAL_BK/wp-content.tar.gz" -C "$WP_PATH" wp-content 2>>"$LOG"; then log "Bestandsbackup mislukt. Update afgebroken voor de veiligheid."; send_report "GESTOPT: geen bestandsbackup"; finish 1 fi log " Lokale backup klaar in $LOCAL_BK" fi ls -1dt "$DATA_DIR"/backup-* 2>/dev/null | tail -n +4 | xargs -r rm -rf # bewaar de laatste 3 versions > "$DATA_DIR/versions-before.csv" # 3. bijwerken log "Stap 3: updates installeren" $WP core update >>"$LOG" 2>&1 $WP core update-db >>"$LOG" 2>&1 $WP plugin update --all >>"$LOG" 2>&1 $WP theme update --all >>"$LOG" 2>&1 $WP language core update >>"$LOG" 2>&1 $WP language plugin update --all >>"$LOG" 2>&1 $WP language theme update --all >>"$LOG" 2>&1 $WPFULL maintenance-mode deactivate >/dev/null 2>&1 purge_cache versions > "$DATA_DIR/versions-after.csv" CHANGES="$(diff "$DATA_DIR/versions-before.csv" "$DATA_DIR/versions-after.csv" | grep '^>' | sed 's/^> //')" if [ -z "$CHANGES" ]; then log " Geen updates beschikbaar." else log " Bijgewerkt:" while IFS=, read -r kind name new; do old="$(grep "^$kind,$name," "$DATA_DIR/versions-before.csv" | cut -d, -f3)" log " - $kind $name: ${old:-nieuw} -> $new" done <<< "$CHANGES" fi # 4. werkt de site nog? log "Stap 4: site controleren na de update" if check_site_twice; then log "Alles werkt." if [ -z "$CHANGES" ]; then send_report "OK: geen updates nodig" 0; else send_report "OK: updates geïnstalleerd" 0; fi finish 0 fi # 5. oude versies terugzetten log "Stap 5: PROBLEEM na de update. Oude versies terugzetten..." while IFS=, read -r kind name new; do [ -z "$kind" ] && continue old="$(grep "^$kind,$name," "$DATA_DIR/versions-before.csv" | cut -d, -f3)" [ -z "$old" ] && continue log " $kind $name terug naar $old" case "$kind" in core) $WP core update --version="$old" --force >>"$LOG" 2>&1 ;; plugin) $WP plugin install "$name" --version="$old" --force >>"$LOG" 2>&1 ;; theme) $WP theme install "$name" --version="$old" --force >>"$LOG" 2>&1 ;; esac done <<< "$CHANGES" purge_cache if check_site_twice; then log "Herstel gelukt: de site werkt weer met de oude versies." send_report "HERSTELD: updates teruggedraaid, graag nakijken"; finish 2 fi # 6. backup terugzetten log "Stap 6: site werkt nog steeds niet. Backup terugzetten..." if [ -n "$BACKUP_ID" ]; then snapbak-user restore "$BACKUP_ID" --what files --yes --wait >>"$LOG" 2>&1 else tar -xzf "$LOCAL_BK/wp-content.tar.gz" -C "$WP_PATH" >>"$LOG" 2>&1 fi if [ "$RESTORE_DB" = "yes" ]; then log " Database van vóór de update terugzetten (RESTORE_DB=yes)" $WP db import "$LOCAL_BK/database.sql" >>"$LOG" 2>&1 else log " Database NIET teruggezet (RESTORE_DB=no). Kopie staat in $LOCAL_BK/database.sql" fi purge_cache if check_site_twice; then log "Herstel uit backup gelukt." send_report "HERSTELD UIT BACKUP: graag nakijken"; finish 3 fi log "HERSTEL MISLUKT: de site werkt nog steeds niet. Handmatige hulp nodig." send_report "NOODGEVAL: site werkt niet, handmatig ingrijpen nodig"; finish 4