From 9f776c4006cac3a12bddd1698981f9221edd17e771f365ba94440b331ae16ef9 Mon Sep 17 00:00:00 2001 From: mkt Date: Mon, 1 Jun 2026 20:23:09 +0200 Subject: [PATCH] EspoCRM related shell scripts, initial versions --- backup_espocrm.sh | 34 +++++++++ create_pod_espocrm.sh | 169 ++++++++++++++++++++++++++++++++++++++++++ reset_pod_espocrm.sh | 49 ++++++++++++ stop_pod_espocrm.sh | 24 ++++++ 4 files changed, 276 insertions(+) create mode 100755 backup_espocrm.sh create mode 100755 create_pod_espocrm.sh create mode 100755 reset_pod_espocrm.sh create mode 100755 stop_pod_espocrm.sh diff --git a/backup_espocrm.sh b/backup_espocrm.sh new file mode 100755 index 0000000..2e46ca6 --- /dev/null +++ b/backup_espocrm.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# To be run by user mkt to back up the EspoCRM pod: a MariaDB dump and a +# tarball of the EspoCRM data dir (config.php with crypt/passwordSalt key, +# data/upload, custom). Manual invocation only (no timer, no cron). + +# Environment variables +POD_NAME='espocrm_pod' +DB_CTR='mariadb_ctr' +DB_NAME='espocrm' +BIND_DIR="$HOME/.local/share/$POD_NAME" +ESPO_DATA_DIR="$BIND_DIR/espocrm" # /var/www/html +BAK_DIR="$HOME/bak" +TS=$(date +%Y%m%d-%H%M%S) + +mkdir -p "$BAK_DIR" + +# DB dump: run inside the container so no password is needed in this script; +# MARIADB_ROOT_PASSWORD is taken from the container's own environment. +DB_BAK="$BAK_DIR/espocrm-db-$TS.sql.gz" +podman exec "$DB_CTR" sh -c \ + 'exec mariadb-dump --single-transaction --routines --triggers -uroot -p"$MARIADB_ROOT_PASSWORD" '"$DB_NAME" \ + | gzip > "$DB_BAK" +echo "Wrote DB dump $DB_BAK (rc=$?)" + +# File backup of the EspoCRM data directory +FILES_BAK="$BAK_DIR/espocrm-files-$TS.tar.gz" +tar -czf "$FILES_BAK" -C "$ESPO_DATA_DIR" . +echo "Wrote files backup $FILES_BAK (rc=$?)" + +echo "" +echo "Backup complete:" +echo " $DB_BAK" +echo " $FILES_BAK" diff --git a/create_pod_espocrm.sh b/create_pod_espocrm.sh new file mode 100755 index 0000000..032b6c7 --- /dev/null +++ b/create_pod_espocrm.sh @@ -0,0 +1,169 @@ +#!/bin/bash + +# To be run by user mkt to create the EspoCRM pod (MariaDB + web + scheduler +# daemon) as a rootless Podman pod and to create/enable the systemd --user +# services for autostart. Podman 4.3.1 (no Quadlet): uses +# `podman generate systemd --files --new`. + +# 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' +DB_IMAGE='docker.io/library/mariadb:11.4.12' + +HOST_LOCAL_IP='127.0.0.1' +WEB_HOST_PORT='8093' # -> container port 80 (EspoCRM web) +DB_HOST_PORT='8094' # -> container port 3306 (host-side DB access) + +NET_OPTS='slirp4netns:allow_host_loopback=true,port_handler=slirp4netns' + +# Data (bind mounts, no named volumes) +BIND_DIR="$HOME/.local/share/$POD_NAME" +DB_DATA_DIR="$BIND_DIR/mariadb" # -> /var/lib/mysql +ESPO_DATA_DIR="$BIND_DIR/espocrm" # -> /var/www/html + +# systemd --user paths +USER_SYSTEMD_DIR="$HOME/.config/systemd/user" +WEB_UNIT_FILE="$USER_SYSTEMD_DIR/container-${WEB_CTR}.service" +DAEMON_UNIT_FILE="$USER_SYSTEMD_DIR/container-${DAEMON_CTR}.service" + +# Credentials (plaintext deliberately accepted: private repo / company secret) +MARIADB_ROOT_PASSWORD='KqHZUEih4FQY9bTX1VqjS2VbrScrYyi+' +ESPOCRM_DB_PASSWORD='hsILtz+BLs/+8T8MEjO9tIt4fDPPRgB7' +ESPOCRM_ADMIN_PASSWORD='Mb5/toETVFzdn+kX6iRxllzXNbcIQIUT' + +DB_NAME='espocrm' +DB_USER='espocrm' +ADMIN_USER='admin' +SITE_URL="http://${HOST_LOCAL_IP}:${WEB_HOST_PORT}" + +# Prepare directories +mkdir -p "$DB_DATA_DIR" "$ESPO_DATA_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_LOCAL_IP}:${WEB_HOST_PORT}:80 \ + -p ${HOST_LOCAL_IP}:${DB_HOST_PORT}:3306 + echo "Pod '$POD_NAME' created (rc=$?)" +else + echo "Pod '$POD_NAME' already exists." +fi + +# MariaDB container (start first) +podman rm -f "$DB_CTR" +podman run -d --name "$DB_CTR" --pod "$POD_NAME" \ + -e MARIADB_ROOT_PASSWORD="$MARIADB_ROOT_PASSWORD" \ + -e MARIADB_DATABASE="$DB_NAME" \ + -e MARIADB_USER="$DB_USER" \ + -e MARIADB_PASSWORD="$ESPOCRM_DB_PASSWORD" \ + -v "$DB_DATA_DIR":/var/lib/mysql \ + "$DB_IMAGE" \ + --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci +echo "Started $DB_CTR container (rc=$?)" + +# Wait for the DB to be ready +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=$?)" + +# EspoCRM web container (start after the DB) +podman rm -f "$WEB_CTR" +podman run -d --name "$WEB_CTR" --pod "$POD_NAME" \ + -e ESPOCRM_DATABASE_PLATFORM=Mysql \ + -e ESPOCRM_DATABASE_HOST="$HOST_LOCAL_IP" \ + -e ESPOCRM_DATABASE_PORT=3306 \ + -e ESPOCRM_DATABASE_NAME="$DB_NAME" \ + -e ESPOCRM_DATABASE_USER="$DB_USER" \ + -e ESPOCRM_DATABASE_PASSWORD="$ESPOCRM_DB_PASSWORD" \ + -e ESPOCRM_ADMIN_USERNAME="$ADMIN_USER" \ + -e ESPOCRM_ADMIN_PASSWORD="$ESPOCRM_ADMIN_PASSWORD" \ + -e ESPOCRM_SITE_URL="$SITE_URL" \ + -e ESPOCRM_CONFIG_USE_WEB_SOCKET=false \ + -v "$ESPO_DATA_DIR":/var/www/html \ + "$ESPO_IMAGE" +echo "Started $WEB_CTR container (rc=$?)" + +# Wait for EspoCRM install/response (HTTP 200/302, max ~3 min) +echo "Waiting for EspoCRM to install and 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=$?)" + +# EspoCRM scheduler/daemon container (start last; reads data/config.php) +podman rm -f "$DAEMON_CTR" +podman run -d --name "$DAEMON_CTR" --pod "$POD_NAME" \ + --entrypoint docker-daemon.sh \ + -v "$ESPO_DATA_DIR":/var/www/html \ + "$ESPO_IMAGE" +echo "Started $DAEMON_CTR container (rc=$?)" + +# Generate systemd service files +cd "$USER_SYSTEMD_DIR" +podman generate systemd --files --new --name "$POD_NAME" +echo "Generated systemd service files (rc=$?)" + +# Enforce start order: web waits for DB port, daemon waits for web port. +# Insert an ExecStartPre wait loop immediately BEFORE the container run line. +awk -v ip="$HOST_LOCAL_IP" -v port="$DB_HOST_PORT" ' +BEGIN { inserted=0 } +/^ExecStart=\/usr\/bin\/podman run/ && inserted==0 { + print "ExecStartPre=/bin/bash -ceu \047for i in $(seq 1 60); do (exec 3<>/dev/tcp/" ip "/" port ") 2>/dev/null && exit 0; sleep 2; done; echo \"dep " ip ":" port " not up\" >&2; exit 1\047" + inserted=1 +} +{ print } +' "$WEB_UNIT_FILE" > "${WEB_UNIT_FILE}.tmp" +mv "${WEB_UNIT_FILE}.tmp" "$WEB_UNIT_FILE" +echo "Injected DB readiness check (${HOST_LOCAL_IP}:${DB_HOST_PORT}) into ${WEB_CTR} unit" + +awk -v ip="$HOST_LOCAL_IP" -v port="$WEB_HOST_PORT" ' +BEGIN { inserted=0 } +/^ExecStart=\/usr\/bin\/podman run/ && inserted==0 { + print "ExecStartPre=/bin/bash -ceu \047for i in $(seq 1 90); do (exec 3<>/dev/tcp/" ip "/" port ") 2>/dev/null && exit 0; sleep 2; done; echo \"dep " ip ":" port " not up\" >&2; exit 1\047" + inserted=1 +} +{ print } +' "$DAEMON_UNIT_FILE" > "${DAEMON_UNIT_FILE}.tmp" +mv "${DAEMON_UNIT_FILE}.tmp" "$DAEMON_UNIT_FILE" +echo "Injected web readiness check (${HOST_LOCAL_IP}:${WEB_HOST_PORT}) into ${DAEMON_CTR} unit" + +# Stop & remove the live pod and containers +podman pod stop --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 (the systemd-managed instance is what runs in the end) +systemctl --user daemon-reload +systemctl --user enable --now "pod-${POD_NAME}.service" +echo "Enabled systemd service pod-${POD_NAME}.service (rc=$?)" +systemctl --user enable --now "container-${DB_CTR}.service" +echo "Enabled systemd service container-${DB_CTR}.service (rc=$?)" +systemctl --user enable --now "container-${WEB_CTR}.service" +echo "Enabled systemd service container-${WEB_CTR}.service (rc=$?)" +systemctl --user enable --now "container-${DAEMON_CTR}.service" +echo "Enabled systemd service container-${DAEMON_CTR}.service (rc=$?)" + +# Closing hints +echo "" +echo "EspoCRM is reachable at ${SITE_URL} (admin / see ESPOCRM_ADMIN_PASSWORD)" +echo "Status: systemctl --user status pod-${POD_NAME}.service" +echo "Status: systemctl --user status container-${DB_CTR}.service container-${WEB_CTR}.service container-${DAEMON_CTR}.service" +echo "View logs: journalctl --user -u container-${WEB_CTR}.service -f" diff --git a/reset_pod_espocrm.sh b/reset_pod_espocrm.sh new file mode 100755 index 0000000..7da240c --- /dev/null +++ b/reset_pod_espocrm.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# To be run by user mkt. DANGEROUS: disables and removes the EspoCRM pod, +# all containers, the generated systemd units AND ALL DATA (DB + files). +# Requires an exact confirmation phrase. + +# Environment variables +POD_NAME='espocrm_pod' +DB_CTR='mariadb_ctr' +WEB_CTR='espocrm_ctr' +DAEMON_CTR='espocrm_daemon_ctr' +BIND_DIR="$HOME/.local/share/$POD_NAME" +USER_SYSTEMD_DIR="$HOME/.config/systemd/user" + +# Extra safety prompt +read -r -p "WARNING: this deletes the pod, containers, and ALL data under $BIND_DIR. To confirm, type 'RESET espocrm_pod': " C +[ "$C" = "RESET espocrm_pod" ] || { echo "Aborted."; exit 1; } + +# Disable & stop the systemd-managed services +systemctl --user disable --now "container-${DAEMON_CTR}.service" +echo "Disabled container-${DAEMON_CTR}.service (rc=$?)" +systemctl --user disable --now "container-${WEB_CTR}.service" +echo "Disabled container-${WEB_CTR}.service (rc=$?)" +systemctl --user disable --now "container-${DB_CTR}.service" +echo "Disabled container-${DB_CTR}.service (rc=$?)" +systemctl --user disable --now "pod-${POD_NAME}.service" +echo "Disabled pod-${POD_NAME}.service (rc=$?)" + +# Remove the live pod and containers (if any) +podman pod rm -f --ignore "$POD_NAME" +echo "Removed pod $POD_NAME (rc=$?)" + +# Delete the generated unit files +rm -f \ + "$USER_SYSTEMD_DIR/pod-${POD_NAME}.service" \ + "$USER_SYSTEMD_DIR/container-${DB_CTR}.service" \ + "$USER_SYSTEMD_DIR/container-${WEB_CTR}.service" \ + "$USER_SYSTEMD_DIR/container-${DAEMON_CTR}.service" +echo "Removed generated systemd unit files (rc=$?)" + +systemctl --user daemon-reload +echo "Reloaded systemd --user daemon (rc=$?)" + +# Delete all data (point of no return) +rm -rf "$BIND_DIR" +echo "Deleted data directory $BIND_DIR (rc=$?)" + +echo "" +echo "Reset complete. Run create_pod_espocrm.sh again to reinstall EspoCRM." diff --git a/stop_pod_espocrm.sh b/stop_pod_espocrm.sh new file mode 100755 index 0000000..cfcba1b --- /dev/null +++ b/stop_pod_espocrm.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# To be run by user mkt to stop the systemd-managed EspoCRM pod and +# containers. Does NOT disable the services: autostart stays in place. + +# Environment variables +POD_NAME='espocrm_pod' +DB_CTR='mariadb_ctr' +WEB_CTR='espocrm_ctr' +DAEMON_CTR='espocrm_daemon_ctr' + +# Stop in reverse dependency order: daemon -> web -> db -> pod +systemctl --user stop "container-${DAEMON_CTR}.service" +echo "Stopped container-${DAEMON_CTR}.service (rc=$?)" +systemctl --user stop "container-${WEB_CTR}.service" +echo "Stopped container-${WEB_CTR}.service (rc=$?)" +systemctl --user stop "container-${DB_CTR}.service" +echo "Stopped container-${DB_CTR}.service (rc=$?)" +systemctl --user stop "pod-${POD_NAME}.service" +echo "Stopped pod-${POD_NAME}.service (rc=$?)" + +echo "" +echo "EspoCRM stopped (autostart still enabled)." +echo "Start again: systemctl --user start pod-${POD_NAME}.service container-${DB_CTR}.service container-${WEB_CTR}.service container-${DAEMON_CTR}.service"