WP Lokal zu Domain Push mit wp-cli, rsync und ssh

Verwendung

  1. LocalWP Site entwickeln
  2. Dort Site Shell anklicken
  3. Von dort aus wp-push sitename
  4. zieht sitename.conf aus /Sites/deployments Ordner
  5. Migration wird durchgeführt
  6. Fertig

Wichtig

Füge alles was mit dem Theme zu tun hat in Themes Folder. Also alle svgs, woff2s, images etc.

wp-push.sh

Bash
#!/bin/bash
# ============================================================
# wp-push.sh — Generic WordPress Local → Remote push
#
# Usage:
#   ./wp-push.sh <config-file> [options]
#
# Options:
#   --no-db                Skip DB
#   --no-plugins           Skip plugins folder
#   --no-themes            Skip themes folder
#   --no-mu-plugins        Skip mu-plugins folder
#   --no-uploads           Skip uploads folder
#   --no-languages         Skip languages folder (.mo/.po Übersetzungen)
#   --plugin=NAME[,NAME]   Only sync these plugin folders (no themes/mu/uploads)
#   --theme=NAME[,NAME]    Only sync these theme folders
#   --only-db              DB only, no files
#   --only-files           Files only, no DB
#
# Examples:
#   ./wp-push.sh sites/kzyk.conf
#   ./wp-push.sh sites/kzyk.conf --plugin=my-plugin           # nur Plugin + DB
#   ./wp-push.sh sites/kzyk.conf --plugin=my-plugin --no-db   # nur Plugin
#   ./wp-push.sh sites/kzyk.conf --only-db
# ============================================================
set -euo pipefail

DEPLOYMENTS_DIR="${DEPLOYMENTS_DIR:-$HOME/Sites/deployments}"

