Files
bin/restore_espocrm.sh

171 lines
6.2 KiB
Bash
Executable File

#!/bin/bash
# To be run by user mkt to restore the EspoCRM pod from a backup made by
# backup_espocrm.sh (a MariaDB dump + a tarball of the EspoCRM data dir).
# Counterpart of backup_espocrm.sh. DANGEROUS: overwrites the current DB and
# the current EspoCRM data dir with the backed-up state.
#
# Usage:
# restore_espocrm.sh [TIMESTAMP] # restore a given set (default: latest)
# restore_espocrm.sh --list # list available backup sets
# restore_espocrm.sh --yes [TS] # skip the confirmation prompt
#
# A backup set is the pair of files
# $BAK_DIR/espocrm-db-<TS>.sql.gz and $BAK_DIR/espocrm-files-<TS>.tar.gz
# Environment variables
POD_NAME='espocrm_pod'
DB_CTR='mariadb_ctr'
WEB_CTR='espocrm_ctr'
DAEMON_CTR='espocrm_daemon_ctr'
ESPO_IMAGE='docker.io/espocrm/espocrm:9.3.8'
HOST_LOCAL_IP='127.0.0.1'
WEB_HOST_PORT='8093'
SITE_URL="http://${HOST_LOCAL_IP}:${WEB_HOST_PORT}"
BIND_DIR="$HOME/.local/share/$POD_NAME"
ESPO_DATA_DIR="$BIND_DIR/espocrm" # -> /var/www/html
DB_NAME='espocrm'
BAK_DIR="$HOME/bak"
# systemd --user service names
POD_SVC="pod-${POD_NAME}.service"
DB_SVC="container-${DB_CTR}.service"
WEB_SVC="container-${WEB_CTR}.service"
DAEMON_SVC="container-${DAEMON_CTR}.service"
# List available backup sets (timestamps that have BOTH a db and a files file)
list_sets() {
local f ts
for f in "$BAK_DIR"/espocrm-db-*.sql.gz; do
[ -e "$f" ] || continue
ts=${f##*/espocrm-db-}; ts=${ts%.sql.gz}
[ -e "$BAK_DIR/espocrm-files-$ts.tar.gz" ] && echo "$ts"
done | sort
}
# Parse arguments
ASSUME_YES=0
TS=''
for arg in "$@"; do
case "$arg" in
-l|--list)
echo "Available backup sets in $BAK_DIR:"
list_sets | sed 's/^/ /'
exit 0
;;
-y|--yes) ASSUME_YES=1 ;;
-*) echo "Unknown option: $arg" >&2; exit 2 ;;
*) TS="$arg" ;;
esac
done
# Resolve the timestamp (default: latest set)
if [ -z "$TS" ]; then
TS=$(list_sets | tail -n 1)
if [ -z "$TS" ]; then
echo "ERROR: no backup sets found in $BAK_DIR." >&2
echo "Run backup_espocrm.sh first." >&2
exit 1
fi
echo "No timestamp given; using latest set: $TS"
fi
DB_BAK="$BAK_DIR/espocrm-db-$TS.sql.gz"
FILES_BAK="$BAK_DIR/espocrm-files-$TS.tar.gz"
# Verify the chosen set exists and is intact BEFORE touching the live system
for f in "$DB_BAK" "$FILES_BAK"; do
if [ ! -f "$f" ]; then
echo "ERROR: backup file not found: $f" >&2
echo "Available sets:" >&2; list_sets | sed 's/^/ /' >&2
exit 1
fi
done
gzip -t "$DB_BAK" || { echo "ERROR: $DB_BAK is corrupt (gzip -t failed)." >&2; exit 1; }
gzip -t "$FILES_BAK" || { echo "ERROR: $FILES_BAK is corrupt (gzip -t failed)." >&2; exit 1; }
echo "Backup set '$TS' found and verified:"
echo " $DB_BAK"
echo " $FILES_BAK"
# Confirmation (overwrites current data)
if [ "$ASSUME_YES" -ne 1 ]; then
read -r -p "WARNING: this OVERWRITES the current DB '$DB_NAME' and ALL files under $ESPO_DATA_DIR with backup '$TS'. To confirm, type 'RESTORE espocrm_pod': " C
[ "$C" = "RESTORE espocrm_pod" ] || { echo "Aborted."; exit 1; }
fi
# Make sure the pod and the DB are up (DB is needed for the import).
systemctl --user start "$POD_SVC" "$DB_SVC"
echo "Ensured pod and DB are running (rc=$?)"
echo "Waiting for MariaDB to be ready..."
for i in $(seq 1 60); do
podman exec "$DB_CTR" mariadb-admin ping --silent >/dev/null 2>&1 && break
sleep 2
done
echo "MariaDB ping ready (rc=$?)"
# Stop the web and daemon containers so nothing uses the data dir / DB mid-restore.
systemctl --user stop "$DAEMON_SVC" "$WEB_SVC"
echo "Stopped $WEB_SVC and $DAEMON_SVC (rc=$?)"
# --- Restore the EspoCRM data dir ---------------------------------------------
# Clear the data dir and extract the tarball INSIDE a container that mounts it,
# so the bind-mount ownership is handled inside the user namespace. Use
# --no-same-owner so files land owned by the container root (-> host user mkt),
# matching the official image's code layout. Then chown the writable resources
# (data, custom, client/custom; config.php lives under data) to www-data, so the
# app can write its cache/logs/config. The entrypoint does NOT re-chown existing
# files, so we do it here explicitly.
echo "Restoring EspoCRM files from $FILES_BAK ..."
podman run --rm -i --entrypoint sh \
-v "$ESPO_DATA_DIR":/var/www/html \
"$ESPO_IMAGE" -c '
set -e
find /var/www/html -mindepth 1 -delete
tar -xzf - --no-same-owner -C /var/www/html
for d in data custom client/custom; do
[ -e "/var/www/html/$d" ] && chown -R www-data:www-data "/var/www/html/$d"
done
' < "$FILES_BAK"
echo "Restored EspoCRM files (rc=$?)"
# --- Restore the database -----------------------------------------------------
# The dump is a single-database dump (no CREATE DATABASE), so drop & recreate the
# database with the same charset/collation, then import. Database-level grants
# for the espocrm user survive a DROP DATABASE, so the app keeps its access.
echo "Recreating database '$DB_NAME' ..."
podman exec "$DB_CTR" sh -c \
'exec mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" -e "DROP DATABASE IF EXISTS '"$DB_NAME"'; CREATE DATABASE '"$DB_NAME"' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"'
echo "Recreated database (rc=$?)"
echo "Importing DB dump from $DB_BAK ..."
gunzip -c "$DB_BAK" | podman exec -i "$DB_CTR" sh -c \
'exec mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" '"$DB_NAME"
echo "Imported DB dump (rc=$?)"
# --- Bring the app back up ----------------------------------------------------
# Start the web container first; its entrypoint fixes ownership of the writable
# resources before the daemon (which shares the same data dir) starts.
systemctl --user start "$WEB_SVC"
echo "Started $WEB_SVC (rc=$?)"
echo "Waiting for EspoCRM to respond..."
for i in $(seq 1 90); do
code=$(curl -s -o /dev/null -w '%{http_code}' "${SITE_URL}/" 2>/dev/null)
case "$code" in 200|302) break ;; esac
sleep 2
done
echo "EspoCRM responded with HTTP $code (rc=$?)"
systemctl --user start "$DAEMON_SVC"
echo "Started $DAEMON_SVC (rc=$?)"
# Clear cache / rebuild so the restored config and metadata take effect.
podman exec -u www-data "$WEB_CTR" php command.php rebuild
echo "Rebuilt EspoCRM cache (rc=$?)"
echo ""
echo "Restore of set '$TS' complete."
echo "EspoCRM is reachable at ${SITE_URL}"
echo "Status: systemctl --user status $POD_SVC $DB_SVC $WEB_SVC $DAEMON_SVC"