Removed EspoCRM GUI elements risky for tags; new restore script
This commit is contained in:
85
create_pod_traefik.sh
Executable file
85
create_pod_traefik.sh
Executable 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
170
restore_espocrm.sh
Executable 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"
|
||||||
Reference in New Issue
Block a user