ARG="${1:-}"
if [[ -z "$ARG" ]]; then
  sed -n '2,25p' "$0"
  echo ""
  echo "Available configs in $DEPLOYMENTS_DIR:"
  shopt -s nullglob
  configs=("$DEPLOYMENTS_DIR"/*.conf)
  shopt -u nullglob
  if (( ${#configs[@]} == 0 )); then
    echo "  (none)"
  else
    for c in "${configs[@]}"; do
      name="$(basename "$c" .conf)"
      echo "  - $name"
    done
  fi
  exit 1
fi
shift

# Wenn ARG eine existierende Datei ist → direkt nehmen, sonst in DEPLOYMENTS_DIR suchen
if [[ -f "$ARG" ]]; then
  CONFIG_FILE="$ARG"
elif [[ -f "$DEPLOYMENTS_DIR/$ARG.conf" ]]; then
  CONFIG_FILE="$DEPLOYMENTS_DIR/$ARG.conf"
elif [[ -f "$DEPLOYMENTS_DIR/$ARG" ]]; then
  CONFIG_FILE="$DEPLOYMENTS_DIR/$ARG"
else
  echo "❌ Config nicht gefunden: $ARG"
  echo "   Gesucht in: $DEPLOYMENTS_DIR/$ARG.conf"
  exit 1
fi
echo "📋 Config: $CONFIG_FILE"

# shellcheck disable=SC1090
source "$CONFIG_FILE"

for v in LOCAL_PATH REMOTE_USER REMOTE_HOST REMOTE_PATH REMOTE_URL REMOTE_PREFIX; do
  [[ -z "${!v:-}" ]] && { echo "❌ Missing in config: $v"; exit 1; }
done

# Optional mit Defaults / Auto-Detect
LOCAL_PREFIX="${LOCAL_PREFIX:-wp_}"
if [[ -z "${LOCAL_URL:-}" ]]; then
  LOCAL_URL="$(wp --path="$LOCAL_PATH" option get home 2>/dev/null || true)"
  [[ -z "$LOCAL_URL" ]] && { echo "❌ LOCAL_URL nicht erkannt (kein wp option get home). In conf setzen."; exit 1; }
  echo "🔎 LOCAL_URL auto-detect: $LOCAL_URL"
fi

# Defaults: alles syncen
SYNC_DB=true
SYNC_PLUGINS=true
SYNC_THEMES=true
SYNC_MU_PLUGINS=true
SYNC_UPLOADS=true
SYNC_LANGUAGES=true
PLUGIN_FILTER=""
THEME_FILTER=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    --no-db)         SYNC_DB=false ;;
    --no-plugins)    SYNC_PLUGINS=false ;;
    --no-themes)     SYNC_THEMES=false ;;
    --no-mu-plugins) SYNC_MU_PLUGINS=false ;;
    --no-uploads)    SYNC_UPLOADS=false ;;
    --no-languages)  SYNC_LANGUAGES=false ;;
    --only-db)       SYNC_PLUGINS=false; SYNC_THEMES=false; SYNC_MU_PLUGINS=false; SYNC_UPLOADS=false; SYNC_LANGUAGES=false ;;
    --only-files)    SYNC_DB=false ;;
    --plugin=*)      PLUGIN_FILTER="${1#--plugin=}"; SYNC_PLUGINS=true; SYNC_THEMES=false; SYNC_MU_PLUGINS=false; SYNC_UPLOADS=false; SYNC_LANGUAGES=false ;;
    --theme=*)       THEME_FILTER="${1#--theme=}"; SYNC_THEMES=true; SYNC_PLUGINS=false; SYNC_MU_PLUGINS=false; SYNC_UPLOADS=false; SYNC_LANGUAGES=false ;;
    *) echo "❌ Unknown option: $1"; exit 1 ;;
  esac
  shift
done

DB_FILE="/tmp/wp-push-$$.sql"
SSH_CTRL="/tmp/ssh-ctrl-$$-%h-%p-%r"
SSH_OPTS_STR="-o ControlMaster=auto -o ControlPath=$SSH_CTRL -o ControlPersist=10m"
# shellcheck disable=SC2206
SSH_OPTS=($SSH_OPTS_STR)
SSH_TARGET="$REMOTE_USER@$REMOTE_HOST"

cleanup() {
  rm -f "$DB_FILE" "$DB_FILE.bak"
  ssh "${SSH_OPTS[@]}" -O exit "$SSH_TARGET" 2>/dev/null || true
}
trap cleanup EXIT

wp_local()  { wp --path="$LOCAL_PATH" "$@"; }
wp_remote() { ssh "${SSH_OPTS[@]}" "$SSH_TARGET" "wp --path='$REMOTE_PATH' $*"; }
ssh_run()   { ssh "${SSH_OPTS[@]}" "$SSH_TARGET" "$@"; }

# Sync ein wp-content/<dir>. Wenn $2 (filter) gesetzt: nur Subfolder aus CSV-Liste.
sync_dir() {
  local dir="$1" filter="${2:-}"
  local src="$LOCAL_PATH/wp-content/$dir"
  [[ ! -d "$src" ]] && return 0

  if [[ -z "$filter" ]]; then
    echo "    → $dir"
    rsync -azu -e "ssh $SSH_OPTS_STR" \
      "$src/" "$SSH_TARGET:$REMOTE_PATH/wp-content/$dir/"
  else
    IFS=',' read -ra items <<< "$filter"
    for item in "${items[@]}"; do
      if [[ -e "$src/$item" ]]; then
        echo "    → $dir/$item"
        rsync -azu -e "ssh $SSH_OPTS_STR" \
          "$src/$item" "$SSH_TARGET:$REMOTE_PATH/wp-content/$dir/"
      else
        echo "    ⚠️  $dir/$item nicht gefunden, übersprungen"
      fi
    done
  fi
}

echo "🚀 Push: $LOCAL_URL$REMOTE_URL"
echo ""

# ------------------------------------------------------------
# DB
# ------------------------------------------------------------
if $SYNC_DB; then
  echo "📦 DB exportieren..."
  TABLES="$(wp_local db tables --all-tables-with-prefix --format=csv)"
  wp_local db export "$DB_FILE" --tables="$TABLES" --add-drop-table

  if [[ "$LOCAL_PREFIX" != "$REMOTE_PREFIX" ]]; then
    echo "    Tabellennamen umschreiben: ${LOCAL_PREFIX} → ${REMOTE_PREFIX}"
    sed -i.bak "s/\`${LOCAL_PREFIX}/\`${REMOTE_PREFIX}/g" "$DB_FILE"
    rm -f "$DB_FILE.bak"
  fi

  echo "📤 DB importieren..."
  ssh "${SSH_OPTS[@]}" "$SSH_TARGET" "wp --path='$REMOTE_PATH' db import -" < "$DB_FILE"

  if [[ "$LOCAL_PREFIX" != "$REMOTE_PREFIX" ]]; then
    echo "🔧 Prefix in option_name + meta_key korrigieren..."
    # BINARY + exakter Prefix-Match (case-sensitive, kein LIKE-Wildcard).
    # Sonst matcht z.B. 'wp_%' auch WPLANG, wpforms_*, wpseo_* — und zerstört sie.
    PFLEN=${#LOCAL_PREFIX}
    wp_remote "db query \"UPDATE ${REMOTE_PREFIX}options SET option_name = CONCAT('${REMOTE_PREFIX}', SUBSTRING(option_name, $((PFLEN+1)))) WHERE BINARY SUBSTRING(option_name, 1, ${PFLEN}) = '${LOCAL_PREFIX}'\""
    wp_remote "db query \"UPDATE ${REMOTE_PREFIX}usermeta SET meta_key = CONCAT('${REMOTE_PREFIX}', SUBSTRING(meta_key, $((PFLEN+1)))) WHERE BINARY SUBSTRING(meta_key, 1, ${PFLEN}) = '${LOCAL_PREFIX}'\""
  fi

  echo "🔁 URLs ersetzen: $LOCAL_URL$REMOTE_URL"
  wp_remote "search-replace '$LOCAL_URL' '$REMOTE_URL' --skip-columns=guid --all-tables"
else
  echo "⏭  DB übersprungen"
fi

# ------------------------------------------------------------
# Files
# ------------------------------------------------------------
echo "📁 Files synchronisieren..."
$SYNC_PLUGINS    && sync_dir plugins    "$PLUGIN_FILTER"
$SYNC_THEMES     && sync_dir themes     "$THEME_FILTER"
$SYNC_MU_PLUGINS && sync_dir mu-plugins
$SYNC_UPLOADS    && sync_dir uploads
$SYNC_LANGUAGES  && sync_dir languages
true  # damit set -e nicht bei "false &&" abbricht

# ------------------------------------------------------------
# Flush (nur sinnvoll wenn DB oder Plugins/Themes aktualisiert)
# ------------------------------------------------------------
if $SYNC_DB || $SYNC_PLUGINS || $SYNC_THEMES || $SYNC_MU_PLUGINS; then
  echo "🧹 Rewrite + Cache flush..."
  wp_remote "rewrite flush"
  wp_remote "cache flush"
  ssh_run "wp --path='$REMOTE_PATH' litespeed-purge all 2>/dev/null || true"
fi

echo ""
echo "✅ Fertig! $REMOTE_URL"

demo.conf

Bash
# Site config für wp-push.sh
# Verwendung: wp-push sitename

LOCAL_PATH=~/Sites/localwp/sitename/app/public
LOCAL_URL=https://sitename.local   
LOCAL_PREFIX=wp_ 

REMOTE_USER=SSH-USERNAME
REMOTE_HOST=HOST
REMOTE_PATH=/home/SSH-USERNAME/public_html/sitename.gwsite.ch
REMOTE_URL=https://sitename.gwsite.ch
REMOTE_PREFIX=sitename_

.zshrc

Bash
alias wp-push="~/Sites/deployments/wp-push.sh"