Files
bin/readme.md
mkt cebe829dcd Add README, customization snapshot, and snapshot/restore tooling
Provisioning now restores all GUI customizations on reset+reprovision:

- create_pod_espocrm.sh: deploy the version-controlled espocrm-custom/ tree
  (CTag entity, layouts, i18n, clientDefs, custom views, custom CSS) into the
  pod, then chown www-data and rebuild. Replaces the earlier inline CSS-only
  step. Adds a live-phase cache rebuild so customizations and the client
  cacheTimestamp are refreshed on every run.
- espocrm-custom/: snapshot of custom/ and client/custom/ (source of truth).
- snapshot_espocrm_custom.sh: refresh the snapshot from a running pod.
- readme.md: usage, first-time host setup, image-update and reset workflows.
- Include the task/instruction notes and plan.md for reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:51:02 +02:00

11 KiB

EspoCRM on DesTEngSsv006 — rootless Podman pod

This repository contains the shell scripts that run EspoCRM as a rootless Podman pod under the local user mkt, plus the version-controlled snapshot of all GUI customizations. Everything is driven by a handful of *.sh scripts; you do not edit anything inside the running container by hand.

This is a private repository classified as a company secret. Plaintext passwords live directly in create_pod_espocrm.sh on purpose — keep the repo private.


1. What gets deployed

create_pod_espocrm.sh builds one pod with three containers:

Container Image Role
mariadb_ctr docker.io/library/mariadb:11.4.12 Database (utf8mb4 / utf8mb4_unicode_ci)
espocrm_ctr docker.io/espocrm/espocrm:9.3.8 Web app (Apache)
espocrm_daemon_ctr docker.io/espocrm/espocrm:9.3.8 Scheduler / cron loop

Pod name: espocrm_pod. Images are pinned (no auto-updates).

Ports — loopback only (reach them via an SSH tunnel or a local reverse proxy):

Host address Container Purpose
127.0.0.1:8093 :80 EspoCRM web UI
127.0.0.1:8094 :3306 MariaDB (host-side DB access)

Web UI: http://127.0.0.1:8093 · Admin user: admin (password = the ESPOCRM_ADMIN_PASSWORD value in create_pod_espocrm.sh).

Data lives in bind mounts on the host (not in named volumes):

~/.local/share/espocrm_pod/mariadb    -> /var/lib/mysql     (database files)
~/.local/share/espocrm_pod/espocrm    -> /var/www/html      (app, uploads, config, customizations)

Autostart: Podman 4.3.1 has no Quadlet, so the scripts generate systemd --user units (podman generate systemd --files --new) and enable them. The systemd-managed instance is what actually runs after provisioning.


2. The scripts at a glance

Script What it does
create_pod_espocrm.sh Create/recreate the pod, deploy GUI customizations, rebuild, enable autostart. Safe to re-run.
stop_pod_espocrm.sh Stop the running containers + pod (autostart stays enabled).
backup_espocrm.sh Write a timestamped DB dump + a tarball of the app data dir to ~/bak.
restore_espocrm.sh Restore a backup set (DB + files). --list shows sets, --yes skips the prompt.
snapshot_espocrm_custom.sh Refresh the espocrm-custom/ snapshot from the running pod after GUI edits.
reset_pod_espocrm.sh DANGER: delete the pod, units and all data. Requires typing a phrase.
create_pod_traefik.sh Unrelated reference script (Traefik pod) — kept as the house-style template.

All scripts are run as user mkt, from this directory:

cd ~/bin
./create_pod_espocrm.sh

3. First-time setup on a freshly installed host

The scripts assume the rootless-Podman groundwork is already in place. On a brand new host, verify these prerequisites first (one-time, usually needs root):

  1. Podman installed (4.3.x is what this was built for):
    podman --version
    
  2. User mkt exists and has subuid/subgid ranges (needed for rootless user-namespace mapping):
    grep '^mkt:' /etc/subuid /etc/subgid     # each should print a line
    # if missing (as root):  usermod --add-subuids 524288-589823 --add-subgids 524288-589823 mkt
    
  3. Linger enabled for mkt, so the user's systemd services start at boot and keep running without an active login:
    loginctl show-user mkt -p Linger          # want: Linger=yes
    # if not:  sudo loginctl enable-linger mkt
    
  4. A user systemd session is reachable. When working over plain SSH you may need:
    export XDG_RUNTIME_DIR=/run/user/$(id -u)
    systemctl --user status                    # should respond
    

Then, as mkt:

cd ~/bin
./create_pod_espocrm.sh

On this first run the script will:

  • pull the pinned images,
  • run the EspoCRM installer against an empty database (creates the schema and the admin user from the credentials in the script),
  • deploy all GUI customizations from espocrm-custom/,
  • run a cache rebuild,
  • generate and enable the systemd --user services (autostart).

When it finishes, open http://127.0.0.1:8093 and log in as admin.

Reaching the UI from your workstation: it only listens on loopback. Use an SSH tunnel, e.g. ssh -L 8093:127.0.0.1:8093 mkt@DesTEngSsv006, then browse to http://127.0.0.1:8093 locally.


4. Everyday operations

