Removed EspoCRM GUI elements risky for tags; new restore script

This commit is contained in:
mkt
2026-06-04 09:28:26 +02:00
parent 9f776c4006
commit 03011670a3
2 changed files with 255 additions and 0 deletions

85
create_pod_traefik.sh Executable file
View File

@@ -0,0 +1,85 @@
#!/bin/bash
# To be run by user trf to create the pod and container with
# Traefik and to create the systemd service
# Environment variables
POD_NAME='traefik_pod'
CTR_NAME='traefik_ctr'
IMAGE='docker.io/library/traefik:v3.5.4'
TRAEFIK_HOST_PORT='8080'
DASH_HOST_PORT='8085'
HOST_INGRESS_IP='10.8.0.6'
HOST_LOCAL_IP='127.0.0.1'
NET_OPTS='slirp4netns:allow_host_loopback=true,port_handler=slirp4netns'
BIND_DIR="$HOME/.local/share/$POD_NAME"
DYNAMIC_DIR="$BIND_DIR/dynamic"
STATIC_CFG="$BIND_DIR/traefik.yml"
USER_SYSTEMD_DIR="$HOME/.config/systemd/user"
POD_UNIT_FILE="$USER_SYSTEMD_DIR/pod-${POD_NAME}.service"
# Prepare directories
mkdir -p "$BIND_DIR" "$DYNAMIC_DIR" "$USER_SYSTEMD_DIR"
# Create pod if not yet existing
if ! podman pod exists "$POD_NAME"; then
podman pod create \
-n "$POD_NAME" \
--network "$NET_OPTS" \
-p ${HOST_INGRESS_IP}:${TRAEFIK_HOST_PORT}:${TRAEFIK_HOST_PORT} \
-p ${HOST_LOCAL_IP}:${DASH_HOST_PORT}:${DASH_HOST_PORT}
echo "Pod '$POD_NAME' created (rc=$?)"
else
echo "Pod '$POD_NAME' already exists."
fi
# Traefik container
# Remove old container
podman rm -f "$CTR_NAME"
# New container
podman run -d --name "$CTR_NAME" --pod "$POD_NAME" \
-v "$STATIC_CFG":/etc/traefik/traefik.yml:ro \
-v "$DYNAMIC_DIR":/etc/traefik/dynamic:ro \
"$IMAGE"
echo "Started $CTR_NAME container (rc=$?)"
# Generate systemd service files
cd "$USER_SYSTEMD_DIR"
podman generate systemd --files --new --name "$POD_NAME"
echo "Generated systemd service files (rc=$?)"
# Inject WireGuard IP readiness check into pod-${POD_NAME}.service
awk -v ip="$HOST_INGRESS_IP" '
BEGIN { inserted=0 }
# Insert the readiness check immediately BEFORE the pod create ExecStartPre
/^ExecStartPre=\/usr\/bin\/podman pod create/ && inserted==0 {
# Wait up to ~60s for ip to show up on any interface (safer than assuming wg0)
# Uses grep -F to avoid regex escaping issues with dots in the IP.
print "ExecStartPre=/bin/sh -ceu \047for i in $(seq 1 30); do ip -4 addr show | grep -Fq \" " ip "/\" && exit 0; sleep 2; done; echo \"IP " ip " not up\" >&2; exit 1\047"
inserted=1
}
{ print }
' "$POD_UNIT_FILE" > "${POD_UNIT_FILE}.tmp"
mv "${POD_UNIT_FILE}.tmp" "$POD_UNIT_FILE"
echo "Injected WG IP readiness check for ${HOST_INGRESS_IP} into pod unit"
# Stop & remove live pod and containers
podman pod stop --ignore --time 15 "$POD_NAME"
podman pod rm -f --ignore "$POD_NAME"
if podman pod exists "$POD_NAME"; then
echo "ERROR: Pod $POD_NAME still exists."
exit 1
else
echo "Stopped & removed live pod $POD_NAME and containers"
fi
# Enable systemd services
systemctl --user daemon-reload
systemctl --user enable --now "pod-${POD_NAME}.service"
echo "Enabled systemd service pod-${POD_NAME}.service (rc=$?)"
echo "Status: systemctl --user status pod-${POD_NAME}.service"
echo "View logs: journalctl --user -u pod-${POD_NAME}.service -f"
systemctl --user enable --now "container-${CTR_NAME}.service"
echo "Enabled systemd service container-${CTR_NAME}.service (rc=$?)"
echo "Status: systemctl --user status container-${CTR_NAME}.service"
echo "View logs: journalctl --user -u container-${CTR_NAME}.service -f"

170
restore_espocrm.sh Executable file
View File

@@ -0,0 +1,170 @@
#!/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"