#!/bin/bash
# update-wordpress.sh - update WordPress core to the newest release, safely.
#
# Rewritten 2026-10-26 (Hermes, for CanuteTheGreat). The original version
# (backup: /root/update-wordpress.sh.orig-20261026) replaced the whole
# install from latest.zip as www-data; since the 2026-10 hardening the web
# root is root-owned, so that could no longer work, it only carried over 4
# named plugins (others/themes would have been lost) and its final
# `chown -R www-data` would have undone the hardening.
#
# What it does now (must run as root - see root's crontab):
#   1. compare the installed version with the newest release (api.wordpress.org)
#   2. if newer: rotate backups (wordpress.old -> wordpress.old1) and copy the
#      current install to /var/www/wordpress.old
#   3. update core IN PLACE with WP-CLI (core download --skip-content) (wp-content, plugins, themes, uploads,
#      the PG4WP db.php drop-in, wp-config.php and .htaccess are untouched),
#      run the database upgrade, verify core checksums
#   4. restore the hardened ownership (code root:root; www-data may write only
#      wp-content/uploads and wp-content/pg4wp/logs)
#   5. check the site answers 200 locally; if anything fails, put the backup
#      back and exit non-zero
# The database lives on db.canutethegreat.com and is covered by the nightly
# PostgreSQL backups; this script does not dump it.
#
# FORCE=1 re-installs the current version (used to test the full path).

set -uo pipefail
WP=/var/www/wordpress
BASE=/var/www
LOG=/var/log/update-wordpress.log
SITE_HOST=blog.canutethegreat.com
export HTTP_X_FORWARDED_PROTO=https   # silences wp-config's proxy check under WP-CLI

log() { echo "$(date '+%F %T') $*" | tee -a "$LOG"; }
wpc() { wp --allow-root --path="$WP" "$@" 2>>"$LOG"; }

if [[ $EUID -ne 0 ]]; then
    echo "must run as root (web root is root-owned)" >&2
    exit 1
fi

cur=$(wpc core version) || { log "ERROR: cannot read installed version"; exit 1; }
latest=$(curl -fsS --max-time 30 https://api.wordpress.org/core/version-check/1.7/ \
         | python3 -c 'import json,sys; print(json.load(sys.stdin)["offers"][0]["current"])') \
    || { log "ERROR: cannot read latest version from api.wordpress.org"; exit 1; }

if [[ "$cur" == "$latest" && "${FORCE:-0}" != "1" ]]; then
    echo "No update available! ($cur)"
    exit 0
fi
log "Updating WordPress $cur -> $latest$([[ ${FORCE:-0} == 1 ]] && echo " (FORCE re-install)")"

# Backups: keep two generations, like the original script.
rm -rf "$BASE/wordpress.old1"
[[ -d "$BASE/wordpress.old" ]] && mv "$BASE/wordpress.old" "$BASE/wordpress.old1"
if ! cp -a "$WP" "$BASE/wordpress.old"; then
    log "ERROR: backup copy failed - not updating"
    exit 1
fi

restore() {
    log "ERROR: $1 - restoring the previous install"
    rm -rf "$BASE/wordpress.failed"
    mv "$WP" "$BASE/wordpress.failed"
    cp -a "$BASE/wordpress.old" "$WP"
    log "restored $(wpc core version) (failed tree kept in $BASE/wordpress.failed)"
    exit 1
}

# Not `wp core update`: on this PostgreSQL (PG4WP) site the WordPress update lock
# (INSERT IGNORE into wp_options) is always reported as failed, so it aborts with
# "Another update is currently in progress". Downloading the release over the
# core files does the same file replacement without the lock and never touches
# wp-content.
wpc core download --version="$latest" --force --skip-content >>"$LOG" || restore "core download failed"
wpc core update-db >>"$LOG" || restore "database upgrade failed"
wpc core verify-checksums >>"$LOG" || restore "core checksum verification failed"

# Hardened ownership (same as /etc/cron.d/ctg-wp-updates).
chown -R root:root "$WP"
chown -R www-data:www-data "$WP/wp-content/uploads" "$WP/wp-content/pg4wp/logs" 2>/dev/null
# wp-config.php must stay readable by Apache (www-data): root:root broke the site on 2026-10-27.
chown root:www-data "$WP/wp-config.php" && chmod 640 "$WP/wp-config.php"
# mod_php opcache keeps serving the OLD compiled files: clear it, or the check below passes on a broken tree.
apachectl -k graceful && sleep 3

for path in / /wp-login.php; do
    code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 30 \
           -H "Host: $SITE_HOST" -H 'X-Forwarded-Proto: https' "http://127.0.0.1$path")
    [[ "$code" == "200" ]] || restore "site check $path returned $code"
done

log "OK: WordPress is now $(wpc core version)"