# Start (after a stop) — the systemd units stay enabled, so this also happens on boot:
systemctl --user start pod-espocrm_pod.service \
  container-mariadb_ctr.service container-espocrm_ctr.service container-espocrm_daemon_ctr.service

# Stop everything (autostart remains enabled):
./stop_pod_espocrm.sh

# Status:
systemctl --user status pod-espocrm_pod.service
systemctl --user status container-espocrm_ctr.service

# Live logs:
journalctl --user -u container-espocrm_ctr.service -f

Re-running ./create_pod_espocrm.sh at any time is safe: it recreates the containers from the persisted data, redeploys customizations, and rebuilds.


5. Backups and restore

Backups are manual (no timer/cron). Each run writes two timestamped files to ~/bak: a gzipped SQL dump and a tarball of the app data dir (which includes config.php with the crypt key, uploads, and customizations).

./backup_espocrm.sh                 # creates ~/bak/espocrm-db-<TS>.sql.gz and ...-files-<TS>.tar.gz

./restore_espocrm.sh --list         # list available backup sets
./restore_espocrm.sh                # restore the latest set (asks for confirmation)
./restore_espocrm.sh 20260606-1346  # restore a specific set by timestamp

restore_espocrm.sh checks the archives, drops & re-imports the database, and replaces the app data dir, then rebuilds. Take a backup before any risky operation (image update, reset, major change).


6. GUI customizations (important workflow)

All customizations made through the EspoCRM GUI (Entity Manager, Layout Manager, Label Manager, custom fields/views, custom CSS, …) are stored in espocrm-custom/ in this repo and deployed by create_pod_espocrm.sh. This snapshot is the single source of truth — a reset + reprovision restores it.

Current customizations captured here include the CTag tag entity and its relationships, the Tags panels (Create + button removed, row menu reduced to Unlink), the signature-editor image button, and variable-width Markdown tables.

The golden rule: after you change anything in the EspoCRM GUI, capture it:

./snapshot_espocrm_custom.sh        # pull current customizations into espocrm-custom/
git add espocrm-custom && git commit -m "Update EspoCRM customizations snapshot"

If you skip this, the next create_pod_espocrm.sh run will revert your un-captured GUI changes back to whatever is in espocrm-custom/.

Note: customizations are files. Your actual records (tags, opportunities, contacts, …) are data and live only in the database — protect those with backup_espocrm.sh, not the snapshot.


7. Updating the container images

Images are pinned, so updates are deliberate. Always back up first.

./backup_espocrm.sh

EspoCRM (e.g. 9.3.8 → a newer 9.x)

  1. Edit the version in create_pod_espocrm.sh (the web and daemon share the same ESPO_IMAGE variable, so one change covers both):
    ESPO_IMAGE='docker.io/espocrm/espocrm:<new-version>'
    
  2. Pull and reprovision:
    podman pull docker.io/espocrm/espocrm:<new-version>
    ./create_pod_espocrm.sh
    
    The EspoCRM container entrypoint auto-runs its upgrade when the image version is newer than the installed version (it migrates the database and app files on the bind mount). The script then redeploys customizations and rebuilds.
  3. Verify the UI and your customizations. If you tweaked anything in the GUI as part of the upgrade, re-run ./snapshot_espocrm_custom.sh and commit.

MariaDB (e.g. 11.4.12 → a newer 11.4.x patch)

Patch/minor updates within the same 11.4 LTS series are data-compatible:

# edit DB_IMAGE in create_pod_espocrm.sh, then:
podman pull docker.io/library/mariadb:<new-11.4.x>
./create_pod_espocrm.sh

For a major MariaDB jump (e.g. 11.x → 12.x) do not just swap the tag — back up, then dump/restore into a fresh data dir, and test. When in doubt, take a backup and try it on a throwaway copy first.

After any image change

podman ps                                   # all three containers Up
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8093/   # expect 200
podman image prune                          # optionally drop the old images

Commit the bumped image tag(s) in create_pod_espocrm.sh.


8. Resetting / uninstalling

reset_pod_espocrm.sh destroys the pod, the systemd units, and ALL data (database + uploads). It asks you to type RESET espocrm_pod to proceed.

./backup_espocrm.sh        # do this first if you might want the data back
./reset_pod_espocrm.sh
./create_pod_espocrm.sh    # fresh install; customizations are restored from espocrm-custom/
# (optional) restore your data:
./restore_espocrm.sh

After a reset, a fresh create_pod_espocrm.sh gives you a clean install with all customizations back in place; records come back only via restore.


9. Repository layout

create_pod_espocrm.sh        # provision + autostart + deploy customizations
stop_pod_espocrm.sh          # stop the pod
backup_espocrm.sh            # back up DB + files to ~/bak
restore_espocrm.sh           # restore a backup set
snapshot_espocrm_custom.sh   # refresh espocrm-custom/ from the running pod
reset_pod_espocrm.sh         # DANGER: delete pod + units + all data
espocrm-custom/              # version-controlled GUI customizations (source of truth)
create_pod_traefik.sh        # unrelated reference script (house-style template)
*.md                         # task/instruction notes and this readme