#!/usr/bin/env bash # # Banimark server setup - checks this server can run Banimark and installs the # ionCube Loader if it is missing. Linux (incl. cPanel/WHM, Plesk, DirectAdmin, # CloudLinux), macOS (Homebrew, MAMP) and FreeBSD. Windows: use setup.ps1. # # curl -fsSL https://banimark.com/setup.sh | bash # curl -fsSL https://banimark.com/setup.sh | bash -s -- --check # report only, change nothing # # Options: # --check report only; never changes anything # --yes answer yes to every question (unattended installs) # --php PATH the PHP binary to check (default: ask, when there is more than one) # --help # # Environment (rarely needed): # IONCUBE_ARCHIVE=/path/to/ioncube_loaders_*.tar.gz use a loader archive already downloaded # # Written for the bash that ships with macOS (3.2): no associative arrays, no # ${var,,}, no mapfile. Every change is announced first, asked about, backed up # and checked afterwards - and undone if PHP does not start with it. set -u HQ_URL='https://banimark.com' MIN_PHP='8.2' MIN_LOADER='15' # Banimark's core is encoded with ionCube Encoder 15 LOADER_BASE='https://downloads.ioncube.com/loader_downloads' LOADERS_PAGE='https://www.ioncube.com/loaders.php' REQUIRED_EXTS='json curl pdo mbstring sodium' RECOMMENDED_EXTS='zip' CHECK_ONLY=0 ASSUME_YES=0 PHP_BIN='' while [ $# -gt 0 ]; do case "$1" in --check) CHECK_ONLY=1 ;; --yes|-y) ASSUME_YES=1 ;; --php) shift; PHP_BIN="${1:-}" ;; --php=*) PHP_BIN="${1#--php=}" ;; --help|-h) printf '%s\n' 'Banimark server setup: checks PHP, its extensions and the ionCube Loader, and installs what is missing.' \ ' --check report only; change nothing' ' --yes answer yes to every question' \ ' --php PATH the PHP binary to check' ' IONCUBE_ARCHIVE=/path use a loader archive already downloaded' exit 0 ;; *) echo "Unknown option: $1 (try --help)"; exit 2 ;; esac shift done # ---------------------------------------------------------------- output if [ -t 1 ]; then B=$(printf '\033[1m'); DIM=$(printf '\033[2m'); R=$(printf '\033[31m'); G=$(printf '\033[32m') Y=$(printf '\033[33m'); P=$(printf '\033[35m'); N=$(printf '\033[0m') else B=''; DIM=''; R=''; G=''; Y=''; P=''; N='' fi say() { printf '%s\n' "$*"; } head_() { printf '\n%s%s%s\n' "$B$P" "$*" "$N"; } ok() { printf ' %sāœ“%s %s\n' "$G" "$N" "$*"; } bad() { printf ' %sāœ—%s %s\n' "$R" "$N" "$*"; } warn() { printf ' %s!%s %s\n' "$Y" "$N" "$*"; } note() { printf ' %s%s%s\n' "$DIM" "$*" "$N"; } die() { printf '\n%s%s%s\n' "$R" "$*" "$N"; exit 1; } # Piped through `curl | bash`, stdin IS this script - questions have to be read # from the terminal itself. With no terminal at all, nothing can be asked, so # nothing is changed (unless --yes said so up front). TTY='' if [ -t 0 ]; then TTY=/dev/stdin elif [ -r /dev/tty ] && ( : < /dev/tty ) 2>/dev/null; then TTY=/dev/tty fi # ask "Question?" y|n -> 0 for yes ask() { local q="$1" def="${2:-y}" reply hint if [ "$CHECK_ONLY" = 1 ]; then return 1; fi if [ "$ASSUME_YES" = 1 ]; then return 0; fi if [ -z "$TTY" ]; then return 1; fi if [ "$def" = y ]; then hint='[Y/n]'; else hint='[y/N]'; fi printf ' %s?%s %s %s ' "$P" "$N" "$q" "$hint" IFS= read -r reply < "$TTY" || reply='' case "$reply" in [Yy]*) return 0 ;; [Nn]*) return 1 ;; '') [ "$def" = y ] ;; *) return 1 ;; esac } # ask_line "Prompt" default -> echoes the answer ask_line() { local q="$1" def="${2:-}" reply if [ "$ASSUME_YES" = 1 ] || [ -z "$TTY" ]; then printf '%s' "$def"; return; fi if [ -n "$def" ]; then printf ' %s?%s %s [%s] ' "$P" "$N" "$q" "$def" >&2; else printf ' %s?%s %s ' "$P" "$N" "$q" >&2; fi IFS= read -r reply < "$TTY" || reply='' printf '%s' "${reply:-$def}" } have() { command -v "$1" >/dev/null 2>&1; } TMP=$(mktemp -d 2>/dev/null || mktemp -d -t banimark) WEB_CHECK_FILE='' cleanup() { [ -n "$WEB_CHECK_FILE" ] && $SUDO_WEB rm -f "$WEB_CHECK_FILE" 2>/dev/null rm -rf "$TMP" } SUDO_WEB='' trap cleanup EXIT fetch() { # fetch URL FILE if have curl; then curl -fsSL --retry 2 -o "$2" "$1" elif have wget; then wget -q -O "$2" "$1" else return 1; fi } # ---------------------------------------------------------------- where are we say "${B}Banimark server setup${N}" [ "$CHECK_ONLY" = 1 ] && say "${DIM}Check only - nothing on this server will be changed.${N}" [ "$CHECK_ONLY" = 0 ] && [ "$ASSUME_YES" = 0 ] && [ -z "$TTY" ] && \ say "${Y}No terminal to ask questions on, so this run only checks. Re-run with --yes to let it install.${N}" OS=$(uname -s 2>/dev/null) CPU=$(uname -m 2>/dev/null) case "$OS" in MINGW*|MSYS*|CYGWIN*) die "This is Windows. Run this in PowerShell instead: irm $HQ_URL/setup.ps1 | iex" ;; esac case "$CPU" in x86_64|amd64) ARCH='x86-64' ;; aarch64|arm64) ARCH='aarch64' ;; i386|i686) ARCH='x86' ;; armv7l) ARCH='armv7l' ;; *) ARCH="$CPU" ;; esac case "$OS" in Linux) PLATFORM="lin_${ARCH}"; OS_NAME="Linux" ;; Darwin) [ "$ARCH" = aarch64 ] && ARCH='arm64'; PLATFORM="mac_${ARCH}"; OS_NAME="macOS $(sw_vers -productVersion 2>/dev/null)" ;; FreeBSD) FB=$(uname -r | cut -d. -f1); PLATFORM="fre_${FB}_${ARCH}"; OS_NAME="FreeBSD $FB" ;; *) PLATFORM=''; OS_NAME="$OS" ;; esac if [ "$OS" = Linux ] && [ -r /etc/os-release ]; then OS_NAME=$(. /etc/os-release 2>/dev/null; printf '%s' "${PRETTY_NAME:-Linux}") fi PANEL='' [ -e /usr/local/cpanel/version ] && PANEL='cPanel' [ -e /usr/local/psa/version ] && PANEL='Plesk' [ -d /usr/local/directadmin ] && PANEL='DirectAdmin' [ -d /usr/local/CyberCP ] && PANEL='CyberPanel' CLOUDLINUX=0; [ -e /etc/cloudlinux-release ] && CLOUDLINUX=1 [ "$OS" = Darwin ] && [ -d /Applications/MAMP ] && PANEL="${PANEL:-MAMP}" IS_ROOT=0; [ "$(id -u)" = 0 ] && IS_ROOT=1 CAN_SUDO=0 if [ "$IS_ROOT" = 0 ] && have sudo; then CAN_SUDO=1; fi head_ "This server" ok "$OS_NAME, $CPU" [ -n "$PANEL" ] && ok "Control panel: $PANEL$([ $CLOUDLINUX = 1 ] && printf ' on CloudLinux')" if [ "$IS_ROOT" = 1 ]; then ok "Running as root" elif [ "$CAN_SUDO" = 1 ]; then ok "Running as $(id -un) (sudo available for system changes)" else warn "Running as $(id -un) without sudo - system files cannot be changed from here" fi # ---------------------------------------------------------------- which PHP # Every PHP this server might run a website on. The terminal's `php` is often # NOT the website's: cPanel and Plesk keep one PHP per version and let each # site pick; macOS has Homebrew and MAMP side by side. candidates() { local p command -v php 2>/dev/null for p in php8.5 php8.4 php8.3 php8.2 php85 php84 php83 php82; do command -v "$p" 2>/dev/null; done for p in /usr/bin/php8.? /usr/local/bin/php /usr/local/bin/php8.? \ /opt/homebrew/bin/php /opt/homebrew/opt/php@8.?/bin/php /usr/local/opt/php@8.?/bin/php \ /opt/cpanel/ea-php8?/root/usr/bin/php /opt/alt/php8?/usr/bin/php /opt/plesk/php/8.?/bin/php \ /usr/local/php8?/bin/php /usr/local/lsws/lsphp8?/bin/php /opt/remi/php8?/root/usr/bin/php \ /Applications/MAMP/bin/php/php8.*/bin/php; do [ -x "$p" ] && printf '%s\n' "$p" done } probe() { # probe PHP -> key=value lines "$1" -r ' echo "ver=", PHP_VERSION, "\n"; echo "mm=", PHP_MAJOR_VERSION, ".", PHP_MINOR_VERSION, "\n"; echo "zts=", PHP_ZTS, "\n"; echo "bin=", PHP_BINARY, "\n"; echo "extdir=", ini_get("extension_dir"), "\n"; echo "exts=", strtolower(implode(",", get_loaded_extensions())), ",\n"; echo "pdo=", class_exists("PDO") ? implode(",", PDO::getAvailableDrivers()) : "", "\n"; echo "ioncube=", function_exists("ioncube_loader_version") ? ioncube_loader_version() : "", "\n"; ' 2>/dev/null } field() { printf '%s\n' "$1" | sed -n "s/^$2=//p" | head -1; } if [ -z "$PHP_BIN" ]; then LIST='' SEEN='' for c in $(candidates); do real=$("$c" -r 'echo PHP_BINARY;' 2>/dev/null) || continue [ -z "$real" ] && continue case " $SEEN " in *" $real "*) continue ;; esac SEEN="$SEEN $real" LIST="$LIST$c " done COUNT=$(printf '%s' "$LIST" | grep -c . || true) if [ "$COUNT" = 0 ]; then head_ "PHP" bad "No PHP found on this server." case "$OS" in Darwin) note "Install it with Homebrew: brew install php" ;; Linux) if have apt-get; then note "Debian/Ubuntu: sudo apt-get install php8.3-cli php8.3-fpm (ppa:ondrej/php for newer versions)" elif have dnf || have yum; then note "RHEL/Alma/Rocky: enable Remi (rpms.remirepo.net), then dnf install php82 or php83" fi [ -n "$PANEL" ] && note "$PANEL: install PHP 8.2 or newer from the panel's PHP section." ;; esac exit 1 elif [ "$COUNT" = 1 ]; then PHP_BIN=$(printf '%s' "$LIST" | head -1) else head_ "PHP versions found" i=0 printf '%s' "$LIST" | while IFS= read -r c; do i=$((i + 1)) v=$("$c" -r 'echo PHP_VERSION;' 2>/dev/null) printf ' %s%d)%s PHP %-8s %s\n' "$B" "$i" "$N" "$v" "$c" done note "Pick the PHP your WEBSITE runs on - the one chosen for the site in" note "cPanel's MultiPHP Manager, Plesk's PHP settings, or MAMP/Herd on a Mac." [ -z "$TTY" ] && note "No terminal to ask on: checking number 1. Choose another with --php PATH." pick=$(ask_line "Which one? (number)" 1) case "$pick" in ''|*[!0-9]*) pick=1 ;; esac PHP_BIN=$(printf '%s' "$LIST" | sed -n "${pick}p") [ -z "$PHP_BIN" ] && PHP_BIN=$(printf '%s' "$LIST" | head -1) fi fi [ -x "$PHP_BIN" ] || PHP_BIN=$(command -v "$PHP_BIN" 2>/dev/null || true) [ -n "$PHP_BIN" ] && [ -x "$PHP_BIN" ] || die "That PHP ($PHP_BIN) cannot be run." INFO=$(probe "$PHP_BIN") [ -n "$INFO" ] || die "$PHP_BIN would not run a line of PHP. Its own error: $("$PHP_BIN" -v 2>&1 | head -3)" PHP_VER=$(field "$INFO" ver) PHP_MM=$(field "$INFO" mm) PHP_ZTS=$(field "$INFO" zts) EXT_DIR=$(field "$INFO" extdir) EXTS=$(field "$INFO" exts) PDO_DRIVERS=$(field "$INFO" pdo) LOADER_VER=$(field "$INFO" ioncube) INI_OUT=$("$PHP_BIN" --ini 2>/dev/null) MAIN_INI=$(printf '%s\n' "$INI_OUT" | sed -n 's/^Loaded Configuration File:[[:space:]]*//p' | head -1) INI_PATH=$(printf '%s\n' "$INI_OUT" | sed -n 's/^Configuration File (php.ini) Path:[[:space:]]*//p' | head -1) SCAN_DIR=$(printf '%s\n' "$INI_OUT" | sed -n 's/^Scan for additional .ini files in:[[:space:]]*//p' | head -1) case "$MAIN_INI" in '(none)') MAIN_INI='' ;; esac case "$SCAN_DIR" in '(none)') SCAN_DIR='' ;; esac SCAN_DIR=${SCAN_DIR%%:*} # PHP_INI_SCAN_DIR may list several; the first is PHP's own # ---------------------------------------------------------------- requirements FAILED=0 MISSING_EXTS='' head_ "PHP $PHP_VER ${DIM}$PHP_BIN${N}" mm_num() { printf '%d' $(( ${1%%.*} * 100 + ${1#*.} )); } if [ "$(mm_num "$PHP_MM")" -ge "$(mm_num "$MIN_PHP")" ]; then ok "PHP $PHP_MM (Banimark needs $MIN_PHP or newer)" else bad "PHP $PHP_MM is too old - Banimark needs $MIN_PHP or newer" case "$PANEL" in cPanel) note "cPanel: MultiPHP Manager -> set the site to PHP 8.2 or newer (WHM: EasyApache 4 to install it)." ;; Plesk) note "Plesk: Websites & Domains -> PHP -> choose 8.2 or newer." ;; DirectAdmin) note "DirectAdmin: CustomBuild -> PHP 8.2 or newer." ;; *) note "Install PHP 8.2 or newer, then run this again with --php /path/to/that/php." ;; esac FAILED=1 fi has_ext() { case ",$EXTS" in *",$1,"*) return 0 ;; esac; return 1; } for e in $REQUIRED_EXTS; do if has_ext "$e"; then ok "ext-$e" else bad "ext-$e is missing (required)"; MISSING_EXTS="$MISSING_EXTS $e"; FAILED=1 fi done DRIVERS_OK=0 for d in mysql pgsql sqlite; do case ",$PDO_DRIVERS," in *",$d,"*) DRIVERS_OK=1 ;; esac; done if [ "$DRIVERS_OK" = 1 ]; then ok "a database driver (${PDO_DRIVERS})" else bad "no database driver for PDO (needs pdo_mysql, pdo_pgsql or pdo_sqlite)"; MISSING_EXTS="$MISSING_EXTS pdo_mysql"; FAILED=1 fi for e in $RECOMMENDED_EXTS; do if has_ext "$e"; then ok "ext-$e" else warn "ext-$e is missing - one-click updates from the admin need it (Composer updates do not)"; MISSING_EXTS="$MISSING_EXTS $e" fi done loader_major() { printf '%s' "$1" | cut -d. -f1; } NEED_LOADER=0 if [ -n "$LOADER_VER" ]; then if [ "$(loader_major "$LOADER_VER")" -ge "$MIN_LOADER" ] 2>/dev/null; then ok "ionCube Loader $LOADER_VER" else bad "ionCube Loader $LOADER_VER is too old - Banimark needs $MIN_LOADER or newer"; NEED_LOADER=1 fi else bad "ionCube Loader is not installed (Banimark's core needs it)"; NEED_LOADER=1 fi if have composer; then ok "Composer $(composer --version 2>/dev/null | sed -n 's/^Composer version \([^ ]*\).*/\1/p' | head -1)" else warn "Composer is not installed (only needed if you install Banimark with Composer)" fi if [ -n "$HQ_URL" ] && have curl; then if curl -fsS -o /dev/null --max-time 10 "$HQ_URL/api/v1/releases" 2>/dev/null; then ok "Can reach Banimark HQ (licence checks and updates)" else warn "Could not reach $HQ_URL - licence checks and updates need outbound HTTPS to it" fi fi # ---------------------------------------------------------------- missing extensions pkg_for_ext() { # pkg_for_ext EXT -> a package name for this PHP, or nothing local e="$1" owner prefix if have dpkg && owner=$(dpkg -S "$(readlink -f "$PHP_BIN" 2>/dev/null || printf '%s' "$PHP_BIN")" 2>/dev/null); then case "$e" in pdo_mysql) printf 'php%s-mysql' "$PHP_MM" ;; pdo_sqlite) printf 'php%s-sqlite3' "$PHP_MM" ;; json|pdo|sodium) : ;; # part of php-common on Debian/Ubuntu *) printf 'php%s-%s' "$PHP_MM" "$e" ;; esac return fi if have rpm && owner=$(rpm -qf --qf '%{NAME}\n' "$PHP_BIN" 2>/dev/null | head -1) && [ -n "$owner" ]; then prefix=${owner%cli} # php-cli -> php-, ea-php82-php-cli -> ea-php82-php- [ "$prefix" = "$owner" ] && prefix="${owner}-" case "$e" in pdo_mysql) printf '%smysqlnd' "$prefix" ;; pdo_sqlite|pdo) printf '%spdo' "$prefix" ;; zip) case "$prefix" in ea-*) printf '%szip' "$prefix" ;; *) printf '%specl-zip' "$prefix" ;; esac ;; json) : ;; *) printf '%s%s' "$prefix" "$e" ;; esac fi } if [ -n "$MISSING_EXTS" ]; then PKGS='' for e in $MISSING_EXTS; do p=$(pkg_for_ext "$e"); [ -n "$p" ] && PKGS="$PKGS $p"; done head_ "Missing PHP extensions:$MISSING_EXTS" if [ -n "$PKGS" ] && { [ "$IS_ROOT" = 1 ] || [ "$CAN_SUDO" = 1 ]; }; then SUDO=''; [ "$IS_ROOT" = 0 ] && SUDO='sudo' if have apt-get; then CMD="$SUDO apt-get install -y$PKGS" elif have dnf; then CMD="$SUDO dnf install -y$PKGS" else CMD="$SUDO yum install -y$PKGS"; fi note "$CMD" if ask "Install them now?" y; then if sh -c "$CMD"; then ok "Installed. They load the next time PHP starts." else warn "The package manager refused - install the extensions above from your panel or by hand." fi fi else case "$PANEL" in cPanel) note "cPanel: WHM -> EasyApache 4 -> PHP Extensions (or 'Select PHP Version' on CloudLinux)." ;; Plesk) note "Plesk: Tools & Settings -> PHP Settings -> the version -> extensions." ;; MAMP) note "MAMP's PHP includes these; check the site uses MAMP's PHP 8.2+." ;; *) note "Ask your host to enable them, or install them with your package manager." ;; esac fi fi # ---------------------------------------------------------------- ionCube Loader # PHP reads php.ini, then every file in the scan directory in name order. The # loader refuses to start unless it is the FIRST zend_extension PHP meets - and # most PHPs already load opcache as one. So: if php.ini itself has an active # zend_extension line, the loader goes at the top of php.ini; otherwise a # 00-ioncube.ini in the scan directory comes first by name. active_zend_lines() { [ -f "$1" ] && grep -nE '^[[:space:]]*zend_extension[[:space:]]*=' "$1" 2>/dev/null; } # file writes that may need root w_sudo() { if [ -w "$1" ] || { [ ! -e "$1" ] && [ -w "$(dirname "$1")" ]; }; then printf ''; else printf 'sudo'; fi; } install_loader_for() { # install_loader_for SCAN_DIR MAIN_INI LOADER_PATH -> sets CHANGED_FILE, BACKUP_FILE local scan="$1" ini="$2" so="$3" s target first CHANGED_FILE=''; BACKUP_FILE='' # any active zend_extension in php.ini - opcache, or an older ionCube line # that has to be commented out - means php.ini is the only place to be first if [ -n "$ini" ] && active_zend_lines "$ini" | grep -q .; then target="$ini" elif [ -n "$scan" ] && [ -d "$scan" ]; then target="$scan/00-ioncube.ini" # a scan-dir file sorting BEFORE ours that loads a zend_extension would win first=$( { ls "$scan" 2>/dev/null | grep -v '^00-ioncube\.ini$'; printf '00-ioncube.ini\n'; } | LC_ALL=C sort | while read -r f; do [ "$f" = 00-ioncube.ini ] && break grep -qE '^[[:space:]]*zend_extension' "$scan/$f" 2>/dev/null && { printf '%s' "$f"; break; } done) [ -n "$first" ] && [ -n "$ini" ] && target="$ini" elif [ -n "$ini" ]; then target="$ini" else target="${INI_PATH:-/usr/local/etc/php}/php.ini" fi s=$(w_sudo "$target") if [ -n "$s" ] && [ "$IS_ROOT" = 0 ] && [ "$CAN_SUDO" = 0 ]; then bad "Cannot write $target without root."; return 1 fi if [ "$(basename "$target")" = 00-ioncube.ini ]; then [ -e "$target" ] && { BACKUP_FILE="$target.bak-banimark"; $s cp "$target" "$BACKUP_FILE"; } printf '; Added by Banimark setup. The ionCube Loader must be the FIRST zend_extension PHP loads.\nzend_extension=%s\n' "$so" | $s tee "$target" >/dev/null || return 1 else if [ -e "$target" ]; then BACKUP_FILE="$target.bak-banimark-$(date +%Y%m%d%H%M%S)" $s cp "$target" "$BACKUP_FILE" || return 1 fi # comment out any earlier ionCube line (an old loader, a wrong path), then prepend ours { printf '; Added by Banimark setup. The ionCube Loader must be the FIRST zend_extension PHP loads.\nzend_extension=%s\n\n' "$so" [ -e "$target" ] && sed -E 's/^([[:space:]]*zend_extension[[:space:]]*=.*ioncube.*)$/; \1 ; replaced by Banimark setup/' "$target" } > "$TMP/php.ini.new" || return 1 $s cp "$TMP/php.ini.new" "$target" || return 1 fi CHANGED_FILE="$target" ok "Loader added to $target" [ -n "$BACKUP_FILE" ] && note "Backup: $BACKUP_FILE" return 0 } undo_change() { local s; s=$(w_sudo "$CHANGED_FILE") if [ -n "$BACKUP_FILE" ]; then $s cp "$BACKUP_FILE" "$CHANGED_FILE"; else $s rm -f "$CHANGED_FILE"; fi warn "Undone - $CHANGED_FILE is back as it was." } ioncube_via_panel() { # returns 0 if the panel's own package route was taken local ea pm pkg if [ "$PANEL" = cPanel ] && [ "$IS_ROOT" = 1 ] && { have dnf || have yum; }; then ea=$(printf '%s' "$PHP_BIN" | sed -n 's#^/opt/cpanel/\(ea-php[0-9]*\)/.*#\1#p') [ -z "$ea" ] && return 1 pm=yum; have dnf && pm=dnf pkg=$($pm list available "${ea}-php-ioncube*" 2>/dev/null | awk '/ioncube/ {print $1}' | sed 's/\..*//' | sort -V | tail -1) [ -z "$pkg" ] && return 1 note "cPanel packages the loader for $ea as $pkg - the panel keeps it updated." if ask "Install $pkg with $pm?" y; then $pm install -y "$pkg" && { ok "Installed $pkg"; return 0; } fi return 1 fi return 1 } if [ "$NEED_LOADER" = 1 ]; then head_ "Installing the ionCube Loader for PHP $PHP_MM" DONE=0 if [ "$CHECK_ONLY" = 1 ]; then note "Re-run without --check to install it." elif [ "$IS_ROOT" = 0 ] && [ "$CAN_SUDO" = 0 ] && [ ! -w "${MAIN_INI:-/nonexistent}" ] && [ ! -w "${SCAN_DIR:-/nonexistent}" ]; then # shared hosting: no way to change PHP's config from here if [ "$CLOUDLINUX" = 1 ] || [ "$PANEL" = cPanel ]; then note "On cPanel: Software -> Select PHP Version -> Extensions -> tick ${B}ioncube_loader${N}${DIM} -> it saves itself." note "No 'Select PHP Version'? Ask your host to enable the ionCube Loader for PHP $PHP_MM." elif [ "$PANEL" = Plesk ]; then note "On Plesk: ask your host to enable the ionCube Loader for PHP $PHP_MM (Tools & Settings -> PHP Settings)." else note "This account cannot change PHP's configuration. Ask your host to enable the ionCube Loader" note "for PHP $PHP_MM - most hosts offer it, often as a one-click option." fi elif ioncube_via_panel; then DONE=1 elif [ -z "$PLATFORM" ]; then bad "No ionCube Loader download for $OS $CPU is known to this script." note "See $LOADERS_PAGE" elif ask "Download the ionCube Loader for $OS_NAME ($PLATFORM) and install it?" y; then ARCHIVE="${IONCUBE_ARCHIVE:-}" if [ -z "$ARCHIVE" ]; then ARCHIVE="$TMP/ioncube.tar.gz" say " Downloading $LOADER_BASE/ioncube_loaders_${PLATFORM}.tar.gz ..." fetch "$LOADER_BASE/ioncube_loaders_${PLATFORM}.tar.gz" "$ARCHIVE" || die "The download failed. Check this server can reach downloads.ioncube.com, or fetch the archive yourself from $LOADERS_PAGE and re-run with IONCUBE_ARCHIVE=/path/to/it." fi mkdir -p "$TMP/x" case "$ARCHIVE" in *.zip) unzip -q -o "$ARCHIVE" -d "$TMP/x" || die "Could not unpack $ARCHIVE" ;; *) tar xzf "$ARCHIVE" -C "$TMP/x" || die "Could not unpack $ARCHIVE" ;; esac TS=''; [ "$PHP_ZTS" = 1 ] && TS='_ts' SO=$(find "$TMP/x" -name "ioncube_loader_*_${PHP_MM}${TS}.so" 2>/dev/null | head -1) [ -n "$SO" ] || die "The archive has no loader for PHP $PHP_MM${TS:+ (thread-safe)} yet - see $LOADERS_PAGE" DEST_DIR="$EXT_DIR" { [ -z "$DEST_DIR" ] || [ ! -d "$DEST_DIR" ]; } && DEST_DIR=/usr/local/ioncube s=$(w_sudo "$DEST_DIR/x") [ -n "$s" ] && [ "$IS_ROOT" = 0 ] && [ "$CAN_SUDO" = 0 ] && die "Cannot write to $DEST_DIR without root." $s mkdir -p "$DEST_DIR" && $s cp "$SO" "$DEST_DIR/" && $s chmod 644 "$DEST_DIR/$(basename "$SO")" \ || die "Could not copy the loader into $DEST_DIR" LOADER_SO="$DEST_DIR/$(basename "$SO")" ok "Loader copied to $LOADER_SO" if install_loader_for "$SCAN_DIR" "$MAIN_INI" "$LOADER_SO"; then if "$PHP_BIN" -v 2>&1 | grep -q 'ionCube PHP Loader'; then ok "PHP $PHP_MM now starts with: $("$PHP_BIN" -v 2>&1 | grep -o 'ionCube PHP Loader[^,]*' | head -1)" DONE=1 else bad "PHP does not start cleanly with the loader:" "$PHP_BIN" -v 2>&1 | head -4 | sed 's/^/ /' undo_change $(w_sudo "$LOADER_SO") rm -f "$LOADER_SO" fi fi # Debian/Ubuntu keep one config per way of running PHP - the website's # (fpm, apache2) is not the terminal's (cli) if [ "$DONE" = 1 ]; then case "$SCAN_DIR" in /etc/php/*/cli/conf.d) base=${SCAN_DIR%/cli/conf.d} for sapi in fpm apache2 cgi litespeed; do [ -d "$base/$sapi/conf.d" ] || continue if ask "Also enable it for $sapi ($base/$sapi)?" y; then install_loader_for "$base/$sapi/conf.d" "$base/$sapi/php.ini" "$LOADER_SO" || true fi done ;; esac fi fi if [ "$DONE" = 1 ]; then NEED_LOADER=0 head_ "Restart PHP so the website picks it up" note "PHP reads its configuration only when it starts." RESTARTS='' if have systemctl; then RESTARTS=$(systemctl list-units --type=service --state=running --no-legend 2>/dev/null | awk '{print $1}' \ | grep -E '^(php.*fpm|ea-php.*fpm|plesk-php.*fpm|lsphp.*|apache2|httpd|lsws|lshttpd|litespeed)\.service$' | tr '\n' ' ') fi if [ -n "$RESTARTS" ]; then SUDO=''; [ "$IS_ROOT" = 0 ] && SUDO='sudo' note "Running here: $RESTARTS" if ask "Restart them now?" y; then for u in $RESTARTS; do $SUDO systemctl restart "$u" && ok "Restarted $u" || warn "Could not restart $u"; done fi elif [ "$OS" = Darwin ]; then case "$PHP_BIN" in /Applications/MAMP/*) note "MAMP: Stop and Start the servers in the MAMP app." ;; */homebrew/*|/usr/local/*) svc=$(brew services list 2>/dev/null | awk '/^php/ && $2 == "started" {print $1}' | head -1) if [ -n "$svc" ] && ask "Restart $svc (brew services)?" y; then brew services restart "$svc" else note "Homebrew: brew services restart php (or restart 'php artisan serve' / Valet: valet restart)" fi ;; *) note "Restart whatever serves the site (php artisan serve, Valet, Herd, Apache)." ;; esac else note "Restart your web server or PHP-FPM (e.g. systemctl restart php${PHP_MM}-fpm), or 'php artisan serve'." fi fi fi # ---------------------------------------------------------------- the website's PHP # Everything above checked the terminal's PHP. The website's can differ (other # version, other php.ini) - the one way to know is to ask it. if [ "$CHECK_ONLY" = 0 ] && [ -n "$TTY" ] && [ "$ASSUME_YES" = 0 ]; then head_ "Check the website's PHP too" note "The terminal's PHP and the website's are often not the same. This puts a tiny," note "randomly named check file in the site's public folder, reads it once, and deletes it." if ask "Check the website now?" y; then DOCROOT=$(ask_line "The site's public folder (where index.php is)" "$(pwd)") if [ -d "$DOCROOT/public" ] && [ ! -e "$DOCROOT/index.php" ]; then DOCROOT="$DOCROOT/public"; fi if [ ! -d "$DOCROOT" ]; then warn "$DOCROOT is not a folder - skipped." else NAME="bm-check-$(od -An -N6 -tx1 /dev/urandom 2>/dev/null | tr -d ' \n' || date +%s).php" WEB_CHECK_FILE="$DOCROOT/$NAME" SUDO_WEB=$(w_sudo "$WEB_CHECK_FILE") printf '%s\n' '/dev/null $SUDO_WEB chmod 644 "$WEB_CHECK_FILE" 2>/dev/null SITE=$(ask_line "The site's address (e.g. https://example.com)" "") SITE=${SITE%/} BODY='' if [ -n "$SITE" ] && have curl; then BODY=$(curl -fsSL --max-time 15 "$SITE/$NAME" 2>/dev/null || true); fi if [ -z "$BODY" ]; then note "Could not read it from here. Open this in your browser now:" note " ${SITE:-https://your-site}/$NAME" ask_line "Press Enter when you have looked (the file is deleted next)" "" >/dev/null else printf '%s\n' "$BODY" | sed 's/^/ /' case "$BODY" in *"ionCube Loader: MISSING"*) bad "The WEBSITE's PHP has no loader yet - restart PHP-FPM / the web server, or run this again and pick the PHP the site uses." ;; *"ionCube Loader: "*) ok "The website's PHP has the loader." ;; *) warn "That did not look like the check page - is $DOCROOT the folder $SITE serves?" ;; esac fi $SUDO_WEB rm -f "$WEB_CHECK_FILE" && WEB_CHECK_FILE='' && note "Check file deleted." fi fi fi # ---------------------------------------------------------------- Composer if ! have composer && [ "$CHECK_ONLY" = 0 ] && [ "$FAILED" = 0 ]; then head_ "Composer" note "Needed only if you install Banimark with Composer (Laravel, CodeIgniter, most PHP apps)." if ask "Install Composer now?" n; then fetch https://getcomposer.org/installer "$TMP/composer-setup.php" && fetch https://composer.github.io/installer.sig "$TMP/installer.sig" \ || die "Could not download Composer's installer." if [ "$("$PHP_BIN" -r "echo hash_file('sha384', '$TMP/composer-setup.php');")" != "$(tr -d '[:space:]' < "$TMP/installer.sig")" ]; then die "Composer's installer did not match its published checksum - not run." fi BIN=/usr/local/bin; s=$(w_sudo "$BIN/composer") if [ -n "$s" ] && [ "$IS_ROOT" = 0 ] && [ "$CAN_SUDO" = 0 ]; then BIN="$HOME/bin"; s=''; mkdir -p "$BIN"; fi $s "$PHP_BIN" "$TMP/composer-setup.php" --quiet --install-dir="$BIN" --filename=composer \ && ok "Composer installed: $BIN/composer" || warn "Composer's installer failed." case ":$PATH:" in *":$BIN:"*) ;; *) note "Add $BIN to your PATH to run 'composer'." ;; esac fi fi # ---------------------------------------------------------------- verdict head_ "Result" if [ "$FAILED" = 0 ] && [ "$NEED_LOADER" = 0 ]; then ok "${B}This PHP is ready for Banimark.${N}" note "Next: the install guide for your framework - $HQ_URL/install" exit 0 fi [ "$FAILED" = 1 ] && bad "Some requirements above are still missing." [ "$NEED_LOADER" = 1 ] && bad "The ionCube Loader is still missing." note "Fix the items marked āœ— and run this again. Guides: $HQ_URL/install" exit 1