From a327a08783d7610cc17c8db35fb295c40ec81e0f Mon Sep 17 00:00:00 2001 From: saint Date: Wed, 20 Nov 2024 13:31:49 +1100 Subject: [PATCH] Add Core/bin/checl-wordpressversion, formerly wordpress-vercheck-all.sh, to be run regularly from cronjob to protect against WordPress version downgrades --- bin/check-wordpressversion | 361 +++++++++++++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100755 bin/check-wordpressversion diff --git a/bin/check-wordpressversion b/bin/check-wordpressversion new file mode 100755 index 0000000..10fba57 --- /dev/null +++ b/bin/check-wordpressversion @@ -0,0 +1,361 @@ +#!/bin/bash + +# WordPress Version Check and Update Script +# Copyright 2024 A.D. Federated Computer, Inc. +# saint@federated.computer +# --------------------------------------- +# This script checks the running WordPress version in a Docker container and updates +# the IMAGE_VERSION in the .env file to match if needed. +# +# Version Detection Methods (in order): +# 1. WP-CLI: Uses 'wp core version' +# 2. PHP Direct: Uses wp_eval to get bloginfo('version') +# 3. File Check: Reads version from /bitnami/wordpress/wp-includes/version.php +# 4. Database Primary: Reads from wp_options._transient_wp_core_block_css_files +# 5. Database Backup: Reads from wp_options._site_transient_update_core +# +# The script will: +# - Use the highest valid version found from any method +# - Check if that version is newer than the one in .env +# - If newer, look for matching Docker image on Docker Hub +# - If exact version not found, use next newer version or closest older version +# - Update .env with the new version and add IMAGE_VERSION_HOLD=true if needed + +# Enable debug output +set -x + +# Set your container name and .env file path +CONTAINER_NAME="wordpress" +APP_DIR="/federated/apps/$CONTAINER_NAME" +ENV_FILE="$APP_DIR/.env" +DOCKER_COMPOSE_FILE="$APP_DIR/docker-compose.yml" + +# Function to validate version string +is_valid_version() { + local version=$1 + # Allow x, x.y, x.y.z, or x.y.z.a where all parts are integers + [[ $version =~ ^[0-9]+(\.[0-9]+)*$ ]] || return 1 + # Count parts + local parts + IFS='.' read -ra parts <<< "$version" + # Allow 1 to 4 parts + [ ${#parts[@]} -le 4 ] || return 1 + return 0 +} + +echo "Starting WordPress version check and update script..." + +# Function to get version from different sources +get_wp_version() { + local wpcli_version php_version file_version db_version admin_version highest_version + + # 1. Try wp-cli version + wpcli_version=$(docker exec "$CONTAINER_NAME" wp core version --allow-root) || true + if ! is_valid_version "$wpcli_version"; then wpcli_version=""; fi + + # 2. Try version.php direct access + php_version=$(docker exec "$CONTAINER_NAME" wp eval 'echo get_bloginfo("version");' --allow-root) || true + if ! is_valid_version "$php_version"; then php_version=""; fi + + # 3. Try direct file content check (fixed quotes) + file_version=$(docker exec "$CONTAINER_NAME" bash -c "grep '\\\$wp_version = ' /bitnami/wordpress/wp-includes/version.php | sed -n 's/.*= .\\([0-9.]*\\).*/\\1/p'") || true + if [ -z "$file_version" ]; then + file_version=$(docker exec "$CONTAINER_NAME" bash -c "grep '\\\$wp_version = ' /opt/bitnami/wordpress/wp-includes/version.php | sed -n 's/.*= .\\([0-9.]*\\).*/\\1/p'") || true + fi + if ! is_valid_version "$file_version"; then file_version=""; fi + + # 4. Try database version through wp-cli with socket path + db_version=$(docker exec "$CONTAINER_NAME" bash -c ' + is_valid_version() { + local version=$1 + # Allow x, x.y, x.y.z, or x.y.z.a formats + [[ $version =~ ^[0-9]+(\.[0-9]+)*$ ]] || return 1 + # Count parts and ensure not more than 4 + local parts + IFS='.' read -ra parts <<< "$version" + [ ${#parts[@]} -le 4 ] || return 1 + return 0 + } + + if [ -f /opt/bitnami/mysql/bin/mariadb ]; then + MYSQL_CMD="/opt/bitnami/mysql/bin/mariadb" + else + MYSQL_CMD="/opt/bitnami/mysql/bin/mysql" + fi + export MYSQL_PWD="$WORDPRESS_DATABASE_PASSWORD" + + # First try _transient_wp_core_block_css_files as it has simpler structure + version=$($MYSQL_CMD -u "$WORDPRESS_DATABASE_USER" \ + -h "$WORDPRESS_DATABASE_HOST" \ + --skip-ssl \ + "$WORDPRESS_DATABASE_NAME" \ + -N -e "SELECT option_value FROM wp_options WHERE option_name = \"_transient_wp_core_block_css_files\";" | \ + sed -n "s/.*s:7:\"version\";s:[0-9]*:\"\([0-9][0-9.]*\)\".*/\1/p") + + # Validate first version before accepting it + if ! is_valid_version "$version"; then + # If first attempt fails or gives invalid version, try _site_transient_update_core + version=$($MYSQL_CMD -u "$WORDPRESS_DATABASE_USER" \ + -h "$WORDPRESS_DATABASE_HOST" \ + --skip-ssl \ + "$WORDPRESS_DATABASE_NAME" \ + -N -e "SELECT option_value FROM wp_options WHERE option_name = \"_site_transient_update_core\";" | \ + sed -n "s/.*\"version_checked\":\"\([0-9][0-9.]*\)\".*/\1/p") + fi + + # Only output version if it is valid + if is_valid_version "$version"; then + echo "$version" + fi + ') || true + if ! is_valid_version "$db_version"; then db_version=""; fi + + # 5. Try reading version from version.php using PHP + admin_version=$(docker exec "$CONTAINER_NAME" bash -c "php -r ' + \$files = array(\"/bitnami/wordpress/wp-includes/version.php\", \"/opt/bitnami/wordpress/wp-includes/version.php\"); + foreach (\$files as \$file) { + if (file_exists(\$file)) { + include \$file; + echo \$wp_version; + break; + } + } + '") || true + if ! is_valid_version "$admin_version"; then admin_version=""; fi + + # Log all found versions (to stderr for debugging) + { + echo "WP-CLI reports version: ${wpcli_version:-not found}" >&2 + echo "wp eval reports version: ${php_version:-not found}" >&2 + echo "File check reports version: ${file_version:-not found}" >&2 + echo "Database reports version: ${db_version:-not found}" >&2 + echo "Admin page reports version: ${admin_version:-not found}" >&2 + } + + # Compare all versions and take the highest one + local versions=() + [ ! -z "$wpcli_version" ] && versions+=("$wpcli_version") + [ ! -z "$php_version" ] && versions+=("$php_version") + [ ! -z "$file_version" ] && versions+=("$file_version") + [ ! -z "$db_version" ] && versions+=("$db_version") + [ ! -z "$admin_version" ] && versions+=("$admin_version") + + if [ ${#versions[@]} -eq 0 ]; then + echo "Error: Could not determine WordPress version from any source" >&2 + exit 1 + fi + + # Return just the version number + printf '%s\n' "${versions[@]}" | sort -V | tail -n 1 +} + +# Function to find closest available version on Docker Hub +find_closest_version() { + local target_version=$1 + local available_versions="" + local page=1 + local page_size=100 + local next_url="https://hub.docker.com/v2/repositories/bitnami/wordpress/tags?page_size=$page_size" + + echo "Retrieving available WordPress versions from Docker Hub..." >&2 + + # Keep fetching pages until we hit an empty page or no next URL + while [ ! -z "$next_url" ]; do + echo "Fetching page $page..." >&2 + + # Get the current page and extract versions and next URL + local response=$(curl -s "$next_url") + + # Extract versions from this page + local page_versions=$(echo "$response" | \ + grep -o '"name":"[^"]*"' | \ + grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | \ + sort -V | uniq) + + # If we got no versions, break + if [ -z "$page_versions" ]; then + break + fi + + # Append to our list of versions + available_versions="$available_versions"$'\n'"$page_versions" + + # Get next page URL and decode \u0026 to & + next_url=$(echo "$response" | \ + grep -o '"next":"[^"]*"' | \ + cut -d'"' -f4 | \ + sed 's/\\u0026/\&/g' ) + + # If no next URL or we've hit page limit, break + if [ -z "$next_url" ] || [ $page -ge 10 ]; then + break + fi + + ((page++)) + done + + # Clean up and sort the final list + available_versions=$(echo "$available_versions" | grep -v '^$' | sort -V | uniq) + + if [ -z "$available_versions" ]; then + echo "Failed to get versions from Docker Hub" >&2 + return 1 + fi + + # Print all versions for debugging + echo "Available versions:" >&2 + echo "$available_versions" >&2 + + # First, check if target version exists + if echo "$available_versions" | grep -Fxq "$target_version"; then + printf '%s\n' "$target_version" + return 0 + fi + + # If not, find next newer version + local newer_version + newer_version=$(echo "$available_versions" | awk -v ver="$target_version" '$1 > ver' | head -n1) + + # If no newer version, find closest older version + if [ -z "$newer_version" ]; then + echo "$available_versions" | awk -v ver="$target_version" '$1 <= ver' | tail -n1 + else + printf '%s\n' "$newer_version" + fi +} + +# Function to compare version numbers +version_greater_than() { + local ver1=$1 + local ver2=$2 + + if ! is_valid_version "$ver1" || ! is_valid_version "$ver2"; then + echo "Invalid version format" >&2 + return 1 + fi + + # Convert versions to arrays + local v1_parts=() v2_parts=() + IFS='.' read -ra v1_parts <<< "$ver1" + IFS='.' read -ra v2_parts <<< "$ver2" + + # Pad shorter version with zeros + while [ ${#v1_parts[@]} -lt 4 ]; do + v1_parts+=("0") + done + while [ ${#v2_parts[@]} -lt 4 ]; do + v2_parts+=("0") + done + + # Compare each part numerically + for i in {0..3}; do + if [ "${v1_parts[i]:-0}" -gt "${v2_parts[i]:-0}" ]; then + return 0 # ver1 is greater + elif [ "${v1_parts[i]:-0}" -lt "${v2_parts[i]:-0}" ]; then + return 1 # ver1 is not greater + fi + done + return 1 # versions are equal +} + +# Test cases (can be commented out in production) +test_versions() { + local test_pairs=( + "6.7 6.6.2" # should return 0 (true) + "7 6.7" # should return 0 (true) + "6.6.2 6.6.2" # should return 1 (false) + "6.6.1 6.6.2" # should return 1 (false) + "6.7.0 6.7" # should return 1 (false) + "6.7.1 6.7" # should return 0 (true) + "6.7.0.1 6.7" # should return 0 (true) + ) + + echo "Running version comparison tests..." + for pair in "${test_pairs[@]}"; do + read -r v1 v2 <<< "$pair" + if version_greater_than "$v1" "$v2"; then + echo "$v1 > $v2 (OK)" + else + echo "$v1 <= $v2" + fi + done + + echo "Running version validation tests..." + local test_versions=( + "6" # valid + "6.7" # valid + "6.7.2" # valid + "6.7.2.1" # valid + "6.7.2.1.5" # invalid + "6.7.2a" # invalid + "6.7." # invalid + ".6.7" # invalid + "a.b.c" # invalid + ) + + for ver in "${test_versions[@]}"; do + if is_valid_version "$ver"; then + echo "$ver is valid" + else + echo "$ver is invalid" + fi + done +} + +# Uncomment to run tests +test_versions + +# Get the current WordPress version +current_wp_version=$(get_wp_version | tr -d '\r') +if [ -z "$current_wp_version" ] || ! is_valid_version "$current_wp_version"; then + echo "Failed to get valid WordPress version" + exit 1 +fi +echo "Using WordPress version: $current_wp_version" + +## current_wp_version="6.7.0" + +# Check the current IMAGE_VERSION from the .env file +echo "Reading IMAGE_VERSION from .env file..." +env_wp_version=$(grep '^IMAGE_VERSION=' "$ENV_FILE" | cut -d= -f2 | tr -d '"' | tr -d "'") +if ! is_valid_version "$env_wp_version"; then + echo "Invalid version in .env file" + exit 1 +fi +echo "WordPress version in .env file (IMAGE_VERSION): $env_wp_version" + +# Check if current_wp_version is greater than env_wp_version +if version_greater_than "$current_wp_version" "$env_wp_version"; then + echo "Upgrade detected: WordPress ($current_wp_version) is newer than .env ($env_wp_version)" + + # Find the best available version to use + target_version=$(find_closest_version "$current_wp_version" | tr -d '\r') + + if [ -n "$target_version" ]; then + echo "Found appropriate Docker image version: $target_version" + + # Backup current .env file + echo "Backing up current .env file..." + ### cp "$ENV_FILE" "${ENV_FILE}.backup" + + # Update the IMAGE_VERSION in the .env file + echo "Updating IMAGE_VERSION in .env file to: $target_version" + ### sed -i "s/^IMAGE_VERSION=.*$/IMAGE_VERSION=\"$target_version\"/" "$ENV_FILE" + + # Check if IMAGE_VERSION_HOLD exists, add it if it doesn't + if ! grep -q "^IMAGE_VERSION_HOLD=" "$ENV_FILE"; then + echo "Adding IMAGE_VERSION_HOLD=true after IMAGE_VERSION..." + ### sed -i "/^IMAGE_VERSION=.*/a IMAGE_VERSION_HOLD=true" "$ENV_FILE" + fi + + echo "Successfully updated .env to version $target_version" + else + echo "Failed to find an appropriate version on Docker Hub" + exit 1 + fi +else + echo "No update required. WordPress version ($current_wp_version) is not newer than .env version ($env_wp_version)." +fi + +echo "WordPress version check and update script completed." +