From e41bb430249b3db8f34642d031f7b09f3068c6e3 Mon Sep 17 00:00:00 2001 From: Homelab Cursor Date: Sun, 28 Jun 2026 19:09:30 +0200 Subject: [PATCH] add wp-mirror nightly sync CT101 to CT600 Read-only mysqldump and tar pull; safe for production WordPress on CT101. --- arakava-news/scripts/README-mirror-sync.md | 42 ++++++++ arakava-news/scripts/wp-mirror-sync.sh | 120 +++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 arakava-news/scripts/README-mirror-sync.md create mode 100755 arakava-news/scripts/wp-mirror-sync.sh diff --git a/arakava-news/scripts/README-mirror-sync.md b/arakava-news/scripts/README-mirror-sync.md new file mode 100644 index 000000000..46e01e0f0 --- /dev/null +++ b/arakava-news/scripts/README-mirror-sync.md @@ -0,0 +1,42 @@ +# CT600 wp-mirror Sync + +Nächtlicher Pull **CT101 → CT600** auf **pve-mu-3**. + +## CT101-Sicherheit + +| Operation | Auswirkung auf CT101 | +|-----------|----------------------| +| `mysqldump --single-transaction --quick` | Read-only Snapshot, ~10 s, keine Table-Locks (InnoDB) | +| `tar` aus Docker-Volume | Nur Lesen, kein Container-Restart | +| Health-Check vor/nach Sync | Abbruch wenn Primary down | + +**Nicht** gemacht: `docker restart`, `mysqladmin flush`, Schreibzugriffe, Plugin-Updates. + +## Deployment + +| Ort | Pfad | +|-----|------| +| Git | `arakava-news/scripts/wp-mirror-sync.sh` | +| pve-mu-3 | `/opt/wp-mirror-sync/sync-from-101.sh` | +| Log | `/var/log/wp-mirror-sync.log` | +| Staging | `/opt/wp-mirror-sync/staging/` | + +## Cron (pve-mu-3) + +``` +0 4 * * * root /opt/wp-mirror-sync/sync-from-101.sh >> /var/log/wp-mirror-sync.log 2>&1 +``` + +## Failover pausieren + +```bash +touch /opt/wp-mirror-sync/FAILOVER_ACTIVE +# nach Failback: +rm /opt/wp-mirror-sync/FAILOVER_ACTIVE +``` + +## Manuell + +```bash +ssh pve-mu-3 '/opt/wp-mirror-sync/sync-from-101.sh' +``` diff --git a/arakava-news/scripts/wp-mirror-sync.sh b/arakava-news/scripts/wp-mirror-sync.sh new file mode 100755 index 000000000..a6ed62922 --- /dev/null +++ b/arakava-news/scripts/wp-mirror-sync.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# Pull WordPress CT101 (pve-hetzner) → CT600 wp-mirror (pve-mu-3) +# CT101 impact: read-only mysqldump (--single-transaction) + read-only tar. No restarts. +set -euo pipefail + +HETZNER_HOST="${HETZNER_HOST:-root@100.88.230.59}" +HETZNER_CT="${HETZNER_CT:-101}" +MIRROR_CT="${MIRROR_CT:-600}" +STAGING="${STAGING:-/opt/wp-mirror-sync/staging}" +LOG="${LOG:-/var/log/wp-mirror-sync.log}" +LOCK_FILE="/var/run/wp-mirror-sync.lock" +FAILOVER_FLAG="/opt/wp-mirror-sync/FAILOVER_ACTIVE" +MIRROR_SITEURL="http://192.168.178.142" +MYSQL_USER="wordpress" +MYSQL_PASS="astral66" +MYSQL_DB="wordpress" +WP_VOL="/var/lib/docker/volumes/wordpress_wp_data/_data" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG" +} + +tar_sync_files() { + rm -rf "${STAGING}/wpfiles" + mkdir -p "${STAGING}/wpfiles" + ssh -o BatchMode=yes -o ConnectTimeout=30 "$HETZNER_HOST" \ + "ionice -c3 nice -n 19 pct exec ${HETZNER_CT} -- tar c -C ${WP_VOL} \ + --exclude=wp-content/cache \ + --exclude=wp-content/debug.log \ + --exclude=wp-content/upgrade \ + . 2>/dev/null" | tar x -C "${STAGING}/wpfiles" +} + +exec 9>"$LOCK_FILE" +if ! flock -n 9; then + log "SKIP: another sync is running" + exit 0 +fi + +if [[ -f "$FAILOVER_FLAG" ]]; then + log "SKIP: ${FAILOVER_FLAG} present (manual failover)" + exit 0 +fi + +if ! command -v pct >/dev/null 2>&1; then + log "ABORT: must run on Proxmox host with pct (pve-mu-3)" + exit 1 +fi + +mkdir -p "$STAGING" "$(dirname "$LOG")" + +log "Checking CT101 health..." +if ! ssh -o BatchMode=yes -o ConnectTimeout=15 "$HETZNER_HOST" \ + "pct exec ${HETZNER_CT} -- curl -sf -o /dev/null --max-time 15 http://127.0.0.1/"; then + log "ABORT: CT101 does not respond on http://127.0.0.1/" + exit 1 +fi + +log "START sync CT101 → CT600" + +DB_FILE="${STAGING}/wordpress.sql.gz" +log "DB dump (single-transaction, read-only)..." +if ! ssh -o BatchMode=yes -o ConnectTimeout=30 "$HETZNER_HOST" \ + "ionice -c3 nice -n 19 pct exec ${HETZNER_CT} -- docker exec wordpress-mysql mysqldump \ + -u${MYSQL_USER} -p${MYSQL_PASS} \ + --single-transaction --quick --skip-lock-tables \ + --add-drop-table ${MYSQL_DB} 2>/dev/null" | gzip > "${DB_FILE}.tmp"; then + log "ABORT: mysqldump failed" + rm -f "${DB_FILE}.tmp" + exit 1 +fi +mv "${DB_FILE}.tmp" "$DB_FILE" +log "DB dump size: $(du -h "$DB_FILE" | awk '{print $1}')" + +log "File sync via tar (read-only, ~5–8 min)..." +tar_sync_files +log "Files staged: $(du -sh "${STAGING}/wpfiles" | awk '{print $1}')" + +log "Starting CT600 for import..." +if [[ "$(pct status "$MIRROR_CT" | awk '{print $2}')" != "running" ]]; then + pct start "$MIRROR_CT" +fi +for _ in $(seq 1 45); do + if pct exec "$MIRROR_CT" -- docker info >/dev/null 2>&1; then + break + fi + sleep 2 +done + +pct exec "$MIRROR_CT" -- docker stop wordpress-app 2>/dev/null || true + +log "Import DB into CT600..." +gunzip -c "$DB_FILE" | pct exec "$MIRROR_CT" -- docker exec -i wordpress-mysql \ + mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" "$MYSQL_DB" + +log "Import files into CT600..." +tar c -C "${STAGING}/wpfiles" . 2>/dev/null | pct exec "$MIRROR_CT" -- tar x -C "$WP_VOL" + +log "Reset mirror idle siteurl/home..." +pct exec "$MIRROR_CT" -- docker exec wordpress-mysql mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" "$MYSQL_DB" -e \ + "UPDATE wp_options SET option_value='${MIRROR_SITEURL}' WHERE option_name IN ('siteurl','home');" + +pct exec "$MIRROR_CT" -- docker start wordpress-app +sleep 5 + +POSTS="$(pct exec "$MIRROR_CT" -- docker exec wordpress-mysql mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -N -e \ + "SELECT COUNT(*) FROM wp_posts WHERE post_status='publish' AND post_type='post';" "$MYSQL_DB" 2>/dev/null)" +LAST="$(pct exec "$MIRROR_CT" -- docker exec wordpress-mysql mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -N -e \ + "SELECT MAX(post_date) FROM wp_posts WHERE post_type='post';" "$MYSQL_DB" 2>/dev/null)" +log "Mirror verified: ${POSTS} published posts, latest ${LAST}" + +if ! ssh -o BatchMode=yes -o ConnectTimeout=15 "$HETZNER_HOST" \ + "pct exec ${HETZNER_CT} -- curl -sf -o /dev/null --max-time 15 http://127.0.0.1/"; then + log "WARN: CT101 health check failed AFTER sync — investigate immediately" + exit 1 +fi + +log "Stopping CT600 (standby)..." +pct stop "$MIRROR_CT" +log "DONE"