#!/usr/bin/env bash # PHP Installer # Min. Requirement : GNU/Linux Ubuntu 18.04 # Last Build : 13/02/2022 # Author : MasEDI.Net (me@masedi.net) # Since Version : 1.0.0 # Include helper functions. if [[ "$(type -t run)" != "function" ]]; then BASE_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd ) # shellcheck disable=SC1091 . "${BASE_DIR}/utils.sh" # Make sure only root can run this installer script. requires_root "$@" # Make sure only supported distribution can run this installer script. preflight_system_check fi ## # Add PHP repository. ## function add_php_repo() { echo "Add Ondrej's PHP repository..." DISTRIB_NAME=${DISTRIB_NAME:-$(get_distrib_name)} RELEASE_NAME=${RELEASE_NAME:-$(get_release_name)} case "${DISTRIB_NAME}" in debian) if [[ ! -f "/etc/apt/sources.list.d/ondrej-php-${RELEASE_NAME}.list" ]]; then run curl -sSL -o "/etc/apt/trusted.gpg.d/ondrej-php-${RELEASE_NAME}.gpg" https://packages.sury.org/php/apt.gpg && \ run touch "/etc/apt/sources.list.d/ondrej-php-${RELEASE_NAME}.list" && \ run bash -c "echo 'deb https://packages.sury.org/php/ ${RELEASE_NAME} main' > /etc/apt/sources.list.d/ondrej-php-${RELEASE_NAME}.list" && \ run bash -c "echo 'deb-src https://packages.sury.org/php/ ${RELEASE_NAME} main' >> /etc/apt/sources.list.d/ondrej-php-${RELEASE_NAME}.list" # Add openswoole official repository. case "${RELEASE_NAME}" in buster) OPENSWOOLE_RELEASE_NAME="bionic" ;; bullseye) OPENSWOOLE_RELEASE_NAME="focal" ;; bookworm) OPENSWOOLE_RELEASE_NAME="jammy" ;; esac run gpg --lock-never --keyserver hkp://keyserver.ubuntu.com:80 --no-default-keyring --keyring "/usr/share/keyrings/openswoole-ppa-ubuntu-${OPENSWOOLE_RELEASE_NAME}.gpg" --recv-keys 73414442D33E80F9C7E15E7F1F00974B7E59CCAC && \ run touch "/etc/apt/sources.list.d/openswoole-ppa-ubuntu-${OPENSWOOLE_RELEASE_NAME}.list" && \ run bash -c "echo 'deb [signed-by=/usr/share/keyrings/openswoole-ppa-ubuntu-${OPENSWOOLE_RELEASE_NAME}.gpg] https://ppa.launchpadcontent.net/openswoole/ppa/ubuntu/ ${OPENSWOOLE_RELEASE_NAME} main' > /etc/apt/sources.list.d/openswoole-ppa-ubuntu-${OPENSWOOLE_RELEASE_NAME}.list" && \ run bash -c "echo 'deb-src [signed-by=/usr/share/keyrings/openswoole-ppa-ubuntu-${OPENSWOOLE_RELEASE_NAME}.gpg] https://ppa.launchpadcontent.net/openswoole/ppa/ubuntu/ ${OPENSWOOLE_RELEASE_NAME} main' >> /etc/apt/sources.list.d/openswoole-ppa-ubuntu-${OPENSWOOLE_RELEASE_NAME}.list" else info "PHP package repository already exists." fi ;; ubuntu) if [[ ! -f "/etc/apt/sources.list.d/ondrej-php-${RELEASE_NAME}.list" ]]; then #run apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 14AA40EC0831756756D7F66C4F4EA0AAE5267A6C run gpg --lock-never --keyserver hkp://keyserver.ubuntu.com:80 --no-default-keyring --keyring "/etc/apt/trusted.gpg.d/ondrej-php-${RELEASE_NAME}" --recv-keys 14AA40EC0831756756D7F66C4F4EA0AAE5267A6C && \ run add-apt-repository -y ppa:ondrej/php # Add openswoole official repository. if echo "${PHP_EXTENSIONS}" | grep -qwE "openswoole"; then run add-apt-repository -y ppa:openswoole/ppa fi else info "PHP package repository already exists." fi ;; *) fail "Unable to install PHP, this GNU/Linux distribution is not supported." ;; esac info "Updating repository, please wait..." run apt-get update -q -y && \ run apt-get install -q -y libgd-dev libsodium-dev } ## # Install PHP and extensions. ## function install_php() { export PHP_IS_INSTALLED="no" # PHP version. local PHPv="${1}" if [[ -z "${PHPv}" ]]; then PHPv=${DEFAULT_PHP_VERSION:-"8.3"} fi # Checking if PHP already installed. if [[ -n $(command -v "php${PHPv}") && -n $(command -v "php-fpm${PHPv}") ]]; then PHP_IS_INSTALLED="yes" info "PHP ${PHPv} and it's extensions already exists." else echo "Preparing PHP ${PHPv} installation..." local PHP_EXTS=() local PHP_REPO_EXTS=() local PHP_PECL_EXTS=() local PHP_PECL_FLAG="" # Include user defined extensions from config file. read -r -a PHP_EXTS <<< "${PHP_EXTENSIONS}" PHP_EXTS+=("bcmath" "bz2" "cli" "common" "curl" "dev" "fpm" "gd" "gmp" "gnupg" \ "imap" "intl" "mbstring" "mysql" "opcache" "pcov" "pgsql" "pspell" "readline" \ "ldap" "snmp" "soap" "sqlite3" "tidy" "tokenizer" "xml" "xmlrpc" "xsl" "yaml" "zip") # Add PHP extensions. [[ "${INSTALL_MEMCACHED}" == true ]] && PHP_EXTS+=("memcache" "memcached") [[ "${INSTALL_MONGODB}" == true ]] && PHP_EXTS+=("mongodb") [[ "${INSTALL_REDIS}" == true ]] && PHP_EXTS+=("redis") # Sort PHP extensions. #shellcheck disable=SC2207 PHP_EXTS=($(printf "%s\n" "${PHP_EXTS[@]}" | sort -u | tr '\n' ' ')) # Check additional PHP extensions availability. for EXT_NAME in "${PHP_EXTS[@]}"; do echo -n "Checking extension ${EXT_NAME}... " # Search extension from repository or PECL. if apt-cache search "php${PHPv}-${EXT_NAME}" | grep -c "php${PHPv}-${EXT_NAME}" > /dev/null; then echo "[php${PHPv}-${EXT_NAME}] ✅" PHP_REPO_EXTS+=("php${PHPv}-${EXT_NAME}") elif apt-cache search "php-${EXT_NAME}" | grep -c "php-${EXT_NAME}" > /dev/null; then echo "[php${PHPv}-${EXT_NAME}] ✅" PHP_REPO_EXTS+=("php-${EXT_NAME}") else # Fix PECL Sodium ext name. if [[ "${EXT_NAME}" == "sodium" ]]; then EXT_NAME="libsodium" fi # Check PECL extension is available. if curl -sLI "https://pecl.php.net/rest/r/${EXT_NAME}/allreleases.xml" | grep -q "HTTP/[.12]* [2].."; then echo "[pecl-${EXT_NAME}] ✅" PHP_PECL_EXTS+=("${EXT_NAME}") if [[ "${EXT_NAME}" == "openswoole" ]]; then PHP_PECL_FLAG=' -D enable-sockets="no" enable-openssl="yes" enable-http2="yes" enable-mysqlnd="yes" enable-swoole-json="yes" enable-swoole-curl="yes" enable-cares="yes" with-postgres="no"' fi else echo "Not found." fi fi done # Install PHP and PHP extensions. echo "Installing PHP ${PHPv} and it's extensions..." if [[ "${#PHP_REPO_EXTS[@]}" -gt 0 ]]; then run apt-get install -q -y "php${PHPv}" "${PHP_REPO_EXTS[@]}" \ dh-php php-common php-pear php-xml pkg-php-tools fcgiwrap spawn-fcgi fi # Install PHP extensions from PECL. echo "Installing PHP extensions from PECL repo..." # Sort PHP extensions. #shellcheck disable=SC2207 PHP_PECL_EXTS=($(printf "%s\n" "${PHP_PECL_EXTS[@]}" | sort -u | tr '\n' ' ')) # Remove json extension from PHP greater than 7.4. It is now always available. if [[ $(bc -l <<< "${PHPv//.} > 74") == 1 ]]; then PHP_PECL_EXTS=("${PHP_PECL_EXTS[@]/json/}") fi run pecl channel-update pear.php.net if [[ "${#PHP_PECL_EXTS[@]}" -gt 0 ]]; then run pecl -d "php_suffix=${PHPv}" install"${PHP_PECL_FLAG}" "${PHP_PECL_EXTS[@]}" fi if [[ -n $(command -v "php${PHPv}") ]]; then TOTAL_EXTS=$((${#PHP_EXTS[@]} + ${#PHP_PECL_EXTS[@]})) success "PHP ${PHPv} along with ${TOTAL_EXTS} extensions installed." fi # Unset PHP extensions variables. run unset PHP_EXTS PHP_REPO_EXTS PHP_PECL_EXTS PHP_PECL_FLAG # Enable additional PHP extensions. [[ "${INSTALL_MEMCACHED}" == true ]] && enable_php_memcached "${PHPv}" [[ "${INSTALL_MONGODB}" == true ]] && enable_php_mongodb "${PHPv}" [[ "${INSTALL_REDIS}" == true ]] && enable_php_redis "${PHPv}" # Enable GeoIP extension. if [[ "${PHP_PECL_EXTS[*]}" =~ "geoip" ]]; then echo "Updating PHP ini file with GeoIP extension..." [[ ! -f "/etc/php/${PHPv}/mods-available/geoip.ini" ]] && \ run touch "/etc/php/${PHPv}/mods-available/geoip.ini" run bash -c "echo extension=geoip.so > /etc/php/${PHPv}/mods-available/geoip.ini" if [[ ! -f "/etc/php/${PHPv}/cli/conf.d/20-geoip.ini" ]]; then run ln -s "/etc/php/${PHPv}/mods-available/geoip.ini" \ "/etc/php/${PHPv}/cli/conf.d/20-geoip.ini" fi if [[ ! -f "/etc/php/${PHPv}/fpm/conf.d/20-geoip.ini" ]]; then run ln -s "/etc/php/${PHPv}/mods-available/geoip.ini" \ "/etc/php/${PHPv}/fpm/conf.d/20-geoip.ini" fi fi # Enable Mcrypt extension. if [[ "${PHP_PECL_EXTS[*]}" =~ "mcrypt" ]]; then echo "Updating PHP ini file with Mcrypt extension..." [[ ! -f "/etc/php/${PHPv}/mods-available/mcrypt.ini" ]] && \ run touch "/etc/php/${PHPv}/mods-available/mcrypt.ini" run bash -c "echo extension=mcrypt.so > /etc/php/${PHPv}/mods-available/mcrypt.ini" if [[ ! -f "/etc/php/${PHPv}/cli/conf.d/20-mcrypt.ini" ]]; then run ln -s "/etc/php/${PHPv}/mods-available/mcrypt.ini" \ "/etc/php/${PHPv}/cli/conf.d/20-mcrypt.ini" fi if [[ ! -f "/etc/php/${PHPv}/fpm/conf.d/20-mcrypt.ini" ]]; then run ln -s "/etc/php/${PHPv}/mods-available/mcrypt.ini" \ "/etc/php/${PHPv}/fpm/conf.d/20-mcrypt.ini" fi fi # Create PHP log dir. if [[ ! -d /var/log/php ]]; then run mkdir -p /var/log/php fi if [[ ! -d "/home/${LEMPER_USERNAME}/logs/php" ]]; then run mkdir -p "/home/${LEMPER_USERNAME}/logs/php" fi # Optimize PHP & FPM configuration. optimize_php_fpm "${PHPv}" # Log rotation. add_php_logrotate "${PHPv}" fi } ## # Restart PHP-FPM service. ## function restart_php_fpm() { # PHP version. local PHPv="${1}" if [[ -z "${PHPv}" ]]; then PHPv=${DEFAULT_PHP_VERSION:-"8.3"} fi echo "Restarting PHP-FPM service..." # Restart PHP-FPM service. if [[ "${DRYRUN}" != true ]]; then if [[ $(pgrep -c "php-fpm${PHPv}") -gt 0 ]]; then run systemctl reload "php${PHPv}-fpm" success "php${PHPv}-fpm reloaded successfully." elif [[ -n $(command -v "php-fpm${PHPv}") ]]; then run systemctl start "php${PHPv}-fpm" if [[ $(pgrep -c "php-fpm${PHPv}") -gt 0 ]]; then success "php${PHPv}-fpm started successfully." else error "Something goes wrong with PHP ${PHPv} & FPM installation." fi fi else info "php${PHPv}-fpm reloaded in dry run mode." fi } ## # PHP & FPM Optimization. ## function optimize_php_fpm() { # PHP version. local PHPv="${1}" if [[ -z "${PHPv}" ]]; then PHPv=${DEFAULT_PHP_VERSION:-"8.3"} fi echo "Optimizing PHP ${PHPv} & FPM configuration..." if [[ ! -d "/etc/php/${PHPv}/fpm" ]]; then run mkdir -p "/etc/php/${PHPv}/fpm" fi # Copy the optimized-version of php.ini if [[ -f "etc/php/${PHPv}/fpm/php.ini" ]]; then run mv "/etc/php/${PHPv}/fpm/php.ini" "/etc/php/${PHPv}/fpm/php.ini~" run cp -f "etc/php/${PHPv}/fpm/php.ini" "/etc/php/${PHPv}/fpm/" else if [[ "${DRYRUN}" != true ]]; then if [[ "${ENVIRONMENT}" == prod* ]]; then OVT="${OVT:-"0"}" else OVT="${OVT:-"1"}" # Opcache is revalidated every file changes, good for development. fi cat >> "/etc/php/${PHPv}/fpm/php.ini" <> "/etc/php/${PHPv}/fpm/pool.d/www.conf" < "/etc/php/${PHPv}/fpm/pool.d/${POOLNAME}.conf" < "/etc/logrotate.d/php${PHPv}-fpm" <> "/etc/php/${PHPv}/mods-available/memcache.ini" < /etc/php/${PHPv}/mods-available/mongodb.ini" if [[ ! -f "/etc/php/${PHPv}/cli/conf.d/30-mongodb.ini" ]]; then run ln -s "/etc/php/${PHPv}/mods-available/mongodb.ini" \ "/etc/php/${PHPv}/cli/conf.d/30-mongodb.ini" fi if [[ ! -f "/etc/php/${PHPv}/fpm/conf.d/30-mongodb.ini" ]]; then run ln -s "/etc/php/${PHPv}/mods-available/mongodb.ini" \ "/etc/php/${PHPv}/fpm/conf.d/30-mongodb.ini" fi else info "MongoDB extension already enabled, please confirm it manually" fi else error "MongoDB extension file could not be found, you could install it manually" fi else info "PHP ${PHPv} MongoDB extension optimized in dry run mode." fi } ## # Enable PHP Redis extension. ## function enable_php_redis() { # PHP version. local PHPv="${1}" if [[ -z "${PHPv}" ]]; then PHPv=${DEFAULT_PHP_VERSION:-"8.3"} fi PHP_LIB_DIR=$("php-config${PHPv}" | grep -wE "\--extension-dir" | cut -d'[' -f2 | cut -d']' -f1) REDIS_EXT_PATH="${PHP_LIB_DIR}/redis.so" if [[ "${DRYRUN}" != true ]]; then if [[ -f "${REDIS_EXT_PATH}" ]]; then #run chmod 0644 "${REDIS_EXT_PATH}" if "php${PHPv}" -m | grep -q 'redis'; then echo "Updating PHP ini file with Redis extension..." [[ ! -f "/etc/php/${PHPv}/mods-available/redis.ini" ]] && \ run touch "/etc/php/${PHPv}/mods-available/redis.ini" run bash -c "echo extension=${REDIS_EXT_PATH} > /etc/php/${PHPv}/mods-available/redis.ini" if [[ ! -f "/etc/php/${PHPv}/cli/conf.d/30-redis.ini" ]]; then run ln -s "/etc/php/${PHPv}/mods-available/redis.ini" \ "/etc/php/${PHPv}/cli/conf.d/30-redis.ini" fi if [[ ! -f "/etc/php/${PHPv}/fpm/conf.d/30-redis.ini" ]]; then run ln -s "/etc/php/${PHPv}/mods-available/redis.ini" \ "/etc/php/${PHPv}/fpm/conf.d/30-redis.ini" fi else info "Redis extension already enabled, please confirm it manually" fi else error "Redis extension file could not be found, you could install it manually" fi else info "PHP ${PHPv} Redis extension optimized in dry run mode." fi } ## # Install PHP Composer. ## function install_php_composer() { # PHP version. local PHPv="${1}" if [[ -z "${PHPv}" ]]; then PHPv=${DEFAULT_PHP_VERSION:-"8.3"} fi # Checking if php composer already installed. if [[ -z $(command -v composer) ]]; then if [[ ${AUTO_INSTALL} == true ]]; then DO_INSTALL_COMPOSER="y" else while [[ "${DO_INSTALL_COMPOSER}" != "y" && "${DO_INSTALL_COMPOSER}" != "n" ]]; do read -rp "Do you want to install PHP Composer? [y/n]: " -i n -e DO_INSTALL_COMPOSER done fi if [[ ${DO_INSTALL_COMPOSER} == y* && ${INSTALL_PHP_COMPOSER} == true ]]; then echo "Installing PHP Composer..." local CURRENT_DIR && CURRENT_DIR=$(pwd) run cd "${BUILD_DIR}" || error "Cannot change directory to ${BUILD_DIR}." if [[ -n $(command -v "php${PHPv}") ]]; then PHP_BIN=$(command -v "php${PHPv}") EXPECTED_SIGNATURE="$(curl -sSL -o - https://composer.github.io/installer.sig)" run "${PHP_BIN}" -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" ACTUAL_SIGNATURE="$(${PHP_BIN} -r "echo hash_file('sha384', 'composer-setup.php');")" if [[ "${EXPECTED_SIGNATURE}" == "${ACTUAL_SIGNATURE}" ]]; then local LEMPER_USERNAME=${LEMPER_USERNAME:-"lemper"} run "${PHP_BIN}" composer-setup.php --filename=composer --install-dir=/usr/local/bin --quiet # Fix chmod permission to executable. if [[ -f /usr/local/bin/composer ]]; then run chmod ugo+x /usr/local/bin/composer && \ run ln -sf /usr/local/bin/composer /usr/bin/composer run bash -c "echo '[ -d \"\$HOME/.composer/vendor/bin\" ] && export PATH=\"\$PATH:\$HOME/.composer/vendor/bin\"' >> /home/${LEMPER_USERNAME}/.bashrc" run bash -c "echo '[ -d \"\$HOME/.composer/vendor/bin\" ] && export PATH=\"\$PATH:\$HOME/.composer/vendor/bin\"' >> /home/${LEMPER_USERNAME}/.bash_profile" run bash -c "echo '[ -d \"\$HOME/.composer/vendor/bin\" ] && export PATH=\"\$PATH:\$HOME/.composer/vendor/bin\"' >> /home/${LEMPER_USERNAME}/.profile" fi else error "Invalid PHP Composer installer signature." fi fi #run rm composer-setup.php run cd "${CURRENT_DIR}" || error "Cannot change directory to ${CURRENT_DIR}." fi if [[ -n $(command -v composer) ]]; then success "PHP Composer successfully installed." else error "Something went wrong with PHP Composer installation." fi fi } ## # Install ionCube Loader. ## function install_ioncube_loader() { echo "Installing ionCube PHP loader..." # Delete old loaders file. if [ -d /usr/lib/php/loaders/ioncube ]; then echo "Remove old/existing ionCube PHP loader." run rm -fr /usr/lib/php/loaders/ioncube fi local CURRENT_DIR && CURRENT_DIR=$(pwd) run cd "${BUILD_DIR}" || return 1 echo "Downloading latest ionCube PHP loader..." IC_ARCH=${ARCH:-$(uname -m)} IC_ZIP_FILENAME="ioncube_loaders_linux_${IC_ARCH}.tar.gz" IC_ZIP_URL="https://raw.githubusercontent.com/joglomedia/php-loaders/main/${IC_ZIP_FILENAME}" if curl -sLI "${IC_ZIP_URL}" | grep -q "HTTP/[.12]* [2].."; then run curl -sSL -o "${IC_ZIP_FILENAME}" "${IC_ZIP_URL}" && \ run tar -xzf "${IC_ZIP_FILENAME}" && \ run mv -f ioncube /usr/lib/php/loaders/ else error "Cannot download ionCube PHP loader: 'ioncube_loaders_linux_${IC_ARCH}.tar.gz'." fi run cd "${CURRENT_DIR}" || return 1 } ## # Enable ionCube Loader. ## function enable_ioncube_loader() { # PHP version. local PHPv="${1}" if [ -z "${PHPv}" ]; then PHPv=${DEFAULT_PHP_VERSION:-"8.3"} fi echo "Enable ionCube loader for PHP ${PHPv}." if [[ "${DRYRUN}" != true ]]; then if [[ -f "/usr/lib/php/loaders/ioncube/ioncube_loader_lin_${PHPv}.so" && -n $(command -v "php${PHPv}") ]]; then cat > "/etc/php/${PHPv}/mods-available/ioncube.ini" < "/etc/php/${PHPv}/mods-available/sourceguardian.ini" <