Skip to content

Instantly share code, notes, and snippets.

@lemassykoi
Created May 26, 2025 21:19
Show Gist options
  • Save lemassykoi/73f407f7278fc15dd9febff9a5351281 to your computer and use it in GitHub Desktop.
Save lemassykoi/73f407f7278fc15dd9febff9a5351281 to your computer and use it in GitHub Desktop.
ESPOCRM 9.1.3 Install script without docker, on a fresh Debian 12 VM
#!/usr/bin/env bash
# install_espocrm.sh - Automated, production-ready EspoCRM installer for Debian 12 (without Docker)
# Inspired by EspoCRM official installer
set -euo pipefail
# Default configuration
DOMAIN="espocrm.mydomain.local"
ADMIN_EMAIL="admin@${DOMAIN}"
ESPO_VERSION="9.1.3"
INSTALL_DIR="/var/www/espocrm"
DB_NAME="espocrm"
DB_USER="espocrm_user"
DB_PASS=""
WWW_USER="www-data"
WWW_GROUP="www-data"
function usage() {
cat <<EOF
Usage: $0 [-d domain] [-e admin_email] [-v version] [-i install_dir] \
[-n db_name] [-u db_user] [-p db_pass] [-w web_user]
Options:
-d DOMAIN EspoCRM domain (ServerName) [default: $DOMAIN]
-e ADMIN_EMAIL Admin email for notifications [default: $ADMIN_EMAIL]
-v VERSION EspoCRM version [default: $ESPO_VERSION]
-i INSTALL_DIR Installation directory [default: $INSTALL_DIR]
-n DB_NAME Database name [default: $DB_NAME]
-u DB_USER Database user [default: $DB_USER]
-p DB_PASS Database password (random if omitted)
-w WEB_USER Webserver user (owner) [default: $WWW_USER]
-h Show this help message
EOF
exit 1
}
# Parse CLI arguments
while getopts d:e:v:i:n:u:p:w:h flag; do
case "${flag}" in
d) DOMAIN="${OPTARG}";;
e) ADMIN_EMAIL="${OPTARG}";;
v) ESPO_VERSION="${OPTARG}";;
i) INSTALL_DIR="${OPTARG}";;
n) DB_NAME="${OPTARG}";;
u) DB_USER="${OPTARG}";;
p) DB_PASS="${OPTARG}";;
w) WWW_USER="${OPTARG}"; WWW_GROUP="$WWW_USER";;
*) usage;;
esac
done
# Generate random DB password if not provided
if [ -z "$DB_PASS" ]; then
DB_PASS=$(openssl rand -base64 16)
fi
# Ensure script runs as root
if [ "$EUID" -ne 0 ]; then
echo "Please run as root or with sudo."
exit 1
fi
# Logging helpers
log() { echo -e "[\e[32mINFO\e[0m] $*"; }
error() { echo -e "[\e[31mERROR\e[0m] $*"; exit 1; }
log "Starting EspoCRM ${ESPO_VERSION} installation"
### PART 1: SYSTEM UPDATE & DEPENDENCIES ###
log "Updating apt and installing packages..."
apt update
apt install -y \
apache2 php-fpm php-cli php-mysql php-json php-gd php-zip php-imap \
php-mbstring php-curl php-exif php-ldap php-xml php-bcmath unzip wget openssl mariadb-server
### PART 2: PHP MODULES & CONFIGURATION ###
log "Enabling PHP modules and restarting service..."
phpenmod imap mbstring
PHP_VERSION=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
systemctl restart "php${PHP_VERSION}-fpm"
log "Configuring php.ini..."
for MODE in fpm cli; do
INI_FILE="/etc/php/${PHP_VERSION}/${MODE}/php.ini"
cp -n "$INI_FILE" "${INI_FILE}.orig"
sed -i \
-e "s/^;\?max_execution_time\s*=.*/max_execution_time = 180/" \
-e "s/^;\?max_input_time\s*=.*/max_input_time = 180/" \
-e "s/^;\?memory_limit\s*=.*/memory_limit = 256M/" \
-e "s/^;\?post_max_size\s*=.*/post_max_size = 50M/" \
-e "s/^;\?upload_max_filesize\s*=.*/upload_max_filesize = 50M/" \
"$INI_FILE"
done
### PART 3: APACHE CONFIGURATION ###
log "Configuring Apache virtual host..."
a2enmod rewrite headers expires alias proxy_fcgi setenvif
# Disable default site
a2dissite 000-default.conf
VHOST_CONF="/etc/apache2/sites-available/espocrm.conf"
cat <<EOF > "$VHOST_CONF"
<VirtualHost *:80>
ServerName ${DOMAIN}
ServerAdmin ${ADMIN_EMAIL}
DocumentRoot ${INSTALL_DIR}/public
Alias /client/ ${INSTALL_DIR}/client/
<Directory /var/www/html>
AllowOverride None
Require all granted
</Directory>
<Directory ${INSTALL_DIR}/public>
AllowOverride All
Require all granted
</Directory>
DirectoryIndex index.php index.html
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/php${PHP_VERSION}-fpm.sock|fcgi://localhost/"
</FilesMatch>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/* "access plus 1 month"
ExpiresByType text/css "access plus 1 week"
ExpiresByType application/javascript "access plus 1 week"
</IfModule>
ErrorLog /var/log/apache2/espocrm_error.log
CustomLog /var/log/apache2/espocrm_access.log combined
</VirtualHost>
EOF
a2ensite espocrm.conf
systemctl reload apache2
### PART 4: DATABASE SETUP ###
log "Creating MariaDB database and user..."
mysql -e "CREATE DATABASE IF NOT EXISTS $DB_NAME CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
mysql -e "CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '${DB_PASS}';"
mysql -e "GRANT ALL PRIVILEGES ON $DB_NAME.* TO '${DB_USER}'@'localhost';"
mysql -e "FLUSH PRIVILEGES;"
log "Database: ${DB_NAME}, User: ${DB_USER}, Pass: ${DB_PASS}"
### PART 5: EspoCRM DOWNLOAD & EXTRACT ###
log "Downloading EspoCRM ${ESPO_VERSION}..."
ZIP_FILE="/tmp/espocrm-${ESPO_VERSION}.zip"
wget -q -O "$ZIP_FILE" "https://www.espocrm.com/downloads/EspoCRM-${ESPO_VERSION}.zip"
log "Extracting to ${INSTALL_DIR}..."
# Unpack into a temporary directory to handle nested folder structure
TMP_DIR=$(mktemp -d)
unzip -q "$ZIP_FILE" -d "$TMP_DIR"
# Move contents of the extracted folder into INSTALL_DIR
rm -rf "$INSTALL_DIR"
mkdir -p "$INSTALL_DIR"
EXTRACTED_SUBDIR=$(find "$TMP_DIR" -maxdepth 1 -type d ! -path "$TMP_DIR" | head -n1)
if [ -d "$EXTRACTED_SUBDIR" ]; then
mv "$EXTRACTED_SUBDIR"/* "$INSTALL_DIR"/
else
mv "$TMP_DIR"/* "$INSTALL_DIR"/
fi
# Clean up
rm -rf "$TMP_DIR"
rm -f "$ZIP_FILE"
### PART 6: PERMISSIONS & OWNERSHIP ###
log "Setting permissions and ownership..."
cd "$INSTALL_DIR"
# Default permissions
find . -type d -exec chmod 755 {} +
find . -type f -exec chmod 644 {} +
# Writable directories if they exist
for d in data custom client/custom application/Espo/Modules client/modules; do
if [ -d "$d" ]; then
find "$d" -type d -exec chmod 775 {} +
find "$d" -type f -exec chmod 664 {} +
fi
done
# Executable
if [ -f bin/command ]; then
chmod 754 bin/command
fi
# Ownership
chown -R ${WWW_USER}:${WWW_GROUP} .
### PART 7: CRON JOB ###
log "Installing cron job for EspoCRM..."
CRON_TAB="* * * * * /usr/bin/php -f ${INSTALL_DIR}/cron.php > /dev/null 2>&1"
# Safely append cron job for WWW_USER, preserving existing entries
tmp_cron=$(mktemp)
if crontab -u ${WWW_USER} -l > "$tmp_cron" 2>/dev/null; then
grep -Fv "${INSTALL_DIR}/cron.php" "$tmp_cron" > "${tmp_cron}.filtered"
else
touch "${tmp_cron}.filtered"
fi
printf "%s
" "$CRON_TAB" >> "${tmp_cron}.filtered"
crontab -u ${WWW_USER} "${tmp_cron}.filtered"
rm -f "$tmp_cron" "${tmp_cron}.filtered"
log "EspoCRM ${ESPO_VERSION} installation complete!"
log "!! Take note of SQL User password !!"
log "REBOOT then Visit http://${DOMAIN} to finish setup."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment